@@ -10,7 +10,6 @@ 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
12
import { Emitter } from 'vs/base/common/event' ;
13
- import { randomPath } from 'vs/base/common/extpath' ;
14
13
import { Disposable } from 'vs/base/common/lifecycle' ;
15
14
import { ResourceSet } from 'vs/base/common/map' ;
16
15
import { Schemas } from 'vs/base/common/network' ;
@@ -20,7 +19,7 @@ import { joinPath } from 'vs/base/common/resources';
20
19
import * as semver from 'vs/base/common/semver/semver' ;
21
20
import { isBoolean , isUndefined } from 'vs/base/common/types' ;
22
21
import { URI } from 'vs/base/common/uri' ;
23
- import { generateUuid } from 'vs/base/common/uuid' ;
22
+ import { generateUuid , isUUID } from 'vs/base/common/uuid' ;
24
23
import * as pfs from 'vs/base/node/pfs' ;
25
24
import { extract , ExtractError , IFile , zip } from 'vs/base/node/zip' ;
26
25
import * as nls from 'vs/nls' ;
@@ -410,12 +409,14 @@ export class ExtensionsScanner extends Disposable {
410
409
private readonly _onExtract = this . _register ( new Emitter < URI > ( ) ) ;
411
410
readonly onExtract = this . _onExtract . event ;
412
411
412
+ private cleanUpGeneratedFoldersPromise : Promise < void > = Promise . resolve ( ) ;
413
+
413
414
constructor (
414
415
private readonly beforeRemovingExtension : ( e : ILocalExtension ) => Promise < void > ,
415
416
@IFileService private readonly fileService : IFileService ,
416
417
@IExtensionsScannerService private readonly extensionsScannerService : IExtensionsScannerService ,
417
418
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService : IExtensionsProfileScannerService ,
418
- @INativeEnvironmentService private readonly environmentService : INativeEnvironmentService ,
419
+ @IUriIdentityService private readonly uriIdentityService : IUriIdentityService ,
419
420
@ILogService private readonly logService : ILogService ,
420
421
) {
421
422
super ( ) ;
@@ -425,6 +426,8 @@ export class ExtensionsScanner extends Disposable {
425
426
426
427
async cleanUp ( ) : Promise < void > {
427
428
await this . removeUninstalledExtensions ( ) ;
429
+ this . cleanUpGeneratedFoldersPromise = this . cleanUpGeneratedFoldersPromise . then ( ( ) => this . removeGeneratedFolders ( ) ) ;
430
+ await this . cleanUpGeneratedFoldersPromise ;
428
431
}
429
432
430
433
async scanExtensions ( type : ExtensionType | null , profileLocation : URI ) : Promise < ILocalExtension [ ] > {
@@ -457,8 +460,10 @@ export class ExtensionsScanner extends Disposable {
457
460
}
458
461
459
462
async extractUserExtension ( extensionKey : ExtensionKey , zipPath : string , metadata : Metadata , token : CancellationToken ) : Promise < ILocalExtension > {
463
+ await this . cleanUpGeneratedFoldersPromise . catch ( ( ) => undefined ) ;
464
+
460
465
const folderName = extensionKey . toString ( ) ;
461
- const tempPath = randomPath ( this . environmentService . tmpDir . fsPath ) ;
466
+ const tempPath = path . join ( this . extensionsScannerService . userExtensionsLocation . fsPath , `. ${ generateUuid ( ) } ` ) ;
462
467
const extensionPath = path . join ( this . extensionsScannerService . userExtensionsLocation . fsPath , folderName ) ;
463
468
464
469
try {
@@ -526,7 +531,9 @@ export class ExtensionsScanner extends Disposable {
526
531
527
532
async removeExtension ( extension : ILocalExtension | IScannedExtension , type : string ) : Promise < void > {
528
533
this . logService . trace ( `Deleting ${ type } extension from disk` , extension . identifier . id , extension . location . fsPath ) ;
529
- await this . fileService . del ( extension . location , { recursive : true } ) ;
534
+ const renamedLocation = this . uriIdentityService . extUri . joinPath ( this . uriIdentityService . extUri . dirname ( extension . location ) , `._${ generateUuid ( ) } ` ) ;
535
+ await this . rename ( extension . identifier , extension . location . fsPath , renamedLocation . fsPath , Date . now ( ) + ( 2 * 60 * 1000 ) /* Retry for 2 minutes */ ) ;
536
+ await this . fileService . del ( renamedLocation , { recursive : true } ) ;
530
537
this . logService . info ( 'Deleted from disk' , extension . identifier . id , extension . location . fsPath ) ;
531
538
}
532
539
@@ -687,6 +694,33 @@ export class ExtensionsScanner extends Disposable {
687
694
await Promise . allSettled ( toRemove . map ( e => this . removeUninstalledExtension ( e ) ) ) ;
688
695
}
689
696
697
+ private async removeGeneratedFolders ( ) : Promise < void > {
698
+ this . logService . trace ( 'ExtensionManagementService#removeGeneratedFolders' ) ;
699
+ const promises : Promise < any > [ ] = [ ] ;
700
+ let stat ;
701
+ try {
702
+ stat = await this . fileService . resolve ( this . extensionsScannerService . userExtensionsLocation ) ;
703
+ } catch ( error ) {
704
+ if ( toFileOperationResult ( error ) !== FileOperationResult . FILE_NOT_FOUND ) {
705
+ this . logService . error ( error ) ;
706
+ }
707
+ }
708
+ for ( const child of stat ?. children ?? [ ] ) {
709
+ if ( child . isDirectory && child . name . startsWith ( '._' ) && isUUID ( child . name . substring ( 2 ) ) ) {
710
+ promises . push ( ( async ( ) => {
711
+ this . logService . trace ( 'Deleting the generated extension folder' , child . resource . toString ( ) ) ;
712
+ try {
713
+ await this . fileService . del ( child . resource , { recursive : true } ) ;
714
+ this . logService . info ( 'Deleted the generated extension folder' , child . resource . toString ( ) ) ;
715
+ } catch ( error ) {
716
+ this . logService . error ( error ) ;
717
+ }
718
+ } ) ( ) ) ;
719
+ }
720
+ }
721
+ await Promise . allSettled ( promises ) ;
722
+ }
723
+
690
724
private joinErrors ( errorOrErrors : ( Error | string ) | ( Array < Error | string > ) ) : Error {
691
725
const errors = Array . isArray ( errorOrErrors ) ? errorOrErrors : [ errorOrErrors ] ;
692
726
if ( errors . length === 1 ) {
0 commit comments