backupMainService.ts 6.0 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6 7
import * as fs from 'fs';
import * as path from 'path';
8
import * as crypto from 'crypto';
9
import * as platform from 'vs/base/common/platform';
10
import * as extfs from 'vs/base/node/extfs';
11
import Uri from 'vs/base/common/uri';
D
Daniel Imms 已提交
12
import { IBackupWorkspacesFormat, IBackupMainService } from 'vs/platform/backup/common/backup';
13 14
import { IEnvironmentService } from 'vs/platform/environment/common/environment';

D
Daniel Imms 已提交
15
export class BackupMainService implements IBackupMainService {
16 17 18 19

	public _serviceBrand: any;

	protected backupHome: string;
D
Daniel Imms 已提交
20
	protected workspacesJsonPath: string;
21

22
	private backups: IBackupWorkspacesFormat;
23 24

	constructor(
25
		@IEnvironmentService environmentService: IEnvironmentService
26 27
	) {
		this.backupHome = environmentService.backupHome;
D
Daniel Imms 已提交
28
		this.workspacesJsonPath = environmentService.backupWorkspacesPath;
D
Daniel Imms 已提交
29

30
		this.loadSync();
31 32
	}

33
	public getWorkspaceBackupPaths(): string[] {
34
		return this.backups.folderWorkspaces;
35 36
	}

37 38 39 40
	public getEmptyWorkspaceBackupWindowIds(): string[] {
		return this.backups.emptyWorkspaces;
	}

41
	public pushWorkspaceBackupPathsSync(workspaces: Uri[]): void {
42
		let needsSaving = false;
43
		workspaces.forEach(workspace => {
44 45 46
			if (this.backups.folderWorkspaces.indexOf(workspace.fsPath) === -1) {
				this.backups.folderWorkspaces.push(workspace.fsPath);
				needsSaving = true;
47 48
			}
		});
49 50 51 52

		if (needsSaving) {
			this.saveSync();
		}
53 54
	}

55 56 57 58 59 60 61 62 63 64
	// TODO: Think of a less terrible name
	// TODO: Test
	// TODO: Merge with pushWorkspaceBackupPathsSync?
	public pushEmptyWorkspaceBackupWindowIdSync(vscodeWindowId: string): void {
		if (this.backups.emptyWorkspaces.indexOf(vscodeWindowId) === -1) {
			this.backups.emptyWorkspaces.push(vscodeWindowId);
			this.saveSync();
		}
	}

65 66
	protected removeWorkspaceBackupPathSync(workspace: Uri): void {
		if (!this.backups.folderWorkspaces) {
67 68
			return;
		}
69
		const index = this.backups.folderWorkspaces.indexOf(workspace.fsPath);
70 71 72
		if (index === -1) {
			return;
		}
73
		this.backups.folderWorkspaces.splice(index, 1);
74 75 76
		this.saveSync();
	}

77 78 79 80 81 82 83 84 85 86 87 88 89 90
	// TODO: Test
	// TODO: Merge with removeWorkspaceBackupPathSync?
	private removeEmptyWorkspaceBackupWindowIdSync(vscodeWindowId: string): void {
		if (!this.backups.emptyWorkspaces) {
			return;
		}
		const index = this.backups.emptyWorkspaces.indexOf(vscodeWindowId);
		if (index === -1) {
			return;
		}
		this.backups.emptyWorkspaces.splice(index, 1);
		this.saveSync();
	}

91
	protected loadSync(): void {
92
		let backups: IBackupWorkspacesFormat;
D
Daniel Imms 已提交
93
		try {
94
			backups = JSON.parse(fs.readFileSync(this.workspacesJsonPath, 'utf8').toString()); // invalid JSON or permission issue can happen here
D
Daniel Imms 已提交
95
		} catch (error) {
96
			backups = Object.create(null);
D
Daniel Imms 已提交
97 98 99
		}

		// Ensure folderWorkspaces is a string[]
100 101
		if (backups.folderWorkspaces) {
			const fws = backups.folderWorkspaces;
D
Daniel Imms 已提交
102
			if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) {
103
				backups.folderWorkspaces = [];
D
Daniel Imms 已提交
104
			}
105 106
		} else {
			backups.folderWorkspaces = [];
107 108
		}

109 110 111 112 113 114 115 116
		// Ensure emptyWorkspaces is a string[]
		if (backups.emptyWorkspaces) {
			const fws = backups.emptyWorkspaces;
			if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) {
				backups.emptyWorkspaces = [];
			}
		} else {
			backups.emptyWorkspaces = [];
117 118 119 120 121 122 123 124 125
		}

		this.backups = backups;

		// Validate backup workspaces
		this.validateBackupWorkspaces(backups);
	}

	private validateBackupWorkspaces(backups: IBackupWorkspacesFormat): void {
126 127
		// TODO: Tidy up, improve names, reduce duplication
		const staleBackupWorkspaces: { workspaceIdentifier: string; backupPath: string; }[] = [];
128

129
		backups.folderWorkspaces.forEach(workspacePath => {
130 131
			const backupPath = this.toBackupPath(workspacePath);
			if (!this.hasBackupsSync(backupPath)) {
132 133 134 135 136 137 138 139 140
				staleBackupWorkspaces.push({ workspaceIdentifier: workspacePath, backupPath });
			}
		});
		console.log('checking empty: ' + backups.emptyWorkspaces);
		backups.emptyWorkspaces.forEach(vscodeWindowId => {
			const backupPath = this.toEmptyWorkspaceBackupPath(vscodeWindowId);
			console.log('backupPath: ' + backupPath);
			if (!this.hasBackupsSync(backupPath)) {
				staleBackupWorkspaces.push({ workspaceIdentifier: vscodeWindowId, backupPath });
141 142 143 144
			}
		});

		staleBackupWorkspaces.forEach(staleBackupWorkspace => {
145
			const {backupPath, workspaceIdentifier} = staleBackupWorkspace;
146
			extfs.delSync(backupPath);
147 148
			this.removeWorkspaceBackupPathSync(Uri.file(workspaceIdentifier));
			this.removeEmptyWorkspaceBackupWindowIdSync(workspaceIdentifier);
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
		});
	}

	private hasBackupsSync(backupPath): boolean {
		try {
			const backupSchemas = extfs.readdirSync(backupPath);
			if (backupSchemas.length === 0) {
				return false; // empty backups
			}

			return backupSchemas.some(backupSchema => {
				try {
					return extfs.readdirSync(path.join(backupPath, backupSchema)).length > 0;
				} catch (error) {
					return false; // invalid folder
				}
			});
		} catch (error) {
			return false; // backup path does not exist
168 169 170 171 172 173 174 175 176
		}
	}

	private saveSync(): void {
		try {
			// The user data directory must exist so only the Backup directory needs to be checked.
			if (!fs.existsSync(this.backupHome)) {
				fs.mkdirSync(this.backupHome);
			}
177
			fs.writeFileSync(this.workspacesJsonPath, JSON.stringify(this.backups));
178
		} catch (ex) {
179
			console.error('Could not save workspaces.json', ex);
180 181
		}
	}
182 183

	protected toBackupPath(workspacePath: string): string {
184 185
		const caseAwarePath = platform.isWindows || platform.isMacintosh ? workspacePath.toLowerCase() : workspacePath;
		const workspaceHash = crypto.createHash('md5').update(caseAwarePath).digest('hex');
186 187 188

		return path.join(this.backupHome, workspaceHash);
	}
189 190 191 192

	protected toEmptyWorkspaceBackupPath(vscodeWindowId: string): string {
		return path.join(this.backupHome, vscodeWindowId);
	}
193
}