This commit is contained in:
Baobhan Sith
2025-04-18 15:24:53 +08:00
parent 06723fc145
commit 32e522f9a4
2 changed files with 341 additions and 29 deletions
@@ -58,6 +58,10 @@ export function createSftpActionsManager(
// 用于存储注销函数的数组
const unregisterCallbacks: (() => void)[] = [];
// *** 新增:缓存定义 ***
const directoryCache = new Map<string, { list: FileListItem[], timestamp: number }>();
const CACHE_EXPIRY_MS = 5000; // 缓存 5 秒
// 清理函数,用于注销所有消息处理器
const cleanup = () => {
console.log(`[SFTP ${instanceSessionId}] Cleaning up message handlers.`);
@@ -71,20 +75,30 @@ export function createSftpActionsManager(
// --- Action Methods ---
const loadDirectory = (path: string) => {
// *** 新增:检查缓存 ***
const cachedData = directoryCache.get(path);
const now = Date.now();
if (cachedData && (now - cachedData.timestamp < CACHE_EXPIRY_MS)) {
console.log(`[SFTP ${instanceSessionId}] 使用缓存加载目录: ${path}`);
fileList.value = cachedData.list; // 直接使用缓存数据
isLoading.value = false;
currentPathRef.value = path; // 确保当前路径更新
return; // 从缓存加载,不再发送请求
}
if (!isSftpReady.value) {
// 使用通知 store 显示错误
// *** 新增:如果已经在加载,则阻止新的加载请求 ***
if (isLoading.value) {
console.warn(`[SFTP ${instanceSessionId}] 尝试加载目录 ${path} 但已在加载中。`);
return;
}
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
isLoading.value = false;
fileList.value = [];
fileList.value = []; // 清空列表,因为 SFTP 未就绪
console.warn(`[SFTP ${instanceSessionId}] 尝试加载目录 ${path} 但 SFTP 未就绪。`); // 日志改为中文
return;
}
// *** 新增:如果已经在加载,则阻止新的加载请求 ***
if (isLoading.value) {
console.warn(`[SFTP ${instanceSessionId}] 尝试加载目录 ${path} 但已在加载中。`);
return;
}
console.log(`[SFTP ${instanceSessionId}] 正在加载目录: ${path}`); // 日志改为中文
isLoading.value = true;
@@ -266,12 +280,54 @@ export function createSftpActionsManager(
// 类型断言,因为我们知道 readdir:success 的 payload 是 FileListItem[]
const fileListPayload = payload as FileListItem[];
if (message.path === currentPathRef.value) {
console.log(`[SFTP ${instanceSessionId}] 收到目录 ${message.path} 的文件列表`); // 日志改为中文
fileList.value = fileListPayload.sort(sortFiles);
console.log(`[SFTP ${instanceSessionId}] 收到目录 ${message.path} 的文件列表`);
const newList = fileListPayload.sort(sortFiles); // 先排序
// *** 新增:更新缓存 ***
directoryCache.set(message.path, { list: newList, timestamp: Date.now() });
// *** 新增:增量更新逻辑 ***
const oldList = fileList.value; // 当前显示的列表
const newListMap = new Map(newList.map(item => [item.filename, item]));
const oldListMap = new Map(oldList.map(item => [item.filename, item]));
// 1. 找出需要删除的项
const itemsToRemove: number[] = []; // 存储要删除的索引
for (let i = oldList.length - 1; i >= 0; i--) {
if (!newListMap.has(oldList[i].filename)) {
itemsToRemove.push(i);
}
}
// 从后往前删除,避免索引错乱
itemsToRemove.forEach(index => oldList.splice(index, 1));
// 2. 找出需要更新和添加的项
for (let i = 0; i < newList.length; i++) {
const newItem = newList[i];
const oldItem = oldListMap.get(newItem.filename);
if (oldItem) {
// 更新现有项 (比较关键属性,避免不必要的更新)
const oldIndex = oldList.findIndex(item => item.filename === newItem.filename);
// 简单比较 attrs 的 JSON 字符串,如果不同则更新
if (oldIndex !== -1 && JSON.stringify(oldList[oldIndex].attrs) !== JSON.stringify(newItem.attrs)) {
oldList.splice(oldIndex, 1, newItem); // 使用 splice 替换,确保响应性
console.log(`[SFTP ${instanceSessionId}] 更新文件: ${newItem.filename}`);
}
} else {
// 添加新项 (找到合适的插入位置以保持排序)
let insertIndex = 0;
while (insertIndex < oldList.length && sortFiles(newItem, oldList[insertIndex]) > 0) {
insertIndex++;
}
oldList.splice(insertIndex, 0, newItem);
console.log(`[SFTP ${instanceSessionId}] 添加文件: ${newItem.filename}`);
}
}
isLoading.value = false;
// error.value = null; // 不再需要
} else {
console.log(`[SFTP ${instanceSessionId}] 忽略目录 ${message.path} 的 readdir 成功消息 (当前: ${currentPathRef.value})`); // 日志改为中文
console.log(`[SFTP ${instanceSessionId}] 忽略目录 ${message.path} 的 readdir 成功消息 (当前: ${currentPathRef.value})`);
}
};
@@ -286,12 +342,191 @@ export function createSftpActionsManager(
}
};
const onActionSuccessRefresh = (payload: MessagePayload, message: WebSocketMessage) => {
console.log(`[SFTP ${instanceSessionId}] 操作 ${message.type} 成功。正在刷新当前目录: ${currentPathRef.value}`); // 日志改为中文
loadDirectory(currentPathRef.value);
// error.value = null; // 不再需要
// 移除通用的 onActionSuccessRefresh
// *** 新增:具体操作成功后的处理函数 ***
// 使缓存失效的辅助函数
const invalidateCache = (path: string) => {
if (directoryCache.has(path)) {
directoryCache.delete(path);
console.log(`[SFTP ${instanceSessionId}] 目录缓存已失效: ${path}`);
}
};
// 处理创建目录成功
const onMkdirSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
const newItem = payload as FileListItem | null; // 后端现在会发送 FileListItem 或 null
const parentPath = message.path?.substring(0, message.path.lastIndexOf('/')) || '/';
console.log(`[SFTP ${instanceSessionId}] 创建目录成功: ${message.path}`);
if (parentPath === currentPathRef.value) {
if (newItem) {
// 将新项插入排序列表
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}] 直接添加新目录到列表: ${newItem.filename}`);
// 更新缓存
directoryCache.set(currentPathRef.value, { list: [...fileList.value], timestamp: Date.now() });
} else {
// 如果后端未能提供新项信息,则刷新
console.warn(`[SFTP ${instanceSessionId}] Mkdir success for ${message.path} but no item details received. Reloading.`);
invalidateCache(currentPathRef.value);
loadDirectory(currentPathRef.value);
}
} else {
// 如果创建在其他目录,只需使那个目录的缓存失效
invalidateCache(parentPath);
}
};
// 处理删除目录/文件成功
const onRemoveSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
const removedPath = message.path;
const parentPath = removedPath?.substring(0, removedPath.lastIndexOf('/')) || '/';
const removedFilename = removedPath?.substring(removedPath.lastIndexOf('/') + 1);
console.log(`[SFTP ${instanceSessionId}] 删除成功: ${removedPath}`);
if (parentPath === currentPathRef.value && removedFilename) {
const index = fileList.value.findIndex(item => item.filename === removedFilename);
if (index !== -1) {
fileList.value.splice(index, 1);
console.log(`[SFTP ${instanceSessionId}] 从列表中移除: ${removedFilename}`);
}
invalidateCache(currentPathRef.value); // 使当前目录缓存失效
} else {
// 如果删除的是其他目录的内容,只需使那个目录的缓存失效
invalidateCache(parentPath);
}
};
// 处理重命名成功
const onRenameSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
// 后端现在发送 { oldPath: string, newPath: string, newItem: FileListItem | null }
const renamePayload = payload as { oldPath: string, newPath: string, newItem: FileListItem | null };
const oldParentPath = renamePayload.oldPath.substring(0, renamePayload.oldPath.lastIndexOf('/')) || '/';
const newParentPath = renamePayload.newPath.substring(0, renamePayload.newPath.lastIndexOf('/')) || '/';
const oldFilename = renamePayload.oldPath.substring(renamePayload.oldPath.lastIndexOf('/') + 1);
const newItem = renamePayload.newItem;
console.log(`[SFTP ${instanceSessionId}] 重命名成功: ${renamePayload.oldPath} -> ${renamePayload.newPath}`);
if (oldParentPath === currentPathRef.value && newParentPath === currentPathRef.value) {
const oldIndex = fileList.value.findIndex(item => item.filename === oldFilename);
if (oldIndex !== -1) {
fileList.value.splice(oldIndex, 1); // 先移除旧项
if (newItem) {
// 插入新项到正确位置
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}] 直接更新重命名项: ${oldFilename} -> ${newItem.filename}`);
// 更新缓存
directoryCache.set(currentPathRef.value, { list: [...fileList.value], timestamp: Date.now() });
} else {
// 如果后端未能提供新项信息,则刷新
console.warn(`[SFTP ${instanceSessionId}] Rename success for ${renamePayload.newPath} but no item details received. Reloading.`);
invalidateCache(currentPathRef.value);
loadDirectory(currentPathRef.value);
}
} else {
// 旧文件不在当前列表,可能列表已过时,刷新
console.warn(`[SFTP ${instanceSessionId}] Rename success but old item ${oldFilename} not found in list. Reloading.`);
invalidateCache(currentPathRef.value);
loadDirectory(currentPathRef.value);
}
} else {
// 如果涉及不同目录,使两个目录的缓存都失效
invalidateCache(oldParentPath);
invalidateCache(newParentPath);
// 如果当前目录是其中之一,则刷新
if (currentPathRef.value === oldParentPath || currentPathRef.value === newParentPath) {
loadDirectory(currentPathRef.value);
}
}
};
// 处理修改权限成功
const onChmodSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
const updatedItem = payload as FileListItem | null; // 后端现在会发送 FileListItem 或 null
const targetPath = message.path;
const parentPath = targetPath?.substring(0, targetPath.lastIndexOf('/')) || '/';
const filename = targetPath?.substring(targetPath.lastIndexOf('/') + 1);
console.log(`[SFTP ${instanceSessionId}] 修改权限成功: ${targetPath}`);
if (parentPath === currentPathRef.value && filename) {
if (updatedItem) {
const index = fileList.value.findIndex(item => item.filename === filename);
if (index !== -1) {
fileList.value.splice(index, 1, updatedItem); // 使用 splice 替换以确保响应性
console.log(`[SFTP ${instanceSessionId}] 直接更新权限: ${filename}`);
// 更新缓存
directoryCache.set(currentPathRef.value, { list: [...fileList.value], timestamp: Date.now() });
} else {
// 文件不在列表,可能列表已过时,刷新
console.warn(`[SFTP ${instanceSessionId}] Chmod success but item ${filename} not found in list. Reloading.`);
invalidateCache(currentPathRef.value);
loadDirectory(currentPathRef.value);
}
} else {
// 如果后端未能提供更新信息,则刷新
console.warn(`[SFTP ${instanceSessionId}] Chmod success for ${targetPath} but no item details received. Reloading.`);
invalidateCache(currentPathRef.value);
loadDirectory(currentPathRef.value);
}
} else {
// 如果修改的是其他目录的内容,只需使那个目录的缓存失效
invalidateCache(parentPath);
}
};
// 处理写入文件成功 (新建或修改)
const onWriteFileSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
const updatedItem = payload as FileListItem | null; // 后端现在会发送 FileListItem 或 null
const filePath = message.path;
const parentPath = filePath?.substring(0, filePath.lastIndexOf('/')) || '/';
const filename = filePath?.substring(filePath.lastIndexOf('/') + 1);
console.log(`[SFTP ${instanceSessionId}] 写入文件成功: ${filePath}`);
if (parentPath === currentPathRef.value && filename) {
if (updatedItem) {
const index = fileList.value.findIndex(item => item.filename === filename);
if (index !== -1) {
// 文件已存在,替换
fileList.value.splice(index, 1, updatedItem);
console.log(`[SFTP ${instanceSessionId}] 直接更新文件信息: ${filename}`);
} else {
// 文件是新建的,插入
let insertIndex = 0;
while (insertIndex < fileList.value.length && sortFiles(updatedItem, fileList.value[insertIndex]) > 0) {
insertIndex++;
}
fileList.value.splice(insertIndex, 0, updatedItem);
console.log(`[SFTP ${instanceSessionId}] 直接添加新文件到列表: ${filename}`);
}
// 更新缓存
directoryCache.set(currentPathRef.value, { list: [...fileList.value], timestamp: Date.now() });
} else {
// 如果后端未能提供更新信息,则刷新
console.warn(`[SFTP ${instanceSessionId}] WriteFile success for ${filePath} but no item details received. Reloading.`);
invalidateCache(currentPathRef.value);
loadDirectory(currentPathRef.value);
}
} else {
// 如果写入的是其他目录的内容,只需使那个目录的缓存失效
invalidateCache(parentPath);
}
};
const onActionError = (payload: MessagePayload, message: WebSocketMessage) => {
// 类型断言,因为我们知道这些错误的 payload 是 string
const errorPayload = payload as string;
@@ -312,12 +547,13 @@ export function createSftpActionsManager(
// --- Register Handlers & Store Unregister Callbacks ---
unregisterCallbacks.push(onMessage('sftp:readdir:success', onSftpReaddirSuccess));
unregisterCallbacks.push(onMessage('sftp:readdir:error', onSftpReaddirError));
unregisterCallbacks.push(onMessage('sftp:mkdir:success', onActionSuccessRefresh));
unregisterCallbacks.push(onMessage('sftp:rmdir:success', onActionSuccessRefresh));
unregisterCallbacks.push(onMessage('sftp:unlink:success', onActionSuccessRefresh));
unregisterCallbacks.push(onMessage('sftp:rename:success', onActionSuccessRefresh));
unregisterCallbacks.push(onMessage('sftp:chmod:success', onActionSuccessRefresh));
unregisterCallbacks.push(onMessage('sftp:writefile:success', onActionSuccessRefresh));
// *** 修改:绑定到新的具体处理函数 ***
unregisterCallbacks.push(onMessage('sftp:mkdir:success', onMkdirSuccess));
unregisterCallbacks.push(onMessage('sftp:rmdir:success', onRemoveSuccess)); // 使用 onRemoveSuccess
unregisterCallbacks.push(onMessage('sftp:unlink:success', onRemoveSuccess)); // 使用 onRemoveSuccess
unregisterCallbacks.push(onMessage('sftp:rename:success', onRenameSuccess));
unregisterCallbacks.push(onMessage('sftp:chmod:success', onChmodSuccess));
unregisterCallbacks.push(onMessage('sftp:writefile:success', onWriteFileSuccess)); // 使用 onWriteFileSuccess
unregisterCallbacks.push(onMessage('sftp:mkdir:error', onActionError));
unregisterCallbacks.push(onMessage('sftp:rmdir:error', onActionError));
unregisterCallbacks.push(onMessage('sftp:unlink:error', onActionError));