Skip to content

Commit d5c50f1

Browse files
authored
Support creating partial profile and use default profile for other data (microsoft#187607)
microsoft#156144 - First cut Support creating partial profile and use default profile for other data
1 parent 075f770 commit d5c50f1

25 files changed

+640
-256
lines changed

src/vs/platform/storage/common/storage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
659659
}
660660

661661
export function isProfileUsingDefaultStorage(profile: IUserDataProfile): boolean {
662-
return profile.isDefault || !!profile.useDefaultFlags?.uiState;
662+
return profile.isDefault || !!profile.useDefaultFlags?.globalState;
663663
}
664664

665665
export class InMemoryStorageService extends AbstractStorageService {

src/vs/platform/userDataProfile/common/userDataProfile.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,19 @@ import { generateUuid } from 'vs/base/common/uuid';
2222
import { escapeRegExpCharacters } from 'vs/base/common/strings';
2323
import { isString } from 'vs/base/common/types';
2424

25+
export const enum ProfileResourceType {
26+
Settings = 'settings',
27+
Keybindings = 'keybindings',
28+
Snippets = 'snippets',
29+
Tasks = 'tasks',
30+
Extensions = 'extensions',
31+
GlobalState = 'globalState',
32+
}
33+
2534
/**
2635
* Flags to indicate whether to use the default profile or not.
2736
*/
28-
export type UseDefaultProfileFlags = {
29-
settings?: boolean;
30-
keybindings?: boolean;
31-
tasks?: boolean;
32-
snippets?: boolean;
33-
extensions?: boolean;
34-
uiState?: boolean;
35-
};
37+
export type UseDefaultProfileFlags = { [key in ProfileResourceType]?: boolean };
3638

3739
export interface IUserDataProfile {
3840
readonly id: string;
@@ -135,19 +137,19 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)
135137
};
136138
}
137139

138-
export function toUserDataProfile(id: string, name: string, location: URI, profilesCacheHome: URI, options?: IUserDataProfileOptions): IUserDataProfile {
140+
export function toUserDataProfile(id: string, name: string, location: URI, profilesCacheHome: URI, options?: IUserDataProfileOptions, defaultProfile?: IUserDataProfile): IUserDataProfile {
139141
return {
140142
id,
141143
name,
142144
location,
143145
isDefault: false,
144146
shortName: options?.shortName,
145-
globalStorageHome: joinPath(location, 'globalStorage'),
146-
settingsResource: joinPath(location, 'settings.json'),
147-
keybindingsResource: joinPath(location, 'keybindings.json'),
148-
tasksResource: joinPath(location, 'tasks.json'),
149-
snippetsHome: joinPath(location, 'snippets'),
150-
extensionsResource: joinPath(location, 'extensions.json'),
147+
globalStorageHome: defaultProfile && options?.useDefaultFlags?.globalState ? defaultProfile.globalStorageHome : joinPath(location, 'globalStorage'),
148+
settingsResource: defaultProfile && options?.useDefaultFlags?.settings ? defaultProfile.settingsResource : joinPath(location, 'settings.json'),
149+
keybindingsResource: defaultProfile && options?.useDefaultFlags?.keybindings ? defaultProfile.keybindingsResource : joinPath(location, 'keybindings.json'),
150+
tasksResource: defaultProfile && options?.useDefaultFlags?.tasks ? defaultProfile.tasksResource : joinPath(location, 'tasks.json'),
151+
snippetsHome: defaultProfile && options?.useDefaultFlags?.snippets ? defaultProfile.snippetsHome : joinPath(location, 'snippets'),
152+
extensionsResource: defaultProfile && options?.useDefaultFlags?.extensions ? defaultProfile.extensionsResource : joinPath(location, 'extensions.json'),
151153
cacheHome: joinPath(profilesCacheHome, id),
152154
useDefaultFlags: options?.useDefaultFlags,
153155
isTransient: options?.transient
@@ -235,24 +237,23 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
235237
protected _profilesObject: UserDataProfilesObject | undefined;
236238
protected get profilesObject(): UserDataProfilesObject {
237239
if (!this._profilesObject) {
238-
const profiles = [];
240+
const defaultProfile = this.createDefaultProfile();
241+
const profiles = [defaultProfile];
239242
if (this.enabled) {
240243
try {
241244
for (const storedProfile of this.getStoredProfiles()) {
242245
if (!storedProfile.name || !isString(storedProfile.name) || !storedProfile.location) {
243246
this.logService.warn('Skipping the invalid stored profile', storedProfile.location || storedProfile.name);
244247
continue;
245248
}
246-
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }));
249+
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }, defaultProfile));
247250
}
248251
} catch (error) {
249252
this.logService.error(error);
250253
}
251254
}
252255
const workspaces = new ResourceMap<IUserDataProfile>();
253256
const emptyWindows = new Map<string, IUserDataProfile>();
254-
const defaultProfile = this.createDefaultProfile();
255-
profiles.unshift({ ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true });
256257
if (profiles.length) {
257258
try {
258259
const profileAssociaitions = this.getStoredProfileAssociations();
@@ -283,7 +284,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
283284
}
284285

285286
private createDefaultProfile() {
286-
return toUserDataProfile('__default__profile__', localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome, this.profilesCacheHome);
287+
const defaultProfile = toUserDataProfile('__default__profile__', localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome, this.profilesCacheHome);
288+
return { ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true };
287289
}
288290

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

333-
const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options);
335+
const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options, this.defaultProfile);
334336
await this.fileService.createFolder(profile.location);
335337

336338
const joiners: Promise<void>[] = [];

src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,52 @@ suite('UserDataProfileService (Common)', () => {
178178
assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
179179
});
180180

181+
test('profile using default profile for settings', async () => {
182+
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { settings: true } });
183+
184+
assert.strictEqual(profile.isDefault, false);
185+
assert.deepStrictEqual(profile.useDefaultFlags, { settings: true });
186+
assert.strictEqual(profile.settingsResource.toString(), testObject.defaultProfile.settingsResource.toString());
187+
});
188+
189+
test('profile using default profile for keybindings', async () => {
190+
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { keybindings: true } });
191+
192+
assert.strictEqual(profile.isDefault, false);
193+
assert.deepStrictEqual(profile.useDefaultFlags, { keybindings: true });
194+
assert.strictEqual(profile.keybindingsResource.toString(), testObject.defaultProfile.keybindingsResource.toString());
195+
});
196+
197+
test('profile using default profile for snippets', async () => {
198+
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { snippets: true } });
199+
200+
assert.strictEqual(profile.isDefault, false);
201+
assert.deepStrictEqual(profile.useDefaultFlags, { snippets: true });
202+
assert.strictEqual(profile.snippetsHome.toString(), testObject.defaultProfile.snippetsHome.toString());
203+
});
204+
205+
test('profile using default profile for tasks', async () => {
206+
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { tasks: true } });
207+
208+
assert.strictEqual(profile.isDefault, false);
209+
assert.deepStrictEqual(profile.useDefaultFlags, { tasks: true });
210+
assert.strictEqual(profile.tasksResource.toString(), testObject.defaultProfile.tasksResource.toString());
211+
});
212+
213+
test('profile using default profile for global state', async () => {
214+
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { globalState: true } });
215+
216+
assert.strictEqual(profile.isDefault, false);
217+
assert.deepStrictEqual(profile.useDefaultFlags, { globalState: true });
218+
assert.strictEqual(profile.globalStorageHome.toString(), testObject.defaultProfile.globalStorageHome.toString());
219+
});
220+
221+
test('profile using default profile for extensions', async () => {
222+
const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { extensions: true } });
223+
224+
assert.strictEqual(profile.isDefault, false);
225+
assert.deepStrictEqual(profile.useDefaultFlags, { extensions: true });
226+
assert.strictEqual(profile.extensionsResource.toString(), testObject.defaultProfile.extensionsResource.toString());
227+
});
228+
181229
});

src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface IUserDataProfilesManifestResourcePreview extends IResourcePreview {
2929

3030
export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
3131

32-
protected readonly version: number = 1;
32+
protected readonly version: number = 2;
3333
readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'profiles.json');
3434
readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
3535
readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
@@ -190,7 +190,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
190190
for (const profile of local.added) {
191191
promises.push((async () => {
192192
this.logService.trace(`${this.syncResourceLogLabel}: Creating '${profile.name}' profile...`);
193-
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName });
193+
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
194194
this.logService.info(`${this.syncResourceLogLabel}: Created profile '${profile.name}'.`);
195195
})());
196196
}
@@ -224,7 +224,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
224224
for (const profile of remote?.added || []) {
225225
const collection = await this.userDataSyncStoreService.createCollection(this.syncHeaders);
226226
addedCollections.push(collection);
227-
remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName });
227+
remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
228228
}
229229
} else {
230230
this.logService.info(`${this.syncResourceLogLabel}: Could not create remote profiles as there are too many profiles.`);
@@ -235,7 +235,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
235235
for (const profile of remote?.updated || []) {
236236
const profileToBeUpdated = remoteProfiles.find(({ id }) => profile.id === id);
237237
if (profileToBeUpdated) {
238-
remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { id: profile.id, name: profile.name, collection: profileToBeUpdated.collection, shortName: profile.shortName });
238+
remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { ...profileToBeUpdated, id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
239239
}
240240
}
241241

src/vs/platform/userDataSync/common/userDataSync.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
2121
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
2222
import { ILogService } from 'vs/platform/log/common/log';
2323
import { Registry } from 'vs/platform/registry/common/platform';
24-
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
24+
import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile';
2525

2626
export function getDisallowedIgnoredSettings(): string[] {
2727
const allSettings = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
@@ -325,6 +325,7 @@ export interface ISyncUserDataProfile {
325325
readonly collection: string;
326326
readonly name: string;
327327
readonly shortName?: string;
328+
readonly useDefaultFlags?: UseDefaultProfileFlags;
328329
}
329330

330331
export type ISyncExtension = ILocalSyncExtension | IRemoteSyncExtension;

src/vs/platform/userDataSync/common/userDataSyncService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,10 @@ class ProfileSynchronizer extends Disposable {
622622
if (syncResource === SyncResource.WorkspaceState) {
623623
return;
624624
}
625+
if (syncResource !== SyncResource.Profiles && this.profile.useDefaultFlags?.[syncResource]) {
626+
this.logService.debug(`Skipping syncing ${syncResource} in ${this.profile.name} because it is already synced by default profile`);
627+
return;
628+
}
625629
const disposables = new DisposableStore();
626630
const synchronizer = disposables.add(this.createSynchronizer(syncResource));
627631
disposables.add(synchronizer.onDidChangeStatus(() => this.updateStatus()));

src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ suite('UserDataProfilesManifestSync', () => {
100100
assert.deepStrictEqual(testObject.conflicts.conflicts, []);
101101

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

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

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

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

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

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

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

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

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

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

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

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

204+
test('sync profile that uses default profile', async () => {
205+
await client2.instantiationService.get(IUserDataProfilesService).createProfile('1', 'name 1', { useDefaultFlags: { keybindings: true } });
206+
await client2.sync();
207+
208+
await testObject.sync(await testClient.getResourceManifest());
209+
assert.strictEqual(testObject.status, SyncStatus.Idle);
210+
assert.deepStrictEqual(testObject.conflicts.conflicts, []);
211+
212+
const { content } = await testClient.read(testObject.resource);
213+
assert.ok(content !== null);
214+
const actual = parseRemoteProfiles(content!);
215+
assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]);
216+
217+
assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]);
218+
});
219+
204220
function parseRemoteProfiles(content: string): ISyncUserDataProfile[] {
205221
const syncData: ISyncData = JSON.parse(content);
206222
return JSON.parse(syncData.content);
@@ -209,7 +225,7 @@ suite('UserDataProfilesManifestSync', () => {
209225
function getLocalProfiles(client: UserDataSyncClient): { id: string; name: string; shortName?: string }[] {
210226
return client.instantiationService.get(IUserDataProfilesService).profiles
211227
.slice(1).sort((a, b) => a.name.localeCompare(b.name))
212-
.map(profile => ({ id: profile.id, name: profile.name, shortName: profile.shortName }));
228+
.map(profile => ({ id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags }));
213229
}
214230

215231

0 commit comments

Comments
 (0)