@@ -14,7 +14,11 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
14
14
import { IFileService } from 'vs/platform/files/common/files' ;
15
15
import { createDecorator } from 'vs/platform/instantiation/common/instantiation' ;
16
16
import { ILogService } from 'vs/platform/log/common/log' ;
17
- import { ISingleFolderWorkspaceIdentifier , IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace' ;
17
+ import { ISingleFolderWorkspaceIdentifier , isSingleFolderWorkspaceIdentifier , isWorkspaceIdentifier , IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace' ;
18
+ import { ResourceMap } from 'vs/base/common/map' ;
19
+ import { IStringDictionary } from 'vs/base/common/collections' ;
20
+ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' ;
21
+ import { Promises } from 'vs/base/common/async' ;
18
22
19
23
/**
20
24
* Flags to indicate whether to use the default profile or not.
@@ -66,6 +70,16 @@ export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceI
66
70
67
71
export type DidChangeProfilesEvent = { readonly added : IUserDataProfile [ ] ; readonly removed : IUserDataProfile [ ] ; readonly all : IUserDataProfile [ ] } ;
68
72
73
+ export type WillCreateProfileEvent = {
74
+ profile : IUserDataProfile ;
75
+ join ( promise : Promise < void > ) : void ;
76
+ } ;
77
+
78
+ export type WillRemoveProfileEvent = {
79
+ profile : IUserDataProfile ;
80
+ join ( promise : Promise < void > ) : void ;
81
+ } ;
82
+
69
83
export const IUserDataProfilesService = createDecorator < IUserDataProfilesService > ( 'IUserDataProfilesService' ) ;
70
84
export interface IUserDataProfilesService {
71
85
readonly _serviceBrand : undefined ;
@@ -115,34 +129,249 @@ export function toUserDataProfile(name: string, location: URI, useDefaultFlags?:
115
129
} ;
116
130
}
117
131
132
+ export type UserDataProfilesObject = {
133
+ profiles : IUserDataProfile [ ] ;
134
+ workspaces : ResourceMap < IUserDataProfile > ;
135
+ emptyWindow ?: IUserDataProfile ;
136
+ } ;
137
+
138
+ export type StoredUserDataProfile = {
139
+ name : string ;
140
+ location : URI ;
141
+ useDefaultFlags ?: UseDefaultProfileFlags ;
142
+ } ;
143
+
144
+ export type StoredProfileAssociations = {
145
+ workspaces ?: IStringDictionary < string > ;
146
+ emptyWindow ?: string ;
147
+ } ;
148
+
118
149
export class UserDataProfilesService extends Disposable implements IUserDataProfilesService {
150
+
151
+ protected static readonly PROFILES_KEY = 'userDataProfiles' ;
152
+ protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations' ;
153
+
119
154
readonly _serviceBrand : undefined ;
120
155
156
+ private enabled : boolean = false ;
157
+ protected readonly defaultProfileShouldIncludeExtensionsResourceAlways : boolean = false ;
121
158
readonly profilesHome : URI ;
122
159
123
160
get defaultProfile ( ) : IUserDataProfile { return this . profiles [ 0 ] ; }
124
- protected _profiles : IUserDataProfile [ ] = [ this . createDefaultUserDataProfile ( false ) ] ;
125
- get profiles ( ) : IUserDataProfile [ ] { return this . _profiles ; }
161
+ get profiles ( ) : IUserDataProfile [ ] { return this . profilesObject . profiles ; }
126
162
127
163
protected readonly _onDidChangeProfiles = this . _register ( new Emitter < DidChangeProfilesEvent > ( ) ) ;
128
164
readonly onDidChangeProfiles = this . _onDidChangeProfiles . event ;
129
165
166
+ protected readonly _onWillCreateProfile = this . _register ( new Emitter < WillCreateProfileEvent > ( ) ) ;
167
+ readonly onWillCreateProfile = this . _onWillCreateProfile . event ;
168
+
169
+ protected readonly _onWillRemoveProfile = this . _register ( new Emitter < WillRemoveProfileEvent > ( ) ) ;
170
+ readonly onWillRemoveProfile = this . _onWillRemoveProfile . event ;
171
+
130
172
constructor (
131
173
@IEnvironmentService protected readonly environmentService : IEnvironmentService ,
132
174
@IFileService protected readonly fileService : IFileService ,
175
+ @IUriIdentityService protected readonly uriIdentityService : IUriIdentityService ,
133
176
@ILogService protected readonly logService : ILogService
134
177
) {
135
178
super ( ) ;
136
179
this . profilesHome = joinPath ( this . environmentService . userRoamingDataHome , 'profiles' ) ;
137
180
}
138
181
139
- protected createDefaultUserDataProfile ( extensions : boolean ) : IUserDataProfile {
140
- const profile = toUserDataProfile ( localize ( 'defaultProfile' , "Default" ) , this . environmentService . userRoamingDataHome ) ;
141
- return { ...profile , isDefault : true , extensionsResource : extensions ? profile . extensionsResource : undefined } ;
182
+ setEnablement ( enabled : boolean ) : void {
183
+ if ( this . enabled !== enabled ) {
184
+ this . _profilesObject = undefined ;
185
+ this . enabled = enabled ;
186
+ }
187
+ }
188
+
189
+ protected _profilesObject : UserDataProfilesObject | undefined ;
190
+ protected get profilesObject ( ) : UserDataProfilesObject {
191
+ if ( ! this . _profilesObject ) {
192
+ const profiles = this . enabled ? this . getStoredProfiles ( ) . map < IUserDataProfile > ( storedProfile => toUserDataProfile ( storedProfile . name , storedProfile . location , storedProfile . useDefaultFlags ) ) : [ ] ;
193
+ let emptyWindow : IUserDataProfile | undefined ;
194
+ const workspaces = new ResourceMap < IUserDataProfile > ( ) ;
195
+ if ( profiles . length ) {
196
+ const profileAssicaitions = this . getStoredProfileAssociations ( ) ;
197
+ if ( profileAssicaitions . workspaces ) {
198
+ for ( const [ workspacePath , profilePath ] of Object . entries ( profileAssicaitions . workspaces ) ) {
199
+ const workspace = URI . parse ( workspacePath ) ;
200
+ const profileLocation = URI . parse ( profilePath ) ;
201
+ const profile = profiles . find ( p => this . uriIdentityService . extUri . isEqual ( p . location , profileLocation ) ) ;
202
+ if ( profile ) {
203
+ workspaces . set ( workspace , profile ) ;
204
+ }
205
+ }
206
+ }
207
+ if ( profileAssicaitions . emptyWindow ) {
208
+ const emptyWindowProfileLocation = URI . parse ( profileAssicaitions . emptyWindow ) ;
209
+ emptyWindow = profiles . find ( p => this . uriIdentityService . extUri . isEqual ( p . location , emptyWindowProfileLocation ) ) ;
210
+ }
211
+ }
212
+ const profile = toUserDataProfile ( localize ( 'defaultProfile' , "Default" ) , this . environmentService . userRoamingDataHome ) ;
213
+ profiles . unshift ( { ...profile , isDefault : true , extensionsResource : this . defaultProfileShouldIncludeExtensionsResourceAlways || profiles . length > 0 ? profile . extensionsResource : undefined } ) ;
214
+ this . _profilesObject = { profiles, workspaces, emptyWindow } ;
215
+ }
216
+ return this . _profilesObject ;
217
+ }
218
+
219
+ getProfile ( workspaceIdentifier : WorkspaceIdentifier ) : IUserDataProfile {
220
+ const workspace = this . getWorkspace ( workspaceIdentifier ) ;
221
+ const profile = URI . isUri ( workspace ) ? this . profilesObject . workspaces . get ( workspace ) : this . profilesObject . emptyWindow ;
222
+ return profile ?? this . defaultProfile ;
223
+ }
224
+
225
+ protected getWorkspace ( workspaceIdentifier : WorkspaceIdentifier ) : URI | EmptyWindowWorkspaceIdentifier {
226
+ if ( isSingleFolderWorkspaceIdentifier ( workspaceIdentifier ) ) {
227
+ return workspaceIdentifier . uri ;
228
+ }
229
+ if ( isWorkspaceIdentifier ( workspaceIdentifier ) ) {
230
+ return workspaceIdentifier . configPath ;
231
+ }
232
+ return 'empty-window' ;
233
+ }
234
+
235
+ async createProfile ( name : string , useDefaultFlags ?: UseDefaultProfileFlags , workspaceIdentifier ?: WorkspaceIdentifier ) : Promise < IUserDataProfile > {
236
+ if ( ! this . enabled ) {
237
+ throw new Error ( `Settings Profiles are disabled. Enable them via the '${ PROFILES_ENABLEMENT_CONFIG } ' setting.` ) ;
238
+ }
239
+ if ( this . getStoredProfiles ( ) . some ( p => p . name === name ) ) {
240
+ throw new Error ( `Profile with name ${ name } already exists` ) ;
241
+ }
242
+
243
+ const profile = toUserDataProfile ( name , joinPath ( this . profilesHome , hash ( name ) . toString ( 16 ) ) , useDefaultFlags ) ;
244
+ await this . fileService . createFolder ( profile . location ) ;
245
+
246
+ const joiners : Promise < void > [ ] = [ ] ;
247
+ this . _onWillCreateProfile . fire ( {
248
+ profile,
249
+ join ( promise ) {
250
+ joiners . push ( promise ) ;
251
+ }
252
+ } ) ;
253
+ await Promises . settled ( joiners ) ;
254
+
255
+ this . updateProfiles ( [ profile ] , [ ] ) ;
256
+
257
+ if ( workspaceIdentifier ) {
258
+ await this . setProfileForWorkspace ( profile , workspaceIdentifier ) ;
259
+ }
260
+
261
+ return profile ;
262
+ }
263
+
264
+ async setProfileForWorkspace ( profileToSet : IUserDataProfile , workspaceIdentifier : WorkspaceIdentifier ) : Promise < void > {
265
+ if ( ! this . enabled ) {
266
+ throw new Error ( `Settings Profiles are disabled. Enable them via the '${ PROFILES_ENABLEMENT_CONFIG } ' setting.` ) ;
267
+ }
268
+
269
+ const profile = this . profiles . find ( p => p . id === profileToSet . id ) ;
270
+ if ( ! profile ) {
271
+ throw new Error ( `Profile '${ profileToSet . name } ' does not exist` ) ;
272
+ }
273
+
274
+ this . updateWorkspaceAssociation ( workspaceIdentifier , profile ) ;
275
+ }
276
+
277
+ async unsetWorkspace ( workspaceIdentifier : WorkspaceIdentifier ) : Promise < void > {
278
+ if ( ! this . enabled ) {
279
+ throw new Error ( `Settings Profiles are disabled. Enable them via the '${ PROFILES_ENABLEMENT_CONFIG } ' setting.` ) ;
280
+ }
281
+ this . updateWorkspaceAssociation ( workspaceIdentifier ) ;
282
+ }
283
+
284
+ async removeProfile ( profileToRemove : IUserDataProfile ) : Promise < void > {
285
+ if ( ! this . enabled ) {
286
+ throw new Error ( `Settings Profiles are disabled. Enable them via the '${ PROFILES_ENABLEMENT_CONFIG } ' setting.` ) ;
287
+ }
288
+ if ( profileToRemove . isDefault ) {
289
+ throw new Error ( 'Cannot remove default profile' ) ;
290
+ }
291
+ const profile = this . profiles . find ( p => p . id === profileToRemove . id ) ;
292
+ if ( ! profile ) {
293
+ throw new Error ( `Profile '${ profileToRemove . name } ' does not exist` ) ;
294
+ }
295
+
296
+ const joiners : Promise < void > [ ] = [ ] ;
297
+ this . _onWillRemoveProfile . fire ( {
298
+ profile,
299
+ join ( promise ) {
300
+ joiners . push ( promise ) ;
301
+ }
302
+ } ) ;
303
+ await Promises . settled ( joiners ) ;
304
+
305
+ if ( profile . id === this . profilesObject . emptyWindow ?. id ) {
306
+ this . profilesObject . emptyWindow = undefined ;
307
+ }
308
+ for ( const workspace of [ ...this . profilesObject . workspaces . keys ( ) ] ) {
309
+ if ( profile . id === this . profilesObject . workspaces . get ( workspace ) ?. id ) {
310
+ this . profilesObject . workspaces . delete ( workspace ) ;
311
+ }
312
+ }
313
+ this . updateStoredProfileAssociations ( ) ;
314
+
315
+ this . updateProfiles ( [ ] , [ profile ] ) ;
316
+
317
+ try {
318
+ if ( this . profiles . length === 1 ) {
319
+ await this . fileService . del ( this . profilesHome , { recursive : true } ) ;
320
+ } else {
321
+ await this . fileService . del ( profile . location , { recursive : true } ) ;
322
+ }
323
+ } catch ( error ) {
324
+ this . logService . error ( error ) ;
325
+ }
326
+ }
327
+
328
+ private updateProfiles ( added : IUserDataProfile [ ] , removed : IUserDataProfile [ ] ) {
329
+ const storedProfiles : StoredUserDataProfile [ ] = [ ] ;
330
+ for ( const profile of [ ...this . profilesObject . profiles , ...added ] ) {
331
+ if ( profile . isDefault ) {
332
+ continue ;
333
+ }
334
+ if ( removed . some ( p => profile . id === p . id ) ) {
335
+ continue ;
336
+ }
337
+ storedProfiles . push ( { location : profile . location , name : profile . name , useDefaultFlags : profile . useDefaultFlags } ) ;
338
+ }
339
+ this . saveStoredProfiles ( storedProfiles ) ;
340
+ this . _profilesObject = undefined ;
341
+ this . _onDidChangeProfiles . fire ( { added, removed, all : this . profiles } ) ;
342
+ }
343
+
344
+ private updateWorkspaceAssociation ( workspaceIdentifier : WorkspaceIdentifier , newProfile ?: IUserDataProfile ) {
345
+ const workspace = this . getWorkspace ( workspaceIdentifier ) ;
346
+
347
+ // Folder or Multiroot workspace
348
+ if ( URI . isUri ( workspace ) ) {
349
+ this . profilesObject . workspaces . delete ( workspace ) ;
350
+ if ( newProfile && ! newProfile . isDefault ) {
351
+ this . profilesObject . workspaces . set ( workspace , newProfile ) ;
352
+ }
353
+ }
354
+ // Empty Window
355
+ else {
356
+ this . profilesObject . emptyWindow = ! newProfile ?. isDefault ? newProfile : undefined ;
357
+ }
358
+
359
+ this . updateStoredProfileAssociations ( ) ;
360
+ }
361
+
362
+ private updateStoredProfileAssociations ( ) {
363
+ const workspaces : IStringDictionary < string > = { } ;
364
+ for ( const [ workspace , profile ] of this . profilesObject . workspaces . entries ( ) ) {
365
+ workspaces [ workspace . toString ( ) ] = profile . location . toString ( ) ;
366
+ }
367
+ const emptyWindow = this . profilesObject . emptyWindow ?. location . toString ( ) ;
368
+ this . saveStoredProfileAssociations ( { workspaces, emptyWindow } ) ;
369
+ this . _profilesObject = undefined ;
142
370
}
143
371
144
- createProfile ( name : string , useDefaultFlags ?: UseDefaultProfileFlags , workspaceIdentifier ?: WorkspaceIdentifier ) : Promise < IUserDataProfile > { throw new Error ( 'Not implemented' ) ; }
145
- setProfileForWorkspace ( profile : IUserDataProfile , workspaceIdentifier : WorkspaceIdentifier ) : Promise < void > { throw new Error ( 'Not implemented' ) ; }
146
- getProfile ( workspaceIdentifier : WorkspaceIdentifier ) : IUserDataProfile { throw new Error ( 'Not implemented' ) ; }
147
- removeProfile ( profile : IUserDataProfile ) : Promise < void > { throw new Error ( 'Not implemented' ) ; }
372
+ protected getStoredProfiles ( ) : StoredUserDataProfile [ ] { return [ ] ; }
373
+ protected saveStoredProfiles ( storedProfiles : StoredUserDataProfile [ ] ) : void { throw new Error ( 'not implemented' ) ; }
374
+
375
+ protected getStoredProfileAssociations ( ) : StoredProfileAssociations { return { } ; }
376
+ protected saveStoredProfileAssociations ( storedProfileAssociations : StoredProfileAssociations ) : void { throw new Error ( 'not implemented' ) ; }
148
377
}
0 commit comments