add upload/download function
This commit is contained in:
124
App.tsx
124
App.tsx
@@ -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>
|
||||||
|
|||||||
@@ -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.
Reference in New Issue
Block a user