Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
7
import { IStringDictionary } from '../../../../base/common/collections.js';
8
import { Emitter, Event } from '../../../../base/common/event.js';
9
import { parse, stringify } from '../../../../base/common/marshalling.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
12
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
13
import { IFileService } from '../../../../platform/files/common/files.js';
14
import { IStorageEntry, IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
15
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
16
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
17
import { IUserDataProfile } from '../../../../platform/userDataProfile/common/userDataProfile.js';
18
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, ISyncResourcePreview } from '../../../../platform/userDataSync/common/abstractSynchronizer.js';
19
import { IRemoteUserData, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource, IUserDataSyncResourcePreview } from '../../../../platform/userDataSync/common/userDataSync.js';
20
import { EditSession, IEditSessionsStorageService } from './editSessions.js';
21
import { IWorkspaceIdentityService } from '../../../services/workspaces/common/workspaceIdentityService.js';
22
23
24
class NullBackupStoreService implements IUserDataSyncLocalStoreService {
25
_serviceBrand: undefined;
26
async writeResource(): Promise<void> {
27
return;
28
}
29
async getAllResourceRefs(): Promise<IResourceRefHandle[]> {
30
return [];
31
}
32
async resolveResourceContent(): Promise<string | null> {
33
return null;
34
}
35
36
}
37
38
class NullEnablementService implements IUserDataSyncEnablementService {
39
_serviceBrand: any;
40
41
private _onDidChangeEnablement = new Emitter<boolean>();
42
readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
43
44
private _onDidChangeResourceEnablement = new Emitter<[SyncResource, boolean]>();
45
readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event;
46
47
isEnabled(): boolean { return true; }
48
canToggleEnablement(): boolean { return true; }
49
setEnablement(_enabled: boolean): void { }
50
isResourceEnabled(_resource: SyncResource): boolean { return true; }
51
isResourceEnablementConfigured(_resource: SyncResource): boolean { return false; }
52
setResourceEnablement(_resource: SyncResource, _enabled: boolean): void { }
53
getResourceSyncStateVersion(_resource: SyncResource): string | undefined { return undefined; }
54
55
}
56
57
export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
58
protected override version: number = 1;
59
60
constructor(
61
profile: IUserDataProfile,
62
collection: string | undefined,
63
userDataSyncStoreService: IUserDataSyncStoreService,
64
logService: IUserDataSyncLogService,
65
@IFileService fileService: IFileService,
66
@IEnvironmentService environmentService: IEnvironmentService,
67
@ITelemetryService telemetryService: ITelemetryService,
68
@IConfigurationService configurationService: IConfigurationService,
69
@IStorageService storageService: IStorageService,
70
@IUriIdentityService uriIdentityService: IUriIdentityService,
71
@IWorkspaceIdentityService private readonly workspaceIdentityService: IWorkspaceIdentityService,
72
@IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService,
73
) {
74
const userDataSyncLocalStoreService = new NullBackupStoreService();
75
const userDataSyncEnablementService = new NullEnablementService();
76
super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
77
}
78
79
override async sync(): Promise<IUserDataSyncResourcePreview | null> {
80
const cancellationTokenSource = new CancellationTokenSource();
81
const folders = await this.workspaceIdentityService.getWorkspaceStateFolders(cancellationTokenSource.token);
82
if (!folders.length) {
83
return null;
84
}
85
86
// Ensure we have latest state by sending out onWillSaveState event
87
await this.storageService.flush();
88
89
const keys = this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.USER);
90
if (!keys.length) {
91
return null;
92
}
93
94
const contributedData: IStringDictionary<string> = {};
95
keys.forEach((key) => {
96
const data = this.storageService.get(key, StorageScope.WORKSPACE);
97
if (data) {
98
contributedData[key] = data;
99
}
100
});
101
102
const content: IWorkspaceState = { folders, storage: contributedData, version: this.version };
103
await this.editSessionsStorageService.write('workspaceState', stringify(content));
104
return null;
105
}
106
107
override async apply(): Promise<ISyncResourcePreview | null> {
108
const payload = this.editSessionsStorageService.lastReadResources.get('editSessions')?.content;
109
const workspaceStateId = payload ? (JSON.parse(payload) as EditSession).workspaceStateId : undefined;
110
111
const resource = await this.editSessionsStorageService.read('workspaceState', workspaceStateId);
112
if (!resource) {
113
return null;
114
}
115
116
const remoteWorkspaceState: IWorkspaceState = parse(resource.content);
117
if (!remoteWorkspaceState) {
118
this.logService.info('Skipping initializing workspace state because remote workspace state does not exist.');
119
return null;
120
}
121
122
// Evaluate whether storage is applicable for current workspace
123
const cancellationTokenSource = new CancellationTokenSource();
124
const replaceUris = await this.workspaceIdentityService.matches(remoteWorkspaceState.folders, cancellationTokenSource.token);
125
if (!replaceUris) {
126
this.logService.info('Skipping initializing workspace state because remote workspace state does not match current workspace.');
127
return null;
128
}
129
130
const storage: IStringDictionary<any> = {};
131
for (const key of Object.keys(remoteWorkspaceState.storage)) {
132
storage[key] = remoteWorkspaceState.storage[key];
133
}
134
135
if (Object.keys(storage).length) {
136
// Initialize storage with remote storage
137
const storageEntries: Array<IStorageEntry> = [];
138
for (const key of Object.keys(storage)) {
139
// Deserialize the stored state
140
try {
141
const value = parse(storage[key]);
142
// Run URI conversion on the stored state
143
replaceUris(value);
144
storageEntries.push({ key, value, scope: StorageScope.WORKSPACE, target: StorageTarget.USER });
145
} catch {
146
storageEntries.push({ key, value: storage[key], scope: StorageScope.WORKSPACE, target: StorageTarget.USER });
147
}
148
}
149
this.storageService.storeAll(storageEntries, true);
150
}
151
152
this.editSessionsStorageService.delete('workspaceState', resource.ref);
153
return null;
154
}
155
156
// TODO@joyceerhl implement AbstractSynchronizer in full
157
protected override applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
158
throw new Error('Method not implemented.');
159
}
160
protected override async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, token: CancellationToken): Promise<IResourcePreview[]> {
161
return [];
162
}
163
protected override getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise<IMergeResult> {
164
throw new Error('Method not implemented.');
165
}
166
protected override getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
167
throw new Error('Method not implemented.');
168
}
169
protected override async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
170
return true;
171
}
172
override async hasLocalData(): Promise<boolean> {
173
return false;
174
}
175
override async resolveContent(uri: URI): Promise<string | null> {
176
return null;
177
}
178
}
179
180