@@ -9,7 +9,9 @@ import { CancellationToken } from 'vs/base/common/cancellation';
9
9
import { IStringDictionary } from 'vs/base/common/collections' ;
10
10
import { toErrorMessage } from 'vs/base/common/errorMessage' ;
11
11
import { getErrorMessage } from 'vs/base/common/errors' ;
12
+ import { Emitter } from 'vs/base/common/event' ;
12
13
import { Disposable } from 'vs/base/common/lifecycle' ;
14
+ import { ResourceSet } from 'vs/base/common/map' ;
13
15
import { Schemas } from 'vs/base/common/network' ;
14
16
import * as path from 'vs/base/common/path' ;
15
17
import { isMacintosh , isWindows } from 'vs/base/common/platform' ;
@@ -35,10 +37,10 @@ import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/exten
35
37
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle' ;
36
38
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil' ;
37
39
import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache' ;
38
- import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher' ;
39
- import { ExtensionType , IExtensionManifest , isApplicationScopedExtension , TargetPlatform } from 'vs/platform/extensions/common/extensions' ;
40
+ import { DidChangeProfileExtensionsEvent , ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher' ;
41
+ import { ExtensionType , IExtension , IExtensionManifest , isApplicationScopedExtension , TargetPlatform } from 'vs/platform/extensions/common/extensions' ;
40
42
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator' ;
41
- import { IFileService } from 'vs/platform/files/common/files' ;
43
+ import { FileChangesEvent , IFileService } from 'vs/platform/files/common/files' ;
42
44
import { IInstantiationService , refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation' ;
43
45
import { ILogService } from 'vs/platform/log/common/log' ;
44
46
import { IProductService } from 'vs/platform/product/common/productService' ;
@@ -56,7 +58,7 @@ export const INativeServerExtensionManagementService = refineServiceDecorator<IE
56
58
export interface INativeServerExtensionManagementService extends IExtensionManagementService {
57
59
readonly _serviceBrand : undefined ;
58
60
migrateDefaultProfileExtensions ( ) : Promise < void > ;
59
- markAsUninstalled ( ...extensions : ILocalExtension [ ] ) : Promise < void > ;
61
+ markAsUninstalled ( ...extensions : IExtension [ ] ) : Promise < void > ;
60
62
removeUninstalledExtensions ( ) : Promise < void > ;
61
63
getAllUserInstalled ( ) : Promise < ILocalExtension [ ] > ;
62
64
}
@@ -80,22 +82,18 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
80
82
@IInstantiationService instantiationService : IInstantiationService ,
81
83
@IFileService private readonly fileService : IFileService ,
82
84
@IProductService productService : IProductService ,
83
- @IUriIdentityService uriIdentityService : IUriIdentityService ,
85
+ @IUriIdentityService private readonly uriIdentityService : IUriIdentityService ,
84
86
@IUserDataProfilesService userDataProfilesService : IUserDataProfilesService
85
87
) {
86
88
super ( galleryService , telemetryService , logService , productService , userDataProfilesService ) ;
87
89
const extensionLifecycle = this . _register ( instantiationService . createInstance ( ExtensionsLifecycle ) ) ;
88
90
this . extensionsScanner = this . _register ( instantiationService . createInstance ( ExtensionsScanner , extension => extensionLifecycle . postUninstall ( extension ) ) ) ;
89
91
this . manifestCache = this . _register ( new ExtensionsManifestCache ( environmentService , this ) ) ;
90
92
this . extensionsDownloader = this . _register ( instantiationService . createInstance ( ExtensionsDownloader ) ) ;
91
- const extensionsWatcher = this . _register ( new ExtensionsWatcher ( this , userDataProfilesService , extensionsProfileScannerService , extensionsScannerService , uriIdentityService , fileService , logService ) ) ;
92
93
93
- this . _register ( extensionsWatcher . onDidChangeExtensionsByAnotherSource ( ( { added, removed } ) => {
94
- if ( added . length ) {
95
- this . _onDidInstallExtensions . fire ( added ) ;
96
- }
97
- removed . forEach ( e => this . _onDidUninstallExtension . fire ( e ) ) ;
98
- } ) ) ;
94
+ const extensionsWatcher = this . _register ( new ExtensionsWatcher ( this , userDataProfilesService , extensionsProfileScannerService , uriIdentityService , fileService , logService ) ) ;
95
+ this . _register ( extensionsWatcher . onDidChangeExtensionsByAnotherSource ( e => this . onDidChangeExtensionsFromAnotherSource ( e ) ) ) ;
96
+ this . watchForExtensionsNotInstalledBySystem ( ) ;
99
97
}
100
98
101
99
private _targetPlatformPromise : Promise < TargetPlatform > | undefined ;
@@ -197,7 +195,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
197
195
await this . installFromGallery ( galleryExtension ) ;
198
196
}
199
197
200
- markAsUninstalled ( ...extensions : ILocalExtension [ ] ) : Promise < void > {
198
+ markAsUninstalled ( ...extensions : IExtension [ ] ) : Promise < void > {
201
199
return this . extensionsScanner . setUninstalled ( ...extensions ) ;
202
200
}
203
201
@@ -276,13 +274,85 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
276
274
return files . map ( f => ( < IFile > { path : `extension/${ path . relative ( extension . location . fsPath , f ) } ` , localPath : f } ) ) ;
277
275
}
278
276
277
+ private async onDidChangeExtensionsFromAnotherSource ( { added, removed } : DidChangeProfileExtensionsEvent ) : Promise < void > {
278
+ if ( removed ) {
279
+ for ( const identifier of removed . extensions ) {
280
+ this . _onDidUninstallExtension . fire ( { identifier, profileLocation : removed . profileLocation } ) ;
281
+ }
282
+ }
283
+ if ( added ) {
284
+ const extensions = await this . extensionsScanner . scanExtensions ( ExtensionType . User , added . profileLocation ) ;
285
+ const addedExtensions = extensions . filter ( e => added . extensions . some ( identifier => areSameExtensions ( identifier , e . identifier ) ) ) ;
286
+ this . _onDidInstallExtensions . fire ( addedExtensions . map ( local => ( { identifier : local . identifier , local, profileLocation : added . profileLocation , operation : InstallOperation . None } ) ) ) ;
287
+ }
288
+ }
289
+
290
+ private readonly knownDirectories = new ResourceSet ( ) ;
291
+ private async watchForExtensionsNotInstalledBySystem ( ) : Promise < void > {
292
+ this . _register ( this . extensionsScanner . onExtract ( resource => this . knownDirectories . add ( resource ) ) ) ;
293
+ const stat = await this . fileService . resolve ( this . extensionsScannerService . userExtensionsLocation ) ;
294
+ for ( const childStat of stat . children ?? [ ] ) {
295
+ if ( childStat . isDirectory ) {
296
+ this . knownDirectories . add ( childStat . resource ) ;
297
+ }
298
+ }
299
+ this . _register ( this . fileService . watch ( this . extensionsScannerService . userExtensionsLocation ) ) ;
300
+ this . _register ( this . fileService . onDidFilesChange ( e => this . onDidFilesChange ( e ) ) ) ;
301
+ }
302
+
303
+ private async onDidFilesChange ( e : FileChangesEvent ) : Promise < void > {
304
+ const added : ILocalExtension [ ] = [ ] ;
305
+ for ( const resource of e . rawAdded ) {
306
+ // Check if this is a known directory
307
+ if ( this . knownDirectories . has ( resource ) ) {
308
+ continue ;
309
+ }
310
+
311
+ // Is not immediate child of extensions resource
312
+ if ( ! this . uriIdentityService . extUri . isEqual ( this . uriIdentityService . extUri . dirname ( resource ) , this . extensionsScannerService . userExtensionsLocation ) ) {
313
+ continue ;
314
+ }
315
+
316
+ // .obsolete file changed
317
+ if ( this . uriIdentityService . extUri . isEqual ( resource , this . uriIdentityService . extUri . joinPath ( this . extensionsScannerService . userExtensionsLocation , '.obsolete' ) ) ) {
318
+ continue ;
319
+ }
320
+
321
+ // Ignore changes to files starting with `.`
322
+ if ( this . uriIdentityService . extUri . basename ( resource ) . startsWith ( '.' ) ) {
323
+ continue ;
324
+ }
325
+
326
+ // Check if this is a directory
327
+ if ( ! ( await this . fileService . stat ( resource ) ) . isDirectory ) {
328
+ continue ;
329
+ }
330
+
331
+ // Check if this is an extension added by another source
332
+ // Extension added by another source will not have installed timestamp
333
+ const extension = await this . extensionsScanner . scanUserExtensionAtLocation ( resource ) ;
334
+ if ( extension && extension . installedTimestamp === undefined ) {
335
+ this . knownDirectories . add ( resource ) ;
336
+ added . push ( extension ) ;
337
+ }
338
+ }
339
+
340
+ if ( added . length ) {
341
+ await this . extensionsProfileScannerService . addExtensionsToProfile ( added . map ( local => ( [ local , undefined ] ) ) , this . userDataProfilesService . defaultProfile . extensionsResource ) ;
342
+ this . _onDidInstallExtensions . fire ( added . map ( local => ( { local, version : local . manifest . version , identifier : local . identifier , operation : InstallOperation . None , profileLocation : this . userDataProfilesService . defaultProfile . extensionsResource } ) ) ) ;
343
+ }
344
+ }
345
+
279
346
}
280
347
281
348
export class ExtensionsScanner extends Disposable {
282
349
283
350
private readonly uninstalledPath : string ;
284
351
private readonly uninstalledFileLimiter : Queue < any > ;
285
352
353
+ private readonly _onExtract = this . _register ( new Emitter < URI > ( ) ) ;
354
+ readonly onExtract = this . _onExtract . event ;
355
+
286
356
constructor (
287
357
private readonly beforeRemovingExtension : ( e : ILocalExtension ) => Promise < void > ,
288
358
@IFileService private readonly fileService : IFileService ,
@@ -318,6 +388,18 @@ export class ExtensionsScanner extends Disposable {
318
388
return Promise . all ( scannedExtensions . map ( extension => this . toLocalExtension ( extension ) ) ) ;
319
389
}
320
390
391
+ async scanUserExtensionAtLocation ( location : URI ) : Promise < ILocalExtension | null > {
392
+ try {
393
+ const scannedExtension = await this . extensionsScannerService . scanExistingExtension ( location , ExtensionType . User , { includeInvalid : true } ) ;
394
+ if ( scannedExtension ) {
395
+ return await this . toLocalExtension ( scannedExtension ) ;
396
+ }
397
+ } catch ( error ) {
398
+ this . logService . error ( error ) ;
399
+ }
400
+ return null ;
401
+ }
402
+
321
403
async extractUserExtension ( extensionKey : ExtensionKey , zipPath : string , metadata : Metadata , token : CancellationToken ) : Promise < ILocalExtension > {
322
404
await this . migrateDefaultProfileExtensions ( ) ;
323
405
const folderName = extensionKey . toString ( ) ;
@@ -334,6 +416,7 @@ export class ExtensionsScanner extends Disposable {
334
416
await this . extensionsScannerService . updateMetadata ( URI . file ( tempPath ) , metadata ) ;
335
417
336
418
try {
419
+ this . _onExtract . fire ( URI . file ( extensionPath ) ) ;
337
420
await this . rename ( extensionKey , tempPath , extensionPath , Date . now ( ) + ( 2 * 60 * 1000 ) /* Retry for 2 minutes */ ) ;
338
421
this . logService . info ( 'Renamed to' , extensionPath ) ;
339
422
} catch ( error ) {
@@ -360,7 +443,7 @@ export class ExtensionsScanner extends Disposable {
360
443
return this . withUninstalledExtensions ( ) ;
361
444
}
362
445
363
- async setUninstalled ( ...extensions : ILocalExtension [ ] ) : Promise < void > {
446
+ async setUninstalled ( ...extensions : IExtension [ ] ) : Promise < void > {
364
447
const extensionKeys : ExtensionKey [ ] = extensions . map ( e => ExtensionKey . create ( e ) ) ;
365
448
await this . withUninstalledExtensions ( uninstalled =>
366
449
extensionKeys . forEach ( extensionKey => {
@@ -813,7 +896,7 @@ class UninstallExtensionFromProfileTask extends AbstractExtensionTask<void> impl
813
896
}
814
897
815
898
protected async doRun ( token : CancellationToken ) : Promise < void > {
816
- await this . extensionsProfileScannerService . removeExtensionFromProfile ( this . extension . identifier , this . profileLocation ) ;
899
+ await this . extensionsProfileScannerService . removeExtensionFromProfile ( this . extension , this . profileLocation ) ;
817
900
}
818
901
819
902
}
0 commit comments