Skip to content

Commit

Permalink
Implement workspaceState sync
Browse files Browse the repository at this point in the history
  • Loading branch information
joyceerhl committed May 11, 2023
1 parent 4c45cff commit d41a586
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 174 deletions.
13 changes: 12 additions & 1 deletion src/vs/platform/userDataSync/common/userDataSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export const enum SyncResource {
Extensions = 'extensions',
GlobalState = 'globalState',
Profiles = 'profiles',
WorkspaceState = 'workspaceState',
}
export const ALL_SYNC_RESOURCES: SyncResource[] = [SyncResource.Settings, SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Tasks, SyncResource.Extensions, SyncResource.GlobalState, SyncResource.Profiles];

Expand Down Expand Up @@ -173,7 +174,7 @@ export interface IResourceRefHandle {
created: number;
}

export type ServerResource = SyncResource | 'machines' | 'editSessions';
export type ServerResource = SyncResource | 'machines' | 'editSessions' | 'workspaceState';
export type UserDataSyncStoreType = 'insiders' | 'stable';

export const IUserDataSyncStoreManagementService = createDecorator<IUserDataSyncStoreManagementService>('IUserDataSyncStoreManagementService');
Expand Down Expand Up @@ -359,6 +360,16 @@ export interface IGlobalState {
storage: IStringDictionary<IStorageValue>;
}

export interface IWorkspaceState {
folders: IWorkspaceStateFolder[];
storage: IStringDictionary<string>;
}

export interface IWorkspaceStateFolder {
resourceUri: string;
workspaceFolderIdentity: string;
}

export const enum SyncStatus {
Uninitialized = 'uninitialized',
Idle = 'idle',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc
case SyncResource.GlobalState: return this.getGlobalStateAssociatedResources(uri, profile);
case SyncResource.Extensions: return this.getExtensionsAssociatedResources(uri, profile);
case SyncResource.Profiles: return this.getProfilesAssociatedResources(uri, profile);
case SyncResource.WorkspaceState: return [];
}
}

Expand Down Expand Up @@ -187,6 +188,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc
case SyncResource.GlobalState: return this.resolveGlobalStateNodeContent(syncData, node);
case SyncResource.Extensions: return this.resolveExtensionsNodeContent(syncData, node);
case SyncResource.Profiles: return this.resolveProfileNodeContent(syncData, node);
case SyncResource.WorkspaceState: return null;
}
}

Expand All @@ -203,6 +205,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc
case SyncResource.Keybindings: return null;
case SyncResource.Tasks: return null;
case SyncResource.Snippets: return null;
case SyncResource.WorkspaceState: return null;
}
}

Expand Down
13 changes: 11 additions & 2 deletions src/vs/platform/userDataSync/common/userDataSyncService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}

private async performActionWithProfileSynchronizer<T>(profileSynchronizer: ProfileSynchronizer, action: (synchroniser: IUserDataSynchroniser) => Promise<T | undefined>, disposables: DisposableStore): Promise<T | undefined> {
const allSynchronizers = [...profileSynchronizer.enabled, ...profileSynchronizer.disabled.map(syncResource => disposables.add(profileSynchronizer.createSynchronizer(syncResource)))];
const allSynchronizers = [...profileSynchronizer.enabled, ...profileSynchronizer.disabled.reduce<(IUserDataSynchroniser & IDisposable)[]>((synchronizers, syncResource) => {
if (syncResource !== SyncResource.WorkspaceState) {
synchronizers.push(disposables.add(profileSynchronizer.createSynchronizer(syncResource)));
}
return synchronizers;
}, [])];
for (const synchronizer of allSynchronizers) {
const result = await action(synchronizer);
if (!isUndefined(result)) {
Expand Down Expand Up @@ -614,6 +619,9 @@ class ProfileSynchronizer extends Disposable {
return;
}
}
if (syncResource === SyncResource.WorkspaceState) {
return;
}
const disposables = new DisposableStore();
const synchronizer = disposables.add(this.createSynchronizer(syncResource));
disposables.add(synchronizer.onDidChangeStatus(() => this.updateStatus()));
Expand All @@ -634,7 +642,7 @@ class ProfileSynchronizer extends Disposable {
}
}

createSynchronizer(syncResource: SyncResource): IUserDataSynchroniser & IDisposable {
createSynchronizer(syncResource: Exclude<SyncResource, SyncResource.WorkspaceState>): IUserDataSynchroniser & IDisposable {
switch (syncResource) {
case SyncResource.Settings: return this.instantiationService.createInstance(SettingsSynchroniser, this.profile, this.collection);
case SyncResource.Keybindings: return this.instantiationService.createInstance(KeybindingsSynchroniser, this.profile, this.collection);
Expand Down Expand Up @@ -802,6 +810,7 @@ class ProfileSynchronizer extends Disposable {
case SyncResource.GlobalState: return 4;
case SyncResource.Extensions: return 5;
case SyncResource.Profiles: return 6;
case SyncResource.WorkspaceState: return 7;
}
}

Expand Down
56 changes: 0 additions & 56 deletions src/vs/platform/workspace/browser/editSessionsStorageService.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { basename, isEqualOrParent, joinPath, relativePath } from 'vs/base/common/resources';
import { basename, joinPath, relativePath } from 'vs/base/common/resources';
import { encodeBase64 } from 'vs/base/common/buffer';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProgress, IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress';
import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/browser/editSessionsStorageService';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncErrorCode, UserDataSyncStoreError, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { getFileNamesMessage, IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
Expand Down Expand Up @@ -62,7 +62,12 @@ import { CancellationError } from 'vs/base/common/errors';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IExtensionsViewPaneContainer, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { EditSessionRegistry } from 'vs/platform/workspace/browser/editSessionsStorageService';
import { WorkspaceStateSynchroniser } from 'vs/workbench/contrib/editSessions/common/workspaceStateSync';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IRequestService } from 'vs/platform/request/common/request';
import { EditSessionsStoreClient } from 'vs/workbench/contrib/editSessions/common/editSessionsStorageClient';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IWorkspaceIdentityService } from 'vs/workbench/services/workspaces/common/workspaceIdentityService';

registerSingleton(IEditSessionsLogService, EditSessionsLogService, InstantiationType.Delayed);
registerSingleton(IEditSessionsStorageService, EditSessionsWorkbenchService, InstantiationType.Delayed);
Expand Down Expand Up @@ -121,6 +126,9 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo

private registeredCommands = new Set<string>();

private workspaceStateSynchronizer: IUserDataSynchroniser | undefined;
private editSessionsStorageClient: EditSessionsStoreClient | undefined;

constructor(
@IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService,
@IFileService private readonly fileService: IFileService,
Expand All @@ -147,17 +155,29 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
@IEditorService private readonly editorService: IEditorService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IExtensionService private readonly extensionService: IExtensionService,
@IRequestService private readonly requestService: IRequestService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IWorkspaceIdentityService private readonly workspaceIdentityService: IWorkspaceIdentityService,
) {
super();

this.shouldShowViewsContext = EDIT_SESSIONS_SHOW_VIEW.bindTo(this.contextKeyService);

if (!this.productService['editSessions.store']?.url) {
return;
}

this.editSessionsStorageClient = new EditSessionsStoreClient(URI.parse(this.productService['editSessions.store'].url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService);
this.editSessionsStorageService.storeClient = this.editSessionsStorageClient;
this.workspaceStateSynchronizer = new WorkspaceStateSynchroniser(this.userDataProfilesService.defaultProfile, undefined, this.editSessionsStorageClient, this.logService, this.fileService, this.environmentService, this.telemetryService, this.configurationService, this.storageService, this.uriIdentityService, this.workspaceIdentityService, this.editSessionsStorageService);

this.autoResumeEditSession();

this.registerActions();
this.registerViews();
this.registerContributedEditSessionOptions();

this.shouldShowViewsContext = EDIT_SESSIONS_SHOW_VIEW.bindTo(this.contextKeyService);

this._register(this.fileService.registerProvider(EditSessionsFileSystemProvider.SCHEMA, new EditSessionsFileSystemProvider(this.editSessionsStorageService)));
this.lifecycleService.onWillShutdown((e) => {
if (e.reason !== ShutdownReason.RELOAD && this.editSessionsStorageService.isSignedIn && this.configurationService.getValue('workbench.experimental.cloudChanges.autoStore') === 'onShutdown' && !isWeb) {
Expand Down Expand Up @@ -482,7 +502,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
performance.mark('code/willResumeEditSessionFromIdentifier');

progress?.report({ message: localize('checkingForWorkingChanges', 'Checking for pending cloud changes...') });
const data = serializedData ? { editSession: JSON.parse(serializedData), ref: '' } : await this.editSessionsStorageService.read(ref);
const data = serializedData ? { content: serializedData, ref: '' } : await this.editSessionsStorageService.read('editSessions', ref);
if (!data) {
if (ref === undefined && !silent) {
this.notificationService.info(localize('no cloud changes', 'There are no changes to resume from the cloud.'));
Expand All @@ -494,7 +514,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
}

progress?.report({ message: resumeProgressOptionsTitle });
const editSession = data.editSession;
const editSession = JSON.parse(data.content);
ref = data.ref;

if (editSession.version > EditSessionSchemaVersion) {
Expand All @@ -504,8 +524,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
}

try {
const { changes, conflictingChanges, contributedStateHandlers } = await this.generateChanges(editSession, ref, forceApplyUnrelatedChange, applyPartialMatch);
if (changes.length === 0 && contributedStateHandlers.length === 0) {
const { changes, conflictingChanges } = await this.generateChanges(editSession, ref, forceApplyUnrelatedChange, applyPartialMatch);
if (changes.length === 0) {
return;
}

Expand Down Expand Up @@ -534,12 +554,10 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
}
}

for (const handleContributedState of contributedStateHandlers) {
handleContributedState();
}
await this.workspaceStateSynchronizer?.apply(false, {});

this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`);
await this.editSessionsStorageService.delete(ref);
await this.editSessionsStorageService.delete('editSessions', ref);
this.logService.info(`Deleted edit session with ref ${ref}.`);

this.telemetryService.publicLog2<ResumeEvent, ResumeClassification>('editSessions.resume.outcome', { hashedId: hashedEditSessionId(ref), outcome: 'resumeSucceeded' });
Expand All @@ -553,7 +571,6 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo

private async generateChanges(editSession: EditSession, ref: string, forceApplyUnrelatedChange = false, applyPartialMatch = false) {
const changes: ({ uri: URI; type: ChangeType; contents: string | undefined })[] = [];
const contributedStateHandlers: (() => void)[] = [];
const conflictingChanges = [];
const workspaceFolders = this.contextService.getWorkspace().folders;
const cancellationTokenSource = new CancellationTokenSource();
Expand Down Expand Up @@ -623,44 +640,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
}
}

const incomingFolderUrisToIdentifiers = new Map<string, [string, EditSessionIdentityMatch]>();
for (const folder of editSession.folders) {
const { canonicalIdentity } = folder;
for (const workspaceFolder of workspaceFolders) {
const identity = await this.editSessionIdentityService.getEditSessionIdentifier(workspaceFolder, cancellationTokenSource.token);
if (!identity || !canonicalIdentity || !folder.absoluteUri) {
continue;
}
const match = identity === canonicalIdentity
? EditSessionIdentityMatch.Complete
: await this.editSessionIdentityService.provideEditSessionIdentityMatch(workspaceFolder, identity, canonicalIdentity, cancellationTokenSource.token);
if (!match) {
continue;
}
incomingFolderUrisToIdentifiers.set(folder.absoluteUri.toString(), [workspaceFolder.uri.toString(), match]);
}
}

EditSessionRegistry.getEditSessionContributions().forEach(([key, contrib]) => {
const state = editSession.state[key];
if (state) {
contributedStateHandlers.push(() => contrib.resumeState(state, (incomingUri: URI) => {
for (const absoluteUri of incomingFolderUrisToIdentifiers.keys()) {
if (isEqualOrParent(incomingUri, URI.parse(absoluteUri))) {
const [workspaceFolderUri, match] = incomingFolderUrisToIdentifiers.get(absoluteUri)!;
if (match === EditSessionIdentityMatch.Complete) {
const relativeFilePath = relativePath(URI.parse(absoluteUri), incomingUri);
return relativeFilePath ? joinPath(URI.parse(workspaceFolderUri), relativeFilePath) : incomingUri;
}

}
}
return incomingUri;
}));
}
});

return { changes, conflictingChanges, contributedStateHandlers };
return { changes, conflictingChanges };
}

private async willChangeLocalContents(localChanges: Set<string>, uriWithIncomingChanges: URI, incomingChange: Change) {
Expand Down Expand Up @@ -747,11 +727,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
folders.push({ workingChanges, name: name ?? '', canonicalIdentity: canonicalIdentity ?? undefined, absoluteUri: workspaceFolder?.uri.toString() });
}

// Look through all registered contributions to gather additional state
const contributedData: { [key: string]: unknown } = {};
EditSessionRegistry.getEditSessionContributions().forEach(([key, contrib]) => {
contributedData[key] = contrib.getStateToStore();
});
// Store contributed workspace state
await this.workspaceStateSynchronizer?.sync(null, {});

if (!hasEdits) {
this.logService.info('Skipped storing working changes in the cloud as there are no edits to store.');
Expand All @@ -761,11 +738,11 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
return undefined;
}

const data: EditSession = { folders, version: 2, state: contributedData };
const data: EditSession = { folders, version: 2 };

try {
this.logService.info(`Storing edit session...`);
const ref = await this.editSessionsStorageService.write(data);
const ref = await this.editSessionsStorageService.write('editSessions', data);
this.logService.info(`Stored edit session with ref ${ref}.`);
return ref;
} catch (ex) {
Expand Down

0 comments on commit d41a586

Please sign in to comment.