From ea4e2d212485f0ad8341742aab7ceb39b5395438 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 4 Nov 2026 15:30:17 -0600 Subject: [PATCH] fix: remove the partial file when a desktop download is aborted early --- src/backend/plugins/desktop-local/local-fs.ts | 8 +++++++- .../tests/plugins/ctx-desktop-local.test.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/backend/plugins/desktop-local/local-fs.ts b/src/backend/plugins/desktop-local/local-fs.ts index 6b622fe7f..440eff224 100644 --- a/src/backend/plugins/desktop-local/local-fs.ts +++ b/src/backend/plugins/desktop-local/local-fs.ts @@ -560,7 +560,13 @@ export async function openDownload( } }, abort: async () => { - stream.destroy(); + // The file opens in the background, so wait for the stream to close + // or a late open would leave the partial file behind. + await new Promise((resolve) => { + if (stream.closed) return resolve(); + stream.once("close", () => resolve()); + stream.destroy(); + }); await fsp.rm(partialPath, { force: true }).catch(() => {}); release(); }, diff --git a/src/backend/tests/plugins/ctx-desktop-local.test.ts b/src/backend/tests/plugins/ctx-desktop-local.test.ts index 6e959c524..5b54b4f0c 100644 --- a/src/backend/tests/plugins/ctx-desktop-local.test.ts +++ b/src/backend/tests/plugins/ctx-desktop-local.test.ts @@ -186,6 +186,21 @@ describe("ctx.desktop.local", () => { expect(fs.readdirSync(dir)).toEqual([]); }); + it("leaves no partial file when aborted before the file has opened", async () => { + grants.set("demo", ["desktop:local"]); + const ctx = contextFor("demo", ["desktop:local"]); + for (let i = 0; i < 20; i++) { + const download = await ctx.desktop.local.fs.openDownload({ + rootPath: dir, + destPath: path.join(dir, `early-${i}.txt`), + transferId: `early-${i}`, + }); + await download.abort(); + } + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(fs.readdirSync(dir)).toEqual([]); + }); + it("sends trash and open through Electron main and audits each call", async () => { grants.set("demo", ["desktop:local"]); const ctx = contextFor("demo", ["desktop:local"]);