From 9b817488ff0ec16a085fa195b69a8cee189e2157 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 19 Sep 2025 01:41:40 +0800 Subject: [PATCH] Fix file manager refresh state inconsistency Following Linus's "good taste" principles to eliminate race conditions: - Add request ID tracking to prevent concurrent request conflicts - Simplify loadDirectory function by removing complex reconnection logic - Add reconnection lock to prevent concurrent SSH reconnections - Implement 500ms refresh debouncing to prevent spam clicking - Separate concerns: connection management vs file operations Eliminates "special cases" that caused random state corruption. The data structure now properly tracks request lifecycle. Resolves file folder refresh showing stale content issue. --- .../Apps/File Manager/FileManagerModern.tsx | 116 +++++++++--------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/src/ui/Desktop/Apps/File Manager/FileManagerModern.tsx b/src/ui/Desktop/Apps/File Manager/FileManagerModern.tsx index 43c8d3dc..65eb04f9 100644 --- a/src/ui/Desktop/Apps/File Manager/FileManagerModern.tsx +++ b/src/ui/Desktop/Apps/File Manager/FileManagerModern.tsx @@ -65,7 +65,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { const [files, setFiles] = useState([]); const [isLoading, setIsLoading] = useState(false); const [sshSessionId, setSshSessionId] = useState(null); + const [currentRequestId, setCurrentRequestId] = useState(0); + const [isReconnecting, setIsReconnecting] = useState(false); const [searchQuery, setSearchQuery] = useState(""); + const [lastRefreshTime, setLastRefreshTime] = useState(0); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [pinnedFiles, setPinnedFiles] = useState>(new Set()); const [sidebarRefreshTrigger, setSidebarRefreshTrigger] = useState(0); @@ -144,7 +147,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { // 文件列表更新 useEffect(() => { if (sshSessionId) { - loadDirectory(currentPath); + handleRefreshDirectory(); } }, [sshSessionId, currentPath]); @@ -226,61 +229,62 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { return; } + // Generate unique request ID to prevent race conditions + const requestId = Date.now(); + setCurrentRequestId(requestId); + setIsLoading(true); + try { - setIsLoading(true); - console.log("Loading directory:", path, "with session ID:", sshSessionId); + console.log(`[${requestId}] Loading directory:`, path); - // 首先检查SSH连接状态 - try { - const status = await getSSHStatus(sshSessionId); - console.log("SSH connection status:", status); + const response = await listSSHFiles(sshSessionId, path); - if (!status.connected) { - console.log("SSH not connected, attempting to reconnect..."); - await initializeSSHConnection(); - return; // 重连后会触发useEffect重新加载目录 - } - } catch (statusError) { - console.log("Failed to get SSH status, attempting to reconnect..."); - await initializeSSHConnection(); + // Only process response if this is still the latest request + if (requestId !== currentRequestId) { + console.log(`[${requestId}] Request outdated, ignoring response`); return; } - const response = await listSSHFiles(sshSessionId, path); - console.log("Directory response from backend:", response); + console.log(`[${requestId}] Directory response received:`, response); - // 处理新的返回格式 { files: FileItem[], path: string } const files = Array.isArray(response) ? response : response?.files || []; - console.log("Directory contents loaded:", files.length, "items"); - console.log( - "Files with sizes:", - files.map((f) => ({ name: f.name, size: f.size, type: f.type })), - ); - setFiles(files); clearSelection(); - } catch (error: any) { - console.error("Failed to load directory:", error); - // 如果是连接错误,尝试重连 - if ( - error.message?.includes("connection") || - error.message?.includes("established") - ) { - console.log("Connection error detected, attempting to reconnect..."); - await initializeSSHConnection(); - } else { - toast.error( - t("fileManager.failedToLoadDirectory") + - ": " + - (error.message || error), - ); + console.log(`[${requestId}] Directory loaded successfully:`, files.length, "items"); + } catch (error: any) { + // Only handle error if this is still the latest request + if (requestId !== currentRequestId) { + console.log(`[${requestId}] Request outdated, ignoring error`); + return; } + + console.error(`[${requestId}] Failed to load directory:`, error); + toast.error( + t("fileManager.failedToLoadDirectory") + ": " + (error.message || error) + ); } finally { - setIsLoading(false); + // Only clear loading if this is still the latest request + if (requestId === currentRequestId) { + setIsLoading(false); + } } } + // 防抖刷新函数 - 防止疯狂点击 + function handleRefreshDirectory() { + const now = Date.now(); + const DEBOUNCE_MS = 500; // 500ms防抖 + + if (now - lastRefreshTime < DEBOUNCE_MS) { + console.log("Refresh ignored - too frequent"); + return; + } + + setLastRefreshTime(now); + loadDirectory(currentPath); + } + function handleFilesDropped(fileList: FileList) { if (!sshSessionId) { toast.error(t("fileManager.noSSHConnection")); @@ -352,7 +356,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { toast.success( t("fileManager.fileUploadedSuccessfully", { name: file.name }), ); - loadDirectory(currentPath); + handleRefreshDirectory(); } catch (error: any) { if ( error.message?.includes("connection") || @@ -455,7 +459,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { toast.success( t("fileManager.itemsDeletedSuccessfully", { count: files.length }), ); - loadDirectory(currentPath); + handleRefreshDirectory(); clearSelection(); } catch (error: any) { if ( @@ -807,7 +811,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { } // 刷新文件列表 - loadDirectory(currentPath); + handleRefreshDirectory(); clearSelection(); // 清空剪贴板(剪切操作后,复制操作保留剪贴板内容) @@ -931,7 +935,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { } // 刷新文件列表 - loadDirectory(currentPath); + handleRefreshDirectory(); } catch (error: any) { toast.error(`撤销操作失败: ${error.message || "Unknown error"}`); console.error("Undo failed:", error); @@ -942,16 +946,16 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { setEditingFile(file); } - // 确保SSH连接有效 + // 确保SSH连接有效 - 简化版本,防止并发重连 async function ensureSSHConnection() { - if (!sshSessionId || !currentHost) return; + if (!sshSessionId || !currentHost || isReconnecting) return; try { const status = await getSSHStatus(sshSessionId); - console.log("SSH connection status:", status); - if (!status.connected) { - console.log("SSH not connected, attempting to reconnect..."); + if (!status.connected && !isReconnecting) { + setIsReconnecting(true); + console.log("SSH disconnected, reconnecting..."); await connectSSH(sshSessionId, { hostId: currentHost.id, @@ -969,8 +973,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { console.log("SSH reconnection successful"); } } catch (error) { - console.log("SSH connection check/reconnect failed:", error); + console.log("SSH reconnection failed:", error); throw error; + } finally { + setIsReconnecting(false); } } @@ -1041,7 +1047,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { // 清除编辑状态 setEditingFile(null); - loadDirectory(currentPath); + handleRefreshDirectory(); } catch (error: any) { console.error("Rename failed with error:", { error, @@ -1177,7 +1183,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) { toast.success( `成功移动了 ${successCount} 个项目到 ${targetFolder.name}`, ); - loadDirectory(currentPath); + handleRefreshDirectory(); clearSelection(); // 清除选中状态 } } catch (error: any) { @@ -1602,7 +1608,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {