5
5
6
6
import { Promises } from 'vs/base/common/async' ;
7
7
import { getErrorMessage } from 'vs/base/common/errors' ;
8
- import { Disposable } from 'vs/base/common/lifecycle' ;
8
+ import { Disposable , IDisposable } 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,12 +18,19 @@ 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 class ExtensionsDownloader extends Disposable {
21
+ export interface IExtensionsDownloader extends IDisposable {
22
+ delete ( location : URI ) : Promise < void > ;
23
+ downloadExtension ( extension : IGalleryExtension , operation : InstallOperation ) : Promise < { extensionLocation : URI ; signatureArchiveLocation ?: URI } > ;
24
+ }
25
+
26
+ export class ExtensionsDownloader extends Disposable implements IExtensionsDownloader {
22
27
23
28
readonly extensionsDownloadDir : URI ;
24
29
private readonly cache : number ;
25
30
private readonly cleanUpPromise : Promise < void > ;
26
31
32
+ private static readonly SignatureArchiveExtension = '.sigzip' ;
33
+
27
34
constructor (
28
35
@INativeEnvironmentService environmentService : INativeEnvironmentService ,
29
36
@IFileService private readonly fileService : IFileService ,
@@ -32,21 +39,33 @@ export class ExtensionsDownloader extends Disposable {
32
39
) {
33
40
super ( ) ;
34
41
this . extensionsDownloadDir = URI . file ( environmentService . extensionsDownloadPath ) ;
35
- this . cache = 20 ; // Cache 20 downloads
42
+ this . cache = 20 ; // Cache 20 downloaded VSIX files
36
43
this . cleanUpPromise = this . cleanUp ( ) ;
37
44
}
38
45
39
- async downloadExtension ( extension : IGalleryExtension , operation : InstallOperation ) : Promise < URI > {
46
+ async downloadExtension ( extension : IGalleryExtension , operation : InstallOperation ) : Promise < { extensionLocation : URI ; signatureArchiveLocation ?: URI } > {
40
47
await this . cleanUpPromise ;
41
48
const vsixName = this . getName ( extension ) ;
42
- const location = joinPath ( this . extensionsDownloadDir , vsixName ) ;
49
+ const extensionLocation = joinPath ( this . extensionsDownloadDir , vsixName ) ;
50
+ let signatureArchiveLocation : URI | undefined ;
51
+
52
+ await this . downloadFile ( extensionLocation , extension , operation , 'vsix' , this . extensionGalleryService . download ) ;
53
+
54
+ if ( extension . assets . signature ) {
55
+ signatureArchiveLocation = ExtensionsDownloader . getSignatureArchiveLocation ( extensionLocation ) ;
56
+
57
+ await this . downloadFile ( signatureArchiveLocation , extension , operation , 'signature archive' , this . extensionGalleryService . downloadSignatureArchive ) ;
58
+ }
43
59
44
- // Download only if vsix does not exist
60
+ return { extensionLocation, signatureArchiveLocation } ;
61
+ }
62
+
63
+ private async downloadFile ( location : URI , extension : IGalleryExtension , operation : InstallOperation , fileType : string , download : ( extension : IGalleryExtension , location : URI , operation : InstallOperation ) => Promise < void > ) {
45
64
if ( ! await this . fileService . exists ( location ) ) {
46
- // Download to temporary location first only if vsix does not exist
65
+ // Download to temporary location first only if file does not exist
47
66
const tempLocation = joinPath ( this . extensionsDownloadDir , `.${ generateUuid ( ) } ` ) ;
48
67
if ( ! await this . fileService . exists ( tempLocation ) ) {
49
- await this . extensionGalleryService . download ( extension , tempLocation , operation ) ;
68
+ await download ( extension , tempLocation , operation ) ;
50
69
}
51
70
52
71
try {
@@ -57,16 +76,13 @@ export class ExtensionsDownloader extends Disposable {
57
76
await this . fileService . del ( tempLocation ) ;
58
77
} catch ( e ) { /* ignore */ }
59
78
if ( error . code === 'ENOTEMPTY' ) {
60
- this . logService . info ( `Rename failed because vsix was downloaded by another source. So ignoring renaming.` , extension . identifier . id ) ;
79
+ this . logService . info ( `Rename failed because ${ fileType } was downloaded by another source. So ignoring renaming.` , extension . identifier . id ) ;
61
80
} else {
62
- this . logService . info ( `Rename failed because of ${ getErrorMessage ( error ) } . Deleted the vsix from downloaded location` , tempLocation . path ) ;
81
+ this . logService . info ( `Rename failed because of ${ getErrorMessage ( error ) } . Deleted the ${ fileType } from downloaded location` , tempLocation . path ) ;
63
82
throw error ;
64
83
}
65
84
}
66
-
67
85
}
68
-
69
- return location ;
70
86
}
71
87
72
88
async delete ( location : URI ) : Promise < void > {
@@ -89,15 +105,25 @@ export class ExtensionsDownloader extends Disposable {
89
105
private async cleanUp ( ) : Promise < void > {
90
106
try {
91
107
if ( ! ( await this . fileService . exists ( this . extensionsDownloadDir ) ) ) {
92
- this . logService . trace ( 'Extension VSIX downlads cache dir does not exist' ) ;
108
+ this . logService . trace ( 'Extension VSIX downloads cache dir does not exist' ) ;
93
109
return ;
94
110
}
95
111
const folderStat = await this . fileService . resolve ( this . extensionsDownloadDir , { resolveMetadata : true } ) ;
96
112
if ( folderStat . children ) {
97
113
const toDelete : URI [ ] = [ ] ;
98
114
const all : [ ExtensionKey , IFileStatWithMetadata ] [ ] = [ ] ;
115
+ const signatureArchives = new Set < string > ( ) ;
116
+
99
117
for ( const stat of folderStat . children ) {
100
- const extension = ExtensionKey . parse ( stat . name ) ;
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 ) ;
101
127
if ( extension ) {
102
128
all . push ( [ extension , stat ] ) ;
103
129
}
@@ -111,16 +137,40 @@ export class ExtensionsDownloader extends Disposable {
111
137
}
112
138
distinct . sort ( ( a , b ) => a . mtime - b . mtime ) ; // sort by modified time
113
139
toDelete . push ( ...distinct . slice ( 0 , Math . max ( 0 , distinct . length - this . cache ) ) . map ( s => s . resource ) ) ; // Retain minimum cacheSize and delete the rest
140
+
114
141
await Promises . settled ( toDelete . map ( resource => {
115
142
this . logService . trace ( 'Deleting vsix from cache' , resource . path ) ;
116
- return this . fileService . del ( resource ) ;
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 ;
117
155
} ) ) ;
118
156
}
119
157
} catch ( e ) {
120
158
this . logService . error ( e ) ;
121
159
}
122
160
}
123
161
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
+
124
174
private getName ( extension : IGalleryExtension ) : string {
125
175
return this . cache ? ExtensionKey . create ( extension ) . toString ( ) . toLowerCase ( ) : generateUuid ( ) ;
126
176
}
0 commit comments