FIX: Automatically cleanup deleted files from recent/pinned lists
File Cleanup Implementation: - Detect file-not-found errors when opening files from recent/pinned lists - Automatically remove missing files from both recent and pinned file lists - Refresh sidebar to reflect updated lists immediately after cleanup - Prevent error dialogs from appearing when files are successfully cleaned up Backend Improvements: - Enhanced SSH file manager to return proper 404 status for missing files - Added fileNotFound flag in error responses for better error detection - Improved error categorization for file access failures Frontend Error Handling: - Added onFileNotFound callback prop to FileWindow component - Implemented handleFileNotFound function in FileManagerModern - Enhanced error detection logic to catch various "file not found" scenarios - Better error messages with internationalization support Translation Additions: - fileNotFoundAndRemoved: Notify user when file is cleaned up - failedToLoadFile: Generic file loading error message - serverErrorOccurred: Server error fallback message - Chinese translations for all new error messages Technical Details: - Uses existing removeRecentFile and removePinnedFile API calls - Triggers sidebar refresh via setSidebarRefreshTrigger - Maintains backward compatibility with existing error handling - Preserves error logging for debugging purposes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -622,9 +622,19 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
|
|||||||
fileLogger.error(
|
fileLogger.error(
|
||||||
`SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`,
|
`SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check if it's a "file not found" error
|
||||||
|
const isFileNotFound =
|
||||||
|
errorData.includes("No such file or directory") ||
|
||||||
|
errorData.includes("cannot access") ||
|
||||||
|
errorData.includes("not found");
|
||||||
|
|
||||||
return res
|
return res
|
||||||
.status(500)
|
.status(isFileNotFound ? 404 : 500)
|
||||||
.json({ error: `Command failed: ${errorData}` });
|
.json({
|
||||||
|
error: `Command failed: ${errorData}`,
|
||||||
|
fileNotFound: isFileNotFound
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ content: data, path: filePath });
|
res.json({ content: data, path: filePath });
|
||||||
|
|||||||
@@ -860,6 +860,9 @@
|
|||||||
"modified": "Modified",
|
"modified": "Modified",
|
||||||
"largeFileWarning": "Large File Warning",
|
"largeFileWarning": "Large File Warning",
|
||||||
"largeFileWarningDesc": "This file is {{size}} in size, which may cause performance issues when opened as text.",
|
"largeFileWarningDesc": "This file is {{size}} in size, which may cause performance issues when opened as text.",
|
||||||
|
"fileNotFoundAndRemoved": "File \"{{name}}\" not found and has been removed from recent/pinned files",
|
||||||
|
"failedToLoadFile": "Failed to load file: {{error}}",
|
||||||
|
"serverErrorOccurred": "Server error occurred. Please try again later.",
|
||||||
"fileSavedSuccessfully": "File saved successfully",
|
"fileSavedSuccessfully": "File saved successfully",
|
||||||
"autoSaveFailed": "Auto-save failed",
|
"autoSaveFailed": "Auto-save failed",
|
||||||
"fileAutoSaved": "File auto-saved",
|
"fileAutoSaved": "File auto-saved",
|
||||||
|
|||||||
@@ -769,6 +769,9 @@
|
|||||||
"modified": "修改时间",
|
"modified": "修改时间",
|
||||||
"largeFileWarning": "大文件警告",
|
"largeFileWarning": "大文件警告",
|
||||||
"largeFileWarningDesc": "此文件大小为 {{size}},以文本形式打开可能会导致性能问题。",
|
"largeFileWarningDesc": "此文件大小为 {{size}},以文本形式打开可能会导致性能问题。",
|
||||||
|
"fileNotFoundAndRemoved": "文件 \"{{name}}\" 未找到,已从最近访问/固定文件中移除",
|
||||||
|
"failedToLoadFile": "加载文件失败:{{error}}",
|
||||||
|
"serverErrorOccurred": "服务器错误,请稍后重试。",
|
||||||
"failedToDeleteItem": "删除项目失败",
|
"failedToDeleteItem": "删除项目失败",
|
||||||
"itemRenamedSuccessfully": "{{type}}重命名成功",
|
"itemRenamedSuccessfully": "{{type}}重命名成功",
|
||||||
"failedToRenameItem": "重命名项目失败",
|
"failedToRenameItem": "重命名项目失败",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
addRecentFile,
|
addRecentFile,
|
||||||
addPinnedFile,
|
addPinnedFile,
|
||||||
removePinnedFile,
|
removePinnedFile,
|
||||||
|
removeRecentFile,
|
||||||
addFolderShortcut,
|
addFolderShortcut,
|
||||||
getPinnedFiles,
|
getPinnedFiles,
|
||||||
} from "@/ui/main-axios.ts";
|
} from "@/ui/main-axios.ts";
|
||||||
@@ -757,6 +758,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
sshHost={currentHost}
|
sshHost={currentHost}
|
||||||
initialX={offsetX}
|
initialX={offsetX}
|
||||||
initialY={offsetY}
|
initialY={offsetY}
|
||||||
|
onFileNotFound={handleFileNotFound}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1553,6 +1555,26 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
await handleFileOpen(file);
|
await handleFileOpen(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle file not found - cleanup from recent and pinned lists
|
||||||
|
async function handleFileNotFound(file: FileItem) {
|
||||||
|
if (!currentHost) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Remove from recent files
|
||||||
|
await removeRecentFile(currentHost.id, file.path);
|
||||||
|
|
||||||
|
// Remove from pinned files
|
||||||
|
await removePinnedFile(currentHost.id, file.path);
|
||||||
|
|
||||||
|
// Trigger sidebar refresh to update the UI
|
||||||
|
setSidebarRefreshTrigger(prev => prev + 1);
|
||||||
|
|
||||||
|
console.log(`Cleaned up missing file from recent/pinned lists: ${file.path}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to cleanup missing file:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Clear createIntent when path changes
|
// Clear createIntent when path changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
connectSSH,
|
connectSSH,
|
||||||
} from "@/ui/main-axios";
|
} from "@/ui/main-axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface FileItem {
|
interface FileItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -43,6 +44,7 @@ interface FileWindowProps {
|
|||||||
sshHost: SSHHost;
|
sshHost: SSHHost;
|
||||||
initialX?: number;
|
initialX?: number;
|
||||||
initialY?: number;
|
initialY?: number;
|
||||||
|
onFileNotFound?: (file: FileItem) => void; // Callback for when file is not found
|
||||||
// readOnly parameter removed, determined internally by FileViewer based on file type
|
// readOnly parameter removed, determined internally by FileViewer based on file type
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ export function FileWindow({
|
|||||||
sshHost,
|
sshHost,
|
||||||
initialX = 100,
|
initialX = 100,
|
||||||
initialY = 100,
|
initialY = 100,
|
||||||
|
onFileNotFound,
|
||||||
}: FileWindowProps) {
|
}: FileWindowProps) {
|
||||||
const {
|
const {
|
||||||
closeWindow,
|
closeWindow,
|
||||||
@@ -62,6 +65,8 @@ export function FileWindow({
|
|||||||
windows,
|
windows,
|
||||||
} = useWindowManager();
|
} = useWindowManager();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [content, setContent] = useState<string>("");
|
const [content, setContent] = useState<string>("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isEditable, setIsEditable] = useState(false);
|
const [isEditable, setIsEditable] = useState(false);
|
||||||
@@ -192,9 +197,26 @@ export function FileWindow({
|
|||||||
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
|
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
// Check if file not found (common error messages from cat command)
|
||||||
`Failed to load file: ${error.message || errorData?.error || "Unknown error"}`,
|
const errorMessage = errorData?.error || error.message || "Unknown error";
|
||||||
);
|
const isFileNotFound =
|
||||||
|
errorData?.fileNotFound ||
|
||||||
|
error.response?.status === 404 ||
|
||||||
|
errorMessage.includes("No such file or directory") ||
|
||||||
|
errorMessage.includes("cannot access") ||
|
||||||
|
errorMessage.includes("not found");
|
||||||
|
|
||||||
|
if (isFileNotFound && onFileNotFound) {
|
||||||
|
// Notify parent component about the missing file for cleanup
|
||||||
|
onFileNotFound(file);
|
||||||
|
toast.error(t("fileManager.fileNotFoundAndRemoved", { name: file.name }));
|
||||||
|
} else {
|
||||||
|
toast.error(t("fileManager.failedToLoadFile", {
|
||||||
|
error: errorMessage.includes("Server error occurred") ?
|
||||||
|
t("fileManager.serverErrorOccurred") :
|
||||||
|
errorMessage
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user