fix: use streaming download for large files in file manager

This commit is contained in:
ZacharyZcR
2026-05-13 16:52:56 +08:00
parent ec87f8a4d1
commit fafc1437cd
3 changed files with 73 additions and 31 deletions
+49
View File
@@ -4870,6 +4870,55 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => {
});
});
app.post("/ssh/file_manager/ssh/downloadFileStream", async (req, res) => {
const { sessionId, path: filePath } = req.body;
const userId = (req as AuthenticatedRequest).userId;
if (!sessionId || !filePath) {
return res.status(400).json({ error: "Missing download parameters" });
}
const sshConn = sshSessions[sessionId];
if (!sshConn?.isConnected) {
return res.status(400).json({ error: "SSH session not found or not connected" });
}
if (!verifySessionOwnership(sshConn, userId)) {
return res.status(403).json({ error: "Session access denied" });
}
sshConn.lastActive = Date.now();
try {
const sftp = await getSessionSftp(sshConn);
const stats = await new Promise<{ size: number; isFile: () => boolean }>((resolve, reject) => {
sftp.stat(filePath, (err, s) => (err ? reject(err) : resolve(s)));
});
if (!stats.isFile()) {
return res.status(400).json({ error: "Cannot download directories" });
}
const fileName = filePath.split("/").pop() || "download";
res.setHeader("Content-Type", "application/octet-stream");
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`);
res.setHeader("Content-Length", String(stats.size));
const readStream = sftp.createReadStream(filePath);
readStream.on("error", (err) => {
if (!res.headersSent) {
res.status(500).json({ error: `Download failed: ${err.message}` });
} else {
res.destroy();
}
});
readStream.pipe(res);
} catch (err) {
if (!res.headersSent) {
res.status(500).json({ error: `Download failed: ${(err as Error).message}` });
}
}
});
/**
* @openapi
* /ssh/file_manager/ssh/copyItem:
+5 -31
View File
@@ -864,38 +864,12 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
try {
await ensureSSHConnection();
const response = await downloadSSHFile(sshSessionId, file.path);
const { downloadSSHFileStream } = await import("@/main-axios.ts");
await downloadSSHFileStream(sshSessionId, file.path);
const content = response?.content as string | undefined;
const mimeType = response?.mimeType as string | undefined;
const fileName = response?.fileName as string | undefined;
if (content) {
const byteCharacters = atob(content);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], {
type: mimeType || "application/octet-stream",
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName || file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
toast.success(
t("fileManager.fileDownloadedSuccessfully", { name: file.name }),
);
} else {
toast.error(t("fileManager.failedToDownloadFile"));
}
toast.success(
t("fileManager.fileDownloadedSuccessfully", { name: file.name }),
);
} catch (error: unknown) {
const err = error instanceof Error ? error : null;
if (
+19
View File
@@ -2082,6 +2082,25 @@ export async function downloadSSHFile(
}
}
export async function downloadSSHFileStream(
sessionId: string,
filePath: string,
): Promise<void> {
const response = await fileManagerApi.post(
"/ssh/downloadFileStream",
{ sessionId, path: filePath },
{ responseType: "blob" },
);
const blob = response.data as Blob;
const fileName = filePath.split("/").pop() || "download";
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}
export async function createSSHFile(
sessionId: string,
path: string,