|
1 | 1 | import * as child_process from 'child_process';
|
2 | 2 | import * as fs from 'fs';
|
3 |
| -import * as http from 'http'; |
4 | 3 | import * as https from 'https';
|
5 | 4 | import * as os from 'os';
|
6 |
| -import { extname } from 'path'; |
7 |
| -import { promisify } from 'util'; |
8 |
| -import { OutputChannel, ProgressLocation, window, workspace, WorkspaceFolder } from 'vscode'; |
| 5 | +import { OutputChannel, workspace, WorkspaceFolder } from 'vscode'; |
9 | 6 | import { Logger } from 'vscode-languageclient';
|
10 | 7 | import * as which from 'which';
|
11 |
| -import * as yazul from 'yauzl'; |
12 |
| -import { createGunzip } from 'zlib'; |
13 | 8 |
|
14 | 9 | // Used for environment variables later on
|
15 | 10 | export interface IEnvVars {
|
@@ -125,18 +120,6 @@ export function comparePVP(l: string, r: string): number {
|
125 | 120 | */
|
126 | 121 | const userAgentHeader = { 'User-Agent': 'vscode-haskell' };
|
127 | 122 |
|
128 |
| -/** downloadFile may get called twice on the same src and destination: |
129 |
| - * When this happens, we should only download the file once but return two |
130 |
| - * promises that wait on the same download. This map keeps track of which |
131 |
| - * files are currently being downloaded and we short circuit any calls to |
132 |
| - * downloadFile which have a hit in this map by returning the promise stored |
133 |
| - * here. |
134 |
| - * Note that we have to use a double nested map since array/pointer/object |
135 |
| - * equality is by reference, not value in Map. And we are using a tuple of |
136 |
| - * [src, dest] as the key. |
137 |
| - */ |
138 |
| -const inFlightDownloads = new Map<string, Map<string, Thenable<boolean>>>(); |
139 |
| - |
140 | 123 | export async function httpsGetSilently(options: https.RequestOptions): Promise<string> {
|
141 | 124 | const opts: https.RequestOptions = {
|
142 | 125 | ...options,
|
@@ -176,144 +159,6 @@ export async function httpsGetSilently(options: https.RequestOptions): Promise<s
|
176 | 159 | });
|
177 | 160 | }
|
178 | 161 |
|
179 |
| -async function ignoreFileNotExists(err: NodeJS.ErrnoException): Promise<void> { |
180 |
| - if (err.code === 'ENOENT') { |
181 |
| - return; |
182 |
| - } |
183 |
| - throw err; |
184 |
| -} |
185 |
| - |
186 |
| -export async function downloadFile(titleMsg: string, src: string, dest: string): Promise<boolean> { |
187 |
| - // Check to see if we're already in the process of downloading the same thing |
188 |
| - const inFlightDownload = inFlightDownloads.get(src)?.get(dest); |
189 |
| - if (inFlightDownload) { |
190 |
| - return inFlightDownload; |
191 |
| - } |
192 |
| - |
193 |
| - // If it already is downloaded just use that |
194 |
| - if (fs.existsSync(dest)) { |
195 |
| - return false; |
196 |
| - } |
197 |
| - |
198 |
| - // Download it to a .tmp location first, then rename it! |
199 |
| - // This way if the download fails halfway through or something then we know |
200 |
| - // to delete it and try again |
201 |
| - const downloadDest = dest + '.download'; |
202 |
| - if (fs.existsSync(downloadDest)) { |
203 |
| - fs.unlinkSync(downloadDest); |
204 |
| - } |
205 |
| - |
206 |
| - const downloadTask = window |
207 |
| - .withProgress( |
208 |
| - { |
209 |
| - location: ProgressLocation.Notification, |
210 |
| - title: titleMsg, |
211 |
| - cancellable: false, |
212 |
| - }, |
213 |
| - async (progress) => { |
214 |
| - const p = new Promise<void>((resolve, reject) => { |
215 |
| - const srcUrl = new URL(src); |
216 |
| - const opts: https.RequestOptions = { |
217 |
| - host: srcUrl.host, |
218 |
| - path: srcUrl.pathname, |
219 |
| - protocol: srcUrl.protocol, |
220 |
| - port: srcUrl.port, |
221 |
| - headers: userAgentHeader, |
222 |
| - }; |
223 |
| - getWithRedirects(opts, (res) => { |
224 |
| - const totalSize = parseInt(res.headers['content-length'] || '1', 10); |
225 |
| - const fileStream = fs.createWriteStream(downloadDest, { mode: 0o744 }); |
226 |
| - let curSize = 0; |
227 |
| - |
228 |
| - // Decompress it if it's a gzip or zip |
229 |
| - const needsGunzip = |
230 |
| - res.headers['content-type'] === 'application/gzip' || extname(srcUrl.pathname ?? '') === '.gz'; |
231 |
| - const needsUnzip = |
232 |
| - res.headers['content-type'] === 'application/zip' || extname(srcUrl.pathname ?? '') === '.zip'; |
233 |
| - if (needsGunzip) { |
234 |
| - const gunzip = createGunzip(); |
235 |
| - gunzip.on('error', reject); |
236 |
| - res.pipe(gunzip).pipe(fileStream); |
237 |
| - } else if (needsUnzip) { |
238 |
| - const zipDest = downloadDest + '.zip'; |
239 |
| - const zipFs = fs.createWriteStream(zipDest); |
240 |
| - zipFs.on('error', reject); |
241 |
| - zipFs.on('close', () => { |
242 |
| - yazul.open(zipDest, (err, zipfile) => { |
243 |
| - if (err) { |
244 |
| - throw err; |
245 |
| - } |
246 |
| - if (!zipfile) { |
247 |
| - throw Error("Couldn't decompress zip"); |
248 |
| - } |
249 |
| - |
250 |
| - // We only expect *one* file inside each zip |
251 |
| - zipfile.on('entry', (entry: yazul.Entry) => { |
252 |
| - zipfile.openReadStream(entry, (err2, readStream) => { |
253 |
| - if (err2) { |
254 |
| - throw err2; |
255 |
| - } |
256 |
| - readStream?.pipe(fileStream); |
257 |
| - }); |
258 |
| - }); |
259 |
| - }); |
260 |
| - }); |
261 |
| - res.pipe(zipFs); |
262 |
| - } else { |
263 |
| - res.pipe(fileStream); |
264 |
| - } |
265 |
| - |
266 |
| - function toMB(bytes: number) { |
267 |
| - return bytes / (1024 * 1024); |
268 |
| - } |
269 |
| - |
270 |
| - res.on('data', (chunk: Buffer) => { |
271 |
| - curSize += chunk.byteLength; |
272 |
| - const msg = `${toMB(curSize).toFixed(1)}MB / ${toMB(totalSize).toFixed(1)}MB`; |
273 |
| - progress.report({ message: msg, increment: (chunk.length / totalSize) * 100 }); |
274 |
| - }); |
275 |
| - res.on('error', reject); |
276 |
| - fileStream.on('close', resolve); |
277 |
| - }).on('error', reject); |
278 |
| - }); |
279 |
| - try { |
280 |
| - await p; |
281 |
| - // Finally rename it to the actual dest |
282 |
| - fs.renameSync(downloadDest, dest); |
283 |
| - } finally { |
284 |
| - // And remember to remove it from the list of current downloads |
285 |
| - inFlightDownloads.get(src)?.delete(dest); |
286 |
| - } |
287 |
| - } |
288 |
| - ) |
289 |
| - .then(() => true); |
290 |
| - |
291 |
| - try { |
292 |
| - if (inFlightDownloads.has(src)) { |
293 |
| - inFlightDownloads.get(src)?.set(dest, downloadTask); |
294 |
| - } else { |
295 |
| - inFlightDownloads.set(src, new Map([[dest, downloadTask]])); |
296 |
| - } |
297 |
| - return await downloadTask; |
298 |
| - } catch (e: any) { |
299 |
| - await promisify(fs.unlink)(downloadDest).catch(ignoreFileNotExists); |
300 |
| - throw new Error(`Failed to download ${src}:\n${e.message}`); |
301 |
| - } |
302 |
| -} |
303 |
| - |
304 |
| -function getWithRedirects(opts: https.RequestOptions, f: (res: http.IncomingMessage) => void): http.ClientRequest { |
305 |
| - return https.get(opts, (res) => { |
306 |
| - if (res.statusCode === 301 || res.statusCode === 302) { |
307 |
| - if (!res.headers.location) { |
308 |
| - console.error('301/302 without a location header'); |
309 |
| - return; |
310 |
| - } |
311 |
| - https.get(res.headers.location, f); |
312 |
| - } else { |
313 |
| - f(res); |
314 |
| - } |
315 |
| - }); |
316 |
| -} |
317 | 162 |
|
318 | 163 | /*
|
319 | 164 | * Checks if the executable is on the PATH
|
|
0 commit comments