backupMainService.ts 4.2 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 9
import * as crypto from 'crypto';
import * as extfs from 'vs/base/node/extfs';
10
import Uri from 'vs/base/common/uri';
D
Daniel Imms 已提交
11
import { IBackupWorkspacesFormat, IBackupMainService } from 'vs/platform/backup/common/backup';
12 13
import { IEnvironmentService } from 'vs/platform/environment/common/environment';

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

	public _serviceBrand: any;

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

21
	private backups: IBackupWorkspacesFormat;
22 23

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

29
		this.loadSync();
30 31
	}

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

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

		if (needsSaving) {
			this.saveSync();
		}
48 49
	}

50 51
	protected removeWorkspaceBackupPathSync(workspace: Uri): void {
		if (!this.backups.folderWorkspaces) {
52 53
			return;
		}
54
		const index = this.backups.folderWorkspaces.indexOf(workspace.fsPath);
55 56 57
		if (index === -1) {
			return;
		}
58
		this.backups.folderWorkspaces.splice(index, 1);
59 60 61
		this.saveSync();
	}

62
	protected loadSync(): void {
63
		let backups: IBackupWorkspacesFormat;
D
Daniel Imms 已提交
64
		try {
65
			backups = JSON.parse(fs.readFileSync(this.workspacesJsonPath, 'utf8').toString()); // invalid JSON or permission issue can happen here
D
Daniel Imms 已提交
66
		} catch (error) {
67
			backups = Object.create(null);
D
Daniel Imms 已提交
68 69 70
		}

		// Ensure folderWorkspaces is a string[]
71 72
		if (backups.folderWorkspaces) {
			const fws = backups.folderWorkspaces;
D
Daniel Imms 已提交
73
			if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) {
74
				backups = Object.create(null);
D
Daniel Imms 已提交
75
			}
76 77
		}

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
		if (!backups.folderWorkspaces) {
			backups.folderWorkspaces = [];
		}

		this.backups = backups;

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

	private validateBackupWorkspaces(backups: IBackupWorkspacesFormat): void {
		const staleBackupWorkspaces: { workspacePath: string; backupPath: string; }[] = [];

		const backupWorkspaces = backups.folderWorkspaces;
		backupWorkspaces.forEach(workspacePath => {
			const backupPath = this.toBackupPath(workspacePath);
			if (!this.hasBackupsSync(backupPath)) {
				staleBackupWorkspaces.push({ workspacePath, backupPath });
			}
		});

		staleBackupWorkspaces.forEach(staleBackupWorkspace => {
			const {backupPath, workspacePath} = staleBackupWorkspace;
			extfs.delSync(backupPath);
			this.removeWorkspaceBackupPathSync(Uri.file(workspacePath));
		});
	}

	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
122 123 124 125 126 127 128 129 130
		}
	}

	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);
			}
131
			fs.writeFileSync(this.workspacesJsonPath, JSON.stringify(this.backups));
132
		} catch (ex) {
133
			console.error('Could not save workspaces.json', ex);
134 135
		}
	}
136 137

	protected toBackupPath(workspacePath: string): string {
D
Daniel Imms 已提交
138
		const workspaceHash = crypto.createHash('md5').update(workspacePath.toLowerCase()).digest('hex');
139 140 141

		return path.join(this.backupHome, workspaceHash);
	}
142
}