storageService.ts 7.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import types = require('vs/base/common/types');
import errors = require('vs/base/common/errors');
import strings = require('vs/base/common/strings');
J
Johannes Rieken 已提交
10 11
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
E
Erich Gamma 已提交
12 13 14 15 16 17 18 19 20 21 22

// Browser localStorage interface
export interface IStorage {
	length: number;
	key(index: number): string;
	clear(): void;
	setItem(key: string, value: any): void;
	getItem(key: string): string;
	removeItem(key: string): void;
}

B
Benjamin Pasero 已提交
23
export class StorageService implements IStorageService {
24

25
	public _serviceBrand: any;
26

E
Erich Gamma 已提交
27
	private static COMMON_PREFIX = 'storage://';
28
	/*private*/ static GLOBAL_PREFIX = StorageService.COMMON_PREFIX + 'global/';
B
Benjamin Pasero 已提交
29
	private static WORKSPACE_PREFIX = StorageService.COMMON_PREFIX + 'workspace/';
E
Erich Gamma 已提交
30 31 32 33 34 35 36 37
	private static WORKSPACE_IDENTIFIER = 'workspaceIdentifier';
	private static NO_WORKSPACE_IDENTIFIER = '__$noWorkspace__';

	private workspaceStorage: IStorage;
	private globalStorage: IStorage;

	private workspaceKey: string;

38 39 40
	constructor(
		globalStorage: IStorage,
		workspaceStorage: IStorage,
B
Benjamin Pasero 已提交
41
		@IWorkspaceContextService contextService: IWorkspaceContextService
42
	) {
B
Benjamin Pasero 已提交
43
		const workspace = contextService.getWorkspace();
E
Erich Gamma 已提交
44

45
		this.globalStorage = globalStorage;
46
		this.workspaceStorage = workspaceStorage || globalStorage;
E
Erich Gamma 已提交
47 48 49 50 51

		// Calculate workspace storage key
		this.workspaceKey = this.getWorkspaceKey(workspace);

		// Make sure to delete all workspace storage if the workspace has been recreated meanwhile
52
		const workspaceUniqueId: number = workspace ? workspace.uid : void 0;
E
Erich Gamma 已提交
53 54 55 56 57 58 59 60 61 62 63
		if (types.isNumber(workspaceUniqueId)) {
			this.cleanupWorkspaceScope(workspaceUniqueId, workspace.name);
		}
	}

	private getWorkspaceKey(workspace?: IWorkspace): string {
		let workspaceUri: string = null;
		if (workspace && workspace.resource) {
			workspaceUri = workspace.resource.toString();
		}

B
Benjamin Pasero 已提交
64
		return workspaceUri ? this.calculateWorkspaceKey(workspaceUri) : StorageService.NO_WORKSPACE_IDENTIFIER;
65 66 67
	}

	private calculateWorkspaceKey(workspaceUrl: string): string {
B
Benjamin Pasero 已提交
68 69
		const root = 'file:///';
		const index = workspaceUrl.indexOf(root);
70 71 72 73 74
		if (index === 0) {
			return strings.rtrim(workspaceUrl.substr(root.length), '/') + '/';
		}

		return workspaceUrl;
E
Erich Gamma 已提交
75 76 77 78 79
	}

	private cleanupWorkspaceScope(workspaceId: number, workspaceName: string): void {

		// Get stored identifier from storage
B
Benjamin Pasero 已提交
80
		const id = this.getInteger(StorageService.WORKSPACE_IDENTIFIER, StorageScope.WORKSPACE);
E
Erich Gamma 已提交
81 82 83

		// If identifier differs, assume the workspace got recreated and thus clean all storage for this workspace
		if (types.isNumber(id) && workspaceId !== id) {
B
Benjamin Pasero 已提交
84 85 86
			const keyPrefix = this.toStorageKey('', StorageScope.WORKSPACE);
			const toDelete: string[] = [];
			const length = this.workspaceStorage.length;
E
Erich Gamma 已提交
87 88

			for (let i = 0; i < length; i++) {
B
Benjamin Pasero 已提交
89
				const key = this.workspaceStorage.key(i);
B
Benjamin Pasero 已提交
90
				if (key.indexOf(StorageService.WORKSPACE_PREFIX) < 0) {
E
Erich Gamma 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
					continue; // ignore stored things that don't belong to storage service or are defined globally
				}

				// Check for match on prefix
				if (key.indexOf(keyPrefix) === 0) {
					toDelete.push(key);
				}
			}

			if (toDelete.length > 0) {
				console.warn('Clearing previous version of local storage for workspace ', workspaceName);
			}

			// Run the delete
			toDelete.forEach((keyToDelete) => {
				this.workspaceStorage.removeItem(keyToDelete);
			});
		}

		// Store workspace identifier now
		if (workspaceId !== id) {
B
Benjamin Pasero 已提交
112
			this.store(StorageService.WORKSPACE_IDENTIFIER, workspaceId, StorageScope.WORKSPACE);
E
Erich Gamma 已提交
113 114 115 116 117 118 119 120 121
		}
	}

	public clear(): void {
		this.globalStorage.clear();
		this.workspaceStorage.clear();
	}

	public store(key: string, value: any, scope = StorageScope.GLOBAL): void {
B
Benjamin Pasero 已提交
122
		const storage = (scope === StorageScope.GLOBAL) ? this.globalStorage : this.workspaceStorage;
E
Erich Gamma 已提交
123 124 125 126 127 128

		if (types.isUndefinedOrNull(value)) {
			this.remove(key, scope); // we cannot store null or undefined, in that case we remove the key
			return;
		}

B
Benjamin Pasero 已提交
129
		const storageKey = this.toStorageKey(key, scope);
E
Erich Gamma 已提交
130 131 132 133 134 135 136 137 138 139

		// Store
		try {
			storage.setItem(storageKey, value);
		} catch (error) {
			errors.onUnexpectedError(error);
		}
	}

	public get(key: string, scope = StorageScope.GLOBAL, defaultValue?: any): string {
B
Benjamin Pasero 已提交
140
		const storage = (scope === StorageScope.GLOBAL) ? this.globalStorage : this.workspaceStorage;
E
Erich Gamma 已提交
141

B
Benjamin Pasero 已提交
142
		const value = storage.getItem(this.toStorageKey(key, scope));
E
Erich Gamma 已提交
143 144 145 146 147 148 149 150
		if (types.isUndefinedOrNull(value)) {
			return defaultValue;
		}

		return value;
	}

	public remove(key: string, scope = StorageScope.GLOBAL): void {
B
Benjamin Pasero 已提交
151 152
		const storage = (scope === StorageScope.GLOBAL) ? this.globalStorage : this.workspaceStorage;
		const storageKey = this.toStorageKey(key, scope);
E
Erich Gamma 已提交
153 154 155 156 157 158

		// Remove
		storage.removeItem(storageKey);
	}

	public swap(key: string, valueA: any, valueB: any, scope = StorageScope.GLOBAL, defaultValue?: any): void {
B
Benjamin Pasero 已提交
159
		const value = this.get(key, scope);
E
Erich Gamma 已提交
160 161 162 163 164 165 166 167 168 169
		if (types.isUndefinedOrNull(value) && defaultValue) {
			this.store(key, defaultValue, scope);
		} else if (value === valueA.toString()) { // Convert to string because store is string based
			this.store(key, valueB, scope);
		} else {
			this.store(key, valueA, scope);
		}
	}

	public getInteger(key: string, scope = StorageScope.GLOBAL, defaultValue?: number): number {
B
Benjamin Pasero 已提交
170
		const value = this.get(key, scope, defaultValue);
E
Erich Gamma 已提交
171 172 173 174 175 176 177 178 179

		if (types.isUndefinedOrNull(value)) {
			return defaultValue;
		}

		return parseInt(value, 10);
	}

	public getBoolean(key: string, scope = StorageScope.GLOBAL, defaultValue?: boolean): boolean {
B
Benjamin Pasero 已提交
180
		const value = this.get(key, scope, defaultValue);
E
Erich Gamma 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193 194

		if (types.isUndefinedOrNull(value)) {
			return defaultValue;
		}

		if (types.isString(value)) {
			return value.toLowerCase() === 'true' ? true : false;
		}

		return value ? true : false;
	}

	private toStorageKey(key: string, scope: StorageScope): string {
		if (scope === StorageScope.GLOBAL) {
B
Benjamin Pasero 已提交
195
			return StorageService.GLOBAL_PREFIX + key.toLowerCase();
E
Erich Gamma 已提交
196 197
		}

B
Benjamin Pasero 已提交
198
		return StorageService.WORKSPACE_PREFIX + this.workspaceKey + key.toLowerCase();
E
Erich Gamma 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
	}
}

// In-Memory Local Storage Implementation
export class InMemoryLocalStorage implements IStorage {
	private store: { [key: string]: string; };

	constructor() {
		this.store = {};
	}

	public get length() {
		return Object.keys(this.store).length;
	}

	public key(index: number): string {
B
Benjamin Pasero 已提交
215
		const keys = Object.keys(this.store);
E
Erich Gamma 已提交
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
		if (keys.length > index) {
			return keys[index];
		}

		return null;
	}

	public clear(): void {
		this.store = {};
	}

	public setItem(key: string, value: any): void {
		this.store[key] = value.toString();
	}

	public getItem(key: string): string {
B
Benjamin Pasero 已提交
232
		const item = this.store[key];
E
Erich Gamma 已提交
233 234 235 236 237 238 239 240 241 242 243 244
		if (!types.isUndefinedOrNull(item)) {
			return item;
		}

		return null;
	}

	public removeItem(key: string): void {
		delete this.store[key];
	}
}

245
export const inMemoryLocalStorageInstance = new InMemoryLocalStorage();