|
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