未验证 提交 1d229976 编写于 作者: B Benjamin Pasero 提交者: GitHub

Merge pull request #63651 from Microsoft/ben/storagehint

Refactor storage service and improve perf
......@@ -510,7 +510,7 @@
"./vs/platform/statusbar/common/statusbar.ts",
"./vs/platform/storage/common/storage.ts",
"./vs/platform/storage/common/storageLegacyService.ts",
"./vs/platform/storage/node/storageService.ts",
//"./vs/platform/storage/node/storageService.ts", TODO@Ben bring back when storageMigration is removed
"./vs/platform/telemetry/browser/errorTelemetry.ts",
"./vs/platform/telemetry/common/telemetry.ts",
"./vs/platform/telemetry/common/telemetryService.ts",
......
......@@ -13,9 +13,20 @@ import { basename } from 'path';
import { mark } from 'vs/base/common/performance';
import { rename, unlinkIgnoreError, copy, renameIgnoreError } from 'vs/base/node/pfs';
export enum StorageHint {
// A hint to the storage that the storage
// does not exist on disk yet. This allows
// the storage library to improve startup
// time by not checking the storage for data.
STORAGE_DOES_NOT_EXIST
}
export interface IStorageOptions {
path: string;
hint?: StorageHint;
logging?: IStorageLoggingOptions;
}
......@@ -58,6 +69,8 @@ export interface IStorage extends IDisposable {
export class Storage extends Disposable implements IStorage {
_serviceBrand: any;
static IN_MEMORY_PATH = ':memory:';
private static readonly FLUSH_DELAY = 100;
private _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
......@@ -73,7 +86,7 @@ export class Storage extends Disposable implements IStorage {
private pendingDeletes: Set<string> = new Set<string>();
private pendingInserts: Map<string, string> = new Map();
constructor(options: IStorageOptions) {
constructor(private options: IStorageOptions) {
super();
this.storage = new SQLiteStorageImpl(options);
......@@ -92,6 +105,13 @@ export class Storage extends Disposable implements IStorage {
this.state = StorageState.Initialized;
if (this.options.hint === StorageHint.STORAGE_DOES_NOT_EXIST) {
// return early if we know the storage file does not exist. this is a performance
// optimization to not load all items of the underlying storage if we know that
// there can be no items because the storage does not exist.
return Promise.resolve();
}
return this.storage.getItems().then(items => {
this.cache = items;
});
......@@ -239,7 +259,6 @@ export class SQLiteStorageImpl {
private static measuredRequireDuration: boolean; // TODO@Ben remove me after a while
private static IN_MEMORY_PATH = ':memory:';
private static BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY
private name: string;
......@@ -323,7 +342,7 @@ export class SQLiteStorageImpl {
// If the DB closed successfully and we are not running in-memory
// make a backup of the DB so that we can use it as fallback in
// case the actual DB becomes corrupt.
if (result.path !== SQLiteStorageImpl.IN_MEMORY_PATH) {
if (result.path !== Storage.IN_MEMORY_PATH) {
return this.backup(result).then(resolve, error => {
this.logger.error(`[storage ${this.name}] backup(): ${error}`);
......@@ -338,7 +357,7 @@ export class SQLiteStorageImpl {
}
private backup(db: IOpenDatabaseResult): Promise<void> {
if (db.path === SQLiteStorageImpl.IN_MEMORY_PATH) {
if (db.path === Storage.IN_MEMORY_PATH) {
return Promise.resolve(); // no backups when running in-memory
}
......@@ -371,7 +390,7 @@ export class SQLiteStorageImpl {
// In case of any error to open the DB, use an in-memory
// DB so that we always have a valid DB to talk to.
this.doOpen(SQLiteStorageImpl.IN_MEMORY_PATH).then(resolve, reject);
this.doOpen(Storage.IN_MEMORY_PATH).then(resolve, reject);
};
this.doOpen(path).then(resolve, error => {
......
......@@ -7,21 +7,31 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Storage, IStorageLoggingOptions, NullStorage, IStorage } from 'vs/base/node/storage';
import { Storage, IStorageLoggingOptions, NullStorage, IStorage, StorageHint } from 'vs/base/node/storage';
import { IStorageLegacyService, StorageLegacyScope } from 'vs/platform/storage/common/storageLegacyService';
import { startsWith } from 'vs/base/common/strings';
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, basename } from 'path';
import { copy } from 'vs/base/node/pfs';
import { join } from 'path';
import { copy, exists, mkdirp, readdir, writeFile } from 'vs/base/node/pfs';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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 interface IStorageServiceOptions {
storeInMemory?: boolean;
disableGlobalStorage?: boolean;
}
export class StorageService extends Disposable implements IStorageService {
_serviceBrand: any;
static IN_MEMORY_PATH = ':memory:';
private static WORKSPACE_STORAGE_NAME = 'storage.db';
private static WORKSPACE_META_NAME = 'workspace.json';
private _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
get onDidChangeStorage(): Event<IWorkspaceStorageChangeEvent> { return this._onDidChangeStorage.event; }
......@@ -60,9 +70,9 @@ export class StorageService extends Disposable implements IStorageService {
private loggingOptions: IStorageLoggingOptions;
constructor(
workspaceStoragePath: string,
disableGlobalStorage: boolean,
@ILogService logService: ILogService
private options: IStorageServiceOptions,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService
) {
super();
......@@ -81,14 +91,191 @@ export class StorageService extends Disposable implements IStorageService {
}
};
this.globalStorageWorkspacePath = workspaceStoragePath === StorageService.IN_MEMORY_PATH ? StorageService.IN_MEMORY_PATH : StorageService.IN_MEMORY_PATH;
this.globalStorage = disableGlobalStorage ? new NullStorage() : new Storage({ path: this.globalStorageWorkspacePath, logging: this.loggingOptions });
// Global Storage
this.globalStorageWorkspacePath = options.storeInMemory ? Storage.IN_MEMORY_PATH : Storage.IN_MEMORY_PATH;
this.globalStorage = options.disableGlobalStorage ? new NullStorage() : new Storage({ path: this.globalStorageWorkspacePath, logging: this.loggingOptions });
this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL)));
this.createWorkspaceStorage(workspaceStoragePath);
// Workspace Storage (in-memory only, other users require the initalize() call)
if (options.storeInMemory) {
this.createWorkspaceStorage(Storage.IN_MEMORY_PATH, StorageHint.STORAGE_DOES_NOT_EXIST);
}
}
private handleDidChangeStorage(key: string, scope: StorageScope): void {
this._onDidChangeStorage.fire({ key, scope });
}
initialize(payload: IWorkspaceInitializationPayload): Thenable<void> {
// Prepare workspace storage folder for DB
return this.prepareWorkspaceStorageFolder(payload).then(result => {
let workspaceStoragePath: string;
let workspaceStorageExists: Thenable<boolean>;
if (this.options.storeInMemory) {
workspaceStoragePath = Storage.IN_MEMORY_PATH;
workspaceStorageExists = Promise.resolve(true);
} else {
workspaceStoragePath = join(result.path, StorageService.WORKSPACE_STORAGE_NAME);
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');
// Migrate storage if this is the first start and we are not using in-memory
let migrationPromise: Thenable<void>;
if (!this.options.storeInMemory && !exists) {
migrationPromise = this.migrateWorkspaceStorage(payload);
} else {
migrationPromise = Promise.resolve();
}
return migrationPromise.then(() => {
mark('willInitGlobalStorage');
return this.globalStorage.init().then(() => {
mark('didInitGlobalStorage');
});
});
});
});
});
}
// 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.restore',
'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 || key.indexOf('viewservice.') === 0 || endsWith(key, '.state')) {
this.store(key, value, StorageScope.WORKSPACE);
}
});
}
} catch (error) {
onUnexpectedError(error);
this.logService.error(error);
}
mark('didMigrateWorkspaceStorageKeys');
});
}
private createWorkspaceStorage(workspaceStoragePath: string): void {
private createWorkspaceStorage(workspaceStoragePath: string, hint?: StorageHint): IStorage {
// Dispose old (if any)
this.workspaceStorage = dispose(this.workspaceStorage);
......@@ -96,26 +283,54 @@ export class StorageService extends Disposable implements IStorageService {
// Create new
this.workspaceStoragePath = workspaceStoragePath;
this.workspaceStorage = new Storage({ path: workspaceStoragePath, logging: this.loggingOptions });
this.workspaceStorage = new Storage({ path: workspaceStoragePath, logging: this.loggingOptions, hint });
this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.WORKSPACE));
return this.workspaceStorage;
}
private handleDidChangeStorage(key: string, scope: StorageScope): void {
this._onDidChangeStorage.fire({ key, scope });
private getWorkspaceStorageFolderPath(payload: IWorkspaceInitializationPayload): string {
return join(this.environmentService.workspaceStorageHome, payload.id); // workspace home + workspace id;
}
init(): Promise<void> {
mark('willInitWorkspaceStorage');
return this.workspaceStorage.init().then(() => {
mark('didInitWorkspaceStorage');
private prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload): Thenable<{ path: string, wasCreated: boolean }> {
const workspaceStorageFolderPath = this.getWorkspaceStorageFolderPath(payload);
mark('willInitGlobalStorage');
return this.globalStorage.init().then(() => {
mark('didInitGlobalStorage');
return exists(workspaceStorageFolderPath).then(exists => {
if (exists) {
return { path: workspaceStorageFolderPath, wasCreated: false };
}
return mkdirp(workspaceStorageFolderPath).then(() => {
// Write metadata into folder
this.ensureWorkspaceStorageFolderMeta(payload);
return { path: workspaceStorageFolderPath, wasCreated: true };
});
});
}
private ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload): void {
let meta: object | undefined = void 0;
if (isSingleFolderWorkspaceInitializationPayload(payload)) {
meta = { folder: payload.folder.toString() };
} else if (isWorkspaceIdentifier(payload)) {
meta = { configuration: payload.configPath };
}
if (meta) {
const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), StorageService.WORKSPACE_META_NAME);
exists(workspaceStorageMetaPath).then(exists => {
if (exists) {
return void 0; // already existing
}
return writeFile(workspaceStorageMetaPath, JSON.stringify(meta, void 0, 2));
}).then(null, error => onUnexpectedError(error));
}
}
get(key: string, scope: StorageScope, fallbackValue: string): string;
get(key: string, scope: StorageScope): string | undefined;
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
......@@ -217,23 +432,24 @@ export class StorageService extends Disposable implements IStorageService {
});
}
migrate(toWorkspaceStorageFolder: string): Thenable<void> {
if (this.workspaceStoragePath === StorageService.IN_MEMORY_PATH) {
migrate(toWorkspace: IWorkspaceInitializationPayload): Thenable<void> {
if (this.workspaceStoragePath === Storage.IN_MEMORY_PATH) {
return Promise.resolve(); // no migration needed if running in memory
}
// Compute new workspace storage path based on workspace identifier
const newWorkspaceStoragePath = join(toWorkspaceStorageFolder, basename(this.workspaceStoragePath));
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(() => {
return copy(this.workspaceStoragePath, newWorkspaceStoragePath).then(() => {
this.createWorkspaceStorage(newWorkspaceStoragePath);
return this.workspaceStorage.init();
// Prepare new workspace storage folder
return this.prepareWorkspaceStorageFolder(toWorkspace).then(result => {
const newWorkspaceStoragePath = join(result.path, StorageService.WORKSPACE_STORAGE_NAME);
// Copy current storage over to new workspace storage
return copy(this.workspaceStoragePath, newWorkspaceStoragePath).then(() => {
// Recreate and init workspace storage
return this.createWorkspaceStorage(newWorkspaceStoragePath).init();
});
});
});
}
......
......@@ -12,6 +12,8 @@ import { join } from 'path';
import { tmpdir } from 'os';
import { mkdirp, del } from 'vs/base/node/pfs';
import { NullLogService } from 'vs/platform/log/common/log';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
suite('StorageService', () => {
......@@ -81,27 +83,38 @@ suite('StorageService', () => {
}
test('Migrate Data', async () => {
class StorageTestEnvironmentService extends EnvironmentService {
constructor(private workspaceStorageFolderPath: string, private _extensionsPath) {
super(parseArgs(process.argv), process.execPath);
}
get workspaceStorageHome(): string {
return this.workspaceStorageFolderPath;
}
get extensionsPath(): string {
return this._extensionsPath;
}
}
const storageDir = uniqueStorageDir();
await mkdirp(storageDir);
const storage = new StorageService(join(storageDir, 'storage.db'), false, new NullLogService());
await storage.init();
const storage = new StorageService({}, new NullLogService(), new StorageTestEnvironmentService(storageDir, storageDir));
await storage.initialize({ id: String(Date.now()) });
storage.store('bar', 'foo', StorageScope.WORKSPACE);
storage.store('barNumber', 55, StorageScope.WORKSPACE);
storage.store('barBoolean', true, StorageScope.GLOBAL);
const newStorageDir = uniqueStorageDir();
await mkdirp(newStorageDir);
await storage.migrate(newStorageDir);
await storage.migrate({ id: String(Date.now() + 100) });
equal(storage.get('bar', StorageScope.WORKSPACE), 'foo');
equal(storage.getInteger('barNumber', StorageScope.WORKSPACE), 55);
equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true);
await storage.close();
await del(newStorageDir, tmpdir());
await del(storageDir, tmpdir());
});
});
\ No newline at end of file
......@@ -7,7 +7,7 @@ import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { getRandomTestPath, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { StorageService } from 'vs/platform/storage/node/storageService';
import { NullLogService } from 'vs/platform/log/common/log';
......@@ -21,10 +21,10 @@ suite('Telemetry - common properties', function () {
const commit: string = void 0;
const version: string = void 0;
let nestStorage2Service: IStorageService;
let testStorageService: IStorageService;
setup(() => {
nestStorage2Service = new StorageService(':memory:', false, new NullLogService());
testStorageService = new StorageService({ storeInMemory: true }, new NullLogService(), TestEnvironmentService);
});
teardown(done => {
......@@ -34,7 +34,7 @@ suite('Telemetry - common properties', function () {
test('default', async function () {
await mkdirp(parentDir);
fs.writeFileSync(installSource, 'my.install.source');
const props = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', installSource);
assert.ok('commitHash' in props);
assert.ok('sessionID' in props);
assert.ok('timestamp' in props);
......@@ -55,22 +55,22 @@ suite('Telemetry - common properties', function () {
assert.ok('common.instanceId' in props, 'instanceId');
assert.ok('common.machineId' in props, 'machineId');
fs.unlinkSync(installSource);
const props_1 = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
const props_1 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', installSource);
assert.ok(!('common.source' in props_1));
});
test('lastSessionDate when aviablale', async function () {
nestStorage2Service.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.GLOBAL);
testStorageService.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.GLOBAL);
const props = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', installSource);
assert.ok('common.lastSessionDate' in props); // conditional, see below
assert.ok('common.isNewSession' in props);
assert.equal(props['common.isNewSession'], 0);
});
test('values chance on ask', async function () {
const props = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', installSource);
let value1 = props['common.sequence'];
let value2 = props['common.sequence'];
assert.ok(value1 !== value2, 'seq');
......
......@@ -128,3 +128,13 @@ export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifi
// Empty workspace
return undefined;
}
export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier;
export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: ISingleFolderWorkspaceIdentifier; }
export interface IEmptyWorkspaceInitializationPayload { id: string; }
export type IWorkspaceInitializationPayload = IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload;
export function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload {
return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier));
}
\ No newline at end of file
......@@ -13,10 +13,10 @@ import * as comparer from 'vs/base/common/comparers';
import * as platform from 'vs/base/common/platform';
import { URI as uri } from 'vs/base/common/uri';
import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload } from 'vs/workbench/services/configuration/node/configurationService';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { stat, exists, writeFile, readdir } from 'vs/base/node/pfs';
import { stat } from 'vs/base/node/pfs';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import * as gracefulFs from 'graceful-fs';
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
......@@ -31,7 +31,7 @@ import { IUpdateService } from 'vs/platform/update/common/update';
import { URLHandlerChannel, URLServiceChannelClient } from 'vs/platform/url/node/urlIpc';
import { IURLService } from 'vs/platform/url/common/url';
import { WorkspacesChannelClient } from 'vs/platform/workspaces/node/workspacesIpc';
import { IWorkspacesService, ISingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesService, ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import * as fs from 'fs';
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
......@@ -43,12 +43,9 @@ import { RelayURLService } from 'vs/platform/url/common/urlService';
import { MenubarChannelClient } from 'vs/platform/menubar/node/menubarIpc';
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
import { Schemas } from 'vs/base/common/network';
import { sanitizeFilePath, mkdirp } from 'vs/base/node/extfs';
import { basename, join } from 'path';
import { sanitizeFilePath } from 'vs/base/node/extfs';
import { basename } from 'path';
import { createHash } from 'crypto';
import { StorageObject, parseFolderStorage, parseMultiRootStorage, parseNoWorkspaceStorage, parseEmptyStorage } from 'vs/platform/storage/common/storageLegacyMigration';
import { StorageScope } from 'vs/platform/storage/common/storage';
import { endsWith } from 'vs/base/common/strings';
import { IdleValue } from 'vs/base/common/async';
import { setGlobalLeakWarningThreshold } from 'vs/base/common/event';
......@@ -139,11 +136,6 @@ function openWorkbench(configuration: IWindowConfiguration): Promise<void> {
storageService
}, mainServices, mainProcessClient, configuration);
// Store meta file in workspace storage after workbench is running
shell.onRunning(() => {
ensureWorkspaceStorageFolderMeta(payload, workspaceService, environmentService);
});
// Gracefully Shutdown Storage
shell.onShutdown(event => {
event.join(storageService.close());
......@@ -247,196 +239,17 @@ function createWorkspaceService(payload: IWorkspaceInitializationPayload, enviro
}
function createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Thenable<StorageService> {
const useInMemoryStorage = !!environmentService.extensionTestsPath; // no storage during extension tests!
const storageService = new StorageService({ disableGlobalStorage: true, storeInMemory: useInMemoryStorage }, logService, environmentService);
// Prepare the workspace storage folder
return prepareWorkspaceStorageFolder(payload, environmentService).then(result => {
// Return early if we are using in-memory storage
const useInMemoryStorage = !!environmentService.extensionTestsPath; /* never keep any state when running extension tests */
if (useInMemoryStorage) {
const storageService = new StorageService(StorageService.IN_MEMORY_PATH, true, logService);
return storageService.init().then(() => storageService);
}
// Otherwise do a migration of previous workspace data if the DB does not exist yet
// TODO@Ben remove me after some milestones
const workspaceStorageDBPath = join(result.path, 'storage.db');
perf.mark('willCheckWorkspaceStorageExists');
return exists(workspaceStorageDBPath).then(exists => {
perf.mark('didCheckWorkspaceStorageExists');
const storageService = new StorageService(workspaceStorageDBPath, true, logService);
return storageService.init().then(() => {
if (exists) {
return storageService; // return early if DB was already there
}
perf.mark('willMigrateWorkspaceStorageKeys');
return readdir(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.restore',
'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) {
storageService.store(supportedKey, value, StorageScope.WORKSPACE);
}
// fix lowercased ".numberOfVisibleViews"
else if (endsWith(key, '.numberOfVisibleViews'.toLowerCase())) {
const normalizedKey = key.substring(0, key.length - '.numberOfVisibleViews'.length) + '.numberOfVisibleViews';
storageService.store(normalizedKey, value, StorageScope.WORKSPACE);
}
// support dynamic keys
else if (key.indexOf('memento/') === 0 || key.indexOf('viewservice.') === 0 || endsWith(key, '.state')) {
storageService.store(key, value, StorageScope.WORKSPACE);
}
});
}
} catch (error) {
onUnexpectedError(error);
logService.error(error);
}
perf.mark('didMigrateWorkspaceStorageKeys');
return storageService;
});
});
});
});
}
function getWorkspaceStoragePath(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService): string {
return join(environmentService.workspaceStorageHome, payload.id); // workspace home + workspace id;
}
function prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService): Thenable<{ path: string, wasCreated: boolean }> {
const workspaceStoragePath = getWorkspaceStoragePath(payload, environmentService);
return exists(workspaceStoragePath).then(exists => {
if (exists) {
return { path: workspaceStoragePath, wasCreated: false };
}
return storageService.initialize(payload).then(() => storageService, error => {
onUnexpectedError(error);
logService.error(error);
return mkdirp(workspaceStoragePath).then(() => ({ path: workspaceStoragePath, wasCreated: true }));
return storageService;
});
}
function ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload, workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): void {
const state = workspaceService.getWorkbenchState();
if (state === WorkbenchState.EMPTY) {
return; // no storage meta for empty workspaces
}
const workspaceStorageMetaPath = join(getWorkspaceStoragePath(payload, environmentService), 'workspace.json');
exists(workspaceStorageMetaPath).then(exists => {
if (exists) {
return void 0; // already existing
}
const workspace = workspaceService.getWorkspace();
return writeFile(workspaceStorageMetaPath, JSON.stringify({
configuration: workspace.configuration ? uri.revive(workspace.configuration).toString() : void 0,
folder: state === WorkbenchState.FOLDER ? uri.revive(workspace.folders[0].uri).toString() : void 0
}, undefined, 2));
}).then(null, error => onUnexpectedError(error));
}
function createStorageLegacyService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageLegacyService {
let workspaceId: string;
......
......@@ -126,9 +126,6 @@ export class WorkbenchShell extends Disposable {
private readonly _onShutdown = this._register(new Emitter<ShutdownEvent>());
get onShutdown(): Event<ShutdownEvent> { return this._onShutdown.event; }
private readonly _onRunning = this._register(new Emitter<void>());
get onRunning(): Event<void> { return this._onRunning.event; }
private storageService: DelegatingStorageService;
private environmentService: IEnvironmentService;
private logService: ILogService;
......@@ -217,10 +214,8 @@ export class WorkbenchShell extends Disposable {
// Startup Workbench
workbench.startup().then(startupInfos => {
// Set lifecycle phase to `Runnning` so that other contributions can
// now do something we also emit this as event to interested parties outside
// Set lifecycle phase to `Runnning`
this.lifecycleService.phase = LifecyclePhase.Running;
this._onRunning.fire();
// Startup Telemetry
this.logStartupTelemetry(startupInfos);
......
......@@ -23,7 +23,7 @@ import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationC
import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
......@@ -38,15 +38,6 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { isEqual } from 'vs/base/common/resources';
export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier;
export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: ISingleFolderWorkspaceIdentifier; }
export interface IEmptyWorkspaceInitializationPayload { id: string; }
export type IWorkspaceInitializationPayload = IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload;
export function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload {
return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier));
}
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
public _serviceBrand: any;
......
......@@ -16,7 +16,8 @@ import { parseArgs } from 'vs/platform/environment/node/argv';
import * as pfs from 'vs/base/node/pfs';
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload } from 'vs/workbench/services/configuration/node/configurationService';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
......
......@@ -25,9 +25,6 @@ import { distinct } from 'vs/base/common/arrays';
import { isLinux } from 'vs/base/common/platform';
import { isEqual } from 'vs/base/common/resources';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { join } from 'path';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { mkdirp } from 'vs/base/node/pfs';
export class WorkspaceEditingService implements IWorkspaceEditingService {
......@@ -42,8 +39,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
@IExtensionService private extensionService: IExtensionService,
@IBackupFileService private backupFileService: IBackupFileService,
@INotificationService private notificationService: INotificationService,
@ICommandService private commandService: ICommandService,
@IEnvironmentService private environmentService: IEnvironmentService
@ICommandService private commandService: ICommandService
) {
}
......@@ -243,13 +239,9 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
}
private migrateStorage(toWorkspace: IWorkspaceIdentifier): Thenable<void> {
const newWorkspaceStorageHome = join(this.environmentService.workspaceStorageHome, toWorkspace.id);
const storageImpl = this.storageService as DelegatingStorageService;
return mkdirp(newWorkspaceStorageHome).then(() => {
const storageImpl = this.storageService as DelegatingStorageService;
return storageImpl.storage.migrate(newWorkspaceStorageHome);
});
return storageImpl.storage.migrate(toWorkspace);
}
private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
......
......@@ -532,7 +532,7 @@ export class TestPartService implements IPartService {
export class TestStorageService extends StorageService {
constructor() {
super(':memory:', false, new NullLogService());
super({ storeInMemory: true }, new NullLogService(), TestEnvironmentService);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册