Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile support for global storage #152045

Merged
merged 16 commits into from
Jun 15, 2022
Merged
2 changes: 1 addition & 1 deletion src/vs/base/browser/indexedDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class IndexedDB {
return new IndexedDB(database, name);
}

static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
private static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
mark(`code/willOpenDatabase/${name}`);
try {
return await IndexedDB.doOpenDatabase(name, version, stores);
Expand Down
4 changes: 2 additions & 2 deletions src/vs/code/electron-main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedPro
import { ISignService } from 'vs/platform/sign/common/sign';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc';
import { GlobalStorageMainService, IGlobalStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { ApplicationStorageMainService, IApplicationStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
import { ITelemetryService, machineIdKey, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
Expand Down Expand Up @@ -648,7 +648,7 @@ export class CodeApplication extends Disposable {

// Storage
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
services.set(IGlobalStorageMainService, new SyncDescriptor(GlobalStorageMainService));
services.set(IApplicationStorageMainService, new SyncDescriptor(ApplicationStorageMainService));

// External terminal
if (isWindows) {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/externalServices/common/serviceMachineId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';

export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: IStorageService | undefined): Promise<string> {
let uuid: string | null = storageService ? storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null : null;
let uuid: string | null = storageService ? storageService.get('storage.serviceMachineId', StorageScope.APPLICATION) || null : null;
if (uuid) {
return uuid;
}
Expand All @@ -31,7 +31,7 @@ export async function getServiceMachineId(environmentService: IEnvironmentServic
}
}
if (storageService) {
storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL, StorageTarget.MACHINE);
storageService.store('storage.serviceMachineId', uuid, StorageScope.APPLICATION, StorageTarget.MACHINE);
}
return uuid;
}
14 changes: 11 additions & 3 deletions src/vs/platform/notification/common/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@ export enum NeverShowAgainScope {
WORKSPACE,

/**
* Will never show this notification on any workspace again.
* Will never show this notification on any workspace of the same
* profile again.
*/
GLOBAL
GLOBAL,

/**
* Will never show this notification on any workspace across all
* profiles again.
*/
APPLICATION
}

export interface INeverShowAgainOptions {
Expand All @@ -65,7 +72,8 @@ export interface INeverShowAgainOptions {

/**
* Whether to persist the choice in the current workspace or for all workspaces. By
* default it will be persisted for all workspaces (= `NeverShowAgainScope.GLOBAL`).
* default it will be persisted for all workspaces across all profiles
* (= `NeverShowAgainScope.APPLICATION`).
*/
readonly scope?: NeverShowAgainScope;
}
Expand Down
95 changes: 68 additions & 27 deletions src/vs/platform/storage/browser/storageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,76 +12,115 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'
import { InMemoryStorageDatabase, isStorageItemsChangeEvent, IStorage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage';
import { ILogService } from 'vs/platform/log/common/log';
import { AbstractStorageService, IS_NEW_KEY, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';

export class BrowserStorageService extends AbstractStorageService {

private static BROWSER_DEFAULT_FLUSH_INTERVAL = 5 * 1000; // every 5s because async operations are not permitted on shutdown

private applicationStorage: IStorage | undefined;
private globalStorage: IStorage | undefined;
private workspaceStorage: IStorage | undefined;

private applicationStorageDatabase: IIndexedDBStorageDatabase | undefined;
private globalStorageDatabase: IIndexedDBStorageDatabase | undefined;
private workspaceStorageDatabase: IIndexedDBStorageDatabase | undefined;

get hasPendingUpdate(): boolean {
return Boolean(this.globalStorageDatabase?.hasPendingUpdate || this.workspaceStorageDatabase?.hasPendingUpdate);
return Boolean(
this.applicationStorageDatabase?.hasPendingUpdate ||
this.globalStorageDatabase?.hasPendingUpdate ||
this.workspaceStorageDatabase?.hasPendingUpdate
);
}

constructor(
private readonly payload: IAnyWorkspaceIdentifier,
@ILogService private readonly logService: ILogService
@ILogService private readonly logService: ILogService,
@IUserDataProfilesService private readonly userDataProfileService: IUserDataProfilesService
) {
super({ flushInterval: BrowserStorageService.BROWSER_DEFAULT_FLUSH_INTERVAL });
}

private getId(scope: StorageScope): string {
return scope === StorageScope.GLOBAL ? 'global' : this.payload.id;
switch (scope) {
case StorageScope.APPLICATION:
return 'global'; // use the default profile global DB for application scope
case StorageScope.GLOBAL:
if (this.userDataProfileService.currentProfile.isDefault) {
return 'global'; // default profile DB has a fixed name for backwards compatibility
} else {
return `global-${this.userDataProfileService.currentProfile.id}`;
}
case StorageScope.WORKSPACE:
return this.payload.id;
}
}

protected async doInitialize(): Promise<void> {

// Create Storage in Parallel
const [workspaceStorageDatabase, globalStorageDatabase] = await Promises.settled([
IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.WORKSPACE) }, this.logService),
IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.GLOBAL), broadcastChanges: true /* only for global storage */ }, this.logService)
]);
const promises: Promise<IIndexedDBStorageDatabase>[] = [];
promises.push(IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.APPLICATION), broadcastChanges: true }, this.logService));
promises.push(IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.WORKSPACE) }, this.logService));
if (!this.userDataProfileService.currentProfile.isDefault) {

// If we are in default profile, the global storage is
// actually the same as application storage. As such we
// avoid creating the storage library a second time on
// the same DB.

promises.push(IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.GLOBAL), broadcastChanges: true }, this.logService));
}
const [applicationStorageDatabase, workspaceStorageDatabase, globalStorageDatabase] = await Promises.settled(promises);

// Workspace Storage
this.workspaceStorageDatabase = this._register(workspaceStorageDatabase);
this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase));
this._register(this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)));

// Application Storage
this.applicationStorageDatabase = this._register(applicationStorageDatabase);
this.applicationStorage = this._register(new Storage(this.applicationStorageDatabase));
this._register(this.applicationStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.APPLICATION, key)));

// Global Storage
this.globalStorageDatabase = this._register(globalStorageDatabase);
this.globalStorage = this._register(new Storage(this.globalStorageDatabase));
if (globalStorageDatabase) {
this.globalStorageDatabase = this._register(globalStorageDatabase);
this.globalStorage = this._register(new Storage(this.globalStorageDatabase));
} else {
this.globalStorage = this.applicationStorage;
}
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));

// Init both
// Init storages
await Promises.settled([
this.workspaceStorage.init(),
this.globalStorage.init()
this.globalStorage.init(),
this.applicationStorage.init()
]);

// Check to see if this is the first time we are "opening" the application
const firstOpen = this.globalStorage.getBoolean(IS_NEW_KEY);
if (firstOpen === undefined) {
this.globalStorage.set(IS_NEW_KEY, true);
} else if (firstOpen) {
this.globalStorage.set(IS_NEW_KEY, false);
}

// Check to see if this is the first time we are "opening" this workspace
const firstWorkspaceOpen = this.workspaceStorage.getBoolean(IS_NEW_KEY);
if (firstWorkspaceOpen === undefined) {
this.workspaceStorage.set(IS_NEW_KEY, true);
} else if (firstWorkspaceOpen) {
this.workspaceStorage.set(IS_NEW_KEY, false);
// Apply is-new markers
for (const storage of [this.applicationStorage, this.globalStorage, this.workspaceStorage]) {
const firstOpen = storage.getBoolean(IS_NEW_KEY);
if (firstOpen === undefined) {
storage.set(IS_NEW_KEY, true);
} else if (firstOpen) {
storage.set(IS_NEW_KEY, false);
}
}
}

protected getStorage(scope: StorageScope): IStorage | undefined {
return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
switch (scope) {
case StorageScope.APPLICATION:
return this.applicationStorage;
case StorageScope.GLOBAL:
return this.globalStorage;
default:
return this.workspaceStorage;
}
}

protected getLogDetails(scope: StorageScope): string | undefined {
Expand Down Expand Up @@ -115,6 +154,7 @@ export class BrowserStorageService extends AbstractStorageService {
// On all other browsers, we keep the databases opened because
// we expect data to be written when the unload happens.
if (isSafari) {
this.applicationStorage?.close();
this.globalStorageDatabase?.close();
this.workspaceStorageDatabase?.close();
}
Expand All @@ -127,7 +167,7 @@ export class BrowserStorageService extends AbstractStorageService {
async clear(): Promise<void> {

// Clear key/values
for (const scope of [StorageScope.GLOBAL, StorageScope.WORKSPACE]) {
for (const scope of [StorageScope.APPLICATION, StorageScope.GLOBAL, StorageScope.WORKSPACE]) {
for (const target of [StorageTarget.USER, StorageTarget.MACHINE]) {
for (const key of this.keys(scope, target)) {
this.remove(key, scope);
Expand All @@ -139,6 +179,7 @@ export class BrowserStorageService extends AbstractStorageService {

// Clear databases
await Promises.settled([
this.applicationStorageDatabase?.clear() ?? Promise.resolve(),
this.globalStorageDatabase?.clear() ?? Promise.resolve(),
this.workspaceStorageDatabase?.clear() ?? Promise.resolve()
]);
Expand Down
Loading