Skip to content

Commit 46f460f

Browse files
authored
Merge pull request #44 from MiniMax-AI-Dev/fix/download-stability
fix(download): add error handling, backpressure, and flush-wait
2 parents da70ccb + 89afbb7 commit 46f460f

File tree

1 file changed

+28
-6
lines changed

1 file changed

+28
-6
lines changed

src/files/download.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { createWriteStream } from 'fs';
1+
import { createWriteStream, unlinkSync } from 'fs';
22
import { createProgressBar } from '../output/progress';
3+
import { CLIError } from '../errors/base';
4+
import { ExitCode } from '../errors/codes';
35

46
export async function downloadFile(
57
url: string,
@@ -9,33 +11,53 @@ export async function downloadFile(
911
const res = await fetch(url);
1012

1113
if (!res.ok) {
12-
throw new Error(`Download failed: HTTP ${res.status}`);
14+
throw new CLIError(`Download failed: HTTP ${res.status}`, ExitCode.GENERAL);
1315
}
1416

1517
const contentLength = Number(res.headers.get('content-length') || 0);
1618
const reader = res.body?.getReader();
17-
if (!reader) throw new Error('No response body');
19+
if (!reader) throw new CLIError('No response body', ExitCode.GENERAL);
1820

1921
const writer = createWriteStream(destPath);
2022
const progress = contentLength > 0 && !opts?.quiet
2123
? createProgressBar(contentLength, 'Downloading')
2224
: null;
2325

2426
let received = 0;
27+
let completed = false;
2528

2629
try {
30+
const writeError = new Promise<never>((_, reject) => {
31+
writer.on('error', reject);
32+
});
33+
2734
while (true) {
28-
const { done, value } = await reader.read();
35+
const { done, value } = await Promise.race([
36+
reader.read(),
37+
writeError,
38+
]) as ReadableStreamReadResult<Uint8Array>;
2939
if (done) break;
3040

31-
writer.write(value);
41+
const ok = writer.write(value);
42+
if (!ok) await new Promise(r => writer.once('drain', r));
43+
3244
received += value.byteLength;
3345
progress?.update(received);
3446
}
47+
completed = true;
3548
} finally {
3649
reader.releaseLock();
37-
writer.end();
3850
progress?.finish();
51+
52+
await new Promise<void>((resolve, reject) => {
53+
writer.on('finish', resolve);
54+
writer.on('error', reject);
55+
writer.end();
56+
});
57+
58+
if (!completed) {
59+
try { unlinkSync(destPath); } catch { /* best effort */ }
60+
}
3961
}
4062

4163
return { size: received };

0 commit comments

Comments
 (0)