5
5
6
6
import { Promises } from 'vs/base/common/async' ;
7
7
import { getErrorMessage } from 'vs/base/common/errors' ;
8
- import { Disposable , IDisposable } from 'vs/base/common/lifecycle' ;
8
+ import { Disposable } from 'vs/base/common/lifecycle' ;
9
9
import { isWindows } from 'vs/base/common/platform' ;
10
10
import { joinPath } from 'vs/base/common/resources' ;
11
11
import * as semver from 'vs/base/common/semver/semver' ;
@@ -18,19 +18,14 @@ import { ExtensionKey, groupByExtension } from 'vs/platform/extensionManagement/
18
18
import { IFileService , IFileStatWithMetadata } from 'vs/platform/files/common/files' ;
19
19
import { ILogService } from 'vs/platform/log/common/log' ;
20
20
21
- export interface IExtensionsDownloader extends IDisposable {
22
- delete ( location : URI ) : Promise < void > ;
23
- downloadExtension ( extension : IGalleryExtension , operation : InstallOperation ) : Promise < { extensionLocation : URI ; signatureArchiveLocation ?: URI } > ;
24
- }
21
+ export class ExtensionsDownloader extends Disposable {
25
22
26
- export class ExtensionsDownloader extends Disposable implements IExtensionsDownloader {
23
+ private static readonly SignatureArchiveExtension = '.sigzip' ;
27
24
28
25
readonly extensionsDownloadDir : URI ;
29
26
private readonly cache : number ;
30
27
private readonly cleanUpPromise : Promise < void > ;
31
28
32
- private static readonly SignatureArchiveExtension = '.sigzip' ;
33
-
34
29
constructor (
35
30
@INativeEnvironmentService environmentService : INativeEnvironmentService ,
36
31
@IFileService private readonly fileService : IFileService ,
@@ -43,44 +38,46 @@ export class ExtensionsDownloader extends Disposable implements IExtensionsDownl
43
38
this . cleanUpPromise = this . cleanUp ( ) ;
44
39
}
45
40
46
- async downloadExtension ( extension : IGalleryExtension , operation : InstallOperation ) : Promise < { extensionLocation : URI ; signatureArchiveLocation ?: URI } > {
41
+ async downloadVSIX ( extension : IGalleryExtension , operation : InstallOperation ) : Promise < URI > {
47
42
await this . cleanUpPromise ;
48
- const vsixName = this . getName ( extension ) ;
49
- const extensionLocation = joinPath ( this . extensionsDownloadDir , vsixName ) ;
50
- let signatureArchiveLocation : URI | undefined ;
51
43
52
- await this . downloadFile ( extensionLocation , extension , operation , 'vsix' , this . extensionGalleryService . download ) ;
53
-
54
- if ( extension . assets . signature ) {
55
- signatureArchiveLocation = ExtensionsDownloader . getSignatureArchiveLocation ( extensionLocation ) ;
44
+ const location = joinPath ( this . extensionsDownloadDir , this . getName ( extension ) ) ;
45
+ await this . downloadFile ( extension , location , location => this . extensionGalleryService . download ( extension , location , operation ) ) ;
46
+ return location ;
47
+ }
56
48
57
- await this . downloadFile ( signatureArchiveLocation , extension , operation , 'signature archive' , this . extensionGalleryService . downloadSignatureArchive ) ;
58
- }
49
+ async downloadSignatureArchive ( extension : IGalleryExtension ) : Promise < URI > {
50
+ await this . cleanUpPromise ;
59
51
60
- return { extensionLocation, signatureArchiveLocation } ;
52
+ const location = joinPath ( this . extensionsDownloadDir , `${ this . getName ( extension ) } ${ ExtensionsDownloader . SignatureArchiveExtension } ` ) ;
53
+ await this . downloadFile ( extension , location , location => this . extensionGalleryService . downloadSignatureArchive ( extension , location ) ) ;
54
+ return location ;
61
55
}
62
56
63
- private async downloadFile ( location : URI , extension : IGalleryExtension , operation : InstallOperation , fileType : string , download : ( extension : IGalleryExtension , location : URI , operation : InstallOperation ) => Promise < void > ) {
64
- if ( ! await this . fileService . exists ( location ) ) {
65
- // Download to temporary location first only if file does not exist
66
- const tempLocation = joinPath ( this . extensionsDownloadDir , `.${ generateUuid ( ) } ` ) ;
67
- if ( ! await this . fileService . exists ( tempLocation ) ) {
68
- await download ( extension , tempLocation , operation ) ;
69
- }
57
+ private async downloadFile ( extension : IGalleryExtension , location : URI , downloadFn : ( location : URI ) => Promise < void > ) : Promise < void > {
58
+ // Do not download if exists
59
+ if ( await this . fileService . exists ( location ) ) {
60
+ return ;
61
+ }
62
+
63
+ // Download to temporary location first only if file does not exist
64
+ const tempLocation = joinPath ( this . extensionsDownloadDir , `.${ generateUuid ( ) } ` ) ;
65
+ if ( ! await this . fileService . exists ( tempLocation ) ) {
66
+ await downloadFn ( tempLocation ) ;
67
+ }
70
68
69
+ try {
70
+ // Rename temp location to original
71
+ await this . rename ( tempLocation , location , Date . now ( ) + ( 2 * 60 * 1000 ) /* Retry for 2 minutes */ ) ;
72
+ } catch ( error ) {
71
73
try {
72
- // Rename temp location to original
73
- await this . rename ( tempLocation , location , Date . now ( ) + ( 2 * 60 * 1000 ) /* Retry for 2 minutes */ ) ;
74
- } catch ( error ) {
75
- try {
76
- await this . fileService . del ( tempLocation ) ;
77
- } catch ( e ) { /* ignore */ }
78
- if ( error . code === 'ENOTEMPTY' ) {
79
- this . logService . info ( `Rename failed because ${ fileType } was downloaded by another source. So ignoring renaming.` , extension . identifier . id ) ;
80
- } else {
81
- this . logService . info ( `Rename failed because of ${ getErrorMessage ( error ) } . Deleted the ${ fileType } from downloaded location` , tempLocation . path ) ;
82
- throw error ;
83
- }
74
+ await this . fileService . del ( tempLocation ) ;
75
+ } catch ( e ) { /* ignore */ }
76
+ if ( error . code === 'ENOTEMPTY' ) {
77
+ this . logService . info ( `Rename failed because the file was downloaded by another source. So ignoring renaming.` , extension . identifier . id , location . path ) ;
78
+ } else {
79
+ this . logService . info ( `Rename failed because of ${ getErrorMessage ( error ) } . Deleted the file from downloaded location` , tempLocation . path ) ;
80
+ throw error ;
84
81
}
85
82
}
86
83
}
@@ -111,24 +108,21 @@ export class ExtensionsDownloader extends Disposable implements IExtensionsDownl
111
108
const folderStat = await this . fileService . resolve ( this . extensionsDownloadDir , { resolveMetadata : true } ) ;
112
109
if ( folderStat . children ) {
113
110
const toDelete : URI [ ] = [ ] ;
114
- const all : [ ExtensionKey , IFileStatWithMetadata ] [ ] = [ ] ;
115
- const signatureArchives = new Set < string > ( ) ;
111
+ const vsixs : [ ExtensionKey , IFileStatWithMetadata ] [ ] = [ ] ;
112
+ const signatureArchives : URI [ ] = [ ] ;
116
113
117
114
for ( const stat of folderStat . children ) {
118
- const name = stat . name ;
119
-
120
- if ( ExtensionsDownloader . isSignatureArchive ( name ) ) {
121
- const extensionPath = ExtensionsDownloader . getExtensionPath ( name ) ;
122
- signatureArchives . add ( extensionPath ) ;
123
- continue ;
124
- }
125
-
126
- const extension = ExtensionKey . parse ( name ) ;
127
- if ( extension ) {
128
- all . push ( [ extension , stat ] ) ;
115
+ if ( stat . name . endsWith ( ExtensionsDownloader . SignatureArchiveExtension ) ) {
116
+ signatureArchives . push ( stat . resource ) ;
117
+ } else {
118
+ const extension = ExtensionKey . parse ( stat . name ) ;
119
+ if ( extension ) {
120
+ vsixs . push ( [ extension , stat ] ) ;
121
+ }
129
122
}
130
123
}
131
- const byExtension = groupByExtension ( all , ( [ extension ] ) => extension ) ;
124
+
125
+ const byExtension = groupByExtension ( vsixs , ( [ extension ] ) => extension ) ;
132
126
const distinct : IFileStatWithMetadata [ ] = [ ] ;
133
127
for ( const p of byExtension ) {
134
128
p . sort ( ( a , b ) => semver . rcompare ( a [ 0 ] . version , b [ 0 ] . version ) ) ;
@@ -137,40 +131,18 @@ export class ExtensionsDownloader extends Disposable implements IExtensionsDownl
137
131
}
138
132
distinct . sort ( ( a , b ) => a . mtime - b . mtime ) ; // sort by modified time
139
133
toDelete . push ( ...distinct . slice ( 0 , Math . max ( 0 , distinct . length - this . cache ) ) . map ( s => s . resource ) ) ; // Retain minimum cacheSize and delete the rest
134
+ toDelete . push ( ...signatureArchives ) ; // Delete all signature archives
140
135
141
136
await Promises . settled ( toDelete . map ( resource => {
142
- this . logService . trace ( 'Deleting vsix from cache' , resource . path ) ;
143
-
144
- let promise = Promise . resolve ( ) ;
145
-
146
- if ( signatureArchives . has ( resource . fsPath ) ) {
147
- const signatureArchiveLocation = ExtensionsDownloader . getSignatureArchiveLocation ( resource ) ;
148
-
149
- promise = promise . then ( ( ) => this . fileService . del ( signatureArchiveLocation ) ) ;
150
- }
151
-
152
- promise = promise . then ( ( ) => this . fileService . del ( resource ) ) ;
153
-
154
- return promise ;
137
+ this . logService . trace ( 'Deleting from cache' , resource . path ) ;
138
+ return this . fileService . del ( resource ) ;
155
139
} ) ) ;
156
140
}
157
141
} catch ( e ) {
158
142
this . logService . error ( e ) ;
159
143
}
160
144
}
161
145
162
- private static getExtensionPath ( signatureArchivePath : string ) : string {
163
- return signatureArchivePath . substring ( 0 , signatureArchivePath . length - ExtensionsDownloader . SignatureArchiveExtension . length ) ;
164
- }
165
-
166
- private static getSignatureArchiveLocation ( extensionLocation : URI ) : URI {
167
- return URI . file ( extensionLocation . fsPath + ExtensionsDownloader . SignatureArchiveExtension ) ;
168
- }
169
-
170
- private static isSignatureArchive ( name : string ) : boolean {
171
- return name . endsWith ( ExtensionsDownloader . SignatureArchiveExtension ) ;
172
- }
173
-
174
146
private getName ( extension : IGalleryExtension ) : string {
175
147
return this . cache ? ExtensionKey . create ( extension ) . toString ( ) . toLowerCase ( ) : generateUuid ( ) ;
176
148
}
0 commit comments