@@ -19,7 +19,7 @@ import { joinPath } from 'vs/base/common/resources';
19
19
import * as semver from 'vs/base/common/semver/semver' ;
20
20
import { isBoolean , isUndefined } from 'vs/base/common/types' ;
21
21
import { URI } from 'vs/base/common/uri' ;
22
- import { generateUuid } from 'vs/base/common/uuid' ;
22
+ import { generateUuid , isUUID } from 'vs/base/common/uuid' ;
23
23
import * as pfs from 'vs/base/node/pfs' ;
24
24
import { extract , ExtractError , IFile , zip } from 'vs/base/node/zip' ;
25
25
import * as nls from 'vs/nls' ;
@@ -409,11 +409,14 @@ export class ExtensionsScanner extends Disposable {
409
409
private readonly _onExtract = this . _register ( new Emitter < URI > ( ) ) ;
410
410
readonly onExtract = this . _onExtract . event ;
411
411
412
+ private cleanUpGeneratedFoldersPromise : Promise < void > = Promise . resolve ( ) ;
413
+
412
414
constructor (
413
415
private readonly beforeRemovingExtension : ( e : ILocalExtension ) => Promise < void > ,
414
416
@IFileService private readonly fileService : IFileService ,
415
417
@IExtensionsScannerService private readonly extensionsScannerService : IExtensionsScannerService ,
416
418
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService : IExtensionsProfileScannerService ,
419
+ @IUriIdentityService private readonly uriIdentityService : IUriIdentityService ,
417
420
@ILogService private readonly logService : ILogService ,
418
421
) {
419
422
super ( ) ;
@@ -423,6 +426,8 @@ export class ExtensionsScanner extends Disposable {
423
426
424
427
async cleanUp ( ) : Promise < void > {
425
428
await this . removeUninstalledExtensions ( ) ;
429
+ this . cleanUpGeneratedFoldersPromise = this . cleanUpGeneratedFoldersPromise . then ( ( ) => this . removeGeneratedFolders ( ) ) ;
430
+ await this . cleanUpGeneratedFoldersPromise ;
426
431
}
427
432
428
433
async scanExtensions ( type : ExtensionType | null , profileLocation : URI ) : Promise < ILocalExtension [ ] > {
@@ -455,6 +460,8 @@ export class ExtensionsScanner extends Disposable {
455
460
}
456
461
457
462
async extractUserExtension ( extensionKey : ExtensionKey , zipPath : string , metadata : Metadata , token : CancellationToken ) : Promise < ILocalExtension > {
463
+ await this . cleanUpGeneratedFoldersPromise . catch ( ( ) => undefined ) ;
464
+
458
465
const folderName = extensionKey . toString ( ) ;
459
466
const tempPath = path . join ( this . extensionsScannerService . userExtensionsLocation . fsPath , `.${ generateUuid ( ) } ` ) ;
460
467
const extensionPath = path . join ( this . extensionsScannerService . userExtensionsLocation . fsPath , folderName ) ;
@@ -524,7 +531,9 @@ export class ExtensionsScanner extends Disposable {
524
531
525
532
async removeExtension ( extension : ILocalExtension | IScannedExtension , type : string ) : Promise < void > {
526
533
this . logService . trace ( `Deleting ${ type } extension from disk` , extension . identifier . id , extension . location . fsPath ) ;
527
- 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 } ) ;
528
537
this . logService . info ( 'Deleted from disk' , extension . identifier . id , extension . location . fsPath ) ;
529
538
}
530
539
@@ -685,6 +694,33 @@ export class ExtensionsScanner extends Disposable {
685
694
await Promise . allSettled ( toRemove . map ( e => this . removeUninstalledExtension ( e ) ) ) ;
686
695
}
687
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 ( 1 ) ) ) {
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
+
688
724
private joinErrors ( errorOrErrors : ( Error | string ) | ( Array < Error | string > ) ) : Error {
689
725
const errors = Array . isArray ( errorOrErrors ) ? errorOrErrors : [ errorOrErrors ] ;
690
726
if ( errors . length === 1 ) {
0 commit comments