1
1
import * as assert from 'assert' ;
2
+ import * as crypto from 'crypto' ;
2
3
import * as fs from 'fs' ;
3
4
import fetch from 'node-fetch' ;
5
+ import * as path from 'path' ;
4
6
import * as stream from 'stream' ;
5
7
import * as util from 'util' ;
6
8
import * as vscode from 'vscode' ;
9
+ import * as zlib from 'zlib' ;
7
10
8
11
const pipeline = util . promisify ( stream . pipeline ) ;
9
12
@@ -62,24 +65,34 @@ export interface GithubRelease {
62
65
} > ;
63
66
}
64
67
65
- export async function download (
66
- downloadUrl : string ,
67
- destinationPath : string ,
68
- progressTitle : string ,
69
- { mode } : { mode ?: number } = { } ,
70
- ) {
68
+ interface DownloadOpts {
69
+ progressTitle : string ;
70
+ url : string ;
71
+ dest : string ;
72
+ mode ?: number ;
73
+ gunzip ?: boolean ;
74
+ }
75
+
76
+ export async function download ( opts : DownloadOpts ) {
77
+ // Put artifact into a temporary file (in the same dir for simplicity)
78
+ // to prevent partially downloaded files when user kills vscode
79
+ const dest = path . parse ( opts . dest ) ;
80
+ const randomHex = crypto . randomBytes ( 5 ) . toString ( 'hex' ) ;
81
+ const tempFile = path . join ( dest . dir , `${ dest . name } ${ randomHex } ` ) ;
82
+
71
83
await vscode . window . withProgress (
72
84
{
73
85
location : vscode . ProgressLocation . Notification ,
74
86
cancellable : false ,
75
- title : progressTitle ,
87
+ title : opts . progressTitle ,
76
88
} ,
77
89
async ( progress , _cancellationToken ) => {
78
90
let lastPercentage = 0 ;
79
91
await downloadFile (
80
- downloadUrl ,
81
- destinationPath ,
82
- mode ,
92
+ opts . url ,
93
+ tempFile ,
94
+ opts . mode ,
95
+ Boolean ( opts . gunzip ) ,
83
96
( readBytes , totalBytes ) => {
84
97
const newPercentage = ( readBytes / totalBytes ) * 100 ;
85
98
progress . report ( {
@@ -92,6 +105,8 @@ export async function download(
92
105
) ;
93
106
} ,
94
107
) ;
108
+
109
+ return fs . promises . rename ( tempFile , opts . dest ) ;
95
110
}
96
111
97
112
/**
@@ -104,6 +119,7 @@ async function downloadFile(
104
119
url : string ,
105
120
destFilePath : fs . PathLike ,
106
121
mode : number | undefined ,
122
+ gunzip : boolean ,
107
123
onProgress : ( readBytes : number , totalBytes : number ) => void ,
108
124
) : Promise < void > {
109
125
const res = await fetch ( url ) ;
@@ -136,13 +152,13 @@ async function downloadFile(
136
152
} ) ;
137
153
138
154
const destFileStream = fs . createWriteStream ( destFilePath , { mode } ) ;
155
+ const srcStream = gunzip ? res . body . pipe ( zlib . createGunzip ( ) ) : res . body ;
139
156
140
- await pipeline ( res . body , destFileStream ) ;
157
+ await pipeline ( srcStream , destFileStream ) ;
141
158
return new Promise < void > ( resolve => {
142
159
destFileStream . on ( 'close' , resolve ) ;
143
160
destFileStream . destroy ( ) ;
144
-
145
- // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131
146
- // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776
161
+ // This workaround is awaiting to be removed when vscode moves to newer nodejs version:
162
+ // https://github.com/rust-analyzer/rust-analyzer/issues/3167
147
163
} ) ;
148
164
}
0 commit comments