mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 02:48:30 -05:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -67,17 +67,16 @@ export interface IStorageService {
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
*/
|
||||
getInteger<R extends number | undefined>(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getInteger<R extends number | undefined>(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
|
||||
getInteger(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
|
||||
|
||||
/**
|
||||
* Store a string value under the given key to storage. The value will
|
||||
* be converted to a string.
|
||||
* Store a value under the given key to storage. The value will be converted to a string.
|
||||
*
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
*/
|
||||
store(key: string, value: any, scope: StorageScope): void;
|
||||
store(key: string, value: string | boolean | number, scope: StorageScope): void;
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from storage.
|
||||
@@ -154,7 +153,7 @@ export class InMemoryStorageService extends Disposable implements IStorageServic
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
store(key: string, value: any, scope: StorageScope): Promise<void> {
|
||||
store(key: string, value: string | boolean | number, scope: StorageScope): Promise<void> {
|
||||
|
||||
// We remove the key for undefined/null values
|
||||
if (isUndefinedOrNull(value)) {
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { StorageLegacyService, IStorageLegacy } from 'vs/platform/storage/common/storageLegacyService';
|
||||
import { endsWith, startsWith, rtrim } from 'vs/base/common/strings';
|
||||
|
||||
/**
|
||||
* We currently store local storage with the following format:
|
||||
*
|
||||
* [Global]
|
||||
* storage://global/<key>
|
||||
*
|
||||
* [Workspace]
|
||||
* storage://workspace/<folder>/<key>
|
||||
* storage://workspace/empty:<id>/<key>
|
||||
* storage://workspace/root:<id>/<key>
|
||||
*
|
||||
* <folder>
|
||||
* macOS/Linux: /some/folder/path
|
||||
* Windows: c%3A/Users/name/folder (normal path)
|
||||
* file://localhost/c%24/name/folder (unc path)
|
||||
*
|
||||
* [no workspace]
|
||||
* storage://workspace/__$noWorkspace__<key>
|
||||
* => no longer being used (used for empty workspaces previously)
|
||||
*/
|
||||
|
||||
const COMMON_WORKSPACE_PREFIX = `${StorageLegacyService.COMMON_PREFIX}workspace/`;
|
||||
const NO_WORKSPACE_PREFIX = 'storage://workspace/__$noWorkspace__';
|
||||
|
||||
export type StorageObject = { [key: string]: string };
|
||||
|
||||
export interface IParsedStorage {
|
||||
multiRoot: Map<string, StorageObject>;
|
||||
folder: Map<string, StorageObject>;
|
||||
empty: Map<string, StorageObject>;
|
||||
noWorkspace: StorageObject;
|
||||
}
|
||||
|
||||
export function parseFolderStorage(storage: IStorageLegacy, folderId: string): StorageObject {
|
||||
|
||||
const workspaces: { prefix: string; resource: string; }[] = [];
|
||||
const activeKeys = new Set<string>();
|
||||
|
||||
for (let i = 0; i < storage.length; i++) {
|
||||
const key = storage.key(i);
|
||||
|
||||
// Workspace Storage (storage://workspace/)
|
||||
if (!startsWith(key, StorageLegacyService.WORKSPACE_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
activeKeys.add(key);
|
||||
|
||||
// We are looking for key: storage://workspace/<folder>/workspaceIdentifier to be able to find all folder
|
||||
// paths that are known to the storage. is the only way how to parse all folder paths known in storage.
|
||||
if (endsWith(key, StorageLegacyService.WORKSPACE_IDENTIFIER)) {
|
||||
|
||||
// storage://workspace/<folder>/workspaceIdentifier => <folder>/
|
||||
let workspace = key.substring(StorageLegacyService.WORKSPACE_PREFIX.length, key.length - StorageLegacyService.WORKSPACE_IDENTIFIER.length);
|
||||
|
||||
// macOS/Unix: Users/name/folder/
|
||||
// Windows: c%3A/Users/name/folder/
|
||||
if (!startsWith(workspace, 'file:')) {
|
||||
workspace = `file:///${rtrim(workspace, '/')}`;
|
||||
}
|
||||
|
||||
// Windows UNC path: file://localhost/c%3A/Users/name/folder/
|
||||
else {
|
||||
workspace = rtrim(workspace, '/');
|
||||
}
|
||||
|
||||
// storage://workspace/<folder>/workspaceIdentifier => storage://workspace/<folder>/
|
||||
const prefix = key.substr(0, key.length - StorageLegacyService.WORKSPACE_IDENTIFIER.length);
|
||||
if (startsWith(workspace, folderId)) {
|
||||
workspaces.push({ prefix, resource: workspace });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With all the folder paths known we can now extract storage for each path. We have to go through all workspaces
|
||||
// from the longest path first to reliably extract the storage. The reason is that one folder path can be a parent
|
||||
// of another folder path and as such a simple indexOf check is not enough.
|
||||
const workspacesByLength = workspaces.sort((w1, w2) => w1.prefix.length >= w2.prefix.length ? -1 : 1);
|
||||
|
||||
const folderWorkspaceStorage: StorageObject = Object.create(null);
|
||||
|
||||
workspacesByLength.forEach(workspace => {
|
||||
activeKeys.forEach(key => {
|
||||
if (!startsWith(key, workspace.prefix)) {
|
||||
return; // not part of workspace prefix or already handled
|
||||
}
|
||||
|
||||
activeKeys.delete(key);
|
||||
|
||||
if (workspace.resource === folderId) {
|
||||
// storage://workspace/<folder>/someKey => someKey
|
||||
const storageKey = key.substr(workspace.prefix.length);
|
||||
folderWorkspaceStorage[storageKey] = storage.getItem(key);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return folderWorkspaceStorage;
|
||||
}
|
||||
|
||||
export function parseNoWorkspaceStorage(storage: IStorageLegacy) {
|
||||
const noWorkspacePrefix = `${StorageLegacyService.WORKSPACE_PREFIX}__$noWorkspace__`;
|
||||
|
||||
const noWorkspaceStorage: StorageObject = Object.create(null);
|
||||
for (let i = 0; i < storage.length; i++) {
|
||||
const key = storage.key(i);
|
||||
|
||||
// No Workspace key is for extension development windows
|
||||
if (startsWith(key, noWorkspacePrefix) && !endsWith(key, StorageLegacyService.WORKSPACE_IDENTIFIER)) {
|
||||
// storage://workspace/__$noWorkspace__someKey => someKey
|
||||
noWorkspaceStorage[key.substr(NO_WORKSPACE_PREFIX.length)] = storage.getItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
return noWorkspaceStorage;
|
||||
}
|
||||
|
||||
export function parseEmptyStorage(storage: IStorageLegacy, targetWorkspaceId: string): StorageObject {
|
||||
const emptyStoragePrefix = `${COMMON_WORKSPACE_PREFIX}${targetWorkspaceId}/`;
|
||||
|
||||
const emptyWorkspaceStorage: StorageObject = Object.create(null);
|
||||
for (let i = 0; i < storage.length; i++) {
|
||||
const key = storage.key(i);
|
||||
|
||||
if (startsWith(key, emptyStoragePrefix) && !endsWith(key, StorageLegacyService.WORKSPACE_IDENTIFIER)) {
|
||||
// storage://workspace/empty:<id>/someKey => someKey
|
||||
emptyWorkspaceStorage[key.substr(emptyStoragePrefix.length)] = storage.getItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
return emptyWorkspaceStorage;
|
||||
}
|
||||
|
||||
export function parseMultiRootStorage(storage: IStorageLegacy, targetWorkspaceId: string): StorageObject {
|
||||
const multiRootStoragePrefix = `${COMMON_WORKSPACE_PREFIX}${targetWorkspaceId}/`;
|
||||
|
||||
const multiRootWorkspaceStorage: StorageObject = Object.create(null);
|
||||
for (let i = 0; i < storage.length; i++) {
|
||||
const key = storage.key(i);
|
||||
|
||||
if (startsWith(key, multiRootStoragePrefix) && !endsWith(key, StorageLegacyService.WORKSPACE_IDENTIFIER)) {
|
||||
// storage://workspace/root:<id>/someKey => someKey
|
||||
multiRootWorkspaceStorage[key.substr(multiRootStoragePrefix.length)] = storage.getItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
return multiRootWorkspaceStorage;
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
// Browser localStorage interface
|
||||
export interface IStorageLegacy {
|
||||
length: number;
|
||||
key(index: number): string | null;
|
||||
setItem(key: string, value: any): void;
|
||||
getItem(key: string): string | null;
|
||||
removeItem(key: string): void;
|
||||
}
|
||||
|
||||
export const ID = 'storageLegacyService';
|
||||
|
||||
export const IStorageLegacyService = createDecorator<IStorageLegacyService>(ID);
|
||||
|
||||
export interface IStorageLegacyService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Store a string value under the given key to local storage.
|
||||
*
|
||||
* The optional scope argument allows to define the scope of the operation.
|
||||
*/
|
||||
store(key: string, value: any, scope?: StorageLegacyScope): void;
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from local storage.
|
||||
*
|
||||
* The optional scope argument allows to define the scope of the operation.
|
||||
*/
|
||||
remove(key: string, scope?: StorageLegacyScope): void;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from local storage. Use
|
||||
* the provided defaultValue if the element is null or undefined.
|
||||
*
|
||||
* The optional scope argument allows to define the scope of the operation.
|
||||
*/
|
||||
get(key: string, scope?: StorageLegacyScope, defaultValue?: string): string | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from local storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a number using parseInt with a base of 10.
|
||||
*
|
||||
* The optional scope argument allows to define the scope of the operation.
|
||||
*/
|
||||
getInteger(key: string, scope?: StorageLegacyScope, defaultValue?: number): number | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from local storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a boolean.
|
||||
*
|
||||
* The optional scope argument allows to define the scope of the operation.
|
||||
*/
|
||||
getBoolean(key: string, scope?: StorageLegacyScope, defaultValue?: boolean): boolean | undefined;
|
||||
}
|
||||
|
||||
export const enum StorageLegacyScope {
|
||||
|
||||
/**
|
||||
* The stored data will be scoped to all workspaces of this domain.
|
||||
*/
|
||||
GLOBAL,
|
||||
|
||||
/**
|
||||
* The stored data will be scoped to the current workspace.
|
||||
*/
|
||||
WORKSPACE
|
||||
}
|
||||
|
||||
|
||||
export class StorageLegacyService implements IStorageLegacyService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
static readonly COMMON_PREFIX = 'storage://';
|
||||
static readonly GLOBAL_PREFIX = `${StorageLegacyService.COMMON_PREFIX}global/`;
|
||||
static readonly WORKSPACE_PREFIX = `${StorageLegacyService.COMMON_PREFIX}workspace/`;
|
||||
static readonly WORKSPACE_IDENTIFIER = 'workspaceidentifier';
|
||||
static readonly NO_WORKSPACE_IDENTIFIER = '__$noWorkspace__';
|
||||
|
||||
private _workspaceStorage: IStorageLegacy;
|
||||
private _globalStorage: IStorageLegacy;
|
||||
|
||||
private workspaceKey: string;
|
||||
private _workspaceId: string | undefined;
|
||||
|
||||
constructor(
|
||||
globalStorage: IStorageLegacy,
|
||||
workspaceStorage: IStorageLegacy,
|
||||
workspaceId?: string,
|
||||
legacyWorkspaceId?: number
|
||||
) {
|
||||
this._globalStorage = globalStorage;
|
||||
this._workspaceStorage = workspaceStorage || globalStorage;
|
||||
|
||||
this.setWorkspaceId(workspaceId, legacyWorkspaceId);
|
||||
}
|
||||
|
||||
get workspaceId(): string | undefined {
|
||||
return this._workspaceId;
|
||||
}
|
||||
|
||||
setWorkspaceId(workspaceId: string | undefined, legacyWorkspaceId?: number): void {
|
||||
this._workspaceId = workspaceId;
|
||||
|
||||
// Calculate workspace storage key
|
||||
this.workspaceKey = this.getWorkspaceKey(workspaceId);
|
||||
|
||||
// Make sure to delete all workspace storage if the workspace has been recreated meanwhile
|
||||
// which is only possible if a id property is provided that we can check on
|
||||
if (types.isNumber(legacyWorkspaceId)) {
|
||||
this.cleanupWorkspaceScope(legacyWorkspaceId);
|
||||
} else {
|
||||
// ensure that we always store a workspace identifier because this key
|
||||
// is used to migrate data out as needed
|
||||
const workspaceIdentifier = this.getInteger(StorageLegacyService.WORKSPACE_IDENTIFIER, StorageLegacyScope.WORKSPACE);
|
||||
if (!workspaceIdentifier) {
|
||||
this.store(StorageLegacyService.WORKSPACE_IDENTIFIER, 42, StorageLegacyScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get globalStorage(): IStorageLegacy {
|
||||
return this._globalStorage;
|
||||
}
|
||||
|
||||
get workspaceStorage(): IStorageLegacy {
|
||||
return this._workspaceStorage;
|
||||
}
|
||||
|
||||
private getWorkspaceKey(id?: string): string {
|
||||
if (!id) {
|
||||
return StorageLegacyService.NO_WORKSPACE_IDENTIFIER;
|
||||
}
|
||||
|
||||
// Special case file:// URIs: strip protocol from key to produce shorter key
|
||||
const fileProtocol = 'file:///';
|
||||
if (id.indexOf(fileProtocol) === 0) {
|
||||
id = id.substr(fileProtocol.length);
|
||||
}
|
||||
|
||||
// Always end with "/"
|
||||
return `${strings.rtrim(id, '/')}/`;
|
||||
}
|
||||
|
||||
private cleanupWorkspaceScope(workspaceUid: number): void {
|
||||
|
||||
// Get stored identifier from storage
|
||||
perf.mark('willReadWorkspaceIdentifier');
|
||||
const id = this.getInteger(StorageLegacyService.WORKSPACE_IDENTIFIER, StorageLegacyScope.WORKSPACE);
|
||||
perf.mark('didReadWorkspaceIdentifier');
|
||||
|
||||
// If identifier differs, assume the workspace got recreated and thus clean all storage for this workspace
|
||||
if (types.isNumber(id) && workspaceUid !== id) {
|
||||
const keyPrefix = this.toStorageKey('', StorageLegacyScope.WORKSPACE);
|
||||
const toDelete: string[] = [];
|
||||
const length = this._workspaceStorage.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const key = this._workspaceStorage.key(i);
|
||||
if (!key || key.indexOf(StorageLegacyService.WORKSPACE_PREFIX) < 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the delete
|
||||
toDelete.forEach((keyToDelete) => {
|
||||
this._workspaceStorage.removeItem(keyToDelete);
|
||||
});
|
||||
}
|
||||
|
||||
// Store workspace identifier now
|
||||
if (workspaceUid !== id) {
|
||||
this.store(StorageLegacyService.WORKSPACE_IDENTIFIER, workspaceUid, StorageLegacyScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
store(key: string, value: any, scope = StorageLegacyScope.GLOBAL): void {
|
||||
const storage = (scope === StorageLegacyScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
|
||||
|
||||
if (types.isUndefinedOrNull(value)) {
|
||||
this.remove(key, scope); // we cannot store null or undefined, in that case we remove the key
|
||||
return;
|
||||
}
|
||||
|
||||
const storageKey = this.toStorageKey(key, scope);
|
||||
|
||||
// Store
|
||||
try {
|
||||
storage.setItem(storageKey, value);
|
||||
} catch (error) {
|
||||
errors.onUnexpectedError(error);
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string, scope = StorageLegacyScope.GLOBAL, defaultValue?: any): string {
|
||||
const storage = (scope === StorageLegacyScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
|
||||
|
||||
const value = storage.getItem(this.toStorageKey(key, scope));
|
||||
if (types.isUndefinedOrNull(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
getInteger(key: string, scope = StorageLegacyScope.GLOBAL, defaultValue: number = 0): number {
|
||||
const value = this.get(key, scope, defaultValue);
|
||||
|
||||
if (types.isUndefinedOrNull(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
getBoolean(key: string, scope = StorageLegacyScope.GLOBAL, defaultValue: boolean = false): boolean {
|
||||
const value = this.get(key, scope, defaultValue);
|
||||
|
||||
if (types.isUndefinedOrNull(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (types.isString(value)) {
|
||||
return value.toLowerCase() === 'true' ? true : false;
|
||||
}
|
||||
|
||||
return value ? true : false;
|
||||
}
|
||||
|
||||
remove(key: string, scope = StorageLegacyScope.GLOBAL): void {
|
||||
const storage = (scope === StorageLegacyScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
|
||||
const storageKey = this.toStorageKey(key, scope);
|
||||
|
||||
// Remove
|
||||
storage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
private toStorageKey(key: string, scope: StorageLegacyScope): string {
|
||||
if (scope === StorageLegacyScope.GLOBAL) {
|
||||
return StorageLegacyService.GLOBAL_PREFIX + key.toLowerCase();
|
||||
}
|
||||
|
||||
return StorageLegacyService.WORKSPACE_PREFIX + this.workspaceKey + key.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
export class InMemoryLocalStorage implements IStorageLegacy {
|
||||
private store: { [key: string]: string; };
|
||||
|
||||
constructor() {
|
||||
this.store = {};
|
||||
}
|
||||
|
||||
get length() {
|
||||
return Object.keys(this.store).length;
|
||||
}
|
||||
|
||||
key(index: number): string | null {
|
||||
const keys = Object.keys(this.store);
|
||||
if (keys.length > index) {
|
||||
return keys[index];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
setItem(key: string, value: any): void {
|
||||
this.store[key] = value.toString();
|
||||
}
|
||||
|
||||
getItem(key: string): string | null {
|
||||
const item = this.store[key];
|
||||
if (!types.isUndefinedOrNull(item)) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
delete this.store[key];
|
||||
}
|
||||
}
|
||||
|
||||
export const inMemoryLocalStorageInstance = new InMemoryLocalStorage();
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { Event, Emitter, debounceEvent } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { StorageMainService, IStorageChangeEvent } from 'vs/platform/storage/node/storageMainService';
|
||||
import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/node/storage';
|
||||
import { mapToSerializable, serializableToMap, values } from 'vs/base/common/map';
|
||||
@@ -40,7 +40,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
|
||||
|
||||
// Listen for changes in global storage to send to listeners
|
||||
// that are listening. Use a debouncer to reduce IPC traffic.
|
||||
this._register(debounceEvent(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[], cur: IStorageChangeEvent) => {
|
||||
this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[], cur: IStorageChangeEvent) => {
|
||||
if (!prev) {
|
||||
prev = [cur];
|
||||
} else {
|
||||
@@ -57,7 +57,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
|
||||
|
||||
private serializeEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent {
|
||||
const items = new Map<Key, Value>();
|
||||
events.forEach(event => items.set(event.key, this.storageMainService.get(event.key, null)));
|
||||
events.forEach(event => items.set(event.key, this.storageMainService.get(event.key)));
|
||||
|
||||
return { items: mapToSerializable(items) } as ISerializableItemsChangeEvent;
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(_, command: string, arg?: any): Thenable<any> {
|
||||
call(_, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'getItems': {
|
||||
return Promise.resolve(mapToSerializable(this.storageMainService.items));
|
||||
@@ -125,11 +125,11 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS
|
||||
}
|
||||
}
|
||||
|
||||
getItems(): Thenable<Map<string, string>> {
|
||||
getItems(): Promise<Map<string, string>> {
|
||||
return this.channel.call('getItems').then((data: Item[]) => serializableToMap(data));
|
||||
}
|
||||
|
||||
updateItems(request: IUpdateRequest): Thenable<void> {
|
||||
updateItems(request: IUpdateRequest): Promise<void> {
|
||||
let updateCount = 0;
|
||||
const serializableRequest: ISerializableUpdateRequest = Object.create(null);
|
||||
|
||||
@@ -150,11 +150,11 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS
|
||||
return this.channel.call('updateItems', serializableRequest);
|
||||
}
|
||||
|
||||
checkIntegrity(full: boolean): Thenable<string> {
|
||||
checkIntegrity(full: boolean): Promise<string> {
|
||||
return this.channel.call('checkIntegrity', full);
|
||||
}
|
||||
|
||||
close(): Thenable<void> {
|
||||
close(): Promise<void> {
|
||||
|
||||
// when we are about to close, we start to ignore main-side changes since we close anyway
|
||||
this.onDidChangeItemsOnMainListener = dispose(this.onDidChangeItemsOnMainListener);
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface IStorageMainService {
|
||||
* the provided defaultValue if the element is null or undefined.
|
||||
*/
|
||||
get(key: string, fallbackValue: string): string;
|
||||
get(key: string, fallbackValue?: string): string | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
@@ -46,6 +47,7 @@ export interface IStorageMainService {
|
||||
* will be converted to a boolean.
|
||||
*/
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
@@ -53,6 +55,7 @@ export interface IStorageMainService {
|
||||
* will be converted to a number using parseInt with a base of 10.
|
||||
*/
|
||||
getInteger(key: string, fallbackValue: number): number;
|
||||
getInteger(key: string, fallbackValue?: number): number | undefined;
|
||||
|
||||
/**
|
||||
* Store a string value under the given key to storage. The value will
|
||||
@@ -74,7 +77,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private static STORAGE_NAME = 'temp.vscdb';
|
||||
private static STORAGE_NAME = 'state.vscdb';
|
||||
|
||||
private _onDidChangeStorage: Emitter<IStorageChangeEvent> = this._register(new Emitter<IStorageChangeEvent>());
|
||||
get onDidChangeStorage(): Event<IStorageChangeEvent> { return this._onDidChangeStorage.event; }
|
||||
@@ -87,9 +90,9 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
private storage: IStorage;
|
||||
|
||||
constructor(
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -98,7 +101,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
}
|
||||
|
||||
private get storagePath(): string {
|
||||
if (!!this.environmentService.extensionTestsPath || !process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
|
||||
if (!!this.environmentService.extensionTestsPath) {
|
||||
return SQLiteStorageDatabase.IN_MEMORY_PATH; // no storage during extension tests!
|
||||
}
|
||||
|
||||
@@ -109,7 +112,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
const loggedStorageErrors = new Set<string>();
|
||||
|
||||
return {
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : void 0,
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined,
|
||||
logError: error => {
|
||||
this.logService.error(error);
|
||||
|
||||
@@ -130,7 +133,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
} as ISQLiteStorageDatabaseLoggingOptions;
|
||||
}
|
||||
|
||||
initialize(): Thenable<void> {
|
||||
initialize(): Promise<void> {
|
||||
const useInMemoryStorage = this.storagePath === SQLiteStorageDatabase.IN_MEMORY_PATH;
|
||||
|
||||
let globalStorageExists: Promise<boolean>;
|
||||
@@ -158,7 +161,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
}).then(() => {
|
||||
|
||||
// Migrate storage if this is the first start and we are not using in-memory
|
||||
let migrationPromise: Thenable<void>;
|
||||
let migrationPromise: Promise<void>;
|
||||
if (!useInMemoryStorage && !exists) {
|
||||
// TODO@Ben remove global storage migration and move Storage creation back to ctor
|
||||
migrationPromise = this.migrateGlobalStorage().then(() => this.logService.info('[storage] migrated global storage'), error => this.logService.error(`[storage] migration error ${error}`));
|
||||
@@ -171,10 +174,10 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
});
|
||||
}
|
||||
|
||||
private migrateGlobalStorage(): Thenable<void> {
|
||||
private migrateGlobalStorage(): Promise<void> {
|
||||
this.logService.info('[storage] migrating global storage from localStorage into SQLite');
|
||||
|
||||
const localStorageDBBackup = join(this.environmentService.userDataPath, 'Local Storage', 'file__0.localstorage.vscmig');
|
||||
const localStorageDBBackup = join(this.environmentService.userDataPath, 'Local Storage', 'file__0.vscmig');
|
||||
|
||||
return exists(localStorageDBBackup).then(exists => {
|
||||
if (!exists) {
|
||||
@@ -345,32 +348,35 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string, fallbackValue: string): string {
|
||||
get(key: string, fallbackValue: string): string;
|
||||
get(key: string, fallbackValue?: string): string | undefined;
|
||||
get(key: string, fallbackValue?: string): string | undefined {
|
||||
return this.storage.get(key, fallbackValue);
|
||||
}
|
||||
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean {
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined {
|
||||
return this.storage.getBoolean(key, fallbackValue);
|
||||
}
|
||||
|
||||
getInteger(key: string, fallbackValue: number): number {
|
||||
getInteger(key: string, fallbackValue: number): number;
|
||||
getInteger(key: string, fallbackValue?: number): number | undefined;
|
||||
getInteger(key: string, fallbackValue?: number): number | undefined {
|
||||
return this.storage.getInteger(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: any): Thenable<void> {
|
||||
store(key: string, value: any): Promise<void> {
|
||||
return this.storage.set(key, value);
|
||||
}
|
||||
|
||||
remove(key: string): Thenable<void> {
|
||||
remove(key: string): Promise<void> {
|
||||
return this.storage.delete(key);
|
||||
}
|
||||
|
||||
close(): Thenable<void> {
|
||||
close(): Promise<void> {
|
||||
this.logService.trace('StorageMainService#close() - begin');
|
||||
|
||||
// Signal to storage that we are about to close
|
||||
this.storage.beforeClose();
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this._onWillSaveState.fire();
|
||||
|
||||
@@ -383,7 +389,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
});
|
||||
}
|
||||
|
||||
checkIntegrity(full: boolean): Thenable<string> {
|
||||
checkIntegrity(full: boolean): Promise<string> {
|
||||
return this.storage.checkIntegrity(full);
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,15 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason } from 'vs/platform/storage/common/storage';
|
||||
import { Storage, ISQLiteStorageDatabaseLoggingOptions, IStorage, StorageHint, IStorageDatabase, SQLiteStorageDatabase } from 'vs/base/node/storage';
|
||||
import { IStorageLegacyService, StorageLegacyScope } from 'vs/platform/storage/common/storageLegacyService';
|
||||
import { startsWith, endsWith } from 'vs/base/common/strings';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { localize } from 'vs/nls';
|
||||
import { mark, getDuration } from 'vs/base/common/performance';
|
||||
import { join } from 'path';
|
||||
import { copy, exists, mkdirp, readdir, writeFile } from 'vs/base/node/pfs';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { StorageObject, parseMultiRootStorage, parseFolderStorage, parseNoWorkspaceStorage, parseEmptyStorage } from 'vs/platform/storage/common/storageLegacyMigration';
|
||||
|
||||
export class StorageService extends Disposable implements IStorageService {
|
||||
_serviceBrand: any;
|
||||
@@ -37,7 +33,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
private _hasErrors = false;
|
||||
get hasErrors(): boolean { return this._hasErrors; }
|
||||
|
||||
private bufferedWorkspaceStorageErrors?: (string | Error)[] = [];
|
||||
private bufferedWorkspaceStorageErrors?: Array<string | Error> = [];
|
||||
private _onWorkspaceStorageError: Emitter<string | Error> = this._register(new Emitter<string | Error>());
|
||||
get onWorkspaceStorageError(): Event<string | Error> {
|
||||
if (Array.isArray(this.bufferedWorkspaceStorageErrors)) {
|
||||
@@ -49,7 +45,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
this.bufferedWorkspaceStorageErrors = void 0;
|
||||
this.bufferedWorkspaceStorageErrors = undefined;
|
||||
}
|
||||
|
||||
return this._onWorkspaceStorageError.event;
|
||||
@@ -63,30 +59,28 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
|
||||
constructor(
|
||||
globalStorageDatabase: IStorageDatabase,
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Global Storage
|
||||
this.globalStorage = new Storage(globalStorageDatabase);
|
||||
if (process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL)));
|
||||
}
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL)));
|
||||
}
|
||||
|
||||
private handleDidChangeStorage(key: string, scope: StorageScope): void {
|
||||
this._onDidChangeStorage.fire({ key, scope });
|
||||
}
|
||||
|
||||
initialize(payload: IWorkspaceInitializationPayload): Thenable<void> {
|
||||
initialize(payload: IWorkspaceInitializationPayload): Promise<void> {
|
||||
return Promise.all([
|
||||
this.initializeGlobalStorage(),
|
||||
this.initializeWorkspaceStorage(payload)
|
||||
]).then(() => void 0);
|
||||
]).then(() => undefined);
|
||||
}
|
||||
|
||||
private initializeGlobalStorage(): Thenable<void> {
|
||||
private initializeGlobalStorage(): Promise<void> {
|
||||
mark('willInitGlobalStorage');
|
||||
|
||||
return this.globalStorage.init().then(() => {
|
||||
@@ -98,173 +92,26 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
});
|
||||
}
|
||||
|
||||
private initializeWorkspaceStorage(payload: IWorkspaceInitializationPayload): Thenable<void> {
|
||||
private initializeWorkspaceStorage(payload: IWorkspaceInitializationPayload): Promise<void> {
|
||||
|
||||
// Prepare workspace storage folder for DB
|
||||
return this.prepareWorkspaceStorageFolder(payload).then(result => {
|
||||
const useInMemoryStorage = !!this.environmentService.extensionTestsPath; // no storage during extension tests!
|
||||
|
||||
let workspaceStoragePath: string;
|
||||
let workspaceStorageExists: Thenable<boolean>;
|
||||
if (useInMemoryStorage) {
|
||||
workspaceStoragePath = SQLiteStorageDatabase.IN_MEMORY_PATH;
|
||||
workspaceStorageExists = Promise.resolve(true);
|
||||
} else {
|
||||
workspaceStoragePath = join(result.path, StorageService.WORKSPACE_STORAGE_NAME);
|
||||
// Create workspace storage and initalize
|
||||
mark('willInitWorkspaceStorage');
|
||||
return this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, StorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init().then(() => {
|
||||
mark('didInitWorkspaceStorage');
|
||||
}, error => {
|
||||
mark('didInitWorkspaceStorage');
|
||||
|
||||
mark('willCheckWorkspaceStorageExists');
|
||||
workspaceStorageExists = exists(workspaceStoragePath).then(exists => {
|
||||
mark('didCheckWorkspaceStorageExists');
|
||||
|
||||
return exists;
|
||||
});
|
||||
}
|
||||
|
||||
return workspaceStorageExists.then(exists => {
|
||||
|
||||
// Create workspace storage and initalize
|
||||
mark('willInitWorkspaceStorage');
|
||||
return this.createWorkspaceStorage(workspaceStoragePath, result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : void 0).init().then(() => {
|
||||
mark('didInitWorkspaceStorage');
|
||||
}, error => {
|
||||
mark('didInitWorkspaceStorage');
|
||||
|
||||
return Promise.reject(error);
|
||||
}).then(() => {
|
||||
|
||||
// Migrate storage if this is the first start and we are not using in-memory
|
||||
let migrationPromise: Thenable<void>;
|
||||
if (!useInMemoryStorage && !exists) {
|
||||
migrationPromise = this.migrateWorkspaceStorage(payload);
|
||||
} else {
|
||||
migrationPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
return migrationPromise;
|
||||
});
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}).then(undefined, error => {
|
||||
onUnexpectedError(error);
|
||||
|
||||
// TODO@Ben remove migration after a while
|
||||
private migrateWorkspaceStorage(payload: IWorkspaceInitializationPayload): Thenable<void> {
|
||||
mark('willMigrateWorkspaceStorageKeys');
|
||||
return readdir(this.environmentService.extensionsPath).then(extensions => {
|
||||
|
||||
// Otherwise, we migrate data from window.localStorage over
|
||||
try {
|
||||
let workspaceItems: StorageObject;
|
||||
if (isWorkspaceIdentifier(payload)) {
|
||||
workspaceItems = parseMultiRootStorage(window.localStorage, `root:${payload.id}`);
|
||||
} else if (isSingleFolderWorkspaceInitializationPayload(payload)) {
|
||||
workspaceItems = parseFolderStorage(window.localStorage, payload.folder.toString());
|
||||
} else {
|
||||
if (payload.id === 'ext-dev') {
|
||||
workspaceItems = parseNoWorkspaceStorage(window.localStorage);
|
||||
} else {
|
||||
workspaceItems = parseEmptyStorage(window.localStorage, `${payload.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceItemsKeys = workspaceItems ? Object.keys(workspaceItems) : [];
|
||||
if (workspaceItemsKeys.length > 0) {
|
||||
const supportedKeys = new Map<string, string>();
|
||||
[
|
||||
'workbench.search.history',
|
||||
'history.entries',
|
||||
'ignoreNetVersionError',
|
||||
'ignoreEnospcError',
|
||||
'extensionUrlHandler.urlToHandle',
|
||||
'terminal.integrated.isWorkspaceShellAllowed',
|
||||
'workbench.tasks.ignoreTask010Shown',
|
||||
'workbench.tasks.recentlyUsedTasks',
|
||||
'workspaces.dontPromptToOpen',
|
||||
'output.activechannel',
|
||||
'outline/state',
|
||||
'extensionsAssistant/workspaceRecommendationsIgnore',
|
||||
'extensionsAssistant/dynamicWorkspaceRecommendations',
|
||||
'debug.repl.history',
|
||||
'editor.matchCase',
|
||||
'editor.wholeWord',
|
||||
'editor.isRegex',
|
||||
'lifecyle.lastShutdownReason',
|
||||
'debug.selectedroot',
|
||||
'debug.selectedconfigname',
|
||||
'debug.breakpoint',
|
||||
'debug.breakpointactivated',
|
||||
'debug.functionbreakpoint',
|
||||
'debug.exceptionbreakpoint',
|
||||
'debug.watchexpressions',
|
||||
'workbench.sidebar.activeviewletid',
|
||||
'workbench.panelpart.activepanelid',
|
||||
'workbench.zenmode.active',
|
||||
'workbench.centerededitorlayout.active',
|
||||
'workbench.sidebar.hidden',
|
||||
'workbench.panel.hidden',
|
||||
'workbench.panel.location',
|
||||
'extensionsIdentifiers/disabled',
|
||||
'extensionsIdentifiers/enabled',
|
||||
'scm.views',
|
||||
'suggest/memories/first',
|
||||
'suggest/memories/recentlyUsed',
|
||||
'suggest/memories/recentlyUsedByPrefix',
|
||||
'workbench.view.explorer.numberOfVisibleViews',
|
||||
'workbench.view.extensions.numberOfVisibleViews',
|
||||
'workbench.view.debug.numberOfVisibleViews',
|
||||
'workbench.explorer.views.state',
|
||||
'workbench.view.extensions.state',
|
||||
'workbench.view.debug.state',
|
||||
'memento/workbench.editor.walkThroughPart',
|
||||
'memento/workbench.editor.settings2',
|
||||
'memento/workbench.editor.htmlPreviewPart',
|
||||
'memento/workbench.editor.defaultPreferences',
|
||||
'memento/workbench.editors.files.textFileEditor',
|
||||
'memento/workbench.editors.logViewer',
|
||||
'memento/workbench.editors.textResourceEditor',
|
||||
'memento/workbench.panel.output'
|
||||
].forEach(key => supportedKeys.set(key.toLowerCase(), key));
|
||||
|
||||
// Support extension storage as well (always the ID of the extension)
|
||||
extensions.forEach(extension => {
|
||||
let extensionId: string;
|
||||
if (extension.indexOf('-') >= 0) {
|
||||
extensionId = extension.substring(0, extension.lastIndexOf('-')); // convert "author.extension-0.2.5" => "author.extension"
|
||||
} else {
|
||||
extensionId = extension;
|
||||
}
|
||||
|
||||
if (extensionId) {
|
||||
supportedKeys.set(extensionId.toLowerCase(), extensionId);
|
||||
}
|
||||
});
|
||||
|
||||
workspaceItemsKeys.forEach(key => {
|
||||
const value = workspaceItems[key];
|
||||
|
||||
// first check for a well known supported key and store with realcase value
|
||||
const supportedKey = supportedKeys.get(key);
|
||||
if (supportedKey) {
|
||||
this.store(supportedKey, value, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
// fix lowercased ".numberOfVisibleViews"
|
||||
else if (endsWith(key, '.numberOfVisibleViews'.toLowerCase())) {
|
||||
const normalizedKey = key.substring(0, key.length - '.numberOfVisibleViews'.length) + '.numberOfVisibleViews';
|
||||
this.store(normalizedKey, value, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
// support dynamic keys
|
||||
else if (key.indexOf('memento/') === 0 || endsWith(key, '.state')) {
|
||||
this.store(key, value, StorageScope.WORKSPACE);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
mark('didMigrateWorkspaceStorageKeys');
|
||||
// Upon error, fallback to in-memory storage
|
||||
return this.createWorkspaceStorage(SQLiteStorageDatabase.IN_MEMORY_PATH).init();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -272,7 +119,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
|
||||
// Logger for workspace storage
|
||||
const workspaceLoggingOptions: ISQLiteStorageDatabaseLoggingOptions = {
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : void 0,
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined,
|
||||
logError: error => {
|
||||
this.logService.error(error);
|
||||
|
||||
@@ -302,7 +149,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
return join(this.environmentService.workspaceStorageHome, payload.id); // workspace home + workspace id;
|
||||
}
|
||||
|
||||
private prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload): Thenable<{ path: string, wasCreated: boolean }> {
|
||||
private prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload): Promise<{ path: string, wasCreated: boolean }> {
|
||||
const workspaceStorageFolderPath = this.getWorkspaceStorageFolderPath(payload);
|
||||
|
||||
return exists(workspaceStorageFolderPath).then(exists => {
|
||||
@@ -321,7 +168,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
}
|
||||
|
||||
private ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload): void {
|
||||
let meta: object | undefined = void 0;
|
||||
let meta: object | undefined = undefined;
|
||||
if (isSingleFolderWorkspaceInitializationPayload(payload)) {
|
||||
meta = { folder: payload.folder.toString() };
|
||||
} else if (isWorkspaceIdentifier(payload)) {
|
||||
@@ -332,11 +179,11 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), StorageService.WORKSPACE_META_NAME);
|
||||
exists(workspaceStorageMetaPath).then(exists => {
|
||||
if (exists) {
|
||||
return void 0; // already existing
|
||||
return undefined; // already existing
|
||||
}
|
||||
|
||||
return writeFile(workspaceStorageMetaPath, JSON.stringify(meta, void 0, 2));
|
||||
}).then(null, error => onUnexpectedError(error));
|
||||
return writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2));
|
||||
}).then(undefined, error => onUnexpectedError(error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +205,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
return this.getStorage(scope).getInteger(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: any, scope: StorageScope): void {
|
||||
store(key: string, value: string | boolean | number, scope: StorageScope): void {
|
||||
this.getStorage(scope).set(key, value);
|
||||
}
|
||||
|
||||
@@ -368,10 +215,6 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
|
||||
close(): Promise<void> {
|
||||
|
||||
// Signal to storage that we are about to close
|
||||
this.globalStorage.beforeClose();
|
||||
this.workspaceStorage.beforeClose();
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });
|
||||
|
||||
@@ -394,7 +237,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorage.size : this.workspaceStorage.size;
|
||||
}
|
||||
|
||||
checkIntegrity(scope: StorageScope, full: boolean): Thenable<string> {
|
||||
checkIntegrity(scope: StorageScope, full: boolean): Promise<string> {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorage.checkIntegrity(full) : this.workspaceStorage.checkIntegrity(full);
|
||||
}
|
||||
|
||||
@@ -449,7 +292,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
});
|
||||
}
|
||||
|
||||
migrate(toWorkspace: IWorkspaceInitializationPayload): Thenable<void> {
|
||||
migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
|
||||
if (this.workspaceStoragePath === SQLiteStorageDatabase.IN_MEMORY_PATH) {
|
||||
return Promise.resolve(); // no migration needed if running in memory
|
||||
}
|
||||
@@ -480,129 +323,15 @@ export class LogStorageAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IStorageService private storageService: DelegatingStorageService,
|
||||
@IWindowService private windowService: IWindowService
|
||||
@IStorageService private readonly storageService: StorageService,
|
||||
@IWindowService private readonly windowService: IWindowService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Thenable<void> {
|
||||
this.storageService.storage.logStorage();
|
||||
run(): Promise<void> {
|
||||
this.storageService.logStorage();
|
||||
|
||||
return this.windowService.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
export class DelegatingStorageService extends Disposable implements IStorageService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
|
||||
get onDidChangeStorage(): Event<IWorkspaceStorageChangeEvent> { return this._onDidChangeStorage.event; }
|
||||
|
||||
private _onWillSaveState: Emitter<IWillSaveStateEvent> = this._register(new Emitter<IWillSaveStateEvent>());
|
||||
get onWillSaveState(): Event<IWillSaveStateEvent> { return this._onWillSaveState.event; }
|
||||
|
||||
private closed: boolean;
|
||||
private useLegacyWorkspaceStorage: boolean;
|
||||
|
||||
constructor(
|
||||
private storageService: IStorageService,
|
||||
private storageLegacyService: IStorageLegacyService,
|
||||
private logService: ILogService,
|
||||
configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.useLegacyWorkspaceStorage = configurationService.inspect<boolean>('workbench.enableLegacyStorage').value === true;
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.storageService.onDidChangeStorage(e => this._onDidChangeStorage.fire(e)));
|
||||
this._register(this.storageService.onWillSaveState(e => this._onWillSaveState.fire(e)));
|
||||
|
||||
const globalKeyMarker = 'storage://global/';
|
||||
|
||||
window.addEventListener('storage', e => {
|
||||
if (e.key && startsWith(e.key, globalKeyMarker)) {
|
||||
const key = e.key.substr(globalKeyMarker.length);
|
||||
|
||||
this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get storage(): StorageService {
|
||||
return this.storageService as StorageService;
|
||||
}
|
||||
|
||||
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
|
||||
if (!this.useLegacyWorkspaceStorage) {
|
||||
if (scope === StorageScope.WORKSPACE || process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
|
||||
return this.storageService.get(key, scope, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
return this.storageLegacyService.get(key, this.convertScope(scope), fallbackValue);
|
||||
}
|
||||
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
|
||||
if (!this.useLegacyWorkspaceStorage) {
|
||||
if (scope === StorageScope.WORKSPACE || process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
|
||||
return this.storageService.getBoolean(key, scope, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
return this.storageLegacyService.getBoolean(key, this.convertScope(scope), fallbackValue);
|
||||
}
|
||||
|
||||
getInteger(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined {
|
||||
if (!this.useLegacyWorkspaceStorage) {
|
||||
if (scope === StorageScope.WORKSPACE || process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
|
||||
return this.storageService.getInteger(key, scope, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
return this.storageLegacyService.getInteger(key, this.convertScope(scope), fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: any, scope: StorageScope): void {
|
||||
if (this.closed) {
|
||||
this.logService.warn(`Unsupported write (store) access after close (key: ${key})`);
|
||||
|
||||
return; // prevent writing after close to detect late write access
|
||||
}
|
||||
|
||||
this.storageLegacyService.store(key, value, this.convertScope(scope));
|
||||
|
||||
this.storageService.store(key, value, scope);
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
if (this.closed) {
|
||||
this.logService.warn(`Unsupported write (remove) access after close (key: ${key})`);
|
||||
|
||||
return; // prevent writing after close to detect late write access
|
||||
}
|
||||
|
||||
this.storageLegacyService.remove(key, this.convertScope(scope));
|
||||
|
||||
this.storageService.remove(key, scope);
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
const promise = this.storage.close();
|
||||
|
||||
this.closed = true;
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private convertScope(scope: StorageScope): StorageLegacyScope {
|
||||
return scope === StorageScope.GLOBAL ? StorageLegacyScope.GLOBAL : StorageLegacyScope.WORKSPACE;
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { StorageLegacyScope, StorageLegacyService } from 'vs/platform/storage/common/storageLegacyService';
|
||||
import { parseEmptyStorage, parseMultiRootStorage, parseFolderStorage } from 'vs/platform/storage/common/storageLegacyMigration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
suite('Storage Migration', () => {
|
||||
let storage = window.localStorage;
|
||||
|
||||
setup(() => {
|
||||
storage.clear();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
storage.clear();
|
||||
});
|
||||
|
||||
test('Parse Storage (mixed)', () => {
|
||||
|
||||
// Fill the storage with multiple workspaces of all kinds (empty, root, folders)
|
||||
const workspaceIds = [
|
||||
|
||||
// Multi Root Workspace
|
||||
URI.from({ path: '1500007676869', scheme: 'root' }).toString(),
|
||||
URI.from({ path: '2500007676869', scheme: 'root' }).toString(),
|
||||
URI.from({ path: '3500007676869', scheme: 'root' }).toString(),
|
||||
|
||||
// Empty Workspace
|
||||
URI.from({ path: '4500007676869', scheme: 'empty' }).toString(),
|
||||
URI.from({ path: '5500007676869', scheme: 'empty' }).toString(),
|
||||
URI.from({ path: '6500007676869', scheme: 'empty' }).toString(),
|
||||
|
||||
// Unix Paths
|
||||
URI.file('/some/folder/folder1').toString(),
|
||||
URI.file('/some/folder/folder2').toString(),
|
||||
URI.file('/some/folder/folder3').toString(),
|
||||
URI.file('/some/folder/folder1/sub1').toString(),
|
||||
URI.file('/some/folder/folder2/sub2').toString(),
|
||||
URI.file('/some/folder/folder3/sub3').toString(),
|
||||
|
||||
// Windows Paths
|
||||
URI.file('c:\\some\\folder\\folder1').toString(),
|
||||
URI.file('c:\\some\\folder\\folder2').toString(),
|
||||
URI.file('c:\\some\\folder\\folder3').toString(),
|
||||
URI.file('c:\\some\\folder\\folder1\\sub1').toString(),
|
||||
URI.file('c:\\some\\folder\\folder2\\sub2').toString(),
|
||||
URI.file('c:\\some\\folder\\folder3\\sub3').toString(),
|
||||
|
||||
// UNC Paths
|
||||
'file://localhost/c%3A/some/folder/folder1',
|
||||
'file://localhost/c%3A/some/folder/folder2',
|
||||
'file://localhost/c%3A/some/folder/folder3',
|
||||
'file://localhost/c%3A/some/folder/folder1/sub1',
|
||||
'file://localhost/c%3A/some/folder/folder2/sub2',
|
||||
'file://localhost/c%3A/some/folder/folder3/sub3'
|
||||
];
|
||||
|
||||
const services = workspaceIds.map(id => createService(id));
|
||||
|
||||
services.forEach((service, index) => {
|
||||
let expectedKeyCount = 4;
|
||||
let storageToTest;
|
||||
|
||||
const workspaceId = workspaceIds[index];
|
||||
if (startsWith(workspaceId, 'file:')) {
|
||||
storageToTest = parseFolderStorage(storage, workspaceId);
|
||||
expectedKeyCount++; // workspaceIdentifier gets added!
|
||||
} else if (startsWith(workspaceId, 'empty:')) {
|
||||
storageToTest = parseEmptyStorage(storage, workspaceId);
|
||||
} else if (startsWith(workspaceId, 'root:')) {
|
||||
storageToTest = parseMultiRootStorage(storage, workspaceId);
|
||||
}
|
||||
|
||||
assert.equal(Object.keys(storageToTest).length, expectedKeyCount, 's');
|
||||
assert.equal(storageToTest['key1'], service.get('key1', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(storageToTest['key2.something'], service.get('key2.something', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(storageToTest['key3/special'], service.get('key3/special', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(storageToTest['key4 space'], service.get('key4 space', StorageLegacyScope.WORKSPACE));
|
||||
});
|
||||
});
|
||||
|
||||
test('Parse Storage (handle subfolders properly)', () => {
|
||||
const ws1 = URI.file('/some/folder/folder1').toString();
|
||||
const ws2 = URI.file('/some/folder/folder1/sub1').toString();
|
||||
|
||||
const s1 = new StorageLegacyService(storage, storage, ws1, Date.now());
|
||||
const s2 = new StorageLegacyService(storage, storage, ws2, Date.now());
|
||||
|
||||
s1.store('s1key1', 'value1', StorageLegacyScope.WORKSPACE);
|
||||
s1.store('s1key2.something', JSON.stringify({ foo: 'bar' }), StorageLegacyScope.WORKSPACE);
|
||||
s1.store('s1key3/special', true, StorageLegacyScope.WORKSPACE);
|
||||
s1.store('s1key4 space', 4, StorageLegacyScope.WORKSPACE);
|
||||
|
||||
s2.store('s2key1', 'value1', StorageLegacyScope.WORKSPACE);
|
||||
s2.store('s2key2.something', JSON.stringify({ foo: 'bar' }), StorageLegacyScope.WORKSPACE);
|
||||
s2.store('s2key3/special', true, StorageLegacyScope.WORKSPACE);
|
||||
s2.store('s2key4 space', 4, StorageLegacyScope.WORKSPACE);
|
||||
|
||||
|
||||
const s1Storage = parseFolderStorage(storage, ws1);
|
||||
assert.equal(Object.keys(s1Storage).length, 5);
|
||||
assert.equal(s1Storage['s1key1'], s1.get('s1key1', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(s1Storage['s1key2.something'], s1.get('s1key2.something', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(s1Storage['s1key3/special'], s1.get('s1key3/special', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(s1Storage['s1key4 space'], s1.get('s1key4 space', StorageLegacyScope.WORKSPACE));
|
||||
|
||||
const s2Storage = parseFolderStorage(storage, ws2);
|
||||
assert.equal(Object.keys(s2Storage).length, 5);
|
||||
assert.equal(s2Storage['s2key1'], s2.get('s2key1', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(s2Storage['s2key2.something'], s2.get('s2key2.something', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(s2Storage['s2key3/special'], s2.get('s2key3/special', StorageLegacyScope.WORKSPACE));
|
||||
assert.equal(s2Storage['s2key4 space'], s2.get('s2key4 space', StorageLegacyScope.WORKSPACE));
|
||||
});
|
||||
|
||||
function createService(workspaceId?: string): StorageLegacyService {
|
||||
const service = new StorageLegacyService(storage, storage, workspaceId, workspaceId && startsWith(workspaceId, 'file:') ? Date.now() : void 0);
|
||||
|
||||
// Unrelated
|
||||
storage.setItem('foo', 'bar');
|
||||
storage.setItem('storage://foo', 'bar');
|
||||
storage.setItem('storage://global/storage://foo', 'bar');
|
||||
|
||||
// Global
|
||||
service.store('key1', 'value1');
|
||||
service.store('key2.something', JSON.stringify({ foo: 'bar' }));
|
||||
service.store('key3/special', true);
|
||||
service.store('key4 space', 4);
|
||||
|
||||
// Workspace
|
||||
service.store('key1', 'value1', StorageLegacyScope.WORKSPACE);
|
||||
service.store('key2.something', JSON.stringify({ foo: 'bar' }), StorageLegacyScope.WORKSPACE);
|
||||
service.store('key3/special', true, StorageLegacyScope.WORKSPACE);
|
||||
service.store('key4 space', 4, StorageLegacyScope.WORKSPACE);
|
||||
|
||||
return service;
|
||||
}
|
||||
});
|
||||
@@ -30,10 +30,10 @@ suite('StorageService', () => {
|
||||
const storage = new TestStorageService();
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', scope);
|
||||
strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, void 0));
|
||||
strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!));
|
||||
|
||||
storage.remove('Monaco.IDE.Core.Storage.Test.remove', scope);
|
||||
ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, void 0));
|
||||
ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!));
|
||||
}
|
||||
|
||||
test('Get Data, Integer, Boolean (global, in-memory)', () => {
|
||||
@@ -55,22 +55,22 @@ suite('StorageService', () => {
|
||||
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, false), false);
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.get', 'foobar', scope);
|
||||
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, void 0), 'foobar');
|
||||
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), 'foobar');
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.get', '', scope);
|
||||
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, void 0), '');
|
||||
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), '');
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.getInteger', 5, scope);
|
||||
strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, void 0), 5);
|
||||
strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, (undefined)!), 5);
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.getInteger', 0, scope);
|
||||
strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, void 0), 0);
|
||||
strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, (undefined)!), 0);
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, scope);
|
||||
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, void 0), true);
|
||||
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), true);
|
||||
|
||||
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', false, scope);
|
||||
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, void 0), false);
|
||||
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), false);
|
||||
|
||||
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', scope, 'getDefault'), 'getDefault');
|
||||
strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getIntegerDefault', scope, 5), 5);
|
||||
|
||||
Reference in New Issue
Block a user