src/vs/platform/userDataSync/common/snippetsSync.ts

552 LOC · 473 covered · 79 uncovered · 90 ranges · 585 concepts · 24 introducers · 348 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- abstractSynchronizer.ts ×49
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 { VSBuffer } from '../../../base/common/buffer.js';
7 > import { CancellationToken } from '../../../base/common/cancellation.js';
8 > import { IStringDictionary } from '../../../base/common/collections.js';
9 > import { Event } from '../../../base/common/event.js';
10 > import { deepClone } from '../../../base/common/objects.js';
11 > import { URI } from '../../../base/common/uri.js';
12 > import { IConfigurationService } from '../../configuration/common/configuration.js';
13 > import { IEnvironmentService } from '../../environment/common/environment.js';
14 > import { FileOperationError, FileOperationResult, IFileContent, IFileService, IFileStat } from '../../files/common/files.js';
15 > import { IStorageService } from '../../storage/common/storage.js';
16 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
17 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
18 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
19 > import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from './abstractSynchronizer.js';
20 > import { areSame, IMergeResult as ISnippetsMergeResult, merge } from './snippetsMerge.js';
21 > import { Change, IRemoteUserData, ISyncData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from './userDataSync.js';
22 >
23 > interface ISnippetsResourcePreview extends IFileResourcePreview {
24 > previewResult: IMergeResult;
25 > }
26 >
27 > interface ISnippetsAcceptedResourcePreview extends IFileResourcePreview {
28 > acceptResult: IAcceptResult;
29 > }
30 >
31 > export function parseSnippets(syncData: ISyncData): IStringDictionary<string> {
32 > return JSON.parse(syncData.content); snippetsSync.ts ×3
33 > }
35 > export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
36 >
37 > protected readonly version: number = 1;
38 > private readonly snippetsFolder: URI;
39 >
40 > constructor(
41 > profile: IUserDataProfile, userDataSyncService.ts ×19
42 > collection: string | undefined,
43 > @IEnvironmentService environmentService: IEnvironmentService,
44 > @IFileService fileService: IFileService,
45 > @IStorageService storageService: IStorageService,
46 > @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
47 > @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService,
48 > @IUserDataSyncLogService logService: IUserDataSyncLogService,
49 > @IConfigurationService configurationService: IConfigurationService,
50 > @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
51 > @ITelemetryService telemetryService: ITelemetryService,
52 > @IUriIdentityService uriIdentityService: IUriIdentityService,
53 > ) {
54 > super({ syncResource: SyncResource.Snippets, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
55 > this.snippetsFolder = profile.snippetsHome;
56 > this._register(this.fileService.watch(environmentService.userRoamingDataHome));
57 > this._register(this.fileService.watch(this.snippetsFolder));
58 > this._register(Event.filter(this.fileService.onDidFilesChange, e => e.affects(this.snippetsFolder))(() => this.triggerLocalChange()));
59 > }
61 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean): Promise<ISnippetsResourcePreview[]> {
62 > const local = await this.getSnippetsFileContents(); snippetsSync.ts ×16
63 > const localSnippets = this.toSnippetsContents(local);
64 > const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null;
65 >
66 > // Use remote data as last sync data if last sync data does not exist and remote data is from same machine
67 > lastSyncUserData = lastSyncUserData === null && isRemoteDataFromCurrentMachine ? remoteUserData : lastSyncUserData;
68 > const lastSyncSnippets: IStringDictionary<string> | null = lastSyncUserData && lastSyncUserData.syncData ? this.parseSnippets(lastSyncUserData.syncData) : null;
69 >
70 > if (remoteSnippets) {
71 > this.logService.trace(`${this.syncResourceLogLabel}: Merging remote snippets with local snippets...`); snippetsSync.ts ×3
72 > } else { snippetsSync.ts ×16
73 > this.logService.trace(`${this.syncResourceLogLabel}: Remote snippets does not exist. Synchronizing snippets for the first time.`);
74 > }
75 >
76 > const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets);
77 > return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}, lastSyncSnippets || {});
78 > }
80 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
81 > const lastSyncSnippets: IStringDictionary<string> | null = lastSyncUserData.syncData ? this.parseSnippets(lastSyncUserData.syncData) : null; globalStateSync.ts ×2
82 > if (lastSyncSnippets === null) {
83 return true;
84 }
85 > const local = await this.getSnippetsFileContents(); globalStateSync.ts ×2
86 > const localSnippets = this.toSnippetsContents(local);
87 > const mergeResult = merge(localSnippets, lastSyncSnippets, lastSyncSnippets);
88 > return Object.keys(mergeResult.remote.added).length > 0 || Object.keys(mergeResult.remote.updated).length > 0 || mergeResult.remote.removed.length > 0 || mergeResult.conflicts.length > 0;
89 > }
91 > protected async getMergeResult(resourcePreview: ISnippetsResourcePreview, token: CancellationToken): Promise<IMergeResult> {
92 > return resourcePreview.previewResult; snippetsSync.ts ×11
93 > }
95 > protected async getAcceptResult(resourcePreview: ISnippetsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
97 > /* Accept local resource */
98 > if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) {
99 return {
100 content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
101 localChange: Change.None,
102 remoteChange: resourcePreview.fileContent
103 ? resourcePreview.remoteContent !== null ? Change.Modified : Change.Added
104 : Change.Deleted
105 };
106 }
108 > /* Accept remote resource */
109 > if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) {
110 return {
111 content: resourcePreview.remoteContent,
112 localChange: resourcePreview.remoteContent !== null
113 ? resourcePreview.fileContent ? Change.Modified : Change.Added
114 : Change.Deleted,
115 remoteChange: Change.None,
116 };
117 }
119 > /* Accept preview resource */
120 > if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder)) {
121 > if (content === undefined) {
122 > return {
123 > content: resourcePreview.previewResult.content,
124 > localChange: resourcePreview.previewResult.localChange,
125 > remoteChange: resourcePreview.previewResult.remoteChange,
126 > };
127 > } else {
128 > return { snippetsSync.ts ×3
129 > content,
130 > localChange: content === null
131 > ? resourcePreview.fileContent !== null ? Change.Deleted : Change.None snippetsSync.ts ×2
132 > : Change.Modified, snippetsSync.ts ×2
133 > remoteChange: content === null snippetsSync.ts ×3
134 > ? resourcePreview.remoteContent !== null ? Change.Deleted : Change.None snippetsSync.ts ×2
135 > : Change.Modified snippetsSync.ts ×2
137 > }
139
140 throw new Error(`Invalid Resource: ${resource.toString()}`);
143 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [ISnippetsResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
144 > const accptedResourcePreviews: ISnippetsAcceptedResourcePreview[] = resourcePreviews.map(([resourcePreview, acceptResult]) => ({ ...resourcePreview, acceptResult })); snippetsSync.ts ×5
145 > if (accptedResourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
146 > this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`); snippetsSync.ts ×1
147 > }
149 > if (accptedResourcePreviews.some(({ localChange }) => localChange !== Change.None)) {
150 > // back up all snippets snippetsSync.ts ×6
151 > await this.updateLocalBackup(accptedResourcePreviews);
152 > await this.updateLocalSnippets(accptedResourcePreviews, force);
153 > }
155 > if (accptedResourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None)) {
156 > remoteUserData = await this.updateRemoteSnippets(accptedResourcePreviews, remoteUserData, force); snippetsSync.ts ×4
157 > }
159 > if (lastSyncUserData?.ref !== remoteUserData.ref) {
160 > // update last sync
161 > this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized snippets...`);
162 > await this.updateLastSyncUserData(remoteUserData);
163 > this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized snippets`);
164 > }
165 >
166 > for (const { previewResource } of accptedResourcePreviews) {
167 > // Delete the preview snippetsSync.ts ×4
168 > try {
169 > await this.fileService.del(previewResource);
170 > } catch (e) { /* ignore */ }
171 > }
173 > }
175 > private getResourcePreviews(snippetsMergeResult: ISnippetsMergeResult, localFileContent: IStringDictionary<IFileContent>, remoteSnippets: IStringDictionary<string>, baseSnippets: IStringDictionary<string>): ISnippetsResourcePreview[] {
176 > const resourcePreviews: Map<string, ISnippetsResourcePreview> = new Map<string, ISnippetsResourcePreview>(); snippetsSync.ts ×16
177 >
178 > /* Snippets added remotely -> add locally */
179 > for (const key of Object.keys(snippetsMergeResult.local.added)) {
180 > const previewResult: IMergeResult = { snippetsSync.ts ×2
181 > content: snippetsMergeResult.local.added[key],
182 > hasConflicts: false,
183 > localChange: Change.Added,
184 > remoteChange: Change.None,
185 > };
186 > resourcePreviews.set(key, {
187 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
188 > baseContent: null,
189 > fileContent: null,
190 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
191 > localContent: null,
192 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
193 > remoteContent: remoteSnippets[key],
194 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
195 > previewResult,
196 > localChange: previewResult.localChange,
197 > remoteChange: previewResult.remoteChange,
198 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
199 > });
200 > }
202 > /* Snippets updated remotely -> update locally */
203 > for (const key of Object.keys(snippetsMergeResult.local.updated)) {
204 > const previewResult: IMergeResult = { snippetsSync.ts ×1
205 > content: snippetsMergeResult.local.updated[key],
206 > hasConflicts: false,
207 > localChange: Change.Modified,
208 > remoteChange: Change.None,
209 > };
210 > const localContent = localFileContent[key] ? localFileContent[key].value.toString() : null;
211 > resourcePreviews.set(key, {
212 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
213 > baseContent: baseSnippets[key] ?? null,
214 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
215 > fileContent: localFileContent[key],
216 > localContent,
217 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
218 > remoteContent: remoteSnippets[key],
219 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
220 > previewResult,
221 > localChange: previewResult.localChange,
222 > remoteChange: previewResult.remoteChange,
223 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
224 > });
225 > }
227 > /* Snippets removed remotely -> remove locally */
228 > for (const key of snippetsMergeResult.local.removed) {
229 > const previewResult: IMergeResult = { snippetsSync.ts ×1
230 > content: null,
231 > hasConflicts: false,
232 > localChange: Change.Deleted,
233 > remoteChange: Change.None,
234 > };
235 > const localContent = localFileContent[key] ? localFileContent[key].value.toString() : null;
236 > resourcePreviews.set(key, {
237 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
238 > baseContent: baseSnippets[key] ?? null,
239 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
240 > fileContent: localFileContent[key],
241 > localContent,
242 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
243 > remoteContent: null,
244 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
245 > previewResult,
246 > localChange: previewResult.localChange,
247 > remoteChange: previewResult.remoteChange,
248 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
249 > });
250 > }
252 > /* Snippets added locally -> add remotely */
253 > for (const key of Object.keys(snippetsMergeResult.remote.added)) {
254 > const previewResult: IMergeResult = { snippetsSync.ts ×11
255 > content: snippetsMergeResult.remote.added[key],
256 > hasConflicts: false,
257 > localChange: Change.None,
258 > remoteChange: Change.Added,
259 > };
260 > const localContent = localFileContent[key] ? localFileContent[key].value.toString() : null;
261 > resourcePreviews.set(key, {
262 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
263 > baseContent: baseSnippets[key] ?? null,
264 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
265 > fileContent: localFileContent[key],
266 > localContent,
267 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
268 > remoteContent: null,
269 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
270 > previewResult,
271 > localChange: previewResult.localChange,
272 > remoteChange: previewResult.remoteChange,
273 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
274 > });
275 > }
277 > /* Snippets updated locally -> update remotely */
278 > for (const key of Object.keys(snippetsMergeResult.remote.updated)) {
279 > const previewResult: IMergeResult = { snippetsSync.ts ×1
280 > content: snippetsMergeResult.remote.updated[key],
281 > hasConflicts: false,
282 > localChange: Change.None,
283 > remoteChange: Change.Modified,
284 > };
285 > const localContent = localFileContent[key] ? localFileContent[key].value.toString() : null;
286 > resourcePreviews.set(key, {
287 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
288 > baseContent: baseSnippets[key] ?? null,
289 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
290 > fileContent: localFileContent[key],
291 > localContent,
292 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
293 > remoteContent: remoteSnippets[key],
294 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
295 > previewResult,
296 > localChange: previewResult.localChange,
297 > remoteChange: previewResult.remoteChange,
298 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
299 > });
300 > }
302 > /* Snippets removed locally -> remove remotely */
303 > for (const key of snippetsMergeResult.remote.removed) {
304 > const previewResult: IMergeResult = { snippetsSync.ts ×2
305 > content: null,
306 > hasConflicts: false,
307 > localChange: Change.None,
308 > remoteChange: Change.Deleted,
309 > };
310 > resourcePreviews.set(key, {
311 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
312 > baseContent: baseSnippets[key] ?? null,
313 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
314 > fileContent: null,
315 > localContent: null,
316 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
317 > remoteContent: remoteSnippets[key],
318 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
319 > previewResult,
320 > localChange: previewResult.localChange,
321 > remoteChange: previewResult.remoteChange,
322 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
323 > });
324 > }
326 > /* Snippets with conflicts */
327 > for (const key of snippetsMergeResult.conflicts) {
328 > const previewResult: IMergeResult = { snippetsSync.ts ×1
329 > content: baseSnippets[key] ?? null,
330 > hasConflicts: true,
331 > localChange: localFileContent[key] ? Change.Modified : Change.Added,
332 > remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
333 > };
334 > const localContent = localFileContent[key] ? localFileContent[key].value.toString() : null;
335 > resourcePreviews.set(key, {
336 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
337 > baseContent: baseSnippets[key] ?? null,
338 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
339 > fileContent: localFileContent[key] || null,
340 > localContent,
341 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
342 > remoteContent: remoteSnippets[key] || null,
343 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
344 > previewResult,
345 > localChange: previewResult.localChange,
346 > remoteChange: previewResult.remoteChange,
347 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
348 > });
349 > }
351 > /* Unmodified Snippets */
352 > for (const key of Object.keys(localFileContent)) {
353 > if (!resourcePreviews.has(key)) { snippetsSync.ts ×11
354 > const previewResult: IMergeResult = { snippetsSync.ts ×1
355 > content: localFileContent[key] ? localFileContent[key].value.toString() : null,
356 > hasConflicts: false,
357 > localChange: Change.None,
358 > remoteChange: Change.None
359 > };
360 > const localContent = localFileContent[key] ? localFileContent[key].value.toString() : null;
361 > resourcePreviews.set(key, {
362 > baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }),
363 > baseContent: baseSnippets[key] ?? null,
364 > localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
365 > fileContent: localFileContent[key] || null,
366 > localContent,
367 > remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
368 > remoteContent: remoteSnippets[key] || null,
369 > previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
370 > previewResult,
371 > localChange: previewResult.localChange,
372 > remoteChange: previewResult.remoteChange,
373 > acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
374 > });
375 > }
378 > return [...resourcePreviews.values()];
379 > }
381 > override async resolveContent(uri: URI): Promise<string | null> {
382 if (this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
383 || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
384 || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }))
385 || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
386 return this.resolvePreviewContent(uri);
387 }
388 return null;
389 }
391 > async hasLocalData(): Promise<boolean> {
392 try {
393 const localSnippets = await this.getSnippetsFileContents();
394 if (Object.keys(localSnippets).length) {
395 return true;
396 }
397 } catch (error) {
398 /* ignore error */
399 }
400 return false;
401 }
403 > private async updateLocalBackup(resourcePreviews: IFileResourcePreview[]): Promise<void> {
404 > const local: IStringDictionary<IFileContent> = {}; snippetsSync.ts ×6
405 > for (const resourcePreview of resourcePreviews) {
406 > if (resourcePreview.fileContent) {
407 > local[this.extUri.basename(resourcePreview.localResource)] = resourcePreview.fileContent; snippetsSync.ts ×1
408 > }
410 > await this.backupLocal(JSON.stringify(this.toSnippetsContents(local)));
411 > }
413 > private async updateLocalSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], force: boolean): Promise<void> {
414 > for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) { snippetsSync.ts ×6
415 > if (localChange !== Change.None) {
416 > const key = remoteResource ? this.extUri.basename(remoteResource) : this.extUri.basename(localResource);
417 > const resource = this.extUri.joinPath(this.snippetsFolder, key);
418 >
419 > // Removed
420 > if (localChange === Change.Deleted) {
421 > this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, this.extUri.basename(resource)); snippetsSync.ts ×1
422 > await this.fileService.del(resource);
423 > this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, this.extUri.basename(resource));
424 > }
426 > // Added
427 > else if (localChange === Change.Added) {
428 > this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, this.extUri.basename(resource)); snippetsSync.ts ×2
429 > await this.fileService.createFile(resource, VSBuffer.fromString(acceptResult.content!), { overwrite: force });
430 > this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, this.extUri.basename(resource));
431 > }
433 > // Updated
434 > else {
435 > this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, this.extUri.basename(resource));
436 > await this.fileService.writeFile(resource, VSBuffer.fromString(acceptResult.content!), force ? undefined : fileContent!);
437 > this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, this.extUri.basename(resource));
438 > }
440 > }
441 > }
443 > private async updateRemoteSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
444 > const currentSnippets: IStringDictionary<string> = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {}; snippetsSync.ts ×4
445 > const newSnippets: IStringDictionary<string> = deepClone(currentSnippets);
446 >
447 > for (const { acceptResult, localResource, remoteResource, remoteChange } of resourcePreviews) {
448 > if (remoteChange !== Change.None) {
449 > const key = localResource ? this.extUri.basename(localResource) : this.extUri.basename(remoteResource);
450 > if (remoteChange === Change.Deleted) {
451 > delete newSnippets[key]; snippetsSync.ts ×2
452 > } else { snippetsSync.ts ×4
453 > newSnippets[key] = acceptResult.content!;
454 > }
455 > }
456 > }
457 >
458 > if (!areSame(currentSnippets, newSnippets)) {
459 > // update remote
460 > this.logService.trace(`${this.syncResourceLogLabel}: Updating remote snippets...`);
461 > remoteUserData = await this.updateRemoteUserData(JSON.stringify(newSnippets), forcePush ? null : remoteUserData.ref);
462 > this.logService.info(`${this.syncResourceLogLabel}: Updated remote snippets`);
463 > }
464 > return remoteUserData;
465 > }
467 > private parseSnippets(syncData: ISyncData): IStringDictionary<string> {
468 > return parseSnippets(syncData); snippetsSync.ts ×3
469 > }
471 > private toSnippetsContents(snippetsFileContents: IStringDictionary<IFileContent>): IStringDictionary<string> {
472 > const snippets: IStringDictionary<string> = {}; snippetsSync.ts ×16
473 > for (const key of Object.keys(snippetsFileContents)) {
474 > snippets[key] = snippetsFileContents[key].value.toString(); snippetsSync.ts ×11
475 > }
476 > return snippets; snippetsSync.ts ×16
477 > }
479 > private async getSnippetsFileContents(): Promise<IStringDictionary<IFileContent>> {
480 > const snippets: IStringDictionary<IFileContent> = {}; snippetsSync.ts ×16
481 > let stat: IFileStat;
482 > try {
483 > stat = await this.fileService.resolve(this.snippetsFolder);
484 > } catch (e) {
485 > // No snippets snippetsSync.ts ×2
486 > if (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
487 > return snippets;
488 > } else {
489 throw e;
490 }
492 > for (const entry of stat.children || []) { snippetsSync.ts ×16
493 > const resource = entry.resource; snippetsSync.ts ×11
494 > const extension = this.extUri.extname(resource);
495 > if (extension === '.json' || extension === '.code-snippets') {
496 > const key = this.extUri.relativePath(this.snippetsFolder, resource)!;
497 > const content = await this.fileService.readFile(resource);
498 > snippets[key] = content;
499 > }
500 > }
501 > return snippets;
504 >
505 > export class SnippetsInitializer extends AbstractInitializer {
506 >
507 > constructor(
508 @IFileService fileService: IFileService,
509 @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
510 @IEnvironmentService environmentService: IEnvironmentService,
511 @IUserDataSyncLogService logService: IUserDataSyncLogService,
512 @IStorageService storageService: IStorageService,
513 @IUriIdentityService uriIdentityService: IUriIdentityService,
514 ) {
515 super(SyncResource.Snippets, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
516 }
518 > protected async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
519 const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
520 if (!remoteSnippets) {
521 this.logService.info('Skipping initializing snippets because remote snippets does not exist.');
522 return;
523 }
524
525 const isEmpty = await this.isEmpty();
526 if (!isEmpty) {
527 this.logService.info('Skipping initializing snippets because local snippets exist.');
528 return;
529 }
530
531 for (const key of Object.keys(remoteSnippets)) {
532 const content = remoteSnippets[key];
533 if (content) {
534 const resource = this.extUri.joinPath(this.userDataProfilesService.defaultProfile.snippetsHome, key);
535 await this.fileService.createFile(resource, VSBuffer.fromString(content));
536 this.logService.info('Created snippet', this.extUri.basename(resource));
537 }
538 }
539
540 await this.updateLastSyncUserData(remoteUserData);
541 }
543 > private async isEmpty(): Promise<boolean> {
544 try {
545 const stat = await this.fileService.resolve(this.userDataProfilesService.defaultProfile.snippetsHome);
546 return !stat.children?.length;
547 } catch (error) {
548 return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
549 }
550 }
552 > }