add upload/download function

This commit is contained in:
backuppc
2026-01-20 14:42:44 +09:00
parent 4376babbcc
commit 237ed6ea8b
3 changed files with 197 additions and 3 deletions

124
App.tsx
View File

@@ -8,6 +8,7 @@ import SettingsModal from './components/SettingsModal';
import SiteManagerModal from './components/SiteManagerModal'; import SiteManagerModal from './components/SiteManagerModal';
import HelpModal from './components/HelpModal'; import HelpModal from './components/HelpModal';
import { CreateFolderModal, RenameModal, DeleteModal } from './components/FileActionModals'; import { CreateFolderModal, RenameModal, DeleteModal } from './components/FileActionModals';
import { formatBytes } from './utils/formatters';
const App: React.FC = () => { const App: React.FC = () => {
// --- State --- // --- State ---
@@ -262,7 +263,7 @@ const App: React.FC = () => {
case 'success': case 'success':
addLog('success', data.message); addLog('success', data.message);
if (connection.connected) { if (connectionRef.current.connected) {
ws?.send(JSON.stringify({ command: 'LIST', path: remote.path })); ws?.send(JSON.stringify({ command: 'LIST', path: remote.path }));
} }
ws?.send(JSON.stringify({ command: 'LOCAL_LIST', path: local.path })); ws?.send(JSON.stringify({ command: 'LOCAL_LIST', path: local.path }));
@@ -271,6 +272,45 @@ const App: React.FC = () => {
case 'sites_list': case 'sites_list':
setSavedSites(data.sites); setSavedSites(data.sites);
break; break;
case 'transfer_progress':
setQueue(prev => prev.map(item => {
if (item.id === data.id) {
const total = data.bytesOverall;
const current = data.bytes;
const progress = total > 0 ? Math.round((current / total) * 100) : 0;
return { ...item, progress, status: 'transferring', speed: `${formatBytes(current)} transferred` };
}
return item;
}));
break;
case 'transfer_success':
setQueue(prev => prev.map(item => {
if (item.id === data.id) {
return { ...item, progress: 100, status: 'completed', speed: 'Completed' };
}
return item;
}));
addLog('success', data.message);
// Refresh lists
if (data.path) {
ws?.send(JSON.stringify({ command: 'LOCAL_LIST', path: local.path }));
if (connectionRef.current.connected) {
ws?.send(JSON.stringify({ command: 'LIST', path: remote.path }));
}
}
break;
case 'transfer_error':
setQueue(prev => prev.map(item => {
if (item.id === data.id) {
return { ...item, status: 'failed', speed: data.message };
}
return item;
}));
addLog('error', data.message);
break;
} }
} catch (e) { } catch (e) {
console.error("WS Message Error", e); console.error("WS Message Error", e);
@@ -365,6 +405,74 @@ const App: React.FC = () => {
if (wsRef.current) wsRef.current.send(JSON.stringify({ command: 'LOCAL_LIST', path })); if (wsRef.current) wsRef.current.send(JSON.stringify({ command: 'LOCAL_LIST', path }));
}; };
const handleDownload = () => {
if (!connection.connected || selectedRemoteIds.size === 0) return;
selectedRemoteIds.forEach(id => {
const file = remote.files.find(f => f.id === id);
if (file && file.type === FileType.FILE) {
const transferId = `down-${Date.now()}-${Math.random()}`;
const separator = local.path.includes('\\') ? '\\' : '/';
const cleanPath = local.path.endsWith(separator) ? local.path : local.path + separator;
const localTarget = cleanPath + file.name;
const remoteTarget = remote.path === '/' ? `/${file.name}` : `${remote.path}/${file.name}`;
// Add to Queue
setQueue(prev => [...prev, {
id: transferId,
direction: 'download',
filename: file.name,
progress: 0,
status: 'queued',
speed: 'Pending...'
}]);
addLog('command', `DOWNLOAD ${file.name} -> ${localTarget}`);
wsRef.current?.send(JSON.stringify({
command: 'DOWNLOAD',
localPath: localTarget,
remotePath: remoteTarget,
transferId
}));
}
});
setSelectedRemoteIds(new Set());
};
const handleUpload = () => {
if (!connection.connected || selectedLocalIds.size === 0) return;
selectedLocalIds.forEach(id => {
const file = local.files.find(f => f.id === id);
if (file && file.type === FileType.FILE) {
const transferId = `up-${Date.now()}-${Math.random()}`;
const separator = local.path.includes('\\') ? '\\' : '/';
const cleanPath = local.path.endsWith(separator) ? local.path : local.path + separator;
const localSource = cleanPath + file.name;
const remoteTarget = remote.path === '/' ? `/${file.name}` : `${remote.path}/${file.name}`;
// Add to Queue
setQueue(prev => [...prev, {
id: transferId,
direction: 'upload',
filename: file.name,
progress: 0,
status: 'queued',
speed: 'Pending...'
}]);
addLog('command', `UPLOAD ${file.name} -> ${remoteTarget}`);
wsRef.current?.send(JSON.stringify({
command: 'UPLOAD',
localPath: localSource,
remotePath: remoteTarget,
transferId
}));
}
});
setSelectedLocalIds(new Set());
};
// --- File Action Handlers --- // --- File Action Handlers ---
const initiateCreateFolder = (isLocal: boolean) => { const initiateCreateFolder = (isLocal: boolean) => {
if (!isLocal && !connection.connected) return; if (!isLocal && !connection.connected) return;
@@ -744,10 +852,20 @@ const App: React.FC = () => {
{/* Middle Actions */} {/* Middle Actions */}
<div className="flex md:flex-col items-center justify-center gap-2 p-1"> <div className="flex md:flex-col items-center justify-center gap-2 p-1">
<button className="p-3 bg-white border border-slate-300 shadow-sm rounded hover:bg-blue-50 text-slate-400"> <button
className="p-3 bg-white border border-slate-300 shadow-sm rounded hover:bg-blue-50 text-slate-400 hover:text-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
onClick={handleUpload}
disabled={!connection.connected || selectedLocalIds.size === 0}
title="업로드 (Local -> Remote)"
>
<ArrowRight size={24} strokeWidth={2.5} /> <ArrowRight size={24} strokeWidth={2.5} />
</button> </button>
<button className="p-3 bg-white border border-slate-300 shadow-sm rounded hover:bg-green-50 text-slate-400"> <button
className="p-3 bg-white border border-slate-300 shadow-sm rounded hover:bg-green-50 text-slate-400 hover:text-green-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
onClick={handleDownload}
disabled={!connection.connected || selectedRemoteIds.size === 0}
title="다운로드 (Remote -> Local)"
>
<ArrowLeft size={24} strokeWidth={2.5} /> <ArrowLeft size={24} strokeWidth={2.5} />
</button> </button>
</div> </div>

View File

@@ -236,6 +236,82 @@ function startServer() {
ws.send(JSON.stringify({ type: 'error', message: `삭제 실패: ${err.message}` })); ws.send(JSON.stringify({ type: 'error', message: `삭제 실패: ${err.message}` }));
} }
break; break;
case 'DOWNLOAD':
if (client.closed) {
ws.send(JSON.stringify({ type: 'error', message: 'FTP 연결이 끊어져 있습니다.' }));
return;
}
try {
const { remotePath, localPath, transferId } = data;
// Progress Handler
client.trackProgress(info => {
ws.send(JSON.stringify({
type: 'transfer_progress',
id: transferId,
bytes: info.bytes,
bytesOverall: info.bytesOverall,
name: info.name
}));
});
await client.downloadTo(localPath, remotePath);
client.trackProgress(); // Stop tracking
ws.send(JSON.stringify({
type: 'transfer_success',
id: transferId,
message: '다운로드 완료',
path: localPath
}));
} catch (err) {
client.trackProgress(); // Stop tracking
ws.send(JSON.stringify({
type: 'transfer_error',
id: data.transferId,
message: `다운로드 실패: ${err.message}`
}));
}
break;
case 'UPLOAD':
if (client.closed) {
ws.send(JSON.stringify({ type: 'error', message: 'FTP 연결이 끊어져 있습니다.' }));
return;
}
try {
const { remotePath, localPath, transferId } = data;
// Progress Handler
client.trackProgress(info => {
ws.send(JSON.stringify({
type: 'transfer_progress',
id: transferId,
bytes: info.bytes,
bytesOverall: info.bytesOverall,
name: info.name
}));
});
await client.uploadFrom(localPath, remotePath);
client.trackProgress();
ws.send(JSON.stringify({
type: 'transfer_success',
id: transferId,
message: '업로드 완료',
path: remotePath
}));
} catch (err) {
client.trackProgress();
ws.send(JSON.stringify({
type: 'transfer_error',
id: data.transferId,
message: `업로드 실패: ${err.message}`
}));
}
break;
} }
} catch (err) { } catch (err) {
console.error("오류 발생:", err); console.error("오류 발생:", err);

Binary file not shown.