@@ -11,10 +11,12 @@ import { ResourceMap } from 'vs/base/common/map';
11
11
import { URI , UriComponents } from 'vs/base/common/uri' ;
12
12
import { Metadata } from 'vs/platform/extensionManagement/common/extensionManagement' ;
13
13
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil' ;
14
- import { IExtension , IExtensionIdentifier } from 'vs/platform/extensions/common/extensions' ;
15
- import { FileOperationError , FileOperationResult , IFileService } from 'vs/platform/files/common/files' ;
14
+ import { IExtension , IExtensionIdentifier , isIExtensionIdentifier } from 'vs/platform/extensions/common/extensions' ;
15
+ import { FileOperationResult , IFileService , toFileOperationResult } from 'vs/platform/files/common/files' ;
16
16
import { createDecorator } from 'vs/platform/instantiation/common/instantiation' ;
17
17
import { ILogService } from 'vs/platform/log/common/log' ;
18
+ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile' ;
19
+ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' ;
18
20
19
21
interface IStoredProfileExtension {
20
22
identifier : IExtensionIdentifier ;
@@ -76,6 +78,8 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
76
78
77
79
constructor (
78
80
@IFileService private readonly fileService : IFileService ,
81
+ @IUserDataProfilesService private readonly userDataProfilesService : IUserDataProfilesService ,
82
+ @IUriIdentityService private readonly uriIdentityService : IUriIdentityService ,
79
83
@ILogService private readonly logService : ILogService ,
80
84
) {
81
85
super ( ) ;
@@ -167,9 +171,20 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
167
171
let extensions : IScannedProfileExtension [ ] = [ ] ;
168
172
169
173
// Read
174
+ let storedWebExtensions : IStoredProfileExtension [ ] | undefined ;
170
175
try {
171
176
const content = await this . fileService . readFile ( file ) ;
172
- const storedWebExtensions : IStoredProfileExtension [ ] = JSON . parse ( content . value . toString ( ) ) ;
177
+ storedWebExtensions = < IStoredProfileExtension [ ] > JSON . parse ( content . value . toString ( ) ) ;
178
+ } catch ( error ) {
179
+ if ( toFileOperationResult ( error ) !== FileOperationResult . FILE_NOT_FOUND ) {
180
+ throw error ;
181
+ }
182
+ // migrate from old location, remove this after couple of releases
183
+ if ( this . uriIdentityService . extUri . isEqual ( file , this . userDataProfilesService . defaultProfile . extensionsResource ) ) {
184
+ storedWebExtensions = await this . migrateFromOldDefaultProfileExtensionsLocation ( ) ;
185
+ }
186
+ }
187
+ if ( storedWebExtensions ) {
173
188
for ( const e of storedWebExtensions ) {
174
189
if ( ! e . identifier ) {
175
190
this . logService . info ( 'Ignoring invalid extension while scanning. Identifier does not exist.' , e ) ;
@@ -190,11 +205,6 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
190
205
metadata : e . metadata ,
191
206
} ) ;
192
207
}
193
- } catch ( error ) {
194
- /* Ignore */
195
- if ( ( < FileOperationError > error ) . fileOperationResult !== FileOperationResult . FILE_NOT_FOUND ) {
196
- this . logService . error ( error ) ;
197
- }
198
208
}
199
209
200
210
// Update
@@ -213,6 +223,66 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
213
223
} ) ;
214
224
}
215
225
226
+ private _migrationPromise : Promise < IStoredProfileExtension [ ] | undefined > | undefined ;
227
+ private async migrateFromOldDefaultProfileExtensionsLocation ( ) : Promise < IStoredProfileExtension [ ] | undefined > {
228
+ if ( ! this . _migrationPromise ) {
229
+ this . _migrationPromise = ( async ( ) => {
230
+ const oldDefaultProfileExtensionsLocation = this . uriIdentityService . extUri . joinPath ( this . userDataProfilesService . defaultProfile . location , 'extensions.json' ) ;
231
+ let content : string ;
232
+ try {
233
+ content = ( await this . fileService . readFile ( oldDefaultProfileExtensionsLocation ) ) . value . toString ( ) ;
234
+ } catch ( error ) {
235
+ if ( toFileOperationResult ( error ) === FileOperationResult . FILE_NOT_FOUND ) {
236
+ return undefined ;
237
+ }
238
+ throw error ;
239
+ }
240
+
241
+ this . logService . info ( 'Migrating extensions from old default profile location' , oldDefaultProfileExtensionsLocation . toString ( ) ) ;
242
+ let storedProfileExtensions : IStoredProfileExtension [ ] | undefined ;
243
+ try {
244
+ const parsedData = JSON . parse ( content ) ;
245
+ if ( Array . isArray ( parsedData ) && parsedData . every ( candidate =>
246
+ ! ! ( candidate && typeof candidate === 'object'
247
+ && isIExtensionIdentifier ( candidate . identifier )
248
+ && isUriComponents ( candidate . location )
249
+ && candidate . version && typeof candidate . version === 'string' ) ) ) {
250
+ storedProfileExtensions = parsedData ;
251
+ } else {
252
+ this . logService . warn ( 'Skipping migrating from old default profile locaiton: Found invalid data' , parsedData ) ;
253
+ }
254
+ } catch ( error ) {
255
+ /* Ignore */
256
+ this . logService . error ( error ) ;
257
+ }
258
+
259
+ if ( storedProfileExtensions ) {
260
+ try {
261
+ await this . fileService . createFile ( this . userDataProfilesService . defaultProfile . extensionsResource , VSBuffer . fromString ( JSON . stringify ( storedProfileExtensions ) ) , { overwrite : false } ) ;
262
+ this . logService . info ( 'Migrated extensions from old default profile location to new location' , oldDefaultProfileExtensionsLocation . toString ( ) , this . userDataProfilesService . defaultProfile . extensionsResource . toString ( ) ) ;
263
+ } catch ( error ) {
264
+ if ( toFileOperationResult ( error ) === FileOperationResult . FILE_MODIFIED_SINCE ) {
265
+ this . logService . info ( 'Migration from old default profile location to new location is done by another window' , oldDefaultProfileExtensionsLocation . toString ( ) , this . userDataProfilesService . defaultProfile . extensionsResource . toString ( ) ) ;
266
+ } else {
267
+ throw error ;
268
+ }
269
+ }
270
+ }
271
+
272
+ try {
273
+ await this . fileService . del ( oldDefaultProfileExtensionsLocation ) ;
274
+ } catch ( error ) {
275
+ if ( toFileOperationResult ( error ) !== FileOperationResult . FILE_NOT_FOUND ) {
276
+ this . logService . error ( error ) ;
277
+ }
278
+ }
279
+
280
+ return storedProfileExtensions ;
281
+ } ) ( ) ;
282
+ }
283
+ return this . _migrationPromise ;
284
+ }
285
+
216
286
private getResourceAccessQueue ( file : URI ) : Queue < IScannedProfileExtension [ ] > {
217
287
let resourceQueue = this . resourcesAccessQueueMap . get ( file ) ;
218
288
if ( ! resourceQueue ) {
@@ -222,3 +292,11 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
222
292
return resourceQueue ;
223
293
}
224
294
}
295
+
296
+ function isUriComponents ( thing : unknown ) : thing is UriComponents {
297
+ if ( ! thing ) {
298
+ return false ;
299
+ }
300
+ return typeof ( < any > thing ) . path === 'string' &&
301
+ typeof ( < any > thing ) . scheme === 'string' ;
302
+ }
0 commit comments