33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import * as crypto from 'crypto' ;
76import * as fs from 'fs' ;
87import * as path from 'path' ;
8+ import * as crypto from 'crypto' ;
9+ import * as extfs from 'vs/base/node/extfs' ;
910import Uri from 'vs/base/common/uri' ;
1011import { IBackupWorkspacesFormat , IBackupMainService } from 'vs/platform/backup/common/backup' ;
1112import { IEnvironmentService } from 'vs/platform/environment/common/environment' ;
12- import { ILifecycleMainService } from 'vs/platform/lifecycle/common/mainLifecycle' ;
13- import { VSCodeWindow } from 'vs/code/electron-main/window' ;
1413
1514export class BackupMainService implements IBackupMainService {
1615
@@ -19,86 +18,107 @@ export class BackupMainService implements IBackupMainService {
1918 protected backupHome : string ;
2019 protected workspacesJsonPath : string ;
2120
22- private workspacesJsonContent : IBackupWorkspacesFormat ;
21+ private backups : IBackupWorkspacesFormat ;
2322
2423 constructor (
25- @IEnvironmentService environmentService : IEnvironmentService ,
26- @ILifecycleMainService lifecycleService : ILifecycleMainService
24+ @IEnvironmentService environmentService : IEnvironmentService
2725 ) {
2826 this . backupHome = environmentService . backupHome ;
2927 this . workspacesJsonPath = environmentService . backupWorkspacesPath ;
3028
31- lifecycleService . onBeforeUnload ( this . onBeforeUnloadWindow . bind ( this ) ) ;
32-
3329 this . loadSync ( ) ;
3430 }
3531
36- private onBeforeUnloadWindow ( vscodeWindow : VSCodeWindow ) {
37- if ( vscodeWindow . openedWorkspacePath ) {
38- // Clear out workspace from workspaces.json if it doesn't have any backups
39- const workspaceResource = Uri . file ( vscodeWindow . openedWorkspacePath ) ;
40- if ( ! this . hasWorkspaceBackup ( workspaceResource ) ) {
41- this . removeWorkspaceBackupPathSync ( workspaceResource ) ;
42- }
43- }
44- }
45-
4632 public getWorkspaceBackupPaths ( ) : string [ ] {
47- return this . workspacesJsonContent . folderWorkspaces ;
33+ return this . backups . folderWorkspaces ;
4834 }
4935
5036 public pushWorkspaceBackupPathsSync ( workspaces : Uri [ ] ) : void {
37+ let needsSaving = false ;
5138 workspaces . forEach ( workspace => {
52- // Hot exit is disabled for empty workspaces
53- if ( ! workspace ) {
54- return ;
55- }
56-
57- if ( this . workspacesJsonContent . folderWorkspaces . indexOf ( workspace . fsPath ) === - 1 ) {
58- this . workspacesJsonContent . folderWorkspaces . push ( workspace . fsPath ) ;
39+ if ( this . backups . folderWorkspaces . indexOf ( workspace . fsPath ) === - 1 ) {
40+ this . backups . folderWorkspaces . push ( workspace . fsPath ) ;
41+ needsSaving = true ;
5942 }
6043 } ) ;
61- this . saveSync ( ) ;
44+
45+ if ( needsSaving ) {
46+ this . saveSync ( ) ;
47+ }
6248 }
6349
64- public removeWorkspaceBackupPathSync ( workspace : Uri ) : void {
65- if ( ! this . workspacesJsonContent . folderWorkspaces ) {
50+ protected removeWorkspaceBackupPathSync ( workspace : Uri ) : void {
51+ if ( ! this . backups . folderWorkspaces ) {
6652 return ;
6753 }
68- const index = this . workspacesJsonContent . folderWorkspaces . indexOf ( workspace . fsPath ) ;
54+ const index = this . backups . folderWorkspaces . indexOf ( workspace . fsPath ) ;
6955 if ( index === - 1 ) {
7056 return ;
7157 }
72- this . workspacesJsonContent . folderWorkspaces . splice ( index , 1 ) ;
58+ this . backups . folderWorkspaces . splice ( index , 1 ) ;
7359 this . saveSync ( ) ;
7460 }
7561
76- public hasWorkspaceBackup ( workspace : Uri ) : boolean {
77- return fs . existsSync ( this . getWorkspaceBackupDirectory ( workspace ) ) ;
78- }
79-
80- private getWorkspaceBackupDirectory ( workspace : Uri ) : string {
81- const workspaceHash = crypto . createHash ( 'md5' ) . update ( workspace . fsPath ) . digest ( 'hex' ) ;
82- return path . join ( this . backupHome , workspaceHash ) ;
83- }
84-
8562 protected loadSync ( ) : void {
63+ let backups : IBackupWorkspacesFormat ;
8664 try {
87- this . workspacesJsonContent = JSON . parse ( fs . readFileSync ( this . workspacesJsonPath , 'utf8' ) . toString ( ) ) ; // invalid JSON or permission issue can happen here
65+ backups = JSON . parse ( fs . readFileSync ( this . workspacesJsonPath , 'utf8' ) . toString ( ) ) ; // invalid JSON or permission issue can happen here
8866 } catch ( error ) {
89- this . workspacesJsonContent = Object . create ( null ) ;
67+ backups = Object . create ( null ) ;
9068 }
9169
9270 // Ensure folderWorkspaces is a string[]
93- if ( this . workspacesJsonContent . folderWorkspaces ) {
94- const fws = this . workspacesJsonContent . folderWorkspaces ;
71+ if ( backups . folderWorkspaces ) {
72+ const fws = backups . folderWorkspaces ;
9573 if ( ! Array . isArray ( fws ) || fws . some ( f => typeof f !== 'string' ) ) {
96- this . workspacesJsonContent = Object . create ( null ) ;
74+ backups = Object . create ( null ) ;
9775 }
9876 }
9977
100- if ( ! this . workspacesJsonContent . folderWorkspaces ) {
101- this . workspacesJsonContent . folderWorkspaces = [ ] ;
78+ if ( ! backups . folderWorkspaces ) {
79+ backups . folderWorkspaces = [ ] ;
80+ }
81+
82+ this . backups = backups ;
83+
84+ // Validate backup workspaces
85+ this . validateBackupWorkspaces ( backups ) ;
86+ }
87+
88+ private validateBackupWorkspaces ( backups : IBackupWorkspacesFormat ) : void {
89+ const staleBackupWorkspaces : { workspacePath : string ; backupPath : string ; } [ ] = [ ] ;
90+
91+ const backupWorkspaces = backups . folderWorkspaces ;
92+ backupWorkspaces . forEach ( workspacePath => {
93+ const backupPath = this . toBackupPath ( workspacePath ) ;
94+ if ( ! this . hasBackupsSync ( backupPath ) ) {
95+ staleBackupWorkspaces . push ( { workspacePath, backupPath } ) ;
96+ }
97+ } ) ;
98+
99+ staleBackupWorkspaces . forEach ( staleBackupWorkspace => {
100+ const { backupPath, workspacePath} = staleBackupWorkspace ;
101+ extfs . delSync ( backupPath ) ;
102+ this . removeWorkspaceBackupPathSync ( Uri . file ( workspacePath ) ) ;
103+ } ) ;
104+ }
105+
106+ private hasBackupsSync ( backupPath ) : boolean {
107+ try {
108+ const backupSchemas = extfs . readdirSync ( backupPath ) ;
109+ if ( backupSchemas . length === 0 ) {
110+ return false ; // empty backups
111+ }
112+
113+ return backupSchemas . some ( backupSchema => {
114+ try {
115+ return extfs . readdirSync ( path . join ( backupPath , backupSchema ) ) . length > 0 ;
116+ } catch ( error ) {
117+ return false ; // invalid folder
118+ }
119+ } ) ;
120+ } catch ( error ) {
121+ return false ; // backup path does not exist
102122 }
103123 }
104124
@@ -108,9 +128,15 @@ export class BackupMainService implements IBackupMainService {
108128 if ( ! fs . existsSync ( this . backupHome ) ) {
109129 fs . mkdirSync ( this . backupHome ) ;
110130 }
111- fs . writeFileSync ( this . workspacesJsonPath , JSON . stringify ( this . workspacesJsonContent ) ) ;
131+ fs . writeFileSync ( this . workspacesJsonPath , JSON . stringify ( this . backups ) ) ;
112132 } catch ( ex ) {
113133 console . error ( 'Could not save workspaces.json' , ex ) ;
114134 }
115135 }
136+
137+ protected toBackupPath ( workspacePath : string ) : string {
138+ const workspaceHash = crypto . createHash ( 'md5' ) . update ( workspacePath ) . digest ( 'hex' ) ;
139+
140+ return path . join ( this . backupHome , workspaceHash ) ;
141+ }
116142}
0 commit comments