update
This commit is contained in:
@@ -641,11 +641,38 @@ export class SftpService {
|
|||||||
this.cancelUploadInternal(uploadId, 'Bytes written exceeded total size');
|
this.cancelUploadInternal(uploadId, 'Bytes written exceeded total size');
|
||||||
|
|
||||||
} else if (uploadState.bytesWritten === uploadState.totalSize) {
|
} else if (uploadState.bytesWritten === uploadState.totalSize) {
|
||||||
console.log(`[SFTP Upload ${uploadId}] All bytes (${uploadState.bytesWritten}) received for ${uploadState.remotePath}. Sending success and ending stream.`);
|
console.log(`[SFTP Upload ${uploadId}] All bytes (${uploadState.bytesWritten}) received for ${uploadState.remotePath}. Fetching stats before sending success...`);
|
||||||
// Send success message IMMEDIATELY upon receiving the last expected byte
|
|
||||||
state.ws.send(JSON.stringify({ type: 'sftp:upload:success', payload: { uploadId, remotePath: uploadState.remotePath } }));
|
// Get stats for the newly uploaded file before sending success
|
||||||
// Now end the stream. The 'close' event will handle cleanup.
|
state.sftp!.lstat(uploadState.remotePath, (statErr, stats) => {
|
||||||
uploadState.stream.end();
|
let newItemPayload: any = null; // Default to null payload
|
||||||
|
if (statErr) {
|
||||||
|
console.error(`[SFTP Upload ${uploadId}] lstat after upload ${uploadState.remotePath} failed:`, statErr);
|
||||||
|
// Still send success, but with null payload as item details are unavailable
|
||||||
|
} else {
|
||||||
|
newItemPayload = {
|
||||||
|
filename: uploadState.remotePath.substring(uploadState.remotePath.lastIndexOf('/') + 1),
|
||||||
|
longname: '', // lstat doesn't provide longname
|
||||||
|
attrs: {
|
||||||
|
size: stats.size, uid: stats.uid, gid: stats.gid, mode: stats.mode,
|
||||||
|
atime: stats.atime * 1000, mtime: stats.mtime * 1000,
|
||||||
|
isDirectory: stats.isDirectory(), isFile: stats.isFile(), isSymbolicLink: stats.isSymbolicLink(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
console.log(`[SFTP Upload ${uploadId}] Sending upload success with new item details for ${uploadState.remotePath}`);
|
||||||
|
}
|
||||||
|
// Send success message with the newItem payload (or null if lstat failed)
|
||||||
|
state.ws.send(JSON.stringify({ type: 'sftp:upload:success', payload: newItemPayload, uploadId: uploadId, path: uploadState.remotePath })); // Include uploadId and path for frontend context
|
||||||
|
|
||||||
|
// End the stream *after* lstat completes and success message is sent
|
||||||
|
uploadState.stream.end((endErr: Error | undefined) => { // Add type annotation
|
||||||
|
if (endErr) {
|
||||||
|
console.error(`[SFTP Upload ${uploadId}] Error ending write stream after success for ${uploadState.remotePath}:`, endErr);
|
||||||
|
} else {
|
||||||
|
console.log(`[SFTP Upload ${uploadId}] Write stream ended successfully after success for ${uploadState.remotePath}.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const {
|
|||||||
} = useFileUploader(
|
} = useFileUploader(
|
||||||
currentPath, // 使用从 sftpManager 获取的 currentPath
|
currentPath, // 使用从 sftpManager 获取的 currentPath
|
||||||
fileList, // 传递来自 sftpManager 的 fileList ref
|
fileList, // 传递来自 sftpManager 的 fileList ref
|
||||||
() => loadDirectory(currentPath.value), // Refresh function uses manager's loadDirectory
|
// () => loadDirectory(currentPath.value), // 不再需要传递 refresh 函数
|
||||||
// props.sessionId, // 不再传递 sessionId
|
// props.sessionId, // 不再传递 sessionId
|
||||||
// props.dbConnectionId // 不再传递 dbConnectionId
|
// props.dbConnectionId // 不再传递 dbConnectionId
|
||||||
props.wsDeps // 传递注入的 WebSocket 依赖项
|
props.wsDeps // 传递注入的 WebSocket 依赖项
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const joinPath = (base: string, name: string): string => {
|
|||||||
export function useFileUploader(
|
export function useFileUploader(
|
||||||
currentPathRef: Ref<string>,
|
currentPathRef: Ref<string>,
|
||||||
fileListRef: Readonly<Ref<readonly FileListItem[]>>, // 使用 Readonly 类型
|
fileListRef: Readonly<Ref<readonly FileListItem[]>>, // 使用 Readonly 类型
|
||||||
refreshDirectory: () => void, // 上传成功后刷新目录的回调函数
|
// refreshDirectory: () => void, // 不再需要此回调
|
||||||
// sessionId: string, // 不再需要,因为 wsDeps 包含了会话上下文
|
// sessionId: string, // 不再需要,因为 wsDeps 包含了会话上下文
|
||||||
// dbConnectionId: string, // 不再需要
|
// dbConnectionId: string, // 不再需要
|
||||||
wsDeps: WebSocketDependencies // 注入 WebSocket 依赖项
|
wsDeps: WebSocketDependencies // 注入 WebSocket 依赖项
|
||||||
@@ -204,8 +204,8 @@ export function useFileUploader(
|
|||||||
upload.status = 'success';
|
upload.status = 'success';
|
||||||
upload.progress = 100;
|
upload.progress = 100;
|
||||||
|
|
||||||
// 使用回调刷新目录
|
// 不再调用 refreshDirectory(),由 useSftpActions 处理列表更新
|
||||||
refreshDirectory();
|
// refreshDirectory();
|
||||||
|
|
||||||
// 延迟后从列表中移除
|
// 延迟后从列表中移除
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -561,6 +561,40 @@ export function createSftpActionsManager(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// *** 新增:处理上传成功 ***
|
||||||
|
const onUploadSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||||
|
const newItem = payload as FileListItem | null; // 后端应发送 FileListItem 或 null
|
||||||
|
const parentPath = currentPathRef.value; // 上传总是发生在当前路径
|
||||||
|
const filename = newItem?.filename; // 从 newItem 获取文件名
|
||||||
|
|
||||||
|
console.log(`[SFTP ${instanceSessionId}] 上传文件成功: ${filename ? joinPath(parentPath, filename) : '(未知文件名)'}`); // 改进日志
|
||||||
|
|
||||||
|
if (newItem && filename) { // 确保 newItem 和 filename 都存在
|
||||||
|
const index = fileList.value.findIndex(item => item.filename === filename);
|
||||||
|
if (index !== -1) {
|
||||||
|
// 文件已存在 (覆盖上传),替换
|
||||||
|
fileList.value.splice(index, 1, newItem);
|
||||||
|
console.log(`[SFTP ${instanceSessionId}] 直接更新被覆盖的文件信息: ${filename}`);
|
||||||
|
} else {
|
||||||
|
// 文件是新建的,插入
|
||||||
|
let insertIndex = 0;
|
||||||
|
while (insertIndex < fileList.value.length && sortFiles(newItem, fileList.value[insertIndex]) > 0) {
|
||||||
|
insertIndex++;
|
||||||
|
}
|
||||||
|
fileList.value.splice(insertIndex, 0, newItem);
|
||||||
|
console.log(`[SFTP ${instanceSessionId}] 直接添加新上传的文件到列表: ${filename}`);
|
||||||
|
}
|
||||||
|
// 更新缓存
|
||||||
|
directoryCache.set(currentPathRef.value, { list: [...fileList.value], timestamp: Date.now() });
|
||||||
|
} else if (!newItem) { // 检查 newItem 是否为 null 或 undefined
|
||||||
|
// 如果后端未能提供更新信息,则刷新
|
||||||
|
const filePathForLog = message.path || '(未知路径)'; // 尝试从 message 获取路径用于日志
|
||||||
|
console.warn(`[SFTP ${instanceSessionId}] Upload success for ${filePathForLog} but no item details received. Reloading.`);
|
||||||
|
invalidateCache(currentPathRef.value);
|
||||||
|
loadDirectory(currentPathRef.value);
|
||||||
|
}
|
||||||
|
// 注意:移除了多余的 else 和 else if (!newItem) 块
|
||||||
|
}; // <--- 确保右花括号在这里
|
||||||
|
|
||||||
const onActionError = (payload: MessagePayload, message: WebSocketMessage) => {
|
const onActionError = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||||
// 类型断言,因为我们知道这些错误的 payload 是 string
|
// 类型断言,因为我们知道这些错误的 payload 是 string
|
||||||
@@ -589,6 +623,7 @@ export function createSftpActionsManager(
|
|||||||
unregisterCallbacks.push(onMessage('sftp:rename:success', onRenameSuccess));
|
unregisterCallbacks.push(onMessage('sftp:rename:success', onRenameSuccess));
|
||||||
unregisterCallbacks.push(onMessage('sftp:chmod:success', onChmodSuccess));
|
unregisterCallbacks.push(onMessage('sftp:chmod:success', onChmodSuccess));
|
||||||
unregisterCallbacks.push(onMessage('sftp:writefile:success', onWriteFileSuccess)); // 使用 onWriteFileSuccess
|
unregisterCallbacks.push(onMessage('sftp:writefile:success', onWriteFileSuccess)); // 使用 onWriteFileSuccess
|
||||||
|
unregisterCallbacks.push(onMessage('sftp:upload:success', onUploadSuccess)); // *** 新增:监听上传成功 ***
|
||||||
unregisterCallbacks.push(onMessage('sftp:mkdir:error', onActionError));
|
unregisterCallbacks.push(onMessage('sftp:mkdir:error', onActionError));
|
||||||
unregisterCallbacks.push(onMessage('sftp:rmdir:error', onActionError));
|
unregisterCallbacks.push(onMessage('sftp:rmdir:error', onActionError));
|
||||||
unregisterCallbacks.push(onMessage('sftp:unlink:error', onActionError));
|
unregisterCallbacks.push(onMessage('sftp:unlink:error', onActionError));
|
||||||
|
|||||||
Reference in New Issue
Block a user