Path: blob/main/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';6import { IStringDictionary } from '../../../../base/common/collections.js';7import { Emitter, Event } from '../../../../base/common/event.js';8import { parse, stringify } from '../../../../base/common/marshalling.js';9import { URI } from '../../../../base/common/uri.js';10import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';11import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';12import { IFileService } from '../../../../platform/files/common/files.js';13import { IStorageEntry, IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';14import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';15import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';16import { IUserDataProfile } from '../../../../platform/userDataProfile/common/userDataProfile.js';17import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, ISyncResourcePreview } from '../../../../platform/userDataSync/common/abstractSynchronizer.js';18import { IRemoteUserData, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource, IUserDataSyncResourcePreview } from '../../../../platform/userDataSync/common/userDataSync.js';19import { EditSession, IEditSessionsStorageService } from './editSessions.js';20import { IWorkspaceIdentityService } from '../../../services/workspaces/common/workspaceIdentityService.js';212223class NullBackupStoreService implements IUserDataSyncLocalStoreService {24_serviceBrand: undefined;25async writeResource(): Promise<void> {26return;27}28async getAllResourceRefs(): Promise<IResourceRefHandle[]> {29return [];30}31async resolveResourceContent(): Promise<string | null> {32return null;33}3435}3637class NullEnablementService implements IUserDataSyncEnablementService {38_serviceBrand: any;3940private _onDidChangeEnablement = new Emitter<boolean>();41readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;4243private _onDidChangeResourceEnablement = new Emitter<[SyncResource, boolean]>();44readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event;4546isEnabled(): boolean { return true; }47canToggleEnablement(): boolean { return true; }48setEnablement(_enabled: boolean): void { }49isResourceEnabled(_resource: SyncResource): boolean { return true; }50isResourceEnablementConfigured(_resource: SyncResource): boolean { return false; }51setResourceEnablement(_resource: SyncResource, _enabled: boolean): void { }52getResourceSyncStateVersion(_resource: SyncResource): string | undefined { return undefined; }5354}5556export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {57protected override version: number = 1;5859constructor(60profile: IUserDataProfile,61collection: string | undefined,62userDataSyncStoreService: IUserDataSyncStoreService,63logService: IUserDataSyncLogService,64@IFileService fileService: IFileService,65@IEnvironmentService environmentService: IEnvironmentService,66@ITelemetryService telemetryService: ITelemetryService,67@IConfigurationService configurationService: IConfigurationService,68@IStorageService storageService: IStorageService,69@IUriIdentityService uriIdentityService: IUriIdentityService,70@IWorkspaceIdentityService private readonly workspaceIdentityService: IWorkspaceIdentityService,71@IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService,72) {73const userDataSyncLocalStoreService = new NullBackupStoreService();74const userDataSyncEnablementService = new NullEnablementService();75super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);76}7778override async sync(): Promise<IUserDataSyncResourcePreview | null> {79const cancellationTokenSource = new CancellationTokenSource();80const folders = await this.workspaceIdentityService.getWorkspaceStateFolders(cancellationTokenSource.token);81if (!folders.length) {82return null;83}8485// Ensure we have latest state by sending out onWillSaveState event86await this.storageService.flush();8788const keys = this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.USER);89if (!keys.length) {90return null;91}9293const contributedData: IStringDictionary<string> = {};94keys.forEach((key) => {95const data = this.storageService.get(key, StorageScope.WORKSPACE);96if (data) {97contributedData[key] = data;98}99});100101const content: IWorkspaceState = { folders, storage: contributedData, version: this.version };102await this.editSessionsStorageService.write('workspaceState', stringify(content));103return null;104}105106override async apply(): Promise<ISyncResourcePreview | null> {107const payload = this.editSessionsStorageService.lastReadResources.get('editSessions')?.content;108const workspaceStateId = payload ? (JSON.parse(payload) as EditSession).workspaceStateId : undefined;109110const resource = await this.editSessionsStorageService.read('workspaceState', workspaceStateId);111if (!resource) {112return null;113}114115const remoteWorkspaceState: IWorkspaceState = parse(resource.content);116if (!remoteWorkspaceState) {117this.logService.info('Skipping initializing workspace state because remote workspace state does not exist.');118return null;119}120121// Evaluate whether storage is applicable for current workspace122const cancellationTokenSource = new CancellationTokenSource();123const replaceUris = await this.workspaceIdentityService.matches(remoteWorkspaceState.folders, cancellationTokenSource.token);124if (!replaceUris) {125this.logService.info('Skipping initializing workspace state because remote workspace state does not match current workspace.');126return null;127}128129const storage: IStringDictionary<any> = {};130for (const key of Object.keys(remoteWorkspaceState.storage)) {131storage[key] = remoteWorkspaceState.storage[key];132}133134if (Object.keys(storage).length) {135// Initialize storage with remote storage136const storageEntries: Array<IStorageEntry> = [];137for (const key of Object.keys(storage)) {138// Deserialize the stored state139try {140const value = parse(storage[key]);141// Run URI conversion on the stored state142replaceUris(value);143storageEntries.push({ key, value, scope: StorageScope.WORKSPACE, target: StorageTarget.USER });144} catch {145storageEntries.push({ key, value: storage[key], scope: StorageScope.WORKSPACE, target: StorageTarget.USER });146}147}148this.storageService.storeAll(storageEntries, true);149}150151this.editSessionsStorageService.delete('workspaceState', resource.ref);152return null;153}154155// TODO@joyceerhl implement AbstractSynchronizer in full156protected override applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise<void> {157throw new Error('Method not implemented.');158}159protected override async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, token: CancellationToken): Promise<IResourcePreview[]> {160return [];161}162protected override getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise<IMergeResult> {163throw new Error('Method not implemented.');164}165protected override getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {166throw new Error('Method not implemented.');167}168protected override async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {169return true;170}171override async hasLocalData(): Promise<boolean> {172return false;173}174override async resolveContent(uri: URI): Promise<string | null> {175return null;176}177}178179180