Skip to content

Commit 27190c5

Browse files
committed
TT-6933 show missing files list
TT-6934 switch to nodejs download to fix files with apostrophes
1 parent daad89a commit 27190c5

File tree

12 files changed

+277
-89
lines changed

12 files changed

+277
-89
lines changed

localization/TranscriberAdmin-en-1.2.xliff

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7116,6 +7116,13 @@
71167116
<context context-type="sourcefile">ProjectDownloadAlert.tsx</context>
71177117
</context-group>
71187118
</trans-unit>
7119+
<trans-unit id="projectDownload.missingFileList">
7120+
<source>Show missing files list</source>
7121+
<target/>
7122+
<context-group>
7123+
<context context-type="sourcefile">ProjectDownloadAlert.tsx</context>
7124+
</context-group>
7125+
</trans-unit>
71197126
<trans-unit id="projectDownload.projects">
71207127
<source>Projects</source>
71217128
<target/>

localization/TranscriberAdmin-en.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6100,6 +6100,12 @@
61006100
<target/>
61016101
</segment>
61026102
</unit>
6103+
<unit id="projectDownload.missingFileList">
6104+
<segment>
6105+
<source>Show missing file list</source>
6106+
<target/>
6107+
</segment>
6108+
</unit>
61036109
<unit id="projectDownload.projects">
61046110
<segment>
61056111
<source>Projects</source>

src/main/downloadFile.ts

Lines changed: 98 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import request from 'request';
21
import fs from 'fs-extra';
2+
import https from 'https';
3+
import http from 'http';
4+
import { URL } from 'url';
35
/**
46
* Promise based download file method
7+
* Uses native Node.js http/https modules to preserve S3 pre-signed URLs exactly as-is
58
* See: https://ourcodeworld.com/articles/read/228/how-to-download-a-webfile-with-electron-save-it-and-show-download-progress
69
*/
710

@@ -24,63 +27,105 @@ export const downloadFile = (
2427
let total_bytes = 0;
2528
let error: Error | null = null;
2629

27-
const req = request({
28-
method: 'GET',
29-
uri: url,
30-
});
30+
try {
31+
// Parse URL to get components, but use the original URL string for the request
32+
// to preserve the exact path and query string (important for S3 signatures)
33+
const urlObj = new URL(url);
34+
const isHttps = urlObj.protocol === 'https:';
35+
const client = isHttps ? https : http;
3136

32-
const out = fs.createWriteStream(localPath);
33-
req.pipe(out);
37+
// Use the original URL string to preserve exact path encoding
38+
// This is critical for S3 pre-signed URLs where the signature depends on the exact URL
39+
const options = {
40+
hostname: urlObj.hostname,
41+
port: urlObj.port || (isHttps ? 443 : 80),
42+
path: urlObj.pathname + urlObj.search, // Preserve path and query exactly
43+
method: 'GET',
44+
headers: {
45+
'User-Agent': 'Audio-Project-Manager',
46+
},
47+
};
3448

35-
req.on('response', (data: { headers: { 'content-length': string } }) => {
36-
total_bytes = parseInt(data.headers['content-length'] || '');
37-
if (isNaN(total_bytes)) {
38-
error = new Error('Invalid content-length') as any;
39-
}
40-
if (token) {
41-
downloadMap.set(token, {
42-
received: 0,
43-
total: total_bytes,
44-
error: error,
49+
const out = fs.createWriteStream(localPath);
50+
const req = client.request(options, (res) => {
51+
if (res.statusCode && res.statusCode >= 400) {
52+
error = new Error(
53+
`HTTP ${res.statusCode}: ${res.statusMessage}`
54+
) as any;
55+
res.resume(); // Consume response to free up memory
56+
out.destroy();
57+
if (key) {
58+
const status = downloadMap.get(key);
59+
downloadMap.set(key, { ...status, error });
60+
}
61+
reject(error);
62+
return;
63+
}
64+
65+
total_bytes = parseInt(res.headers['content-length'] || '0', 10);
66+
if (token) {
67+
downloadMap.set(token, {
68+
received: 0,
69+
total: total_bytes,
70+
error: error,
71+
});
72+
key = token;
73+
}
74+
75+
res.on('data', (chunk: Buffer) => {
76+
received_bytes += chunk.length;
77+
if (key) {
78+
const status = downloadMap.get(key);
79+
downloadMap.set(key, { ...status, received: received_bytes });
80+
}
81+
});
82+
83+
res.on('end', () => {
84+
out.end();
4585
});
46-
key = token;
47-
}
48-
});
4986

50-
req.on('data', (chunk: { length: number }) => {
51-
received_bytes += chunk.length;
52-
if (key) {
53-
const status = downloadMap.get(key);
54-
downloadMap.set(key, { ...status, received: received_bytes });
55-
}
56-
});
87+
res.pipe(out);
88+
});
89+
90+
req.on('error', (err: Error) => {
91+
error = err;
92+
out.destroy();
93+
if (key) {
94+
const status = downloadMap.get(key);
95+
downloadMap.set(key, { ...status, error });
96+
}
97+
reject(error);
98+
});
99+
100+
out.on('finish', () => {
101+
let err = error;
102+
if (key) {
103+
err = downloadMap.get(key).error;
104+
}
105+
if (err) {
106+
fs.unlink(localPath).catch(() => {
107+
// Ignore unlink errors
108+
});
109+
reject(err);
110+
} else {
111+
resolve(void 0);
112+
}
113+
});
57114

58-
req.on('error', (err: Error) => {
59-
const error = err;
60-
if (key) {
61-
const status = downloadMap.get(key);
62-
downloadMap.set(key, { ...status, error });
63-
}
64-
});
115+
out.on('error', (err: Error) => {
116+
error = err;
117+
req.destroy();
118+
if (key) {
119+
const status = downloadMap.get(key);
120+
downloadMap.set(key, { ...status, error });
121+
}
122+
reject(error);
123+
});
65124

66-
out.on('finish', () => {
67-
let err = error;
68-
if (key) {
69-
err = downloadMap.get(key).error;
70-
}
71-
if (err) {
72-
fs.unlink(localPath);
73-
reject(err);
74-
} else {
75-
resolve(void 0);
76-
}
77-
});
78-
out.on('error', (err: Error) => {
79-
const error = err;
80-
if (key) {
81-
const status = downloadMap.get(key);
82-
downloadMap.set(key, { ...status, error });
83-
}
84-
});
125+
req.end();
126+
} catch (err) {
127+
const error = err as Error;
128+
reject(error);
129+
}
85130
});
86131
};

0 commit comments

Comments
 (0)