storageService.ts 11.5 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
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
7 8 9
import { Event, Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
B
Benjamin Pasero 已提交
10
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
B
Benjamin Pasero 已提交
11
import { Storage, IStorageLoggingOptions } from 'vs/base/node/storage';
12
import { IStorageLegacyService, StorageLegacyScope } from 'vs/platform/storage/common/storageLegacyService';
13
import { startsWith } from 'vs/base/common/strings';
14 15 16
import { Action } from 'vs/base/common/actions';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { localize } from 'vs/nls';
17
import { mark, getDuration } from 'vs/base/common/performance';
18
import { join, basename } from 'path';
B
Benjamin Pasero 已提交
19
import { copy } from 'vs/base/node/pfs';
20

21
export class StorageService extends Disposable implements IStorageService {
22 23
	_serviceBrand: any;

24
	static IN_MEMORY_PATH = ':memory:';
25

26 27 28
	private _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
	get onDidChangeStorage(): Event<IWorkspaceStorageChangeEvent> { return this._onDidChangeStorage.event; }

29 30
	private _onWillSaveState: Emitter<void> = this._register(new Emitter<void>());
	get onWillSaveState(): Event<void> { return this._onWillSaveState.event; }
31

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
	private bufferedStorageErrors: (string | Error)[] = [];
	private _onStorageError: Emitter<string | Error> = this._register(new Emitter<string | Error>());
	get onStorageError(): Event<string | Error> {
		if (Array.isArray(this.bufferedStorageErrors)) {
			if (this.bufferedStorageErrors.length > 0) {
				const bufferedStorageErrors = this.bufferedStorageErrors;
				setTimeout(() => {
					this._onStorageError.fire(`[startup errors] ${bufferedStorageErrors.join('\n')}`);
				}, 0);
			}

			this.bufferedStorageErrors = void 0;
		}

		return this._onStorageError.event;
	}

B
Benjamin Pasero 已提交
49
	private globalStorage: Storage;
B
Benjamin Pasero 已提交
50
	private globalStorageWorkspacePath: string;
51 52

	private workspaceStoragePath: string;
B
Benjamin Pasero 已提交
53
	private workspaceStorage: Storage;
54 55 56
	private workspaceStorageListener: IDisposable;

	private loggingOptions: IStorageLoggingOptions;
57 58

	constructor(
59
		workspaceStoragePath: string,
60
		@ILogService logService: ILogService,
B
Benjamin Pasero 已提交
61
		@IEnvironmentService environmentService: IEnvironmentService
62 63 64
	) {
		super();

65
		this.loggingOptions = {
B
Benjamin Pasero 已提交
66
			info: environmentService.verbose || environmentService.logStorage,
B
Benjamin Pasero 已提交
67
			infoLogger: msg => logService.info(msg),
68 69 70 71 72 73 74 75 76
			errorLogger: error => {
				logService.error(error);

				if (Array.isArray(this.bufferedStorageErrors)) {
					this.bufferedStorageErrors.push(error);
				} else {
					this._onStorageError.fire(error);
				}
			}
B
Benjamin Pasero 已提交
77 78
		};

B
Benjamin Pasero 已提交
79 80
		this.globalStorageWorkspacePath = workspaceStoragePath === StorageService.IN_MEMORY_PATH ? StorageService.IN_MEMORY_PATH : StorageService.IN_MEMORY_PATH;
		this.globalStorage = new Storage({ path: this.globalStorageWorkspacePath, logging: this.loggingOptions });
81
		this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL)));
82

83
		this.createWorkspaceStorage(workspaceStoragePath);
84 85
	}

86 87 88 89 90 91 92 93 94 95
	private createWorkspaceStorage(workspaceStoragePath: string): void {

		// Dispose old (if any)
		this.workspaceStorage = dispose(this.workspaceStorage);
		this.workspaceStorageListener = dispose(this.workspaceStorageListener);

		// Create new
		this.workspaceStoragePath = workspaceStoragePath;
		this.workspaceStorage = new Storage({ path: workspaceStoragePath, logging: this.loggingOptions });
		this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.WORKSPACE));
96 97
	}

B
Benjamin Pasero 已提交
98 99
	private handleDidChangeStorage(key: string, scope: StorageScope): void {
		this._onDidChangeStorage.fire({ key, scope });
100 101 102
	}

	init(): Promise<void> {
103 104 105 106 107 108 109
		mark('willInitGlobalStorage');
		mark('willInitWorkspaceStorage');

		return Promise.all([
			this.globalStorage.init().then(() => mark('didInitGlobalStorage')),
			this.workspaceStorage.init().then(() => mark('didInitWorkspaceStorage'))
		]).then(() => void 0);
110 111
	}

B
Benjamin Pasero 已提交
112
	get(key: string, scope: StorageScope, fallbackValue?: any): string {
113 114 115
		return this.getStorage(scope).get(key, fallbackValue);
	}

B
Benjamin Pasero 已提交
116
	getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean {
117 118 119
		return this.getStorage(scope).getBoolean(key, fallbackValue);
	}

B
Benjamin Pasero 已提交
120
	getInteger(key: string, scope: StorageScope, fallbackValue?: number): number {
121 122 123
		return this.getStorage(scope).getInteger(key, fallbackValue);
	}

124 125
	store(key: string, value: any, scope: StorageScope): void {
		this.getStorage(scope).set(key, value);
126 127
	}

128 129
	remove(key: string, scope: StorageScope): void {
		this.getStorage(scope).delete(key);
130 131
	}

132
	close(): Promise<void> {
133 134

		// Signal as event so that clients can still store data
135
		this._onWillSaveState.fire();
136 137

		// Do it
B
Benjamin Pasero 已提交
138 139 140 141
		return Promise.all([
			this.globalStorage.close(),
			this.workspaceStorage.close()
		]).then(() => void 0);
142 143
	}

B
Benjamin Pasero 已提交
144
	private getStorage(scope: StorageScope): Storage {
145 146
		return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
	}
147

148 149 150 151
	getSize(scope: StorageScope): number {
		return scope === StorageScope.GLOBAL ? this.globalStorage.size : this.workspaceStorage.size;
	}

152 153 154 155
	checkIntegrity(scope: StorageScope, full: boolean): Promise<string> {
		return scope === StorageScope.GLOBAL ? this.globalStorage.checkIntegrity(full) : this.workspaceStorage.checkIntegrity(full);
	}

156
	logStorage(): Promise<void> {
157 158 159 160 161 162
		return Promise.all([
			this.globalStorage.getItems(),
			this.workspaceStorage.getItems(),
			this.globalStorage.checkIntegrity(true /* full */),
			this.workspaceStorage.checkIntegrity(true /* full */)
		]).then(result => {
B
Benjamin Pasero 已提交
163 164 165 166 167 168 169 170
			const safeParse = (value: string) => {
				try {
					return JSON.parse(value);
				} catch (error) {
					return value;
				}
			};

171
			const globalItems = Object.create(null);
B
Benjamin Pasero 已提交
172
			const globalItemsParsed = Object.create(null);
173
			result[0].forEach((value, key) => {
B
Benjamin Pasero 已提交
174 175 176
				globalItems[key] = value;
				globalItemsParsed[key] = safeParse(value);
			});
177 178

			const workspaceItems = Object.create(null);
B
Benjamin Pasero 已提交
179
			const workspaceItemsParsed = Object.create(null);
180
			result[1].forEach((value, key) => {
B
Benjamin Pasero 已提交
181 182 183
				workspaceItems[key] = value;
				workspaceItemsParsed[key] = safeParse(value);
			});
184

B
Benjamin Pasero 已提交
185
			console.group(`Storage: Global (check: ${result[2]}, load: ${getDuration('willInitGlobalStorage', 'didInitGlobalStorage')}, path: ${this.globalStorageWorkspacePath})`);
186 187 188
			console.table(globalItems);
			console.groupEnd();

B
Benjamin Pasero 已提交
189 190
			console.log(globalItemsParsed);

B
Benjamin Pasero 已提交
191
			console.group(`Storage: Workspace (check: ${result[3]}, load: ${getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage')}, path: ${this.workspaceStoragePath})`);
192 193
			console.table(workspaceItems);
			console.groupEnd();
B
Benjamin Pasero 已提交
194 195

			console.log(workspaceItemsParsed);
196 197
		});
	}
198

B
Benjamin Pasero 已提交
199
	migrate(toWorkspaceStorageFolder: string): Promise<void> {
200 201 202 203 204
		if (this.workspaceStoragePath === StorageService.IN_MEMORY_PATH) {
			return Promise.resolve(); // no migration needed if running in memory
		}

		// Compute new workspace storage path based on workspace identifier
B
Benjamin Pasero 已提交
205
		const newWorkspaceStoragePath = join(toWorkspaceStorageFolder, basename(this.workspaceStoragePath));
206 207 208 209 210 211
		if (this.workspaceStoragePath === newWorkspaceStoragePath) {
			return Promise.resolve(); // guard against migrating to same path
		}

		// Close workspace DB to be able to copy
		return this.workspaceStorage.close().then(() => {
B
Benjamin Pasero 已提交
212 213
			return copy(this.workspaceStoragePath, newWorkspaceStoragePath).then(() => {
				this.createWorkspaceStorage(newWorkspaceStoragePath);
214

B
Benjamin Pasero 已提交
215
				return this.workspaceStorage.init();
216 217 218
			});
		});
	}
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
}

export class LogStorageAction extends Action {

	static readonly ID = 'workbench.action.logStorage';
	static LABEL = localize('logStorage', "Log Storage");

	constructor(
		id: string,
		label: string,
		@IStorageService private storageService: DelegatingStorageService,
		@IWindowService private windowService: IWindowService
	) {
		super(id, label);
	}

	run(): Thenable<void> {
236
		this.storageService.storage.logStorage();
237 238 239

		return this.windowService.openDevTools();
	}
240 241
}

242
export class DelegatingStorageService extends Disposable implements IStorageService {
243 244 245 246 247
	_serviceBrand: any;

	private _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
	get onDidChangeStorage(): Event<IWorkspaceStorageChangeEvent> { return this._onDidChangeStorage.event; }

248 249
	private _onWillSaveState: Emitter<void> = this._register(new Emitter<void>());
	get onWillSaveState(): Event<void> { return this._onWillSaveState.event; }
250

251 252
	private closed: boolean;

253
	constructor(
254 255 256
		private storageService: IStorageService,
		private storageLegacyService: IStorageLegacyService,
		private logService: ILogService
257 258 259
	) {
		super();

260 261 262 263
		this.registerListeners();
	}

	private registerListeners(): void {
264
		this._register(this.storageService.onDidChangeStorage(e => this._onDidChangeStorage.fire(e)));
265
		this._register(this.storageService.onWillSaveState(() => this._onWillSaveState.fire()));
266 267

		const globalKeyMarker = 'storage://global/';
268 269

		window.addEventListener('storage', e => {
270 271 272 273 274
			if (startsWith(e.key, globalKeyMarker)) {
				const key = e.key.substr(globalKeyMarker.length);

				this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL });
			}
275
		});
276 277
	}

278
	get storage(): StorageService {
279
		return this.storageService as StorageService;
280 281
	}

282
	get(key: string, scope: StorageScope, fallbackValue?: any): string {
283 284 285
		if (scope === StorageScope.WORKSPACE) {
			return this.storageService.get(key, scope, fallbackValue);
		}
286

287
		return this.storageLegacyService.get(key, this.convertScope(scope), fallbackValue);
288 289 290
	}

	getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean {
291 292 293
		if (scope === StorageScope.WORKSPACE) {
			return this.storageService.getBoolean(key, scope, fallbackValue);
		}
294

295
		return this.storageLegacyService.getBoolean(key, this.convertScope(scope), fallbackValue);
296 297 298
	}

	getInteger(key: string, scope: StorageScope, fallbackValue?: number): number {
299
		if (scope === StorageScope.WORKSPACE) {
300
			return this.storageService.getInteger(key, scope, fallbackValue);
301
		}
302

303
		return this.storageLegacyService.getInteger(key, this.convertScope(scope), fallbackValue);
304 305
	}

306
	store(key: string, value: any, scope: StorageScope): void {
307
		if (this.closed) {
B
Benjamin Pasero 已提交
308
			this.logService.warn(`Unsupported write (store) access after close (key: ${key})`);
309

310
			return; // prevent writing after close to detect late write access
311 312
		}

313
		this.storageLegacyService.store(key, value, this.convertScope(scope));
314

315
		this.storageService.store(key, value, scope);
316 317
	}

318
	remove(key: string, scope: StorageScope): void {
319
		if (this.closed) {
B
Benjamin Pasero 已提交
320
			this.logService.warn(`Unsupported write (remove) access after close (key: ${key})`);
321

322
			return; // prevent writing after close to detect late write access
323 324
		}

325
		this.storageLegacyService.remove(key, this.convertScope(scope));
326

327
		this.storageService.remove(key, scope);
328 329
	}

330
	close(): Promise<void> {
331
		const promise = this.storage.close();
B
Benjamin Pasero 已提交
332

333 334
		this.closed = true;

B
Benjamin Pasero 已提交
335
		return promise;
336 337
	}

338 339
	private convertScope(scope: StorageScope): StorageLegacyScope {
		return scope === StorageScope.GLOBAL ? StorageLegacyScope.GLOBAL : StorageLegacyScope.WORKSPACE;
340
	}
341
}