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

CLI: support associating a profile to a worksapce #158275

Merged
merged 9 commits into from
Aug 18, 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
1 change: 1 addition & 0 deletions src/vs/platform/environment/common/argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface NativeParsedArgs {
'__enable-file-policy'?: boolean;
editSessionId?: string;
'locate-shell-integration-path'?: string;
'profile'?: string;

// chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches
'no-proxy-server'?: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/environment/node/argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'waitMarkerFilePath': { type: 'string' },
'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") },
'profile': { type: 'string', 'cat': 'o', args: 'settingsProfileName', description: localize('settingsProfileName', "Opens the provided folder or workspace with the given profile. If the profile does not exist, a new empty one is created.") },
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },

'extensions-dir': { type: 'string', deprecates: ['extensionHomePath'], cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
Expand Down
57 changes: 41 additions & 16 deletions src/vs/platform/userDataProfile/common/userDataProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
private readonly _onDidResetWorkspaces = this._register(new Emitter<void>());
readonly onDidResetWorkspaces = this._onDidResetWorkspaces.event;

private profileCreationPromises = new Map<string, Promise<IUserDataProfile>>();

constructor(
@IEnvironmentService protected readonly environmentService: IEnvironmentService,
@IFileService protected readonly fileService: IFileService,
Expand Down Expand Up @@ -224,6 +226,11 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return this._profilesObject;
}

async reload(): Promise<IUserDataProfile[]> {
this._profilesObject = undefined;
return this.profiles;
}

getProfile(workspaceIdentifier: WorkspaceIdentifier, profileToUseIfNotSet: IUserDataProfile): IUserDataProfile {
if (!this.enabled) {
return this.defaultProfile;
Expand Down Expand Up @@ -256,23 +263,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
if (this.getStoredProfiles().some(p => p.name === name)) {
throw new Error(`Profile with name ${name} already exists`);
}

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

const joiners: Promise<void>[] = [];
this._onWillCreateProfile.fire({
profile,
join(promise) {
joiners.push(promise);
}
});
await Promises.settled(joiners);

this.updateProfiles([profile], [], []);
const profile = await this.doCreateProfile(name, useDefaultFlags);

if (workspaceIdentifier) {
await this.setProfileForWorkspace(profile, workspaceIdentifier);
Expand All @@ -281,6 +273,39 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return profile;
}

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

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

const joiners: Promise<void>[] = [];
this._onWillCreateProfile.fire({
profile,
join(promise) {
joiners.push(promise);
}
});
await Promises.settled(joiners);

this.updateProfiles([profile], [], []);
return profile;
} finally {
this.profileCreationPromises.delete(name);
}
})();
this.profileCreationPromises.set(name, profileCreationPromise);
}
return profileCreationPromise;
}

async updateProfile(profileToUpdate: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instant
import { ILogService } from 'vs/platform/log/common/log';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IUserDataProfilesService, WorkspaceIdentifier, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUserDataProfilesService, WorkspaceIdentifier, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent, IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile';
import { IStringDictionary } from 'vs/base/common/collections';

export const IUserDataProfilesMainService = refineServiceDecorator<IUserDataProfilesService, IUserDataProfilesMainService>(IUserDataProfilesService);
export interface IUserDataProfilesMainService extends IUserDataProfilesService {
isEnabled(): boolean;
unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void>;
reload(): Promise<IUserDataProfile[]>;
readonly onWillCreateProfile: Event<WillCreateProfileEvent>;
readonly onWillRemoveProfile: Event<WillRemoveProfileEvent>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa
return this.channel.call('resetWorkspaces');
}

async reload(): Promise<void> {
const all = await this.channel.call<UriDto<IUserDataProfile>[]>('reload');
this._profiles = all.map(profile => reviveProfile(profile, this.profilesHome.scheme));
}

getProfile(workspaceIdentifier: WorkspaceIdentifier, profileToUseIfNotSet: IUserDataProfile): IUserDataProfile { throw new Error('Not implemented'); }
}

14 changes: 13 additions & 1 deletion src/vs/platform/window/common/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,18 @@ export interface IOSConfiguration {
readonly hostname: string;
}

export interface IUserDataProfileInfo {
readonly name?: string;
}

export function isUserDataProfileInfo(thing: unknown): thing is IUserDataProfileInfo {
const candidate = thing as IUserDataProfileInfo | undefined;

return !!(candidate && typeof candidate === 'object'
&& typeof candidate.name === 'string'
);
}

export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs, ISandboxConfiguration {
mainPid: number;

Expand All @@ -283,7 +295,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native

profiles: {
all: readonly UriDto<IUserDataProfile>[];
current: UriDto<IUserDataProfile>;
workspace: UriDto<IUserDataProfile> | IUserDataProfileInfo;
};

homeDir: string;
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/windows/electron-main/windowImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {

get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this._config?.workspace; }

get profile(): IUserDataProfile | undefined { return this.config ? this.userDataProfilesService.getProfile(this.config.workspace ?? 'empty-window', revive(this.config.profiles.current)) : undefined; }
get profile(): IUserDataProfile | undefined { return this.config ? this.userDataProfilesService.getProfile(this.config.workspace ?? 'empty-window', revive(this.config.profiles.workspace)) : undefined; }

get remoteAuthority(): string | undefined { return this._config?.remoteAuthority; }

Expand Down Expand Up @@ -961,7 +961,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
configuration.policiesData = this.policyService.serialize(); // set policies data again
configuration.profiles = {
all: this.userDataProfilesService.profiles,
current: this.profile || this.userDataProfilesService.defaultProfile,
workspace: this.profile || this.userDataProfilesService.defaultProfile
};

// Load config
Expand Down
23 changes: 20 additions & 3 deletions src/vs/platform/windows/electron-main/windowsMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { IAddFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings } from 'vs/platform/window/common/window';
import { IAddFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings, IUserDataProfileInfo } from 'vs/platform/window/common/window';
import { CodeWindow } from 'vs/platform/windows/electron-main/windowImpl';
import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { findWindowOnExtensionDevelopmentPath, findWindowOnFile, findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder';
Expand Down Expand Up @@ -75,6 +75,8 @@ interface IOpenBrowserWindowOptions {
readonly windowToUse?: ICodeWindow;

readonly emptyWindowBackupInfo?: IEmptyWindowBackupInfo;

readonly userDataProfileInfo?: IUserDataProfileInfo;
}

interface IPathResolveOptions {
Expand Down Expand Up @@ -151,6 +153,11 @@ interface IPathToOpen<T = IEditorOptions> extends IPath<T> {
* Optional label for the recent history
*/
label?: string;

/**
* Info of the profile to use
*/
userDataProfileInfo?: IUserDataProfileInfo;
}

interface IWorkspacePathToOpen extends IPathToOpen {
Expand Down Expand Up @@ -692,7 +699,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
forceNewWindow,
forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
filesToOpen,
windowToUse
windowToUse,
userDataProfileInfo: folderOrWorkspace.userDataProfileInfo
});
}

Expand Down Expand Up @@ -847,6 +855,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
pathsToOpen.push(path);
}
}

// Apply profile if any
const profileName = cli['profile'];
if (profileName) {
for (const path of pathsToOpen) {
path.userDataProfileInfo = { name: profileName };
}
}

return pathsToOpen;
}

Expand Down Expand Up @@ -1326,7 +1343,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic

profiles: {
all: this.userDataProfilesService.profiles,
current: this.userDataProfilesService.getProfile(options.workspace ?? 'empty-window', (options.windowToUse ?? this.getLastActiveWindow())?.profile ?? this.userDataProfilesService.defaultProfile),
workspace: options.userDataProfileInfo ?? this.userDataProfilesService.getProfile(options.workspace ?? 'empty-window', (options.windowToUse ?? this.getLastActiveWindow())?.profile ?? this.userDataProfilesService.defaultProfile),
},

homeDir: this.environmentMainService.userHome.fsPath,
Expand Down
26 changes: 20 additions & 6 deletions src/vs/workbench/electron-sandbox/desktop.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { localize } from 'vs/nls';
import product from 'vs/platform/product/common/product';
import { INativeWindowConfiguration, zoomLevelToZoomFactor } from 'vs/platform/window/common/window';
import { INativeWindowConfiguration, IUserDataProfileInfo, isUserDataProfileInfo, zoomLevelToZoomFactor } from 'vs/platform/window/common/window';
import { Workbench } from 'vs/workbench/browser/workbench';
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
Expand Down Expand Up @@ -50,7 +50,7 @@ import { isCI, isMacintosh } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider';
import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider';
import { IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile';
import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
Expand Down Expand Up @@ -241,8 +241,24 @@ export class DesktopMain extends Disposable {
// User Data Profiles
const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService);
serviceCollection.set(IUserDataProfilesService, userDataProfilesService);
const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.current, userDataProfilesService.profilesHome.scheme), userDataProfilesService);

// User Data profile
let profile: IUserDataProfile | undefined, profileInfo: IUserDataProfileInfo | undefined;
if (isUserDataProfileInfo(this.configuration.profiles.workspace)) {
profileInfo = this.configuration.profiles.workspace;
} else {
profile = reviveProfile(this.configuration.profiles.workspace, userDataProfilesService.profilesHome.scheme);
}
const userDataProfileService = new UserDataProfileService(profile ?? userDataProfilesService.defaultProfile, userDataProfilesService);
serviceCollection.set(IUserDataProfileService, userDataProfileService);
const payload = this.resolveWorkspaceInitializationPayload(environmentService);
bpasero marked this conversation as resolved.
Show resolved Hide resolved
if (profileInfo?.name) {
await userDataProfileService.initProfileWithName(profileInfo.name, payload);

if (!userDataProfilesService.profiles.some(profile => profile.id === userDataProfileService.currentProfile.id)) {
await userDataProfilesService.reload(); // reload profiles if the current profile does not exist
}
}

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
Expand All @@ -253,9 +269,7 @@ export class DesktopMain extends Disposable {
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


const payload = this.resolveWorkspaceInitializationPayload(environmentService);

// Create services that require resolving in parallel
const [configurationService, storageService] = await Promise.all([
this.createWorkspaceService(payload, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, logService, policyService).then(service => {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import { Promises } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUserDataProfile, IUserDataProfilesService, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IAnyWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';

export class UserDataProfileService extends Disposable implements IUserDataProfileService {
Expand All @@ -24,7 +25,7 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi

constructor(
currentProfile: IUserDataProfile,
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService
) {
super();
this._currentProfile = currentProfile;
Expand Down Expand Up @@ -63,4 +64,23 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi
});
await Promises.settled(joiners);
}

async initProfileWithName(profileName: string, anyWorkspaceIdentifier: IAnyWorkspaceIdentifier): Promise<void> {
if (this.currentProfile.name === profileName) {
return;
}
const workspaceIdentifier = this.getWorkspaceIdentifier(anyWorkspaceIdentifier);
let profile = this.userDataProfilesService.profiles.find(p => p.name === profileName);
if (profile) {
await this.userDataProfilesService.setProfileForWorkspace(profile, workspaceIdentifier);
} else {
profile = await this.userDataProfilesService.createProfile(profileName, undefined, workspaceIdentifier);
}
await this.updateCurrentProfile(profile, false);
}

private getWorkspaceIdentifier(anyWorkspaceIdentifier: IAnyWorkspaceIdentifier): WorkspaceIdentifier {
return isSingleFolderWorkspaceIdentifier(anyWorkspaceIdentifier) || isWorkspaceIdentifier(anyWorkspaceIdentifier) ? anyWorkspaceIdentifier : 'empty-window';
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const TestNativeWindowConfiguration: INativeWindowConfiguration = {
homeDir: homeDir,
tmpDir: tmpdir(),
userDataDir: getUserDataPath(args),
profiles: { current: NULL_PROFILE, all: [NULL_PROFILE] },
profiles: { workspace: NULL_PROFILE, all: [NULL_PROFILE] },
...args
};

Expand Down