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

Support creating partial profile and use default profile for other data #187607

Merged
merged 1 commit into from Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/platform/storage/common/storage.ts
Expand Up @@ -659,7 +659,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
}

export function isProfileUsingDefaultStorage(profile: IUserDataProfile): boolean {
return profile.isDefault || !!profile.useDefaultFlags?.uiState;
return profile.isDefault || !!profile.useDefaultFlags?.globalState;
}

export class InMemoryStorageService extends AbstractStorageService {
Expand Down
44 changes: 23 additions & 21 deletions src/vs/platform/userDataProfile/common/userDataProfile.ts
Expand Up @@ -22,17 +22,19 @@ import { generateUuid } from 'vs/base/common/uuid';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { isString } from 'vs/base/common/types';

export const enum ProfileResourceType {
Settings = 'settings',
Keybindings = 'keybindings',
Snippets = 'snippets',
Tasks = 'tasks',
Extensions = 'extensions',
GlobalState = 'globalState',
}

/**
* Flags to indicate whether to use the default profile or not.
*/
export type UseDefaultProfileFlags = {
settings?: boolean;
keybindings?: boolean;
tasks?: boolean;
snippets?: boolean;
extensions?: boolean;
uiState?: boolean;
};
export type UseDefaultProfileFlags = { [key in ProfileResourceType]?: boolean };

export interface IUserDataProfile {
readonly id: string;
Expand Down Expand Up @@ -135,19 +137,19 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)
};
}

export function toUserDataProfile(id: string, name: string, location: URI, profilesCacheHome: URI, options?: IUserDataProfileOptions): IUserDataProfile {
export function toUserDataProfile(id: string, name: string, location: URI, profilesCacheHome: URI, options?: IUserDataProfileOptions, defaultProfile?: IUserDataProfile): IUserDataProfile {
return {
id,
name,
location,
isDefault: false,
shortName: options?.shortName,
globalStorageHome: joinPath(location, 'globalStorage'),
settingsResource: joinPath(location, 'settings.json'),
keybindingsResource: joinPath(location, 'keybindings.json'),
tasksResource: joinPath(location, 'tasks.json'),
snippetsHome: joinPath(location, 'snippets'),
extensionsResource: joinPath(location, 'extensions.json'),
globalStorageHome: defaultProfile && options?.useDefaultFlags?.globalState ? defaultProfile.globalStorageHome : joinPath(location, 'globalStorage'),
settingsResource: defaultProfile && options?.useDefaultFlags?.settings ? defaultProfile.settingsResource : joinPath(location, 'settings.json'),
keybindingsResource: defaultProfile && options?.useDefaultFlags?.keybindings ? defaultProfile.keybindingsResource : joinPath(location, 'keybindings.json'),
tasksResource: defaultProfile && options?.useDefaultFlags?.tasks ? defaultProfile.tasksResource : joinPath(location, 'tasks.json'),
snippetsHome: defaultProfile && options?.useDefaultFlags?.snippets ? defaultProfile.snippetsHome : joinPath(location, 'snippets'),
extensionsResource: defaultProfile && options?.useDefaultFlags?.extensions ? defaultProfile.extensionsResource : joinPath(location, 'extensions.json'),
cacheHome: joinPath(profilesCacheHome, id),
useDefaultFlags: options?.useDefaultFlags,
isTransient: options?.transient
Expand Down Expand Up @@ -235,24 +237,23 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
protected _profilesObject: UserDataProfilesObject | undefined;
protected get profilesObject(): UserDataProfilesObject {
if (!this._profilesObject) {
const profiles = [];
const defaultProfile = this.createDefaultProfile();
const profiles = [defaultProfile];
if (this.enabled) {
try {
for (const storedProfile of this.getStoredProfiles()) {
if (!storedProfile.name || !isString(storedProfile.name) || !storedProfile.location) {
this.logService.warn('Skipping the invalid stored profile', storedProfile.location || storedProfile.name);
continue;
}
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }));
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }, defaultProfile));
}
} catch (error) {
this.logService.error(error);
}
}
const workspaces = new ResourceMap<IUserDataProfile>();
const emptyWindows = new Map<string, IUserDataProfile>();
const defaultProfile = this.createDefaultProfile();
profiles.unshift({ ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true });
if (profiles.length) {
try {
const profileAssociaitions = this.getStoredProfileAssociations();
Expand Down Expand Up @@ -283,7 +284,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
}

private createDefaultProfile() {
return toUserDataProfile('__default__profile__', localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome, this.profilesCacheHome);
const defaultProfile = toUserDataProfile('__default__profile__', localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome, this.profilesCacheHome);
return { ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true };
}

async createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
Expand Down Expand Up @@ -330,7 +332,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return existing;
}

const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options);
const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options, this.defaultProfile);
await this.fileService.createFolder(profile.location);

const joiners: Promise<void>[] = [];
Expand Down
Expand Up @@ -178,4 +178,52 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
});

test('profile using default profile for settings', async () => {
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { settings: true } });

assert.strictEqual(profile.isDefault, false);
assert.deepStrictEqual(profile.useDefaultFlags, { settings: true });
assert.strictEqual(profile.settingsResource.toString(), testObject.defaultProfile.settingsResource.toString());
});

test('profile using default profile for keybindings', async () => {
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { keybindings: true } });

assert.strictEqual(profile.isDefault, false);
assert.deepStrictEqual(profile.useDefaultFlags, { keybindings: true });
assert.strictEqual(profile.keybindingsResource.toString(), testObject.defaultProfile.keybindingsResource.toString());
});

test('profile using default profile for snippets', async () => {
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { snippets: true } });

assert.strictEqual(profile.isDefault, false);
assert.deepStrictEqual(profile.useDefaultFlags, { snippets: true });
assert.strictEqual(profile.snippetsHome.toString(), testObject.defaultProfile.snippetsHome.toString());
});

test('profile using default profile for tasks', async () => {
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { tasks: true } });

assert.strictEqual(profile.isDefault, false);
assert.deepStrictEqual(profile.useDefaultFlags, { tasks: true });
assert.strictEqual(profile.tasksResource.toString(), testObject.defaultProfile.tasksResource.toString());
});

test('profile using default profile for global state', async () => {
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { globalState: true } });

assert.strictEqual(profile.isDefault, false);
assert.deepStrictEqual(profile.useDefaultFlags, { globalState: true });
assert.strictEqual(profile.globalStorageHome.toString(), testObject.defaultProfile.globalStorageHome.toString());
});

test('profile using default profile for extensions', async () => {
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { extensions: true } });

assert.strictEqual(profile.isDefault, false);
assert.deepStrictEqual(profile.useDefaultFlags, { extensions: true });
assert.strictEqual(profile.extensionsResource.toString(), testObject.defaultProfile.extensionsResource.toString());
});

});
Expand Up @@ -29,7 +29,7 @@ interface IUserDataProfilesManifestResourcePreview extends IResourcePreview {

export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {

protected readonly version: number = 1;
protected readonly version: number = 2;
readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'profiles.json');
readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
Expand Down Expand Up @@ -190,7 +190,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
for (const profile of local.added) {
promises.push((async () => {
this.logService.trace(`${this.syncResourceLogLabel}: Creating '${profile.name}' profile...`);
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName });
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
this.logService.info(`${this.syncResourceLogLabel}: Created profile '${profile.name}'.`);
})());
}
Expand Down Expand Up @@ -224,7 +224,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
for (const profile of remote?.added || []) {
const collection = await this.userDataSyncStoreService.createCollection(this.syncHeaders);
addedCollections.push(collection);
remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName });
remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
}
} else {
this.logService.info(`${this.syncResourceLogLabel}: Could not create remote profiles as there are too many profiles.`);
Expand All @@ -235,7 +235,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
for (const profile of remote?.updated || []) {
const profileToBeUpdated = remoteProfiles.find(({ id }) => profile.id === id);
if (profileToBeUpdated) {
remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { id: profile.id, name: profile.name, collection: profileToBeUpdated.collection, shortName: profile.shortName });
remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { ...profileToBeUpdated, id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/vs/platform/userDataSync/common/userDataSync.ts
Expand Up @@ -21,7 +21,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ILogService } from 'vs/platform/log/common/log';
import { Registry } from 'vs/platform/registry/common/platform';
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile';

export function getDisallowedIgnoredSettings(): string[] {
const allSettings = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
Expand Down Expand Up @@ -325,6 +325,7 @@ export interface ISyncUserDataProfile {
readonly collection: string;
readonly name: string;
readonly shortName?: string;
readonly useDefaultFlags?: UseDefaultProfileFlags;
}

export type ISyncExtension = ILocalSyncExtension | IRemoteSyncExtension;
Expand Down
4 changes: 4 additions & 0 deletions src/vs/platform/userDataSync/common/userDataSyncService.ts
Expand Up @@ -622,6 +622,10 @@ class ProfileSynchronizer extends Disposable {
if (syncResource === SyncResource.WorkspaceState) {
return;
}
if (syncResource !== SyncResource.Profiles && this.profile.useDefaultFlags?.[syncResource]) {
this.logService.debug(`Skipping syncing ${syncResource} in ${this.profile.name} because it is already synced by default profile`);
return;
}
const disposables = new DisposableStore();
const synchronizer = disposables.add(this.createSynchronizer(syncResource));
disposables.add(synchronizer.onDidChangeStatus(() => this.updateStatus()));
Expand Down
Expand Up @@ -100,7 +100,7 @@ suite('UserDataProfilesManifestSync', () => {
assert.deepStrictEqual(testObject.conflicts.conflicts, []);

const profiles = getLocalProfiles(testClient);
assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined }]);
assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: undefined }]);
});

test('first time sync when profiles exists', async () => {
Expand All @@ -113,7 +113,7 @@ suite('UserDataProfilesManifestSync', () => {
assert.deepStrictEqual(testObject.conflicts.conflicts, []);

const profiles = getLocalProfiles(testClient);
assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined }, { id: '2', name: 'name 2', shortName: undefined }]);
assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: undefined }, { id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]);

const { content } = await testClient.read(testObject.resource);
assert.ok(content !== null);
Expand All @@ -132,7 +132,7 @@ suite('UserDataProfilesManifestSync', () => {
assert.deepStrictEqual(testObject.conflicts.conflicts, []);

const profiles = getLocalProfiles(testClient);
assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined }]);
assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: undefined }]);

const { content } = await testClient.read(testObject.resource);
assert.ok(content !== null);
Expand All @@ -149,10 +149,10 @@ suite('UserDataProfilesManifestSync', () => {
await testObject.sync(await testClient.getResourceManifest());
assert.strictEqual(testObject.status, SyncStatus.Idle);
assert.deepStrictEqual(testObject.conflicts.conflicts, []);
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: 'short 1' }, { id: '2', name: 'name 2', shortName: undefined }]);
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: 'short 1', useDefaultFlags: undefined }, { id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]);

await client2.sync();
assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 1', shortName: 'short 1' }, { id: '2', name: 'name 2', shortName: undefined }]);
assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 1', shortName: 'short 1', useDefaultFlags: undefined }, { id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]);

const { content } = await testClient.read(testObject.resource);
assert.ok(content !== null);
Expand All @@ -169,10 +169,10 @@ suite('UserDataProfilesManifestSync', () => {
await testObject.sync(await testClient.getResourceManifest());
assert.strictEqual(testObject.status, SyncStatus.Idle);
assert.deepStrictEqual(testObject.conflicts.conflicts, []);
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 2', shortName: '2' }]);
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 2', shortName: '2', useDefaultFlags: undefined }]);

await client2.sync();
assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 2', shortName: '2' }]);
assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 2', shortName: '2', useDefaultFlags: undefined }]);

const { content } = await testClient.read(testObject.resource);
assert.ok(content !== null);
Expand All @@ -190,17 +190,33 @@ suite('UserDataProfilesManifestSync', () => {
await testObject.sync(await testClient.getResourceManifest());
assert.strictEqual(testObject.status, SyncStatus.Idle);
assert.deepStrictEqual(testObject.conflicts.conflicts, []);
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '2', name: 'name 2', shortName: undefined }]);
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]);

await client2.sync();
assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '2', name: 'name 2', shortName: undefined }]);
assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]);

const { content } = await testClient.read(testObject.resource);
assert.ok(content !== null);
const actual = parseRemoteProfiles(content!);
assert.deepStrictEqual(actual, [{ id: '2', name: 'name 2', collection: '2' }]);
});

test('sync profile that uses default profile', async () => {
await client2.instantiationService.get(IUserDataProfilesService).createProfile('1', 'name 1', { useDefaultFlags: { keybindings: true } });
await client2.sync();

await testObject.sync(await testClient.getResourceManifest());
assert.strictEqual(testObject.status, SyncStatus.Idle);
assert.deepStrictEqual(testObject.conflicts.conflicts, []);

const { content } = await testClient.read(testObject.resource);
assert.ok(content !== null);
const actual = parseRemoteProfiles(content!);
assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]);

assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]);
});

function parseRemoteProfiles(content: string): ISyncUserDataProfile[] {
const syncData: ISyncData = JSON.parse(content);
return JSON.parse(syncData.content);
Expand All @@ -209,7 +225,7 @@ suite('UserDataProfilesManifestSync', () => {
function getLocalProfiles(client: UserDataSyncClient): { id: string; name: string; shortName?: string }[] {
return client.instantiationService.get(IUserDataProfilesService).profiles
.slice(1).sort((a, b) => a.name.localeCompare(b.name))
.map(profile => ({ id: profile.id, name: profile.name, shortName: profile.shortName }));
.map(profile => ({ id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags }));
}


Expand Down