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

Enhance IUserDataProfilesService to support settings sync scenarios #160816

Merged
merged 1 commit into from Sep 13, 2022
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
37 changes: 21 additions & 16 deletions src/vs/platform/userDataProfile/common/userDataProfile.ts
Expand Up @@ -6,7 +6,7 @@
import { hash } from 'vs/base/common/hash';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { joinPath } from 'vs/base/common/resources';
import { basename, joinPath } from 'vs/base/common/resources';
import { isUndefined } from 'vs/base/common/types';
import { URI, UriDto } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
Expand Down Expand Up @@ -95,9 +95,10 @@ export interface IUserDataProfilesService {

readonly onDidResetWorkspaces: Event<void>;

createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
createNamedProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
createTransientProfile(workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile>;
createProfile(id: string, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): Promise<IUserDataProfile>;
removeProfile(profile: IUserDataProfile): Promise<void>;

setProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profile: IUserDataProfile): Promise<void>;
Expand Down Expand Up @@ -126,10 +127,10 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)

export const EXTENSIONS_RESOURCE_NAME = 'extensions.json';

export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): IUserDataProfile {
export function toUserDataProfile(id: string, name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): IUserDataProfile {
return {
id: hash(location.path).toString(16),
name: name,
id,
name,
location: location,
isDefault: false,
globalStorageHome: joinPath(location, 'globalStorage'),
Expand Down Expand Up @@ -213,10 +214,10 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
protected _profilesObject: UserDataProfilesObject | undefined;
protected get profilesObject(): UserDataProfilesObject {
if (!this._profilesObject) {
const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : [];
const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : [];
let emptyWindow: IUserDataProfile | undefined;
const workspaces = new ResourceMap<IUserDataProfile>();
const defaultProfile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
const defaultProfile = toUserDataProfile(hash(this.environmentService.userRoamingDataHome.path).toString(16), localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
profiles.unshift({ ...defaultProfile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 || this.transientProfilesObject.profiles.length > 0 ? defaultProfile.extensionsResource : undefined });
if (profiles.length) {
const profileAssicaitions = this.getStoredProfileAssociations();
Expand Down Expand Up @@ -250,15 +251,19 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
nameIndex = index > nameIndex ? index : nameIndex;
}
const name = `${namePrefix} ${nameIndex + 1}`;
return this.createProfile(name, undefined, workspaceIdentifier, true);
return this.createProfile(hash(generateUuid()).toString(16), name, undefined, true, workspaceIdentifier);
}

async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier, transient?: boolean): Promise<IUserDataProfile> {
async createNamedProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
return this.createProfile(hash(generateUuid()).toString(16), name, useDefaultFlags, false, workspaceIdentifier);
}

async createProfile(id: string, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}

const profile = await this.doCreateProfile(name, useDefaultFlags, transient);
const profile = await this.doCreateProfile(id, name, useDefaultFlags, !!transient);

if (workspaceIdentifier) {
await this.setProfileForWorkspace(workspaceIdentifier, profile);
Expand All @@ -267,17 +272,17 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return profile;
}

private async doCreateProfile(name: string, useDefaultFlags: UseDefaultProfileFlags | undefined, transient?: boolean): Promise<IUserDataProfile> {
private async doCreateProfile(id: string, name: string, useDefaultFlags: UseDefaultProfileFlags | undefined, transient: boolean): Promise<IUserDataProfile> {
let profileCreationPromise = this.profileCreationPromises.get(name);
if (!profileCreationPromise) {
profileCreationPromise = (async () => {
try {
const existing = this.profiles.find(p => p.name === name);
const existing = this.profiles.find(p => p.name === name || p.id === id);
if (existing) {
return existing;
}

const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(generateUuid()).toString(16)), useDefaultFlags, transient);
const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), useDefaultFlags, transient);
await this.fileService.createFolder(profile.location);

const joiners: Promise<void>[] = [];
Expand All @@ -300,7 +305,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return profileCreationPromise;
}

async updateProfile(profileToUpdate: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile> {
async updateProfile(profileToUpdate: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
Expand All @@ -310,7 +315,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
throw new Error(`Profile '${profileToUpdate.name}' does not exist`);
}

profile = toUserDataProfile(name, profile.location, useDefaultFlags);
profile = toUserDataProfile(profile.id, name, profile.location, useDefaultFlags, transient ?? profile.isTransient);
this.updateProfiles([], [], [profile]);

return profile;
Expand Down
Expand Up @@ -58,7 +58,7 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
}
if (args.profile) {
const profile = this.profiles.find(p => p.name === args.profile);
return profile ? Promise.resolve(profile) : this.createProfile(args.profile);
return profile ? Promise.resolve(profile) : this.createNamedProfile(args.profile);
}
if (args['profile-temp']) {
return this.createTransientProfile();
Expand Down
Expand Up @@ -48,8 +48,13 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa
this.onDidResetWorkspaces = this.channel.listen<void>('onDidResetWorkspaces');
}

async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [name, useDefaultFlags, workspaceIdentifier]);
async createNamedProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createNamedProfile', [name, useDefaultFlags, workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}

async createProfile(id: string, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [id, name, useDefaultFlags, transient, workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}

Expand All @@ -66,8 +71,8 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa
return this.channel.call('removeProfile', [profile]);
}

async updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('updateProfile', [profile, name, useDefaultFlags]);
async updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('updateProfile', [profile, name, useDefaultFlags, transient]);
return reviveProfile(result, this.profilesHome.scheme);
}

Expand Down
Expand Up @@ -74,12 +74,32 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});

test('create profile with id', async () => {
const profile = await testObject.createProfile('id', 'name');
assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(profile.id, 'id');
assert.deepStrictEqual(profile.name, 'name');
assert.deepStrictEqual(!!profile.isTransient, false);
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
assert.deepStrictEqual(testObject.profiles[1].name, profile.name);
});

test('create profile with id, name and transient', async () => {
const profile = await testObject.createProfile('id', 'name', undefined, true);
assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(profile.id, 'id');
assert.deepStrictEqual(profile.name, 'name');
assert.deepStrictEqual(!!profile.isTransient, true);
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
});

test('create transient profiles', async () => {
const profile1 = await testObject.createTransientProfile();
const profile2 = await testObject.createTransientProfile();
const profile3 = await testObject.createTransientProfile();
const profile4 = await testObject.createProfile('id', 'name', undefined, true);

assert.deepStrictEqual(testObject.profiles.length, 4);
assert.deepStrictEqual(testObject.profiles.length, 5);
assert.deepStrictEqual(profile1.name, 'Temp 1');
assert.deepStrictEqual(profile1.isTransient, true);
assert.deepStrictEqual(testObject.profiles[1].id, profile1.id);
Expand All @@ -89,10 +109,13 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(profile3.name, 'Temp 3');
assert.deepStrictEqual(profile3.isTransient, true);
assert.deepStrictEqual(testObject.profiles[3].id, profile3.id);
assert.deepStrictEqual(profile4.name, 'name');
assert.deepStrictEqual(profile4.isTransient, true);
assert.deepStrictEqual(testObject.profiles[4].id, profile4.id);
});

test('create transient profile when a normal profile with Temp is already created', async () => {
await testObject.createProfile('Temp 1');
await testObject.createNamedProfile('Temp 1');
const profile1 = await testObject.createTransientProfile();

assert.deepStrictEqual(profile1.name, 'Temp 2');
Expand All @@ -116,4 +139,44 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});

test('update named profile', async () => {
const profile = await testObject.createNamedProfile('name');
await testObject.updateProfile(profile, 'name changed');

assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(testObject.profiles[1].name, 'name changed');
assert.deepStrictEqual(!!testObject.profiles[1].isTransient, false);
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
});

test('persist transient profile', async () => {
const profile = await testObject.createTransientProfile();
await testObject.updateProfile(profile, 'saved', undefined, false);

assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(testObject.profiles[1].name, 'saved');
assert.deepStrictEqual(!!testObject.profiles[1].isTransient, false);
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
});

test('persist transient profile (2)', async () => {
const profile = await testObject.createProfile('id', 'name', undefined, true);
await testObject.updateProfile(profile, 'saved', undefined, false);

assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(testObject.profiles[1].name, 'saved');
assert.deepStrictEqual(!!testObject.profiles[1].isTransient, false);
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
});

test('save transient profile', async () => {
const profile = await testObject.createTransientProfile();
await testObject.updateProfile(profile, 'saved');

assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(testObject.profiles[1].name, 'saved');
assert.deepStrictEqual(!!testObject.profiles[1].isTransient, true);
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
});

});
Expand Up @@ -61,13 +61,13 @@ suite('UserDataProfileMainService', () => {
});

test('default profile when there are profiles', async () => {
await testObject.createProfile('test');
await testObject.createNamedProfile('test');
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource?.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString());
});

test('default profile when profiles are removed', async () => {
const profile = await testObject.createProfile('test');
const profile = await testObject.createNamedProfile('test');
await testObject.removeProfile(profile);
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
Expand Down
Expand Up @@ -1435,7 +1435,7 @@ suite('WorkspaceConfigurationService - Profiles', () => {
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService));
userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', 'custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService));
workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, testObject);
Expand Down Expand Up @@ -1531,7 +1531,7 @@ suite('WorkspaceConfigurationService - Profiles', () => {
await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue", "configurationService.profiles.testSetting": "profileValue" }'));
await testObject.reloadConfiguration();

const profile = toUserDataProfile('custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'));
const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'));
await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue2", "configurationService.profiles.testSetting": "profileValue2" }'));
const promise = Event.toPromise(testObject.onDidChangeConfiguration);
await userDataProfileService.updateCurrentProfile(profile, false);
Expand Down
Expand Up @@ -53,7 +53,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse
}

async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise<IUserDataProfile> {
const profile = await this.userDataProfilesService.createProfile(name, useDefaultFlags, this.getWorkspaceIdentifier());
const profile = await this.userDataProfilesService.createNamedProfile(name, useDefaultFlags, this.getWorkspaceIdentifier());
await this.enterProfile(profile, !!fromExisting);
return profile;
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/test/browser/workbenchTestServices.ts
Expand Up @@ -2010,7 +2010,7 @@ export class TestUserDataProfileService implements IUserDataProfileService {
readonly _serviceBrand: undefined;
readonly onDidUpdateCurrentProfile = Event.None;
readonly onDidChangeCurrentProfile = Event.None;
readonly currentProfile = toUserDataProfile('test', URI.file('tests').with({ scheme: 'vscode-tests' }));
readonly currentProfile = toUserDataProfile('test', 'test', URI.file('tests').with({ scheme: 'vscode-tests' }));
async updateCurrentProfile(): Promise<void> { }
}

Expand Down