abstractSynchronizer.ts ×49

Frontier kind: Code frontier

unlabeled · c_3e67f94f4260

348 tests · 21433 LOC · 133 files · introduces 0 tests · 2102 LOC · 24 files

Introduces — evidence that enters the hierarchy at this concept

Code
413 ranges2102 lines · 24 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2864 ranges21433 lines · 133 files · Browse complete extent
All tests (intent)
348 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

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 native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

Showing the top 20 of 24 files by introduced lines: 1970 of 2102 introduced LOC and 388 of 413 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/userDataSync/common/abstractSynchronizer.ts 249 introduced LOC · 49 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- abstractSynchronizer.ts
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 { equals } from '../../../base/common/arrays.js';
7 > import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from '../../../base/common/async.js';
8 > import { VSBuffer } from '../../../base/common/buffer.js';
9 > import { CancellationToken } from '../../../base/common/cancellation.js';
10 > import { IStringDictionary } from '../../../base/common/collections.js';
11 > import { Emitter, Event } from '../../../base/common/event.js';
12 > import { parse, ParseError } from '../../../base/common/json.js';
13 > import { FormattingOptions } from '../../../base/common/jsonFormatter.js';
14 > import { Disposable } from '../../../base/common/lifecycle.js';
15 > import { IExtUri } from '../../../base/common/resources.js';
16 > import { uppercaseFirstLetter } from '../../../base/common/strings.js';
17 > import { isString, isUndefined } from '../../../base/common/types.js';
18 > import { URI } from '../../../base/common/uri.js';
19 > import { IHeaders } from '../../../base/parts/request/common/request.js';
20 > import { localize } from '../../../nls.js';
21 > import { IConfigurationService } from '../../configuration/common/configuration.js';
22 > import { IEnvironmentService } from '../../environment/common/environment.js';
23 > import { FileChangesEvent, FileOperationError, FileOperationResult, IFileContent, IFileService, toFileOperationResult } from '../../files/common/files.js';
24 > import { ILogService } from '../../log/common/log.js';
25 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
26 > import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
27 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
28 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
29 > import {
30 > Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData,
31 > IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLocalStoreService,
32 > IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService,
33 > IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode,
34 > USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, getPathSegments, IUserDataSyncResourceConflicts,
35 > IUserDataSyncResource, IUserDataSyncResourcePreview,
36 > NON_EXISTING_RESOURCE_REF,
37 > } from './userDataSync.js';
38 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
39 >
40 > export function isRemoteUserData(thing: any): thing is IRemoteUserData {
41 if (thing
42 && (thing.ref !== undefined && typeof thing.ref === 'string' && thing.ref !== '')
47 return false;
48 }
50 > export function isSyncData(thing: any): thing is ISyncData {
51 if (thing
52 && (thing.version !== undefined && typeof thing.version === 'number')
66 return false;
67 }
69 > export function getSyncResourceLogLabel(syncResource: SyncResource, profile: IUserDataProfile): string {
70 return `${uppercaseFirstLetter(syncResource)}${profile.isDefault ? '' : ` (${profile.name})`}`;
71 }
73 > export interface IResourcePreview {
74 >
75 > readonly baseResource: URI;
76 > readonly baseContent: string | null;
77 >
78 > readonly remoteResource: URI;
79 > readonly remoteContent: string | null;
80 > readonly remoteChange: Change;
81 >
82 > readonly localResource: URI;
83 > readonly localContent: string | null;
84 > readonly localChange: Change;
85 >
86 > readonly previewResource: URI;
87 > readonly acceptedResource: URI;
88 > }
89 >
90 > export interface IAcceptResult {
91 > readonly content: string | null;
92 > readonly localChange: Change;
93 > readonly remoteChange: Change;
94 > }
95 >
96 > export interface IMergeResult extends IAcceptResult {
97 > readonly hasConflicts: boolean;
98 > }
99 >
100 > interface IEditableResourcePreview extends IBaseResourcePreview, IResourcePreview {
101 > localChange: Change;
102 > remoteChange: Change;
103 > mergeState: MergeState;
104 > acceptResult?: IAcceptResult;
105 > }
106 >
107 > export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
108 > readonly remoteUserData: IRemoteUserData;
109 > readonly lastSyncUserData: IRemoteUserData | null;
110 > readonly resourcePreviews: IEditableResourcePreview[];
111 > }
112 >
113 > interface ILastSyncUserDataState {
114 > readonly ref: string;
115 > readonly version: string | undefined;
116 > [key: string]: any;
117 > }
118 >
119 > export const enum SyncStrategy {
120 > Preview = 'preview', // Merge the local and remote data without applying.
121 > Merge = 'merge', // Merge the local and remote data and apply.
122 > PullOrPush = 'pull-push', // Pull the remote data or push the local data.
123 > }
124 >
125 > export abstract class AbstractSynchroniser extends Disposable implements IUserDataSynchroniser {
126 >
127 > private syncPreviewPromise: CancelablePromise<ISyncResourcePreview> | null = null;
128 >
129 > protected readonly syncFolder: URI;
130 > protected readonly syncPreviewFolder: URI;
131 > protected readonly extUri: IExtUri;
132 > protected readonly currentMachineIdPromise: Promise<string>;
133 >
134 > private _status: SyncStatus = SyncStatus.Idle;
135 > get status(): SyncStatus { return this._status; }
136 > private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
137 > readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
138 >
139 > private _conflicts: IBaseResourcePreview[] = [];
140 > get conflicts(): IUserDataSyncResourceConflicts { return { ...this.syncResource, conflicts: this._conflicts }; }
141 > private _onDidChangeConflicts = this._register(new Emitter<IUserDataSyncResourceConflicts>());
142 > readonly onDidChangeConflicts = this._onDidChangeConflicts.event;
143 >
144 > private readonly localChangeTriggerThrottler = this._register(new ThrottledDelayer<void>(50));
145 > private readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
146 > readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
147 >
148 > protected readonly lastSyncResource: URI;
149 > private readonly lastSyncUserDataStateKey: string;
150 > private hasSyncResourceStateVersionChanged: boolean = false;
151 > protected readonly syncResourceLogLabel: string;
152 >
153 > protected syncHeaders: IHeaders = {};
154 >
155 > readonly resource: SyncResource;
156 >
157 > constructor(
158 readonly syncResource: IUserDataSyncResource,
159 readonly collection: string | undefined,
179 this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
180 }
182 > protected triggerLocalChange(): void {
183 this.localChangeTriggerThrottler.trigger(() => this.doTriggerLocalChange());
184 }
186 > protected async doTriggerLocalChange(): Promise<void> {
187
188 // Sync again if current status is in conflicts
205 }
206 }
208 > protected setStatus(status: SyncStatus): void {
209 if (this._status !== status) {
210 this._status = status;
212 }
213 }
215 > async sync(refOrUserData: string | IUserData | null, preview: boolean = false, userDataSyncConfiguration: IUserDataSyncConfiguration = this.getUserDataSyncConfiguration(), headers: IHeaders = {}): Promise<IUserDataSyncResourcePreview | null> {
216 try {
217 this.syncHeaders = { ...headers };
248 }
249 }
251 > async apply(force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
252 try {
253 this.syncHeaders = { ...headers };
261 }
262 }
264 > async replace(content: string): Promise<boolean> {
265 const syncData = this.parseSyncData(content);
266 if (!syncData) {
297 return true;
298 }
300 > private async isRemoteDataFromCurrentMachine(remoteUserData: IRemoteUserData): Promise<boolean> {
301 const machineId = await this.currentMachineIdPromise;
302 return !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
303 }
305 > protected async getLatestRemoteUserData(refOrLatestData: string | IUserData | null, lastSyncUserData: IRemoteUserData | null): Promise<IRemoteUserData> {
306 if (refOrLatestData === null) {
307 return { ref: NON_EXISTING_RESOURCE_REF, syncData: null };
319 return this.getRemoteUserData(lastSyncUserData);
320 }
322 > private async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise<SyncStatus> {
323 if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
324 throw new UserDataSyncError(localize({ key: 'incompatible', comment: ['This is an error while syncing a resource that its local version is not compatible with its remote version.'] }, "Cannot sync {0} as its local version {1} is not compatible with its remote version {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.IncompatibleLocalContent, this.resource);
354 }
355 }
357 > protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise<SyncStatus> {
358 try {
359
409 }
410 }
412 > async accept(resource: URI, content?: string | null): Promise<ISyncResourcePreview | null> {
413 await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
414 const acceptResult = await this.getAcceptResult(resourcePreview, resource, content, CancellationToken.None);
421 return this.syncPreviewPromise;
422 }
424 > async discard(resource: URI): Promise<ISyncResourcePreview | null> {
425 await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
426 const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None);
434 return this.syncPreviewPromise;
435 }
437 > private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IEditableResourcePreview) => Promise<IEditableResourcePreview>): Promise<void> {
438 if (!this.syncPreviewPromise) {
439 return;
464 }
465 }
467 > private async doApply(force: boolean): Promise<SyncStatus> {
468 if (!this.syncPreviewPromise) {
469 return SyncStatus.Idle;
493 return SyncStatus.Idle;
494 }
496 > private async clearPreviewFolder(): Promise<void> {
497 try {
498 await this.fileService.del(this.syncPreviewFolder, { recursive: true });
499 } catch (error) { /* Ignore */ }
500 }
502 > private updateConflicts(resourcePreviews: IEditableResourcePreview[]): void {
503 const conflicts = resourcePreviews.filter(({ mergeState }) => mergeState === MergeState.Conflict);
504 if (!equals(this._conflicts, conflicts, (a, b) => this.extUri.isEqual(a.previewResource, b.previewResource))) {
507 }
508 }
510 > async hasPreviouslySynced(): Promise<boolean> {
511 const lastSyncData = await this.getLastSyncUserData();
512 return !!lastSyncData && lastSyncData.syncData !== null /* `null` sync data implies resource is not synced */;
513 }
515 > protected async resolvePreviewContent(uri: URI): Promise<string | null> {
516 const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
517 if (syncPreview) {
533 return null;
534 }
536 > async resetLocal(): Promise<void> {
537 this.storageService.remove(this.lastSyncUserDataStateKey, StorageScope.APPLICATION);
538 try {
544 }
545 }
547 > private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, merge: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, token: CancellationToken): Promise<ISyncResourcePreview> {
548 const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine, userDataSyncConfiguration, token);
549
589 return { syncResource: this.resource, profile: this.syncResource.profile, remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine: isRemoteDataFromCurrentMachine };
590 }
592 > async getLastSyncUserData(): Promise<IRemoteUserData | null> {
593 const storedLastSyncUserDataStateContent = this.getStoredLastSyncUserDataStateContent();
594
661 };
662 }
664 > protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
665 if (additionalProps['ref'] || additionalProps['version']) {
666 throw new Error('Cannot have core properties as additional');
677 await this.writeLastSyncStoredRemoteUserData(lastSyncRemoteUserData);
678 }
680 > private getStoredLastSyncUserDataStateContent(): string | undefined {
681 return this.storageService.get(this.lastSyncUserDataStateKey, StorageScope.APPLICATION);
682 }
684 > private async readLastSyncStoredRemoteUserData(): Promise<IRemoteUserData | undefined> {
685 const content = (await this.fileService.readFile(this.lastSyncResource)).value.toString();
686 try {
694 return undefined;
695 }
697 > private async writeLastSyncStoredRemoteUserData(lastSyncRemoteUserData: IRemoteUserData): Promise<void> {
698 await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncRemoteUserData)));
699 }
701 > async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData> {
702 const userData = await this.getUserData(lastSyncData);
703 return this.toRemoteUserData(userData);
704 }
706 > private toRemoteUserData({ ref, content }: IUserData): IRemoteUserData {
707 let syncData: ISyncData | null = null;
708 if (content !== null) {
711 return { ref, syncData };
712 }
714 > protected parseSyncData(content: string): ISyncData {
715 try {
716 const syncData: ISyncData = JSON.parse(content);
723 throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with the current version."), UserDataSyncErrorCode.IncompatibleRemoteContent, this.resource);
724 }
726 > private async getUserData(lastSyncData: IRemoteUserData | null): Promise<IUserData> {
727 const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null;
728 return this.userDataSyncStoreService.readResource(this.resource, lastSyncUserData, this.collection, this.syncHeaders);
729 }
731 > protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
732 const machineId = await this.currentMachineIdPromise;
733 const syncData: ISyncData = { version: this.version, machineId, content };
742 }
743 }
745 > protected async backupLocal(content: string): Promise<void> {
746 const syncData: ISyncData = { version: this.version, content };
747 return this.userDataSyncLocalStoreService.writeResource(this.resource, JSON.stringify(syncData), new Date(), this.syncResource.profile.isDefault ? undefined : this.syncResource.profile.id);
748 }
750 > async stop(): Promise<void> {
751 if (this.status === SyncStatus.Idle) {
752 return;
765 this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
766 }
768 > private getUserDataSyncConfiguration(): IUserDataSyncConfiguration {
769 return this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE);
770 }
772 > protected abstract readonly version: number;
773 > protected abstract generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, token: CancellationToken): Promise<IResourcePreview[]>;
774 > protected abstract getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise<IMergeResult>;
775 > protected abstract getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult>;
776 > protected abstract applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise<void>;
777 > protected abstract hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean>;
778 >
779 > abstract hasLocalData(): Promise<boolean>;
780 > abstract resolveContent(uri: URI): Promise<string | null>;
781 > }
782 >
783 > export interface IFileResourcePreview extends IResourcePreview {
784 > readonly fileContent: IFileContent | null;
785 > }
786 >
787 > export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
788 >
789 > constructor(
790 protected readonly file: URI,
791 syncResource: IUserDataSyncResource,
806 this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
807 }
809 > protected async getLocalFileContent(): Promise<IFileContent | null> {
810 try {
811 return await this.fileService.readFile(this.file);
814 }
815 }
817 > protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null, force: boolean): Promise<void> {
818 try {
819 if (oldContent) {
833 }
834 }
836 > protected async deleteLocalFile(): Promise<void> {
837 try {
838 await this.fileService.del(this.file);
843 }
844 }
846 > private onFileChanges(e: FileChangesEvent): void {
847 if (!e.contains(this.file)) {
848 return;
850 this.triggerLocalChange();
851 }
853 > }
854 >
855 > export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {
856 >
857 > constructor(
858 file: URI,
859 syncResource: IUserDataSyncResource,
881
882 private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
883 > protected getFormattingOptions(): Promise<FormattingOptions> { abstractSynchronizer.ts
884 if (!this._formattingOptions) {
885 this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.file);
887 return this._formattingOptions;
888 }
890 > }
891 >
892 > export abstract class AbstractInitializer implements IUserDataSyncResourceInitializer {
893 >
894 > protected readonly extUri: IExtUri;
895 > private readonly lastSyncResource: URI;
896 >
897 > constructor(
898 readonly resource: SyncResource,
899 @IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService,
907 this.lastSyncResource = getLastSyncResourceUri(undefined, this.resource, environmentService, this.extUri);
908 }
910 > async initialize({ ref, content }: IUserData): Promise<void> {
911 if (!content) {
912 this.logService.info('Remote content does not exist.', this.resource);
925 }
926 }
928 > private parseSyncData(content: string): ISyncData | undefined {
929 try {
930 const syncData: ISyncData = JSON.parse(content);
938 return undefined;
939 }
941 > protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
942 if (additionalProps['ref'] || additionalProps['version']) {
943 throw new Error('Cannot have core properties as additional');
953 await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncRemoteUserData)));
954 }
956 > protected abstract doInitialize(remoteUserData: IRemoteUserData): Promise<void>;
957 >
958 > }
src/vs/platform/userDataSync/common/userDataSyncService.ts 211 introduced LOC · 47 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataSyncService.ts
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 { equals } from '../../../base/common/arrays.js';
7 > import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from '../../../base/common/async.js';
8 > import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
9 > import { toErrorMessage } from '../../../base/common/errorMessage.js';
10 > import { Emitter, Event } from '../../../base/common/event.js';
11 > import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
12 > import { isEqual } from '../../../base/common/resources.js';
13 > import { isBoolean, isUndefined } from '../../../base/common/types.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import { generateUuid } from '../../../base/common/uuid.js';
16 > import { IConfigurationService } from '../../configuration/common/configuration.js';
17 > import { IExtensionGalleryService } from '../../extensionManagement/common/extensionManagement.js';
18 > import { IFileService } from '../../files/common/files.js';
19 > import { IInstantiationService } from '../../instantiation/common/instantiation.js';
20 > import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
21 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
22 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
23 > import { ExtensionsSynchroniser } from './extensionsSync.js';
24 > import { GlobalStateSynchroniser } from './globalStateSync.js';
25 > import { KeybindingsSynchroniser } from './keybindingsSync.js';
26 > import { PromptsSynchronizer } from './promptsSync/promptsSync.js';
27 > import { SettingsSynchroniser } from './settingsSync.js';
28 > import { SnippetsSynchroniser } from './snippetsSync.js';
29 > import { TasksSynchroniser } from './tasksSync.js';
30 > import { McpSynchroniser } from './mcpSync.js';
31 > import { UserDataProfilesManifestSynchroniser } from './userDataProfilesManifestSync.js';
32 > import {
33 > ALL_SYNC_RESOURCES, createSyncHeaders, IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError,
34 > IUserDataSyncResource, ISyncResourceHandle, IUserDataSyncTask, ISyncUserDataProfile, IUserDataManifest, IUserDataSyncConfiguration,
35 > IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService,
36 > SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService, IUserDataSyncActivityData, IUserDataSyncLocalStoreService,
37 > IUserDataSyncLatestData,
38 > IUserData,
39 > isUserDataManifest,
40 > } from './userDataSync.js';
41 >
42 > type SyncErrorClassification = {
43 > owner: 'sandy081';
44 > comment: 'Information about the error that occurred while syncing';
45 > code: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code' };
46 > service: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Settings Sync service for which this error has occurred' };
47 > serverCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Settings Sync service error code' };
48 > url?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Settings Sync resource URL for which this error has occurred' };
49 > resource?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Settings Sync resource for which this error has occurred' };
50 > executionId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Settings Sync execution id for which this error has occurred' };
51 > };
52 >
53 > type SyncErrorEvent = {
54 > code: string;
55 > service: string;
56 > serverCode?: string;
57 > url?: string;
58 > resource?: string;
59 > executionId?: string;
60 > };
61 >
62 > const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime';
63 >
64 > export class UserDataSyncService extends Disposable implements IUserDataSyncService {
65 >
66 > _serviceBrand: undefined;
67 >
68 > private _status: SyncStatus = SyncStatus.Uninitialized;
69 > get status(): SyncStatus { return this._status; }
70 > private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
71 > readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
72 >
73 > private _onDidChangeLocal = this._register(new Emitter<SyncResource>());
74 > readonly onDidChangeLocal = this._onDidChangeLocal.event;
75 >
76 > private _conflicts: IUserDataSyncResourceConflicts[] = [];
77 > get conflicts(): IUserDataSyncResourceConflicts[] { return this._conflicts; }
78 > private _onDidChangeConflicts = this._register(new Emitter<IUserDataSyncResourceConflicts[]>());
79 > readonly onDidChangeConflicts = this._onDidChangeConflicts.event;
80 >
81 > private _syncErrors: IUserDataSyncResourceError[] = [];
82 > private _onSyncErrors = this._register(new Emitter<IUserDataSyncResourceError[]>());
83 > readonly onSyncErrors = this._onSyncErrors.event;
84 >
85 > private _lastSyncTime: number | undefined = undefined;
86 > get lastSyncTime(): number | undefined { return this._lastSyncTime; }
87 > private _onDidChangeLastSyncTime: Emitter<number> = this._register(new Emitter<number>());
88 > readonly onDidChangeLastSyncTime: Event<number> = this._onDidChangeLastSyncTime.event;
89 >
90 > private _onDidResetLocal = this._register(new Emitter<void>());
91 > readonly onDidResetLocal = this._onDidResetLocal.event;
92 >
93 > private _onDidResetRemote = this._register(new Emitter<void>());
94 > readonly onDidResetRemote = this._onDidResetRemote.event;
95 >
96 > private activeProfileSynchronizers = new Map<string, [ProfileSynchronizer, IDisposable]>();
97 >
98 > constructor(
99 @IFileService private readonly fileService: IFileService,
100 @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
116 this._register(new RunOnceScheduler(() => this.cleanUpStaleStorageData(), 5 * 1000 /* after 5s */)).schedule();
117 }
119 > async createSyncTask(manifest: IUserDataManifest | null, disableCache?: boolean): Promise<IUserDataSyncTask> {
120 this.checkEnablement();
121
155 };
156 }
158 > async createManualSyncTask(): Promise<IUserDataManualSyncTask> {
159 this.checkEnablement();
160
230 };
231 }
233 > private async sync(manifestOrLatestData: IUserDataManifest | IUserDataSyncLatestData | null, preview: boolean, executionId: string, token: CancellationToken): Promise<void> {
234 this._syncErrors = [];
235 try {
258 }
259 }
261 > private async syncRemoteProfiles(remoteProfiles: ISyncUserDataProfile[], manifest: IUserDataManifest | IUserDataSyncLatestData | null, preview: boolean, executionId: string, token: CancellationToken): Promise<void> {
262 for (const syncProfile of remoteProfiles) {
263 if (token.isCancellationRequested) {
283 }
284 }
286 > private async applyManualSync(manifestOrLatestData: IUserDataManifest | IUserDataSyncLatestData | null, executionId: string, token: CancellationToken): Promise<void> {
287 try {
288 this.setStatus(SyncStatus.Syncing);
315 }
316 }
318 > private async syncProfile(profileSynchronizer: ProfileSynchronizer, manifestOrLatestData: IUserDataManifest | IUserDataSyncLatestData | null, preview: boolean, executionId: string, token: CancellationToken): Promise<IUserDataSyncResourceError[]> {
319 const errors = await profileSynchronizer.sync(manifestOrLatestData, preview, executionId, token);
320 return errors.map(([syncResource, error]) => ({ profile: profileSynchronizer.profile, syncResource, error }));
321 }
323 > private async stop(): Promise<void> {
324 if (this.status !== SyncStatus.Idle) {
325 await Promise.allSettled(this.getActiveProfileSynchronizers().map(profileSynchronizer => profileSynchronizer.stop()));
326 }
327 }
329 > async resolveContent(resource: URI): Promise<string | null> {
330 const content = await this.userDataSyncResourceProviderService.resolveContent(resource);
331 if (content) {
342 return null;
343 }
345 > async replace(syncResourceHandle: ISyncResourceHandle): Promise<void> {
346 this.checkEnablement();
347
366 return;
367 }
369 > async accept(syncResource: IUserDataSyncResource, resource: URI, content: string | null | undefined, apply: boolean | { force: boolean }): Promise<void> {
370 this.checkEnablement();
371
381 });
382 }
384 > async hasLocalData(): Promise<boolean> {
385 const result = await this.performAction(this.userDataProfilesService.defaultProfile, async synchronizer => {
386 // skip global state synchronizer
392 return !!result;
393 }
395 > async hasPreviouslySynced(): Promise<boolean> {
396 const result = await this.performAction(this.userDataProfilesService.defaultProfile, async synchronizer => {
397 if (await synchronizer.hasPreviouslySynced()) {
402 return !!result;
403 }
405 > async reset(): Promise<void> {
406 this.checkEnablement();
407 await this.resetRemote();
408 await this.resetLocal();
409 }
411 > async resetRemote(): Promise<void> {
412 this.checkEnablement();
413 try {
419 this._onDidResetRemote.fire();
420 }
422 > async resetLocal(): Promise<void> {
423 this.checkEnablement();
424 this._lastSyncTime = undefined;
435 this.logService.info('Did reset the local sync state.');
436 }
438 > private async cleanUpStaleStorageData(): Promise<void> {
439 const allKeys = this.storageService.keys(StorageScope.APPLICATION, StorageTarget.MACHINE);
440 const lastSyncProfileKeys: [string, string][] = [];
475 }
476 }
478 > async cleanUpRemoteData(): Promise<void> {
479 const remoteProfiles = await this.userDataSyncResourceProviderService.getRemoteSyncedProfiles();
480 const remoteProfileCollections = remoteProfiles.map(profile => profile.collection);
501 }
502 }
504 > async saveRemoteActivityData(location: URI): Promise<void> {
505 this.checkEnablement();
506 const data = await this.userDataSyncStoreService.getActivityData();
507 await this.fileService.writeFile(location, data);
508 }
510 > async extractActivityData(activityDataResource: URI, location: URI): Promise<void> {
511 const content = (await this.fileService.readFile(activityDataResource)).value.toString();
512 const activityData: IUserDataSyncActivityData = JSON.parse(content);
530 }
531 }
533 > private async performAction<T>(profile: IUserDataProfile, action: (synchroniser: IUserDataSynchroniser) => Promise<T | undefined>): Promise<T | null> {
534 const disposables = new DisposableStore();
535 try {
561 }
562 }
564 > private async performActionWithProfileSynchronizer<T>(profileSynchronizer: ProfileSynchronizer, action: (synchroniser: IUserDataSynchroniser) => Promise<T | undefined>, disposables: DisposableStore): Promise<T | undefined> {
565 const allSynchronizers = [...profileSynchronizer.enabled, ...profileSynchronizer.disabled.reduce<(IUserDataSynchroniser & IDisposable)[]>((synchronizers, syncResource) => {
566 if (syncResource !== SyncResource.WorkspaceState) {
577 return undefined;
578 }
580 > private setStatus(status: SyncStatus): void {
581 const oldStatus = this._status;
582 if (this._status !== status) {
588 }
589 }
591 > private updateConflicts(): void {
592 const conflicts = this.getActiveProfileSynchronizers().map(synchronizer => synchronizer.conflicts).flat();
593 if (!equals(this._conflicts, conflicts, (a, b) => a.profile.id === b.profile.id && a.syncResource === b.syncResource && equals(a.conflicts, b.conflicts, (a, b) => isEqual(a.previewResource, b.previewResource)))) {
596 }
597 }
599 > private updateLastSyncTime(): void {
600 if (this.status === SyncStatus.Idle) {
601 this._lastSyncTime = new Date().getTime();
604 }
605 }
607 > getOrCreateActiveProfileSynchronizer(profile: IUserDataProfile, syncProfile: ISyncUserDataProfile | undefined): ProfileSynchronizer {
608 let activeProfileSynchronizer = this.activeProfileSynchronizers.get(profile.id);
609 if (activeProfileSynchronizer && activeProfileSynchronizer[0].collection !== syncProfile?.collection) {
623 return activeProfileSynchronizer[0];
624 }
626 > private getActiveProfileSynchronizers(): ProfileSynchronizer[] {
627 const profileSynchronizers: ProfileSynchronizer[] = [];
628 for (const [profileSynchronizer] of this.activeProfileSynchronizers.values()) {
631 return profileSynchronizers;
632 }
634 > private clearActiveProfileSynchronizers(): void {
635 this.activeProfileSynchronizers.forEach(([, disposable]) => disposable.dispose());
636 this.activeProfileSynchronizers.clear();
637 }
639 > private checkEnablement(): void {
640 if (!this.userDataSyncStoreManagementService.userDataSyncStore) {
641 throw new Error('Not enabled');
642 }
643 }
645 > }
646 >
647 >
648 > class ProfileSynchronizer extends Disposable {
649 >
650 > private _enabled: [IUserDataSynchroniser, number, IDisposable][] = [];
651 > get enabled(): IUserDataSynchroniser[] { return this._enabled.sort((a, b) => a[1] - b[1]).map(([synchronizer]) => synchronizer); }
652 >
653 > get disabled(): SyncResource[] { return ALL_SYNC_RESOURCES.filter(syncResource => !this.userDataSyncEnablementService.isResourceEnabled(syncResource)); }
654 >
655 > private _status: SyncStatus = SyncStatus.Idle;
656 > get status(): SyncStatus { return this._status; }
657 > private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
658 > readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
659 >
660 > private _onDidChangeLocal = this._register(new Emitter<SyncResource>());
661 > readonly onDidChangeLocal = this._onDidChangeLocal.event;
662 >
663 > private _conflicts: IUserDataSyncResourceConflicts[] = [];
664 > get conflicts(): IUserDataSyncResourceConflicts[] { return this._conflicts; }
665 > private _onDidChangeConflicts = this._register(new Emitter<IUserDataSyncResourceConflicts[]>());
666 > readonly onDidChangeConflicts = this._onDidChangeConflicts.event;
667 >
668 > constructor(
669 readonly profile: IUserDataProfile,
670 readonly collection: string | undefined,
686 }
687 }
689 > private onDidChangeResourceEnablement(syncResource: SyncResource, enabled: boolean): void {
690 if (enabled) {
691 this.registerSynchronizer(syncResource);
694 }
695 }
697 > protected registerSynchronizer(syncResource: SyncResource): void {
698 if (this._enabled.some(([synchronizer]) => synchronizer.resource === syncResource)) {
699 return;
723 this._enabled.push([synchronizer, order, disposables]);
724 }
726 > private deRegisterSynchronizer(syncResource: SyncResource): void {
727 const index = this._enabled.findIndex(([synchronizer]) => synchronizer.resource === syncResource);
728 if (index !== -1) {
733 }
734 }
736 > createSynchronizer(syncResource: Exclude<SyncResource, SyncResource.WorkspaceState>): IUserDataSynchroniser & IDisposable {
737 switch (syncResource) {
738 case SyncResource.Settings: return this.instantiationService.createInstance(SettingsSynchroniser, this.profile, this.collection);
747 }
748 }
750 > async sync(manifestOrLatestData: IUserDataManifest | IUserDataSyncLatestData | null, preview: boolean, executionId: string, token: CancellationToken): Promise<[SyncResource, UserDataSyncError][]> {
751
752 // Return if cancellation is requested
797 }
798 }
800 > async apply(executionId: string, token: CancellationToken): Promise<void> {
801 const syncHeaders = createSyncHeaders(executionId);
802 for (const synchroniser of this.enabled) {
819 }
820 }
822 > async stop(): Promise<void> {
823 for (const synchroniser of this.enabled) {
824 try {
831 }
832 }
834 > async resetLocal(): Promise<void> {
835 for (const synchroniser of this.enabled) {
836 try {
842 }
843 }
845 > private async getUserDataSyncConfiguration(manifestOrLatestData: IUserDataManifest | IUserDataSyncLatestData | null): Promise<IUserDataSyncConfiguration> {
846 if (!this.profile.isDefault) {
847 return {};
855 return local;
856 }
858 > private getLocalUserDataSyncConfiguration(): IUserDataSyncConfiguration {
859 return this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE);
860 }
862 > private setStatus(status: SyncStatus): void {
863 if (this._status !== status) {
864 this._status = status;
866 }
867 }
869 > private updateStatus(): void {
870 this.updateConflicts();
871 if (this.enabled.some(s => s.status === SyncStatus.HasConflicts)) {
877 return this.setStatus(SyncStatus.Idle);
878 }
880 > private updateConflicts(): void {
881 const conflicts = this.enabled.filter(s => s.status === SyncStatus.HasConflicts)
882 .filter(s => s.conflicts.conflicts.length > 0)
887 }
888 }
890 > private getOrder(syncResource: SyncResource): number {
891 switch (syncResource) {
892 case SyncResource.Settings: return 0;
902 }
903 }
905 >
906 function canBailout(e: unknown): boolean {
907 if (e instanceof UserDataSyncError) {
922 return false;
923 }
925 function reportUserDataSyncError(userDataSyncError: UserDataSyncError, executionId: string, userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, telemetryService: ITelemetryService): void {
926 telemetryService.publicLog2<SyncErrorEvent, SyncErrorClassification>('sync/error',
934 });
935 }
937 function getRefOrUserData(manifestOrLatestData: IUserDataManifest | IUserDataSyncLatestData | null, collection: string | undefined, resource: SyncResource): string | IUserData | undefined {
938 if (isUserDataManifest(manifestOrLatestData)) {
src/vs/platform/userDataSync/common/userDataSyncStoreService.ts 182 introduced LOC · 36 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataSyncStoreService.ts
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 { CancelablePromise, createCancelablePromise, timeout } from '../../../base/common/async.js';
7 > import { CancellationToken } from '../../../base/common/cancellation.js';
8 > import { getErrorMessage, isCancellationError } from '../../../base/common/errors.js';
9 > import { Emitter, Event } from '../../../base/common/event.js';
10 > import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
11 > import { Mimes } from '../../../base/common/mime.js';
12 > import { isWeb } from '../../../base/common/platform.js';
13 > import { ConfigurationSyncStore } from '../../../base/common/product.js';
14 > import { joinPath, relativePath } from '../../../base/common/resources.js';
15 > import { isObject, isString } from '../../../base/common/types.js';
16 > import { URI } from '../../../base/common/uri.js';
17 > import { generateUuid } from '../../../base/common/uuid.js';
18 > import { IHeaders, IRequestContext, IRequestOptions } from '../../../base/parts/request/common/request.js';
19 > import { IConfigurationService } from '../../configuration/common/configuration.js';
20 > import { IEnvironmentService } from '../../environment/common/environment.js';
21 > import { IFileService } from '../../files/common/files.js';
22 > import { IProductService } from '../../product/common/productService.js';
23 > import { asJson, asText, asTextOrError, hasNoContent, IRequestService, isSuccess, isSuccess as isSuccessContext } from '../../request/common/request.js';
24 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
25 > import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
26 > import { HEADER_EXECUTION_ID, HEADER_OPERATION_ID, IAuthenticationProvider, IResourceRefHandle, IUserData, IUserDataManifest, IUserDataSyncLatestData, IUserDataSyncLogService, IUserDataSyncStore, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, ServerResource, SYNC_SERVICE_URL_TYPE, UserDataSyncErrorCode, UserDataSyncStoreError, UserDataSyncStoreType } from './userDataSync.js';
27 > import { VSBufferReadableStream } from '../../../base/common/buffer.js';
28 > import { IStringDictionary } from '../../../base/common/collections.js';
29 >
30 > type IDownloadLatestDataType = {
31 > resources?: {
32 > [resourceId: string]: [IUserData];
33 > };
34 > collections?: {
35 > [collectionId: string]: {
36 > resources?: {
37 > [resourceId: string]: [IUserData];
38 > } | undefined;
39 > };
40 > };
41 > };
42 >
43 > const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
44 > const SYNC_PREVIOUS_STORE = 'sync.previous.store';
45 > const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until';
46 > const USER_SESSION_ID_KEY = 'sync.user-session-id';
47 > const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id';
48 > const REQUEST_SESSION_LIMIT = 100;
49 > const REQUEST_SESSION_INTERVAL = 1000 * 60 * 5; /* 5 minutes */
50 >
51 > type UserDataSyncStore = IUserDataSyncStore & { defaultType: UserDataSyncStoreType };
52 >
53 > export abstract class AbstractUserDataSyncStoreManagementService extends Disposable implements IUserDataSyncStoreManagementService {
54 >
55 > _serviceBrand: undefined;
56 >
57 > private readonly _onDidChangeUserDataSyncStore = this._register(new Emitter<void>());
58 > readonly onDidChangeUserDataSyncStore = this._onDidChangeUserDataSyncStore.event;
59 > private _userDataSyncStore: UserDataSyncStore | undefined;
60 > get userDataSyncStore(): UserDataSyncStore | undefined { return this._userDataSyncStore; }
61 >
62 > protected get userDataSyncStoreType(): UserDataSyncStoreType | undefined {
63 return this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.APPLICATION) as UserDataSyncStoreType;
64 }
65 > protected set userDataSyncStoreType(type: UserDataSyncStoreType | undefined) { userDataSyncStoreService.ts
66 this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.APPLICATION, isWeb ? StorageTarget.USER /* sync in web */ : StorageTarget.MACHINE);
67 }
69 > constructor(
70 @IProductService protected readonly productService: IProductService,
71 @IConfigurationService protected readonly configurationService: IConfigurationService,
77 this._register(Event.filter(storageService.onDidChangeValue(StorageScope.APPLICATION, SYNC_SERVICE_URL_TYPE, disposable), () => this.userDataSyncStoreType !== this.userDataSyncStore?.type, disposable)(() => this.updateUserDataSyncStore()));
78 }
80 > protected updateUserDataSyncStore(): void {
81 this._userDataSyncStore = this.toUserDataSyncStore(this.productService[CONFIGURATION_SYNC_STORE_KEY]);
82 this._onDidChangeUserDataSyncStore.fire();
83 }
85 > protected toUserDataSyncStore(configurationSyncStore: ConfigurationSyncStore & { web?: ConfigurationSyncStore } | undefined): UserDataSyncStore | undefined {
86 if (!configurationSyncStore) {
87 return undefined;
116 return undefined;
117 }
119 > abstract switch(type: UserDataSyncStoreType): Promise<void>;
120 > abstract getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined>;
121 >
122 > }
123 >
124 > export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService {
125 >
126 > private readonly previousConfigurationSyncStore: ConfigurationSyncStore | undefined;
127 >
128 > constructor(
129 @IProductService productService: IProductService,
130 @IConfigurationService configurationService: IConfigurationService,
145 }
146 }
148 > async switch(type: UserDataSyncStoreType): Promise<void> {
149 if (type !== this.userDataSyncStoreType) {
150 this.userDataSyncStoreType = type;
152 }
153 }
155 > async getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined> {
156 return this.toUserDataSyncStore(this.previousConfigurationSyncStore);
157 }
159 >
160 > export class UserDataSyncStoreClient extends Disposable {
161 >
162 > private userDataSyncStoreUrl: URI | undefined;
163 >
164 > private authToken: { token: string; type: string } | undefined;
165 > private readonly commonHeadersPromise: Promise<IHeaders>;
166 > private readonly session: RequestsSession;
167 >
168 > private _onTokenFailed = this._register(new Emitter<UserDataSyncErrorCode>());
169 > readonly onTokenFailed = this._onTokenFailed.event;
170 >
171 > private _onTokenSucceed: Emitter<void> = this._register(new Emitter<void>());
172 > readonly onTokenSucceed: Event<void> = this._onTokenSucceed.event;
173 >
174 > private _donotMakeRequestsUntil: Date | undefined = undefined;
175 > get donotMakeRequestsUntil() { return this._donotMakeRequestsUntil; }
176 > private _onDidChangeDonotMakeRequestsUntil = this._register(new Emitter<void>());
177 > readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
178 >
179 > constructor(
180 userDataSyncStoreUrl: URI | undefined,
181 @IProductService productService: IProductService,
210 }));
211 }
213 > setAuthToken(token: string, type: string): void {
214 this.authToken = { token, type };
215 }
217 > protected updateUserDataSyncStoreUrl(userDataSyncStoreUrl: URI | undefined): void {
218 this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined;
219 }
221 > private initDonotMakeRequestsUntil(): void {
222 const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.APPLICATION);
223 if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) {
225 }
226 }
228 > private resetDonotMakeRequestsUntilPromise: CancelablePromise<void> | undefined = undefined;
229 > private setDonotMakeRequestsUntil(donotMakeRequestsUntil: Date | undefined): void {
230 if (this._donotMakeRequestsUntil?.getTime() !== donotMakeRequestsUntil?.getTime()) {
231 this._donotMakeRequestsUntil = donotMakeRequestsUntil;
247 }
248 }
250 > // #region Collection
251 >
252 > async getAllCollections(headers: IHeaders = {}): Promise<string[]> {
253 if (!this.userDataSyncStoreUrl) {
254 throw new Error('No settings sync store url configured.');
263 return (await asJson<{ id: string }[]>(context))?.map(({ id }) => id) || [];
264 }
266 > async createCollection(headers: IHeaders = {}): Promise<string> {
267 if (!this.userDataSyncStoreUrl) {
268 throw new Error('No settings sync store url configured.');
280 return collectionId;
281 }
283 > async deleteCollection(collection?: string, headers: IHeaders = {}): Promise<void> {
284 if (!this.userDataSyncStoreUrl) {
285 throw new Error('No settings sync store url configured.');
291 await this.request(url, { type: 'DELETE', headers, callSite: 'userDataSync.deleteCollection' }, [], CancellationToken.None);
292 }
294 > // #endregion
295 >
296 > // #region Resource
297 >
298 > async getAllResourceRefs(resource: ServerResource, collection?: string): Promise<IResourceRefHandle[]> {
299 if (!this.userDataSyncStoreUrl) {
300 throw new Error('No settings sync store url configured.');
309 return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
310 }
312 > async resolveResourceContent(resource: ServerResource, ref: string, collection?: string, headers: IHeaders = {}): Promise<string | null> {
313 if (!this.userDataSyncStoreUrl) {
314 throw new Error('No settings sync store url configured.');
323 return content;
324 }
326 > async deleteResource(resource: ServerResource, ref: string | null, collection?: string): Promise<void> {
327 if (!this.userDataSyncStoreUrl) {
328 throw new Error('No settings sync store url configured.');
334 await this.request(url, { type: 'DELETE', headers, callSite: 'userDataSync.deleteResource' }, [], CancellationToken.None);
335 }
337 > async deleteResources(): Promise<void> {
338 if (!this.userDataSyncStoreUrl) {
339 throw new Error('No settings sync store url configured.');
345 await this.request(url, { type: 'DELETE', headers, callSite: 'userDataSync.deleteResources' }, [], CancellationToken.None);
346 }
348 > async readResource(resource: ServerResource, oldValue: IUserData | null, collection?: string, headers: IHeaders = {}): Promise<IUserData> {
349 if (!this.userDataSyncStoreUrl) {
350 throw new Error('No settings sync store url configured.');
382 return userData;
383 }
385 > async writeResource(resource: ServerResource, data: string, ref: string | null, collection?: string, headers: IHeaders = {}): Promise<string> {
386 if (!this.userDataSyncStoreUrl) {
387 throw new Error('No settings sync store url configured.');
403 return newRef;
404 }
406 > // #endregion
407 >
408 > async manifest(oldValue: IUserDataManifest | null, headers: IHeaders = {}): Promise<IUserDataManifest | null> {
409 if (!this.userDataSyncStoreUrl) {
410 throw new Error('No settings sync store url configured.');
460 return manifest;
461 }
463 > async clear(): Promise<void> {
464 if (!this.userDataSyncStoreUrl) {
465 throw new Error('No settings sync store url configured.');
472 this.clearSession();
473 }
475 > async getLatestData(headers: IHeaders = {}): Promise<IUserDataSyncLatestData | null> {
476 if (!this.userDataSyncStoreUrl) {
477 throw new Error('No settings sync store url configured.');
522 return result;
523 }
525 > async getActivityData(): Promise<VSBufferReadableStream> {
526 if (!this.userDataSyncStoreUrl) {
527 throw new Error('No settings sync store url configured.');
543 return context.stream;
544 }
546 > private getResourceUrl(userDataSyncStoreUrl: URI, collection: string | undefined, resource: ServerResource): URI {
547 return collection ? joinPath(userDataSyncStoreUrl, 'collection', collection, 'resource', resource) : joinPath(userDataSyncStoreUrl, 'resource', resource);
548 }
550 > private clearSession(): void {
551 this.storageService.remove(USER_SESSION_ID_KEY, StorageScope.APPLICATION);
552 this.storageService.remove(MACHINE_SESSION_ID_KEY, StorageScope.APPLICATION);
553 }
555 > private async request(url: string, options: IRequestOptions, successCodes: number[], token: CancellationToken): Promise<IRequestContext> {
556 if (!this.authToken) {
557 throw new UserDataSyncStoreError('No Auth Token Available', url, UserDataSyncErrorCode.Unauthorized, undefined, undefined);
684 return context;
685 }
687 > private addSessionHeaders(headers: IHeaders): void {
688 let machineSessionId = this.storageService.get(MACHINE_SESSION_ID_KEY, StorageScope.APPLICATION);
689 if (machineSessionId === undefined) {
698 }
699 }
701 > }
702 >
703 > export class UserDataSyncStoreService extends UserDataSyncStoreClient implements IUserDataSyncStoreService {
704 >
705 > _serviceBrand: undefined;
706 >
707 > constructor(
708 @IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
709 @IProductService productService: IProductService,
717 this._register(userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.updateUserDataSyncStoreUrl(userDataSyncStoreManagementService.userDataSyncStore?.url)));
718 }
720 > }
721 >
722 > export class RequestsSession {
723 >
724 > private requests: string[] = [];
725 > private startTime: Date | undefined = undefined;
726 >
727 > constructor(
728 private readonly limit: number,
729 private readonly interval: number, /* in ms */
731 private readonly logService: IUserDataSyncLogService,
732 ) { }
734 > request(url: string, options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
735 if (this.isExpired()) {
736 this.reset();
749 return this.requestService.request(options, token);
750 }
752 > private isExpired(): boolean {
753 return this.startTime !== undefined && new Date().getTime() - this.startTime.getTime() > this.interval;
754 }
756 > private reset(): void {
757 this.requests = [];
758 this.startTime = undefined;
759 }
761 > }
src/vs/platform/userDataSync/test/common/userDataSyncClient.ts 137 introduced LOC · 22 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataSyncClient.ts
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 { bufferToStream, VSBuffer } from '../../../../base/common/buffer.js';
7 > import { CancellationToken } from '../../../../base/common/cancellation.js';
8 > import { IStringDictionary } from '../../../../base/common/collections.js';
9 > import { Emitter, Event } from '../../../../base/common/event.js';
10 > import { FormattingOptions } from '../../../../base/common/jsonFormatter.js';
11 > import { Disposable } from '../../../../base/common/lifecycle.js';
12 > import { Schemas } from '../../../../base/common/network.js';
13 > import { joinPath } from '../../../../base/common/resources.js';
14 > import { URI } from '../../../../base/common/uri.js';
15 > import { generateUuid } from '../../../../base/common/uuid.js';
16 > import { IHeaders, IRequestContext, IRequestOptions } from '../../../../base/parts/request/common/request.js';
17 > import { IConfigurationService } from '../../../configuration/common/configuration.js';
18 > import { ConfigurationService } from '../../../configuration/common/configurationService.js';
19 > import { IEnvironmentService } from '../../../environment/common/environment.js';
20 > import { GlobalExtensionEnablementService } from '../../../extensionManagement/common/extensionEnablementService.js';
21 > import { DidUninstallExtensionEvent, IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallExtensionResult } from '../../../extensionManagement/common/extensionManagement.js';
22 > import { IFileService } from '../../../files/common/files.js';
23 > import { FileService } from '../../../files/common/fileService.js';
24 > import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js';
25 > import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js';
26 > import { ILogService, NullLogService } from '../../../log/common/log.js';
27 > import product from '../../../product/common/product.js';
28 > import { IProductService } from '../../../product/common/productService.js';
29 > import { AuthInfo, Credentials, IRequestCompleteEvent, IRequestService } from '../../../request/common/request.js';
30 > import { InMemoryStorageService, IStorageService } from '../../../storage/common/storage.js';
31 > import { ITelemetryService } from '../../../telemetry/common/telemetry.js';
32 > import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js';
33 > import { IUriIdentityService } from '../../../uriIdentity/common/uriIdentity.js';
34 > import { UriIdentityService } from '../../../uriIdentity/common/uriIdentityService.js';
35 > import { ExtensionStorageService, IExtensionStorageService } from '../../../extensionManagement/common/extensionStorage.js';
36 > import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from '../../common/ignoredExtensions.js';
37 > import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser, IUserDataResourceManifest, IUserDataCollectionManifest, USER_DATA_SYNC_SCHEME, IUserDataManifest } from '../../common/userDataSync.js';
38 > import { IUserDataSyncAccountService, UserDataSyncAccountService } from '../../common/userDataSyncAccount.js';
39 > import { UserDataSyncLocalStoreService } from '../../common/userDataSyncLocalStoreService.js';
40 > import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from '../../common/userDataSyncMachines.js';
41 > import { UserDataSyncEnablementService } from '../../common/userDataSyncEnablementService.js';
42 > import { UserDataSyncService } from '../../common/userDataSyncService.js';
43 > import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from '../../common/userDataSyncStoreService.js';
44 > import { InMemoryUserDataProfilesService, IUserDataProfile, IUserDataProfilesService } from '../../../userDataProfile/common/userDataProfile.js';
45 > import { NullPolicyService } from '../../../policy/common/policy.js';
46 > import { IUserDataProfileStorageService } from '../../../userDataProfile/common/userDataProfileStorageService.js';
47 > import { TestUserDataProfileStorageService } from '../../../userDataProfile/test/common/userDataProfileStorageService.test.js';
48 > import { IMeteredConnectionService } from '../../../meteredConnection/common/meteredConnection.js';
49 >
50 > export class UserDataSyncClient extends Disposable {
51 >
52 > readonly instantiationService: TestInstantiationService;
53 >
54 > constructor(readonly testServer: UserDataSyncTestServer = new UserDataSyncTestServer()) {
55 super();
56 this.instantiationService = this._register(new TestInstantiationService());
57 }
59 > async setUp(empty: boolean = false): Promise<void> {
60 this._register(registerConfiguration());
61
151 .setResourceEnablement(SyncResource.Prompts, true);
152 }
154 > async sync(): Promise<void> {
155 await (await this.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run();
156 }
158 > read(resource: SyncResource, collection?: string): Promise<IUserData> {
159 return this.instantiationService.get(IUserDataSyncStoreService).readResource(resource, null, collection);
160 }
162 > async getLatestRef(resource: SyncResource): Promise<string | null> {
163 const manifest = await this._getResourceManifest();
164 return manifest?.[resource] ?? null;
165 }
167 > async _getResourceManifest(): Promise<IUserDataResourceManifest | null> {
168 const manifest = await this.instantiationService.get(IUserDataSyncStoreService).manifest(null);
169 return manifest?.latest ?? null;
170 }
172 > getSynchronizer(source: SyncResource): IUserDataSynchroniser {
173 return (this.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getOrCreateActiveProfileSynchronizer(this.instantiationService.get(IUserDataProfilesService).defaultProfile, undefined).enabled.find(s => s.resource === source)!;
174 }
176 > }
177 >
178 > const ALL_SERVER_RESOURCES: ServerResource[] = [...ALL_SYNC_RESOURCES, 'machines'];
179 >
180 > export class UserDataSyncTestServer implements IRequestService {
181 >
182 > _serviceBrand: undefined;
183 >
184 > readonly onDidCompleteRequest = Event.None as Event<IRequestCompleteEvent>;
185 >
186 > readonly url: string = 'http://host:3000';
187 > private session: string | null = null;
188 > private readonly collections = new Map<string, Map<ServerResource, IUserData>>();
189 > private readonly data = new Map<ServerResource, IUserData>();
190 >
191 > private _requests: { url: string; type: string; headers?: IHeaders }[] = [];
192 > get requests(): { url: string; type: string; headers?: IHeaders }[] { return this._requests; }
193 >
194 > private _requestsWithAllHeaders: { url: string; type: string; headers?: IHeaders }[] = [];
195 > get requestsWithAllHeaders(): { url: string; type: string; headers?: IHeaders }[] { return this._requestsWithAllHeaders; }
196 >
197 > private _responses: { status: number }[] = [];
198 > get responses(): { status: number }[] { return this._responses; }
199 > reset(): void { this._requests = []; this._responses = []; this._requestsWithAllHeaders = []; }
200 >
201 > private manifestRef = 0;
202 > private collectionCounter = 0;
203 >
204 > constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER, private readonly retryAfter?: number) { }
205 >
206 > async resolveProxy(url: string): Promise<string | undefined> { return url; }
207 > async lookupAuthorization(authInfo: AuthInfo): Promise<Credentials | undefined> { return undefined; }
208 > async lookupKerberosAuthorization(url: string): Promise<string | undefined> { return undefined; }
209 > async loadCertificates(): Promise<string[]> { return []; }
210 >
211 > async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
212 if (this._requests.length === this.rateLimit) {
213 return this.toResponse(429, this.retryAfter ? { 'retry-after': `${this.retryAfter}` } : undefined);
228 return requestContext;
229 }
231 > private async doRequest(options: IRequestOptions): Promise<IRequestContext> {
232 const versionUrl = `${this.url}/v1/`;
233 const relativePath = options.url!.indexOf(versionUrl) === 0 ? options.url!.substring(versionUrl.length) : undefined;
263 return this.toResponse(501);
264 }
266 > private async getManifest(headers?: IHeaders): Promise<IRequestContext> {
267 if (this.session) {
268 const latest: Record<ServerResource, string> = Object.create({});
285 return this.toResponse(204, { etag: `${this.manifestRef++}` });
286 }
288 > private async getResourceData(collection: string | undefined, resource: string, ref?: string, headers: IHeaders = {}): Promise<IRequestContext> {
289 const collectionData = collection ? this.collections.get(collection) : this.data;
290 if (!collectionData) {
308 return this.toResponse(204);
309 }
311 > private async writeData(collection: string | undefined, resource: string, content: string = '', headers: IHeaders = {}): Promise<IRequestContext> {
312 if (!this.session) {
313 this.session = generateUuid();
329 return this.toResponse(204);
330 }
332 > private async deleteResourceData(collection: string | undefined, resource: string, headers: IHeaders = {}): Promise<IRequestContext> {
333 const collectionData = collection ? this.collections.get(collection) : this.data;
334 if (!collectionData) {
344 return this.toResponse(404);
345 }
347 > private async createCollection(): Promise<IRequestContext> {
348 const collectionId = `${++this.collectionCounter}`;
349 this.collections.set(collectionId, new Map());
350 return this.toResponse(200, {}, collectionId);
351 }
353 > async clear(headers?: IHeaders): Promise<IRequestContext> {
354 this.collections.clear();
355 this.data.clear();
358 return this.toResponse(204);
359 }
361 > private toResponse(statusCode: number, headers?: IHeaders, data?: string): IRequestContext {
362 return {
363 res: {
368 };
369 }
371 >
372 > export class TestUserDataSyncUtilService implements IUserDataSyncUtilService {
373 >
374 > _serviceBrand: undefined;
375 >
376 > async resolveDefaultCoreIgnoredSettings(): Promise<string[]> {
377 return getDefaultIgnoredSettings();
378 }
380 > async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
381 const keys: IStringDictionary<string> = {};
382 for (const keybinding of userbindings) {
385 return keys;
386 }
388 > async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> {
389 return { eol: '\n', insertSpaces: false, tabSize: 4 };
390 }
392 > }
393 >
394 > class TestStorageService extends InMemoryStorageService {
395 > constructor(private readonly profileStorageProfile: IUserDataProfile) {
396 super();
397 }
398 > override hasScope(profile: IUserDataProfile): boolean { userDataSyncClient.ts
399 return this.profileStorageProfile.id === profile.id;
400 }
src/vs/platform/userDataSync/common/extensionsSync.ts 133 introduced LOC · 25 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extensionsSync.ts
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 { Promises } from '../../../base/common/async.js';
7 > import { CancellationToken } from '../../../base/common/cancellation.js';
8 > import { IStringDictionary } from '../../../base/common/collections.js';
9 > import { getErrorMessage } from '../../../base/common/errors.js';
10 > import { Event } from '../../../base/common/event.js';
11 > import { toFormattedString } from '../../../base/common/jsonFormatter.js';
12 > import { DisposableStore } from '../../../base/common/lifecycle.js';
13 > import { compare } from '../../../base/common/strings.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import { IConfigurationService } from '../../configuration/common/configuration.js';
16 > import { IEnvironmentService } from '../../environment/common/environment.js';
17 > import { GlobalExtensionEnablementService } from '../../extensionManagement/common/extensionEnablementService.js';
18 > import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension, DISABLED_EXTENSIONS_STORAGE_PATH, EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, EXTENSION_INSTALL_SOURCE_CONTEXT, InstallExtensionInfo, ExtensionInstallSource, EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT } from '../../extensionManagement/common/extensionManagement.js';
19 > import { areSameExtensions } from '../../extensionManagement/common/extensionManagementUtil.js';
20 > import { ExtensionStorageService, IExtensionStorageService } from '../../extensionManagement/common/extensionStorage.js';
21 > import { ExtensionType, IExtensionIdentifier, isApplicationScopedExtension } from '../../extensions/common/extensions.js';
22 > import { IFileService } from '../../files/common/files.js';
23 > import { IInstantiationService } from '../../instantiation/common/instantiation.js';
24 > import { ServiceCollection } from '../../instantiation/common/serviceCollection.js';
25 > import { ILogService } from '../../log/common/log.js';
26 > import { IStorageService } from '../../storage/common/storage.js';
27 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
28 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
29 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
30 > import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview } from './abstractSynchronizer.js';
31 > import { IMergeResult as IExtensionMergeResult, merge } from './extensionsMerge.js';
32 > import { IIgnoredExtensionsManagementService } from './ignoredExtensions.js';
33 > import { Change, IRemoteUserData, ISyncData, ISyncExtension, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ILocalSyncExtension } from './userDataSync.js';
34 > import { IUserDataProfileStorageService } from '../../userDataProfile/common/userDataProfileStorageService.js';
35 > import { IProductService } from '../../product/common/productService.js';
36 >
37 > type IExtensionResourceMergeResult = IAcceptResult & IExtensionMergeResult;
38 >
39 > interface IExtensionResourcePreview extends IResourcePreview {
40 > readonly localExtensions: ILocalSyncExtension[];
41 > readonly remoteExtensions: ISyncExtension[] | null;
42 > readonly skippedExtensions: ISyncExtension[];
43 > readonly builtinExtensions: IExtensionIdentifier[] | null;
44 > readonly previewResult: IExtensionResourceMergeResult;
45 > }
46 >
47 > interface ILastSyncUserData extends IRemoteUserData {
48 > skippedExtensions: ISyncExtension[] | undefined;
49 > builtinExtensions: IExtensionIdentifier[] | undefined;
50 > }
51 >
52 async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagementService: IExtensionManagementService): Promise<ISyncExtension[]> {
53 const extensions = JSON.parse(syncData.content);
77 return extensions;
78 }
80 > export function parseExtensions(syncData: ISyncData): ISyncExtension[] {
81 return JSON.parse(syncData.content);
82 }
84 > export function stringify(extensions: ISyncExtension[], format: boolean): string {
85 extensions.sort((e1, e2) => {
86 if (!e1.identifier.uuid && e2.identifier.uuid) {
94 return format ? toFormattedString(extensions, {}) : JSON.stringify(extensions);
95 }
97 > export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
98 >
99 > /*
100 > Version 3 - Introduce installed property to skip installing built in extensions
101 > protected readonly version: number = 3;
102 > */
103 > /* Version 4: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
104 > /* Version 5: Introduce extension state */
105 > /* Version 6: Added isApplicationScoped property */
106 > protected readonly version: number = 6;
107 >
108 > private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json');
109 > private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
110 > private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
111 > private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
112 > private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
113 >
114 > private readonly localExtensionsProvider: LocalExtensionsProvider;
115 >
116 > constructor(
117 // profileLocation changes for default profile
118 profile: IUserDataProfile,
143 extensionStorageService.onDidChangeExtensionStorageToSync)(() => this.triggerLocalChange()));
144 }
146 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
147 const remoteExtensions = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
148 const skippedExtensions = lastSyncUserData?.skippedExtensions ?? [];
185 }];
186 }
188 > protected async hasRemoteChanged(lastSyncUserData: ILastSyncUserData): Promise<boolean> {
189 const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
190 const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.syncResource.profile);
192 return remote !== null;
193 }
195 > private getPreviewContent(localExtensions: ISyncExtension[], added: ISyncExtension[], updated: ISyncExtension[], removed: IExtensionIdentifier[]): string {
196 const preview: ISyncExtension[] = [...added, ...updated];
197
216 return this.stringify(preview, false);
217 }
219 > protected async getMergeResult(resourcePreview: IExtensionResourcePreview, token: CancellationToken): Promise<IMergeResult> {
220 return { ...resourcePreview.previewResult, hasConflicts: false };
221 }
223 > protected async getAcceptResult(resourcePreview: IExtensionResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IExtensionResourceMergeResult> {
224
225 /* Accept local resource */
240 throw new Error(`Invalid Resource: ${resource.toString()}`);
241 }
243 > private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
244 const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.syncResource.profile.extensionsResource);
245 const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
255 };
256 }
258 > private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
259 const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.syncResource.profile.extensionsResource);
260 const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
280 }
281 }
283 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IExtensionResourcePreview, IExtensionResourceMergeResult][], force: boolean): Promise<void> {
284 let { skippedExtensions, builtinExtensions, localExtensions } = resourcePreviews[0][0];
285 const { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
310 }
311 }
313 > private computeBuiltinExtensions(localExtensions: ILocalSyncExtension[], previousBuiltinExtensions: IExtensionIdentifier[] | null): IExtensionIdentifier[] {
314 const localExtensionsSet = new Set<string>();
315 const builtinExtensions: IExtensionIdentifier[] = [];
330 return builtinExtensions;
331 }
333 > async resolveContent(uri: URI): Promise<string | null> {
334 if (this.extUri.isEqual(this.remoteResource, uri)
335 || this.extUri.isEqual(this.baseResource, uri)
342 return null;
343 }
345 > private stringify(extensions: ISyncExtension[], format: boolean): string {
346 return stringify(extensions, format);
347 }
349 > async hasLocalData(): Promise<boolean> {
350 try {
351 const { localExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.syncResource.profile);
358 return false;
359 }
361 > }
362 >
363 > export class LocalExtensionsProvider {
364 >
365 > constructor(
366 @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
367 @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService,
372 @IProductService private readonly productService: IProductService,
373 ) { }
375 > async getLocalExtensions(profile: IUserDataProfile): Promise<{ localExtensions: ILocalSyncExtension[]; ignoredExtensions: string[] }> {
376 const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource);
377 const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
413 return { localExtensions, ignoredExtensions };
414 }
416 > async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[], profile: IUserDataProfile): Promise<ISyncExtension[]> {
417 const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.Extensions, profile);
418 const extensionsToInstall: InstallExtensionInfo[] = [];
562 return newSkippedExtensions;
563 }
565 > private updateExtensionState(state: IStringDictionary<any>, extension: ILocalExtension | IGalleryExtension, version: string | undefined, extensionStorageService: IExtensionStorageService): void {
566 const extensionState = extensionStorageService.getExtensionState(extension, true) || {};
567 const keys = version ? extensionStorageService.getKeysForSync({ id: extension.identifier.id, version }) : undefined;
573 extensionStorageService.setExtensionState(extension, extensionState, true);
574 }
576 > private async withProfileScopedServices<T>(profile: IUserDataProfile, fn: (extensionEnablementService: IGlobalExtensionEnablementService, extensionStorageService: IExtensionStorageService) => Promise<T>): Promise<T> {
577 return this.userDataProfileStorageService.withProfileScopedStorageService(profile,
578 async storageService => {
588 });
589 }
591 > }
592 >
593 > export interface IExtensionsInitializerPreviewResult {
594 > readonly installedExtensions: ILocalExtension[];
595 > readonly disabledExtensions: IExtensionIdentifier[];
596 > readonly newExtensions: (IExtensionIdentifier & { preRelease: boolean })[];
597 > readonly remoteExtensions: ISyncExtension[];
598 > }
599 >
600 > export abstract class AbstractExtensionsInitializer extends AbstractInitializer {
601 >
602 > constructor(
603 @IExtensionManagementService protected readonly extensionManagementService: IExtensionManagementService,
604 @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService,
612 super(SyncResource.Extensions, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
613 }
615 > protected async parseExtensions(remoteUserData: IRemoteUserData): Promise<ISyncExtension[] | null> {
616 return remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
617 }
619 > protected generatePreview(remoteExtensions: ISyncExtension[], localExtensions: ILocalExtension[]): IExtensionsInitializerPreviewResult {
620 const installedExtensions: ILocalExtension[] = [];
621 const newExtensions: (IExtensionIdentifier & { preRelease: boolean })[] = [];
642 return { installedExtensions, newExtensions, disabledExtensions, remoteExtensions };
643 }
645 > }
src/vs/platform/userDataSync/common/globalStateSync.ts 127 introduced LOC · 24 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- globalStateSync.ts
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 { getErrorMessage } from '../../../base/common/errors.js';
10 > import { Event } from '../../../base/common/event.js';
11 > import { parse } from '../../../base/common/json.js';
12 > import { toFormattedString } from '../../../base/common/jsonFormatter.js';
13 > import { isWeb } from '../../../base/common/platform.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import { generateUuid } from '../../../base/common/uuid.js';
16 > import { IHeaders } from '../../../base/parts/request/common/request.js';
17 > import { IConfigurationService } from '../../configuration/common/configuration.js';
18 > import { IEnvironmentService } from '../../environment/common/environment.js';
19 > import { IFileService } from '../../files/common/files.js';
20 > import { ILogService } from '../../log/common/log.js';
21 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
22 > import { IStorageEntry, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
23 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
24 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
25 > import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview, isSyncData } from './abstractSynchronizer.js';
26 > import { edit } from './content.js';
27 > import { merge } from './globalStateMerge.js';
28 > import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, IUserData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from './userDataSync.js';
29 > import { UserDataSyncStoreClient } from './userDataSyncStoreService.js';
30 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
31 > import { IUserDataProfileStorageService } from '../../userDataProfile/common/userDataProfileStorageService.js';
32 > import { IInstantiationService } from '../../instantiation/common/instantiation.js';
33 >
34 > const argvStoragePrefx = 'globalState.argv.';
35 > const argvProperties: string[] = ['locale'];
36 >
37 > type StorageKeys = { machine: string[]; user: string[]; unregistered: string[] };
38 >
39 > interface IGlobalStateResourceMergeResult extends IAcceptResult {
40 > readonly local: { added: IStringDictionary<IStorageValue>; removed: string[]; updated: IStringDictionary<IStorageValue> };
41 > readonly remote: { added: string[]; removed: string[]; updated: string[]; all: IStringDictionary<IStorageValue> | null };
42 > }
43 >
44 > interface IGlobalStateResourcePreview extends IResourcePreview {
45 > readonly localUserData: IGlobalState;
46 > readonly previewResult: IGlobalStateResourceMergeResult;
47 > readonly storageKeys: StorageKeys;
48 > }
49 >
50 > export function stringify(globalState: IGlobalState, format: boolean): string {
51 const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : [];
52 const storage: IStringDictionary<IStorageValue> = {};
55 return format ? toFormattedString(globalState, {}) : JSON.stringify(globalState);
56 }
58 > const GLOBAL_STATE_DATA_VERSION = 1;
59 >
60 > /**
61 > * Synchronises global state that includes
62 > * - Global storage with user scope
63 > * - Locale from argv properties
64 > *
65 > * Global storage is synced without checking version just like other resources (settings, keybindings).
66 > * If there is a change in format of the value of a storage key which requires migration then
67 > * Owner of that key should remove that key from user scope and replace that with new user scoped key.
68 > */
69 > export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
70 >
71 > protected readonly version: number = GLOBAL_STATE_DATA_VERSION;
72 > private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'globalState.json');
73 > private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
74 > private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
75 > private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
76 > private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
77 >
78 > private readonly localGlobalStateProvider: LocalGlobalStateProvider;
79 >
80 > constructor(
81 profile: IUserDataProfile,
82 collection: string | undefined,
115 );
116 }
118 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean): Promise<IGlobalStateResourcePreview[]> {
119 const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
120
158 }];
159 }
161 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
162 const lastSyncGlobalState: IGlobalState | null = lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
163 if (lastSyncGlobalState === null) {
169 return remote.all !== null;
170 }
172 > protected async getMergeResult(resourcePreview: IGlobalStateResourcePreview, token: CancellationToken): Promise<IMergeResult> {
173 return { ...resourcePreview.previewResult, hasConflicts: false };
174 }
176 > protected async getAcceptResult(resourcePreview: IGlobalStateResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IGlobalStateResourceMergeResult> {
177
178 /* Accept local resource */
193 throw new Error(`Invalid Resource: ${resource.toString()}`);
194 }
196 > private async acceptLocal(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
197 if (resourcePreview.remoteContent !== null) {
198 const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
215 }
216 }
218 > private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
219 if (resourcePreview.remoteContent !== null) {
220 const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
237 }
238 }
240 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
241 const { localUserData } = resourcePreviews[0][0];
242 const { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
269 }
270 }
272 > async resolveContent(uri: URI): Promise<string | null> {
273 if (this.extUri.isEqual(this.remoteResource, uri)
274 || this.extUri.isEqual(this.baseResource, uri)
281 return null;
282 }
284 > async hasLocalData(): Promise<boolean> {
285 try {
286 const { storage } = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile);
293 return false;
294 }
296 > private async getStorageKeys(lastSyncGlobalState: IGlobalState | null): Promise<StorageKeys> {
297 const storageData = await this.userDataProfileStorageService.readStorageData(this.syncResource.profile);
298 const user: string[] = [], machine: string[] = [];
316 return { user, machine, unregistered };
317 }
319 >
320 > export class LocalGlobalStateProvider {
321 > constructor(
322 @IFileService private readonly fileService: IFileService,
323 @IEnvironmentService private readonly environmentService: IEnvironmentService,
325 @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService
326 ) { }
328 > async getLocalGlobalState(profile: IUserDataProfile): Promise<IGlobalState> {
329 const storage: IStringDictionary<IStorageValue> = {};
330 if (profile.isDefault) {
345 return { storage };
346 }
348 > private async getLocalArgvContent(): Promise<string> {
349 try {
350 this.logService.debug('GlobalStateSync#getLocalArgvContent', this.environmentService.argvResource);
357 return '{}';
358 }
360 > async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary<IStorageValue>; updated: IStringDictionary<IStorageValue>; removed: string[] }, profile: IUserDataProfile): Promise<void> {
361 const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.GlobalState, profile);
362 const argv: IStringDictionary<any> = {};
415 }
416 }
418 >
419 > export class GlobalStateInitializer extends AbstractInitializer {
420 >
421 > constructor(
422 @IStorageService storageService: IStorageService,
423 @IFileService fileService: IFileService,
429 super(SyncResource.GlobalState, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
430 }
432 > protected async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
433 const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
434 if (!remoteGlobalState) {
475 }
476 }
478 > }
479 >
480 > export class UserDataSyncStoreTypeSynchronizer {
481 >
482 > constructor(
483 private readonly userDataSyncStoreClient: UserDataSyncStoreClient,
484 @IStorageService private readonly storageService: IStorageService,
488 ) {
489 }
491 > getSyncStoreType(userData: IUserData): UserDataSyncStoreType | undefined {
492 const remoteGlobalState = this.parseGlobalState(userData);
493 return remoteGlobalState?.storage[SYNC_SERVICE_URL_TYPE]?.value as UserDataSyncStoreType;
494 }
496 > async sync(userDataSyncStoreType: UserDataSyncStoreType): Promise<void> {
497 const syncHeaders = createSyncHeaders(generateUuid());
498 try {
509 }
510 }
512 > private async doSync(userDataSyncStoreType: UserDataSyncStoreType, syncHeaders: IHeaders): Promise<void> {
513 // Read the global state from remote
514 const globalStateUserData = await this.userDataSyncStoreClient.readResource(SyncResource.GlobalState, null, undefined, syncHeaders);
523 await this.userDataSyncStoreClient.writeResource(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, undefined, syncHeaders);
524 }
526 > private parseGlobalState({ content }: IUserData): IGlobalState | null {
527 if (!content) {
528 return null;
src/vs/platform/userDataSync/common/userDataSyncMachines.ts 92 introduced LOC · 14 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataSyncMachines.ts
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable } from '../../../base/common/lifecycle.js';
8 > import { isAndroid, isChrome, isEdge, isFirefox, isSafari, isWeb, Platform, platform, PlatformToString } from '../../../base/common/platform.js';
9 > import { escapeRegExpCharacters } from '../../../base/common/strings.js';
10 > import { localize } from '../../../nls.js';
11 > import { IEnvironmentService } from '../../environment/common/environment.js';
12 > import { IFileService } from '../../files/common/files.js';
13 > import { createDecorator } from '../../instantiation/common/instantiation.js';
14 > import { IProductService } from '../../product/common/productService.js';
15 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
16 > import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
17 > import { IUserData, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncStoreService } from './userDataSync.js';
18 >
19 > export interface IMachineData {
20 > id: string;
21 > name: string;
22 > disabled?: boolean;
23 > platform?: string;
24 > }
25 >
26 > export interface IMachinesData {
27 > version: number;
28 > machines: IMachineData[];
29 > }
30 >
31 > export type IUserDataSyncMachine = Readonly<IMachineData> & { readonly isCurrent: boolean };
32 >
33 > export const IUserDataSyncMachinesService = createDecorator<IUserDataSyncMachinesService>('IUserDataSyncMachinesService');
34 > export interface IUserDataSyncMachinesService {
35 > _serviceBrand: undefined;
36 >
37 > readonly onDidChange: Event<void>;
38 >
39 > getMachines(manifest?: IUserDataManifest): Promise<IUserDataSyncMachine[]>;
40 >
41 > addCurrentMachine(manifest?: IUserDataManifest): Promise<void>;
42 > removeCurrentMachine(manifest?: IUserDataManifest): Promise<void>;
43 > renameMachine(machineId: string, name: string): Promise<void>;
44 > setEnablements(enbalements: [string, boolean][]): Promise<void>;
45 > }
46 >
47 > const currentMachineNameKey = 'sync.currentMachineName';
48 >
49 > const Safari = 'Safari';
50 > const Chrome = 'Chrome';
51 > const Edge = 'Edge';
52 > const Firefox = 'Firefox';
53 > const Android = 'Android';
54 >
55 > export function isWebPlatform(platform: string) {
56 switch (platform) {
57 case Safari:
65 return false;
66 }
68 function getPlatformName(): string {
69 if (isSafari) { return Safari; }
74 return PlatformToString(isWeb ? Platform.Web : platform);
75 }
77 > export class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService {
78 >
79 > private static readonly VERSION = 1;
80 > private static readonly RESOURCE = 'machines';
81 >
82 > _serviceBrand: undefined;
83 >
84 > private readonly _onDidChange = this._register(new Emitter<void>());
85 > readonly onDidChange = this._onDidChange.event;
86 >
87 > private readonly currentMachineIdPromise: Promise<string>;
88 > private userData: IUserData | null = null;
89 >
90 > constructor(
91 @IEnvironmentService environmentService: IEnvironmentService,
92 @IFileService fileService: IFileService,
99 this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
100 }
102 > async getMachines(manifest?: IUserDataManifest): Promise<IUserDataSyncMachine[]> {
103 const currentMachineId = await this.currentMachineIdPromise;
104 const machineData = await this.readMachinesData(manifest);
105 return machineData.machines.map<IUserDataSyncMachine>(machine => ({ ...machine, ...{ isCurrent: machine.id === currentMachineId } }));
106 }
108 > async addCurrentMachine(manifest?: IUserDataManifest): Promise<void> {
109 const currentMachineId = await this.currentMachineIdPromise;
110 const machineData = await this.readMachinesData(manifest);
114 }
115 }
117 > async removeCurrentMachine(manifest?: IUserDataManifest): Promise<void> {
118 const currentMachineId = await this.currentMachineIdPromise;
119 const machineData = await this.readMachinesData(manifest);
124 }
125 }
127 > async renameMachine(machineId: string, name: string, manifest?: IUserDataManifest): Promise<void> {
128 const machineData = await this.readMachinesData(manifest);
129 const machine = machineData.machines.find(({ id }) => id === machineId);
137 }
138 }
140 > async setEnablements(enablements: [string, boolean][]): Promise<void> {
141 const machineData = await this.readMachinesData();
142 for (const [machineId, enabled] of enablements) {
148 await this.writeMachinesData(machineData);
149 }
151 > private computeCurrentMachineName(machines: IMachineData[]): string {
152 const previousName = this.storageService.get(currentMachineNameKey, StorageScope.APPLICATION);
153 if (previousName) {
168 return `${namePrefix} #${nameIndex + 1}`;
169 }
171 > private async readMachinesData(manifest?: IUserDataManifest): Promise<IMachinesData> {
172 this.userData = await this.readUserData(manifest);
173 const machinesData = this.parse(this.userData);
177 return machinesData;
178 }
180 > private async writeMachinesData(machinesData: IMachinesData): Promise<void> {
181 const content = JSON.stringify(machinesData);
182 const ref = await this.userDataSyncStoreService.writeResource(UserDataSyncMachinesService.RESOURCE, content, this.userData?.ref || null);
184 this._onDidChange.fire();
185 }
187 > private async readUserData(manifest?: IUserDataManifest): Promise<IUserData> {
188 if (this.userData) {
189
203 return this.userDataSyncStoreService.readResource(UserDataSyncMachinesService.RESOURCE, this.userData);
204 }
206 > private parse(userData: IUserData): IMachinesData {
207 if (userData.content !== null) {
208 try {
src/vs/platform/userDataSync/common/keybindingsSync.ts 88 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- keybindingsSync.ts
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 { isNonEmptyArray } from '../../../base/common/arrays.js';
7 > import { VSBuffer } from '../../../base/common/buffer.js';
8 > import { CancellationToken } from '../../../base/common/cancellation.js';
9 > import { Event } from '../../../base/common/event.js';
10 > import { parse } from '../../../base/common/json.js';
11 > import { OperatingSystem, OS } from '../../../base/common/platform.js';
12 > import { isUndefined } from '../../../base/common/types.js';
13 > import { URI } from '../../../base/common/uri.js';
14 > import { localize } from '../../../nls.js';
15 > import { IConfigurationService } from '../../configuration/common/configuration.js';
16 > import { IEnvironmentService } from '../../environment/common/environment.js';
17 > import { FileOperationError, FileOperationResult, IFileService } from '../../files/common/files.js';
18 > import { ILogService } from '../../log/common/log.js';
19 > import { IStorageService } from '../../storage/common/storage.js';
20 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
21 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
22 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
23 > import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from './abstractSynchronizer.js';
24 > import { merge } from './keybindingsMerge.js';
25 > import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM } from './userDataSync.js';
26 >
27 > interface ISyncContent {
28 > mac?: string;
29 > linux?: string;
30 > windows?: string;
31 > all?: string;
32 > }
33 >
34 > interface IKeybindingsResourcePreview extends IFileResourcePreview {
35 > previewResult: IMergeResult;
36 > }
37 >
38 > interface ILastSyncUserData extends IRemoteUserData {
39 > platformSpecific?: boolean;
40 > }
41 >
42 > export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean, logService: ILogService): string | null {
43 try {
44 const parsed = <ISyncContent>JSON.parse(syncContent);
59 }
60 }
62 > export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
63 >
64 > /* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
65 > protected readonly version: number = 2;
66 > private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'keybindings.json');
67 > private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
68 > private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
69 > private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
70 > private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
71 >
72 > constructor(
73 profile: IUserDataProfile,
74 collection: string | undefined,
88 this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.keybindingsPerPlatform'))(() => this.triggerLocalChange()));
89 }
91 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise<IKeybindingsResourcePreview[]> {
92 const remoteContent = remoteUserData.syncData ? getKeybindingsContentFromSyncContent(remoteUserData.syncData.content, userDataSyncConfiguration.keybindingsPerPlatform ?? this.syncKeybindingsPerPlatform(), this.logService) : null;
93
163
164 }
166 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
167 const lastSyncContent = this.getKeybindingsContentFromLastSyncUserData(lastSyncUserData);
168 if (lastSyncContent === null) {
176 return result.hasConflicts || result.mergeContent !== lastSyncContent;
177 }
179 > protected async getMergeResult(resourcePreview: IKeybindingsResourcePreview, token: CancellationToken): Promise<IMergeResult> {
180 return resourcePreview.previewResult;
181 }
183 > protected async getAcceptResult(resourcePreview: IKeybindingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
184
185 /* Accept local resource */
220 throw new Error(`Invalid Resource: ${resource.toString()}`);
221 }
223 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IKeybindingsResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
224 const { fileContent } = resourcePreviews[0][0];
225 let { content, localChange, remoteChange } = resourcePreviews[0][1];
265
266 }
268 > async hasLocalData(): Promise<boolean> {
269 try {
270 const localFileContent = await this.getLocalFileContent();
282 return false;
283 }
285 > async resolveContent(uri: URI): Promise<string | null> {
286 if (this.extUri.isEqual(this.remoteResource, uri)
287 || this.extUri.isEqual(this.baseResource, uri)
293 return null;
294 }
296 > private getKeybindingsContentFromLastSyncUserData(lastSyncUserData: ILastSyncUserData): string | null {
297 if (!lastSyncUserData.syncData) {
298 return null;
306 return getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content, this.syncKeybindingsPerPlatform(), this.logService);
307 }
309 > private toSyncContent(keybindingsContent: string, syncContent?: string): string {
310 let parsed: ISyncContent = {};
311 try {
332 return JSON.stringify(parsed);
333 }
335 > private syncKeybindingsPerPlatform(): boolean {
336 return !!this.configurationService.getValue(CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM);
337 }
339 > }
340 >
341 > export class KeybindingsInitializer extends AbstractInitializer {
342 >
343 > constructor(
344 @IFileService fileService: IFileService,
345 @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
351 super(SyncResource.Keybindings, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
352 }
354 > protected async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
355 const keybindingsContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
356 if (!keybindingsContent) {
369 await this.updateLastSyncUserData(remoteUserData);
370 }
372 > private async isEmpty(): Promise<boolean> {
373 try {
374 const fileContent = await this.fileService.readFile(this.userDataProfilesService.defaultProfile.settingsResource);
379 }
380 }
382 > private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
383 try {
384 return getKeybindingsContentFromSyncContent(syncContent, true, this.logService);
src/vs/platform/extensionManagement/common/extensionStorage.ts 87 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extensionStorage.ts
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 { createDecorator } from '../../instantiation/common/instantiation.js';
7 > import { Emitter, Event } from '../../../base/common/event.js';
8 > import { Disposable } from '../../../base/common/lifecycle.js';
9 > import { IProfileStorageValueChangeEvent, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
10 > import { adoptToGalleryExtensionId, areSameExtensions, getExtensionId } from './extensionManagementUtil.js';
11 > import { IProductService } from '../../product/common/productService.js';
12 > import { distinct } from '../../../base/common/arrays.js';
13 > import { ILogService } from '../../log/common/log.js';
14 > import { IExtension } from '../../extensions/common/extensions.js';
15 > import { isString } from '../../../base/common/types.js';
16 > import { IStringDictionary } from '../../../base/common/collections.js';
17 > import { IExtensionManagementService, IGalleryExtension } from './extensionManagement.js';
18 >
19 > export interface IExtensionIdWithVersion {
20 > id: string;
21 > version: string;
22 > }
23 >
24 > export const IExtensionStorageService = createDecorator<IExtensionStorageService>('IExtensionStorageService');
25 >
26 > export interface IExtensionStorageService {
27 > readonly _serviceBrand: undefined;
28 >
29 > getExtensionState(extension: IExtension | IGalleryExtension | string, global: boolean): IStringDictionary<unknown> | undefined;
30 > getExtensionStateRaw(extension: IExtension | IGalleryExtension | string, global: boolean): string | undefined;
31 > setExtensionState(extension: IExtension | IGalleryExtension | string, state: object | undefined, global: boolean): void;
32 >
33 > readonly onDidChangeExtensionStorageToSync: Event<void>;
34 > setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void;
35 > getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined;
36 >
37 > addToMigrationList(from: string, to: string): void;
38 > getSourceExtensionToMigrate(target: string): string | undefined;
39 > }
40 >
41 > const EXTENSION_KEYS_ID_VERSION_REGEX = /^extensionKeys\/([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
42 >
43 > export class ExtensionStorageService extends Disposable implements IExtensionStorageService {
44 >
45 > readonly _serviceBrand: undefined;
46 >
47 > private static LARGE_STATE_WARNING_THRESHOLD = 512 * 1024;
48 >
49 > private static toKey(extension: IExtensionIdWithVersion): string {
50 return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`;
51 }
53 > private static fromKey(key: string): IExtensionIdWithVersion | undefined {
54 const matches = EXTENSION_KEYS_ID_VERSION_REGEX.exec(key);
55 if (matches && matches[1]) {
58 return undefined;
59 }
61 > /* TODO @sandy081: This has to be done across all profiles */
62 > static async removeOutdatedExtensionVersions(extensionManagementService: IExtensionManagementService, storageService: IStorageService): Promise<void> {
63 const extensions = await extensionManagementService.getInstalled();
64 const extensionVersionsToRemove: string[] = [];
75 }
76 }
78 > private static readAllExtensionsWithKeysForSync(storageService: IStorageService): Map<string, string[]> {
79 const extensionsWithKeysForSync = new Map<string, string[]>();
80 const keys = storageService.keys(StorageScope.PROFILE, StorageTarget.MACHINE);
91 return extensionsWithKeysForSync;
92 }
94 > private readonly _onDidChangeExtensionStorageToSync = this._register(new Emitter<void>());
95 > readonly onDidChangeExtensionStorageToSync = this._onDidChangeExtensionStorageToSync.event;
96 >
97 > private readonly extensionsWithKeysForSync: Map<string, string[]>;
98 >
99 > constructor(
100 @IStorageService private readonly storageService: IStorageService,
101 @IProductService private readonly productService: IProductService,
106 this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this._store)(e => this.onDidChangeStorageValue(e)));
107 }
109 > private onDidChangeStorageValue(e: IProfileStorageValueChangeEvent): void {
110
111 // State of extension with keys for sync has changed
131 }
132 }
134 > private getExtensionId(extension: IExtension | IGalleryExtension | string): string {
135 if (isString(extension)) {
136 return extension;
140 return getExtensionId(publisher, name);
141 }
143 > getExtensionState(extension: IExtension | IGalleryExtension | string, global: boolean): IStringDictionary<unknown> | undefined {
144 const extensionId = this.getExtensionId(extension);
145 const jsonValue = this.getExtensionStateRaw(extension, global);
156 return undefined;
157 }
159 > getExtensionStateRaw(extension: IExtension | IGalleryExtension | string, global: boolean): string | undefined {
160 const extensionId = this.getExtensionId(extension);
161 const rawState = this.storageService.get(extensionId, global ? StorageScope.PROFILE : StorageScope.WORKSPACE);
167 return rawState;
168 }
170 > setExtensionState(extension: IExtension | IGalleryExtension | string, state: IStringDictionary<unknown> | undefined, global: boolean): void {
171 const extensionId = this.getExtensionId(extension);
172 if (state === undefined) {
176 }
177 }
179 > setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void {
180 this.storageService.store(ExtensionStorageService.toKey(extensionIdWithVersion), JSON.stringify(keys), StorageScope.PROFILE, StorageTarget.MACHINE);
181 }
183 > getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined {
184 const extensionKeysForSyncFromProduct = this.productService.extensionSyncedKeys?.[extensionIdWithVersion.id.toLowerCase()];
185 const extensionKeysForSyncFromStorageValue = this.storageService.get(ExtensionStorageService.toKey(extensionIdWithVersion), StorageScope.PROFILE);
190 : (extensionKeysForSyncFromStorage || extensionKeysForSyncFromProduct);
191 }
193 > addToMigrationList(from: string, to: string): void {
194 if (from !== to) {
195 // remove the duplicates
199 }
200 }
202 > getSourceExtensionToMigrate(toExtensionId: string): string | undefined {
203 const entry = this.migrationList.find(([, to]) => toExtensionId === to);
204 return entry ? entry[0] : undefined;
205 }
207 > private get migrationList(): [string, string][] {
208 const value = this.storageService.get('extensionStorage.migrationList', StorageScope.APPLICATION, '[]');
209 try {
215 return [];
216 }
218 > private set migrationList(migrationList: [string, string][]) {
219 if (migrationList.length) {
220 this.storageService.store('extensionStorage.migrationList', JSON.stringify(migrationList), StorageScope.APPLICATION, StorageTarget.MACHINE);
src/vs/platform/meteredConnection/common/meteredConnection.ts 86 introduced LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- meteredConnection.ts
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable } from '../../../base/common/lifecycle.js';
8 > import { IConfigurationService } from '../../configuration/common/configuration.js';
9 > import { createDecorator } from '../../instantiation/common/instantiation.js';
10 >
11 > export const IMeteredConnectionService = createDecorator<IMeteredConnectionService>('meteredConnectionService');
12 >
13 > /**
14 > * Service to report on metered connection status.
15 > */
16 > export interface IMeteredConnectionService {
17 > readonly _serviceBrand: undefined;
18 >
19 > /**
20 > * Whether the current network connection is metered.
21 > * Always returns `false` if the `network.meteredConnection` setting is `off`.
22 > * Always returns `true` if the `network.meteredConnection` setting is `on`.
23 > */
24 > readonly isConnectionMetered: boolean;
25 >
26 > /**
27 > * Event that fires when the metered connection status changes.
28 > */
29 > readonly onDidChangeIsConnectionMetered: Event<boolean>;
30 > }
31 >
32 > export const METERED_CONNECTION_SETTING_KEY = 'network.meteredConnection';
33 >
34 > export type MeteredConnectionSettingValue = 'on' | 'off' | 'auto';
35 >
36 > /**
37 > * Network Information API
38 > * See https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API
39 > */
40 > export interface NetworkInformation {
41 > saveData?: boolean;
42 > metered?: boolean;
43 > effectiveType?: 'slow-2g' | '2g' | '3g' | '4g';
44 > addEventListener(type: 'change', listener: () => void): void;
45 > removeEventListener(type: 'change', listener: () => void): void;
46 > }
47 >
48 > /**
49 > * Extended Navigator interface for Network Information API
50 > */
51 > export interface NavigatorWithConnection {
52 > readonly connection?: NetworkInformation;
53 > }
54 >
55 > /**
56 > * Check if the current network connection is metered according to the Network Information API.
57 > */
58 > export function getIsBrowserConnectionMetered() {
59 const connection = (navigator as NavigatorWithConnection).connection;
60 if (!connection) {
69 return effectiveType === '2g' || effectiveType === 'slow-2g';
70 }
72 > /**
73 > * Abstract base class for metered connection services.
74 > */
75 > export abstract class AbstractMeteredConnectionService extends Disposable implements IMeteredConnectionService {
76 > declare readonly _serviceBrand: undefined;
77 >
78 > private readonly _onDidChangeIsConnectionMetered = this._register(new Emitter<boolean>());
79 > public readonly onDidChangeIsConnectionMetered = this._onDidChangeIsConnectionMetered.event;
80 >
81 > private _isConnectionMetered: boolean;
82 > private _isBrowserConnectionMetered: boolean;
83 > private _meteredConnectionSetting: MeteredConnectionSettingValue;
84 >
85 > constructor(configurationService: IConfigurationService, isBrowserConnectionMetered: boolean) {
86 super();
87
100 }));
101 }
103 > public get isConnectionMetered(): boolean {
104 return this._isConnectionMetered;
105 }
107 > protected get isBrowserConnectionMetered(): boolean {
108 return this._isBrowserConnectionMetered;
109 }
111 > public setIsBrowserConnectionMetered(value: boolean) {
112 if (value !== this._isBrowserConnectionMetered) {
113 this._isBrowserConnectionMetered = value;
115 }
116 }
118 > protected onChangeBrowserConnection() {
119 this.onUpdated();
120 }
122 > protected onUpdated() {
123 const value = this._meteredConnectionSetting === 'on' || (this._meteredConnectionSetting !== 'off' && this._isBrowserConnectionMetered);
124 if (value !== this._isConnectionMetered) {
src/vs/platform/userDataSync/common/snippetsSync.ts 77 introduced LOC · 20 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- snippetsSync.ts
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);
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,
42 collection: string | undefined,
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();
63 const localSnippets = this.toSnippetsContents(local);
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;
82 if (lastSyncSnippets === null) {
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;
93 }
95 > protected async getAcceptResult(resourcePreview: ISnippetsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
96
97 /* Accept local resource */
140 throw new Error(`Invalid Resource: ${resource.toString()}`);
141 }
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 }));
145 if (accptedResourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
172
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>();
177
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' }))
388 return null;
389 }
391 > async hasLocalData(): Promise<boolean> {
392 try {
393 const localSnippets = await this.getSnippetsFileContents();
400 return false;
401 }
403 > private async updateLocalBackup(resourcePreviews: IFileResourcePreview[]): Promise<void> {
404 const local: IStringDictionary<IFileContent> = {};
405 for (const resourcePreview of resourcePreviews) {
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) {
415 if (localChange !== Change.None) {
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) : {};
445 const newSnippets: IStringDictionary<string> = deepClone(currentSnippets);
464 return remoteUserData;
465 }
467 > private parseSnippets(syncData: ISyncData): IStringDictionary<string> {
468 return parseSnippets(syncData);
469 }
471 > private toSnippetsContents(snippetsFileContents: IStringDictionary<IFileContent>): IStringDictionary<string> {
472 const snippets: IStringDictionary<string> = {};
473 for (const key of Object.keys(snippetsFileContents)) {
476 return snippets;
477 }
479 > private async getSnippetsFileContents(): Promise<IStringDictionary<IFileContent>> {
480 const snippets: IStringDictionary<IFileContent> = {};
481 let stat: IFileStat;
501 return snippets;
502 }
503 > } snippetsSync.ts
504 >
505 > export class SnippetsInitializer extends AbstractInitializer {
506 >
507 > constructor(
508 @IFileService fileService: IFileService,
509 @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
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) {
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);
src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts 73 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataProfilesManifestSync.ts
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 } from '../../../base/common/cancellation.js';
7 > import { toFormattedString } from '../../../base/common/jsonFormatter.js';
8 > import { URI } from '../../../base/common/uri.js';
9 > import { IConfigurationService } from '../../configuration/common/configuration.js';
10 > import { IEnvironmentService } from '../../environment/common/environment.js';
11 > import { IFileService } from '../../files/common/files.js';
12 > import { IStorageService } from '../../storage/common/storage.js';
13 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
14 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
15 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
16 > import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from './abstractSynchronizer.js';
17 > import { merge } from './userDataProfilesManifestMerge.js';
18 > import { Change, IRemoteUserData, ISyncData, ISyncUserDataProfile, IUserData, IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, UserDataSyncError, UserDataSyncErrorCode } from './userDataSync.js';
19 >
20 > interface IUserDataProfileManifestResourceMergeResult extends IAcceptResult {
21 > readonly local: { added: ISyncUserDataProfile[]; removed: IUserDataProfile[]; updated: ISyncUserDataProfile[] };
22 > readonly remote: { added: IUserDataProfile[]; removed: ISyncUserDataProfile[]; updated: IUserDataProfile[] } | null;
23 > }
24 >
25 > interface IUserDataProfilesManifestResourcePreview extends IResourcePreview {
26 > readonly previewResult: IUserDataProfileManifestResourceMergeResult;
27 > readonly remoteProfiles: ISyncUserDataProfile[] | null;
28 > }
29 >
30 > export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
31 >
32 > protected readonly version: number = 2;
33 > readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'profiles.json');
34 > readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
35 > readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
36 > readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
37 > readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
38 >
39 > constructor(
40 profile: IUserDataProfile,
41 collection: string | undefined,
55 this._register(userDataProfilesService.onDidChangeProfiles(() => this.triggerLocalChange()));
56 }
58 > async getLastSyncedProfiles(): Promise<ISyncUserDataProfile[] | null> {
59 const lastSyncUserData = await this.getLastSyncUserData();
60 return lastSyncUserData?.syncData ? parseUserDataProfilesManifest(lastSyncUserData.syncData) : null;
61 }
63 > async getRemoteSyncedProfiles(refOrLatestData: string | IUserData | null): Promise<ISyncUserDataProfile[] | null> {
64 const lastSyncUserData = await this.getLastSyncUserData();
65 const remoteUserData = await this.getLatestRemoteUserData(refOrLatestData, lastSyncUserData);
66 return remoteUserData?.syncData ? parseUserDataProfilesManifest(remoteUserData.syncData) : null;
67 }
69 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean): Promise<IUserDataProfilesManifestResourcePreview[]> {
70 const remoteProfiles: ISyncUserDataProfile[] | null = remoteUserData.syncData ? parseUserDataProfilesManifest(remoteUserData.syncData) : null;
71 const lastSyncProfiles: ISyncUserDataProfile[] | null = lastSyncUserData?.syncData ? parseUserDataProfilesManifest(lastSyncUserData.syncData) : null;
96 }];
97 }
99 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
100 const lastSyncProfiles: ISyncUserDataProfile[] | null = lastSyncUserData?.syncData ? parseUserDataProfilesManifest(lastSyncUserData.syncData) : null;
101 const localProfiles = this.getLocalUserDataProfiles();
103 return !!remote?.added.length || !!remote?.removed.length || !!remote?.updated.length;
104 }
106 > protected async getMergeResult(resourcePreview: IUserDataProfilesManifestResourcePreview, token: CancellationToken): Promise<IMergeResult> {
107 return { ...resourcePreview.previewResult, hasConflicts: false };
108 }
110 > protected async getAcceptResult(resourcePreview: IUserDataProfilesManifestResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
111 /* Accept local resource */
112 if (this.extUri.isEqual(resource, this.localResource)) {
126 throw new Error(`Invalid Resource: ${resource.toString()}`);
127 }
129 > private async acceptLocal(resourcePreview: IUserDataProfilesManifestResourcePreview): Promise<IUserDataProfileManifestResourceMergeResult> {
130 const localProfiles = this.getLocalUserDataProfiles();
131 const mergeResult = merge(localProfiles, null, null, []);
139 };
140 }
142 > private async acceptRemote(resourcePreview: IUserDataProfilesManifestResourcePreview): Promise<IUserDataProfileManifestResourceMergeResult> {
143 const remoteProfiles: ISyncUserDataProfile[] = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
144 const lastSyncProfiles: ISyncUserDataProfile[] = [];
171 }
172 }
174 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IUserDataProfilesManifestResourcePreview, IUserDataProfileManifestResourceMergeResult][], force: boolean): Promise<void> {
175 const { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
176 if (localChange === Change.None && remoteChange === Change.None) {
256 }
257 }
259 > async updateRemoteProfiles(profiles: ISyncUserDataProfile[], ref: string | null): Promise<IRemoteUserData> {
260 return this.updateRemoteUserData(this.stringifyRemoteProfiles(profiles), ref);
261 }
263 > async hasLocalData(): Promise<boolean> {
264 return this.getLocalUserDataProfiles().length > 0;
265 }
267 > async resolveContent(uri: URI): Promise<string | null> {
268 if (this.extUri.isEqual(this.remoteResource, uri)
269 || this.extUri.isEqual(this.baseResource, uri)
276 return null;
277 }
279 > private getLocalUserDataProfiles(): IUserDataProfile[] {
280 return this.userDataProfilesService.profiles.filter(p => !p.isDefault && !p.isTransient);
281 }
283 > private stringifyRemoteProfiles(profiles: ISyncUserDataProfile[]): string {
284 return JSON.stringify([...profiles].sort((a, b) => a.name.localeCompare(b.name)));
285 }
287 > }
288 >
289 > export function stringifyLocalProfiles(profiles: IUserDataProfile[], format: boolean): string {
290 const result = [...profiles].sort((a, b) => a.name.localeCompare(b.name)).map(p => ({ id: p.id, name: p.name }));
291 return format ? toFormattedString(result, {}) : JSON.stringify(result);
292 }
294 > export function parseUserDataProfilesManifest(syncData: ISyncData): ISyncUserDataProfile[] {
295 return JSON.parse(syncData.content);
296 }
src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts 72 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- promptsSync.ts
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 { URI } from '../../../../base/common/uri.js';
7 > import { Event } from '../../../../base/common/event.js';
8 > import { VSBuffer } from '../../../../base/common/buffer.js';
9 > import { deepClone } from '../../../../base/common/objects.js';
10 > import { IStorageService } from '../../../storage/common/storage.js';
11 > import { ITelemetryService } from '../../../telemetry/common/telemetry.js';
12 > import { IStringDictionary } from '../../../../base/common/collections.js';
13 > import { CancellationToken } from '../../../../base/common/cancellation.js';
14 > import { IUriIdentityService } from '../../../uriIdentity/common/uriIdentity.js';
15 > import { IEnvironmentService } from '../../../environment/common/environment.js';
16 >
17 > import { IUserDataProfile } from '../../../userDataProfile/common/userDataProfile.js';
18 > import { IConfigurationService } from '../../../configuration/common/configuration.js';
19 > import { areSame, IMergeResult as IPromptsMergeResult, merge } from './promptsMerge.js';
20 > import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from '../abstractSynchronizer.js';
21 > import { FileOperationError, FileOperationResult, IFileContent, IFileService, IFileStat } from '../../../files/common/files.js';
22 > import { Change, IRemoteUserData, ISyncData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from '../userDataSync.js';
23 >
24 > interface IPromptsResourcePreview extends IFileResourcePreview {
25 > previewResult: IMergeResult;
26 > }
27 >
28 > interface IPromptsAcceptedResourcePreview extends IFileResourcePreview {
29 > acceptResult: IAcceptResult;
30 > }
31 >
32 > export function parsePrompts(syncData: ISyncData): IStringDictionary<string> {
33 return JSON.parse(syncData.content);
34 }
36 > /**
37 > * Synchronizer class for the "user" prompt files.
38 > * Adopted from {@link SnippetsSynchroniser}.
39 > */
40 > export class PromptsSynchronizer extends AbstractSynchroniser implements IUserDataSynchroniser {
41 >
42 > protected readonly version: number = 1;
43 > private readonly promptsFolder: URI;
44 >
45 > constructor(
46 profile: IUserDataProfile,
47 collection: string | undefined,
78 this._register(Event.filter(this.fileService.onDidFilesChange, e => e.affects(this.promptsFolder))(() => this.triggerLocalChange()));
79 }
81 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean): Promise<IPromptsResourcePreview[]> {
82 const local = await this.getPromptsFileContents();
83 const localPrompts = this.toPromptContents(local);
97 return this.getResourcePreviews(mergeResult, local, remotePrompts || {}, lastSyncPrompts || {});
98 }
100 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
101 const lastSync: IStringDictionary<string> | null = lastSyncUserData.syncData ? this.parsePrompts(lastSyncUserData.syncData) : null;
102 if (lastSync === null) {
108 return Object.keys(mergeResult.remote.added).length > 0 || Object.keys(mergeResult.remote.updated).length > 0 || mergeResult.remote.removed.length > 0 || mergeResult.conflicts.length > 0;
109 }
111 > protected async getMergeResult(resourcePreview: IPromptsResourcePreview, token: CancellationToken): Promise<IMergeResult> {
112 return resourcePreview.previewResult;
113 }
115 > protected async getAcceptResult(resourcePreview: IPromptsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
116
117 /* Accept local resource */
160 throw new Error(`Invalid Resource: ${resource.toString()}`);
161 }
163 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IPromptsResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
164 const accptedResourcePreviews: IPromptsAcceptedResourcePreview[] = resourcePreviews.map(([resourcePreview, acceptResult]) => ({ ...resourcePreview, acceptResult }));
165 if (accptedResourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
192
193 }
195 > private getResourcePreviews(
196 mergeResult: IPromptsMergeResult,
197 localFileContent: IStringDictionary<IFileContent>,
403 return [...resourcePreviews.values()];
404 }
406 > override async resolveContent(uri: URI): Promise<string | null> {
407 if (this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
408 || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
413 return null;
414 }
416 > async hasLocalData(): Promise<boolean> {
417 try {
418 const local = await this.getPromptsFileContents();
425 return false;
426 }
428 > private async updateLocalBackup(resourcePreviews: IFileResourcePreview[]): Promise<void> {
429 const local: IStringDictionary<IFileContent> = {};
430 for (const resourcePreview of resourcePreviews) {
435 await this.backupLocal(JSON.stringify(this.toPromptContents(local)));
436 }
438 > private async updateLocalPrompts(resourcePreviews: IPromptsAcceptedResourcePreview[], force: boolean): Promise<void> {
439 for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) {
440 if (localChange !== Change.None) {
465 }
466 }
468 > private async updateRemotePrompts(resourcePreviews: IPromptsAcceptedResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
469 const currentPrompts: IStringDictionary<string> = remoteUserData.syncData ? this.parsePrompts(remoteUserData.syncData) : {};
470 const newPrompts: IStringDictionary<string> = deepClone(currentPrompts);
489 return remoteUserData;
490 }
492 > private parsePrompts(syncData: ISyncData): IStringDictionary<string> {
493 return parsePrompts(syncData);
494 }
496 > private toPromptContents(fileContents: IStringDictionary<IFileContent>): IStringDictionary<string> {
497 const prompts: IStringDictionary<string> = {};
498 for (const key of Object.keys(fileContents)) {
501 return prompts;
502 }
504 > private async getPromptsFileContents(): Promise<IStringDictionary<IFileContent>> {
505 const prompts: IStringDictionary<IFileContent> = {};
506 let stat: IFileStat;
src/vs/platform/userDataSync/common/settingsSync.ts 68 introduced LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- settingsSync.ts
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 { distinct } from '../../../base/common/arrays.js';
7 > import { VSBuffer } from '../../../base/common/buffer.js';
8 > import { CancellationToken } from '../../../base/common/cancellation.js';
9 > import { Event } from '../../../base/common/event.js';
10 > import { URI } from '../../../base/common/uri.js';
11 > import { localize } from '../../../nls.js';
12 > import { ConfigurationTarget, IConfigurationService } from '../../configuration/common/configuration.js';
13 > import { ConfigurationModelParser } from '../../configuration/common/configurationModels.js';
14 > import { IEnvironmentService } from '../../environment/common/environment.js';
15 > import { IExtensionManagementService } from '../../extensionManagement/common/extensionManagement.js';
16 > import { ExtensionType } from '../../extensions/common/extensions.js';
17 > import { FileOperationError, FileOperationResult, IFileService } from '../../files/common/files.js';
18 > import { IStorageService } from '../../storage/common/storage.js';
19 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
20 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
21 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
22 > import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from './abstractSynchronizer.js';
23 > import { getIgnoredSettings, isEmpty, merge, updateIgnoredSettings } from './settingsMerge.js';
24 > import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, getIgnoredSettingsForExtension, IUserData } from './userDataSync.js';
25 >
26 > interface ISettingsResourcePreview extends IFileResourcePreview {
27 > previewResult: IMergeResult;
28 > }
29 >
30 > export interface ISettingsSyncContent {
31 > settings: string;
32 > }
33 >
34 function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
35 return thing
37 && Object.keys(thing).length === 1;
38 }
40 > export function parseSettingsSyncContent(syncContent: string): ISettingsSyncContent {
41 const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
42 return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
43 }
45 > export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
46 >
47 > /* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
48 > protected readonly version: number = 2;
49 > readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'settings.json');
50 > readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
51 > readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
52 > readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
53 > readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
54 >
55 > constructor(
56 private readonly profile: IUserDataProfile,
57 collection: string | undefined,
321 private systemExtensionsIgnoredSettings: Promise<string[]> | undefined = undefined;
322 private userExtensionsIgnoredSettings: Promise<string[]> | undefined = undefined;
323 > private async getIgnoredSettings(content?: string): Promise<string[]> { settingsSync.ts
324 if (!this.coreIgnoredSettings) {
325 this.coreIgnoredSettings = this.userDataSyncUtilService.resolveDefaultCoreIgnoredSettings();
340 return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content);
341 }
343 > private async getIgnoredSettingForSystemExtensions(): Promise<string[]> {
344 const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System);
345 return distinct(systemExtensions.map(e => getIgnoredSettingsForExtension(e.manifest)).flat());
346 }
348 > private async getIgnoredSettingForUserExtensions(): Promise<string[]> {
349 const userExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User, this.profile.extensionsResource);
350 return distinct(userExtensions.map(e => getIgnoredSettingsForExtension(e.manifest)).flat());
351 }
353 > private validateContent(content: string): void {
354 if (this.hasErrors(content, false)) {
355 throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource);
356 }
357 }
359 > }
360 >
361 > export class SettingsInitializer extends AbstractInitializer {
362 >
363 > constructor(
364 @IFileService fileService: IFileService,
365 @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
371 super(SyncResource.Settings, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
372 }
374 > protected async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
375 const settingsSyncContent = remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
376 if (!settingsSyncContent) {
389 await this.updateLastSyncUserData(remoteUserData);
390 }
392 > private async isEmpty(): Promise<boolean> {
393 try {
394 const fileContent = await this.fileService.readFile(this.userDataProfilesService.defaultProfile.settingsResource);
398 }
399 }
401 > private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
402 try {
403 return parseSettingsSyncContent(syncContent);
src/vs/platform/extensionManagement/common/extensionEnablementService.ts 61 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extensionEnablementService.ts
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable } from '../../../base/common/lifecycle.js';
8 > import { isUndefinedOrNull } from '../../../base/common/types.js';
9 > import { DISABLED_EXTENSIONS_STORAGE_PATH, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from './extensionManagement.js';
10 > import { areSameExtensions } from './extensionManagementUtil.js';
11 > import { IProfileStorageValueChangeEvent, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
12 >
13 > export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService {
14 >
15 > declare readonly _serviceBrand: undefined;
16 >
17 > private _onDidChangeEnablement = this._register(new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>());
18 > readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event;
19 > private readonly storageManager: StorageManager;
20 >
21 > constructor(
22 @IStorageService storageService: IStorageService,
23 @IExtensionManagementService extensionManagementService: IExtensionManagementService,
32 })));
33 }
35 > async enableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean> {
36 if (this._removeFromDisabledExtensions(extension)) {
37 this._onDidChangeEnablement.fire({ extensions: [extension], source });
40 return false;
41 }
43 > async disableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean> {
44 if (this._addToDisabledExtensions(extension)) {
45 this._onDidChangeEnablement.fire({ extensions: [extension], source });
48 return false;
49 }
51 > getDisabledExtensions(): IExtensionIdentifier[] {
52 return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH);
53 }
55 > async getDisabledExtensionsAsync(): Promise<IExtensionIdentifier[]> {
56 return this.getDisabledExtensions();
57 }
59 > private _addToDisabledExtensions(identifier: IExtensionIdentifier): boolean {
60 const disabledExtensions = this.getDisabledExtensions();
61 if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
66 return false;
67 }
69 > private _removeFromDisabledExtensions(identifier: IExtensionIdentifier): boolean {
70 const disabledExtensions = this.getDisabledExtensions();
71 for (let index = 0; index < disabledExtensions.length; index++) {
79 return false;
80 }
82 > private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[]): void {
83 this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions);
84 }
86 > private _getExtensions(storageId: string): IExtensionIdentifier[] {
87 return this.storageManager.get(storageId, StorageScope.PROFILE);
88 }
90 > private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {
91 this.storageManager.set(storageId, extensions, StorageScope.PROFILE);
92 }
94 > }
95 >
96 > export class StorageManager extends Disposable {
97 >
98 > private storage: { [key: string]: string } = Object.create(null);
99 >
100 > private _onDidChange: Emitter<IExtensionIdentifier[]> = this._register(new Emitter<IExtensionIdentifier[]>());
101 > readonly onDidChange: Event<IExtensionIdentifier[]> = this._onDidChange.event;
102 >
103 > constructor(private storageService: IStorageService) {
104 super();
105 this._register(storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this._store)(e => this.onDidStorageChange(e)));
106 }
108 > get(key: string, scope: StorageScope): IExtensionIdentifier[] {
109 let value: string;
110 if (scope === StorageScope.PROFILE) {
118 return JSON.parse(value);
119 }
121 > set(key: string, value: IExtensionIdentifier[], scope: StorageScope): void {
122 const newValue: string = JSON.stringify(value.map(({ id, uuid }): IExtensionIdentifier => ({ id, uuid })));
123 const oldValue = this._get(key, scope);
133 }
134 }
136 > private onDidStorageChange(storageChangeEvent: IProfileStorageValueChangeEvent): void {
137 if (!isUndefinedOrNull(this.storage[storageChangeEvent.key])) {
138 const newValue = this._get(storageChangeEvent.key, storageChangeEvent.scope);
149 }
150 }
152 > private _get(key: string, scope: StorageScope): string {
153 return this.storageService.get(key, scope, '[]');
154 }
156 > private _set(key: string, value: string | undefined, scope: StorageScope): void {
157 if (value) {
158 // Enablement state is synced separately through extensions
src/vs/platform/userDataSync/common/abstractJsonSynchronizer.ts 51 introduced LOC · 10 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- abstractJsonSynchronizer.ts
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 } from '../../../base/common/cancellation.js';
7 > import { URI } from '../../../base/common/uri.js';
8 > import { IConfigurationService } from '../../configuration/common/configuration.js';
9 > import { IEnvironmentService } from '../../environment/common/environment.js';
10 > import { IFileService } from '../../files/common/files.js';
11 > import { IStorageService } from '../../storage/common/storage.js';
12 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
13 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
14 > import { IUserDataProfile } from '../../userDataProfile/common/userDataProfile.js';
15 > import { AbstractFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from './abstractSynchronizer.js';
16 > import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, USER_DATA_SYNC_SCHEME, SyncResource } from './userDataSync.js';
17 >
18 > export interface IJsonResourcePreview extends IFileResourcePreview {
19 > previewResult: IMergeResult;
20 > }
21 >
22 > export abstract class AbstractJsonSynchronizer extends AbstractFileSynchroniser implements IUserDataSynchroniser {
23 >
24 > protected readonly version: number = 1;
25 > private readonly previewResource: URI;
26 > private readonly baseResource: URI;
27 > private readonly localResource: URI;
28 > private readonly remoteResource: URI;
29 > private readonly acceptedResource: URI;
30 >
31 > constructor(
32 fileResource: URI,
33 syncResourceMetadata: { syncResource: SyncResource; profile: IUserDataProfile },
53 this.acceptedResource = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
54 }
56 > protected abstract getContentFromSyncContent(syncContent: string): string | null;
57 > protected abstract toSyncContent(content: string | null): object;
58 >
59 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise<IJsonResourcePreview[]> {
60 const remoteContent = remoteUserData.syncData ? this.getContentFromSyncContent(remoteUserData.syncData.content) : null;
61
121 }];
122 }
124 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
125 const lastSyncContent: string | null = lastSyncUserData?.syncData ? this.getContentFromSyncContent(lastSyncUserData.syncData.content) : null;
126 if (lastSyncContent === null) {
133 return result.hasLocalChanged || result.hasRemoteChanged;
134 }
136 > protected async getMergeResult(resourcePreview: IJsonResourcePreview, token: CancellationToken): Promise<IMergeResult> {
137 return resourcePreview.previewResult;
138 }
140 > protected async getAcceptResult(resourcePreview: IJsonResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
141 /* Accept local resource */
142 if (this.extUri.isEqual(resource, this.localResource)) {
176 throw new Error(`Invalid Resource: ${resource.toString()}`);
177 }
179 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IJsonResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
180 const { fileContent } = resourcePreviews[0][0];
181 const { content, localChange, remoteChange } = resourcePreviews[0][1];
216 }
217 }
219 > async hasLocalData(): Promise<boolean> {
220 return this.fileService.exists(this.file);
221 }
223 > async resolveContent(uri: URI): Promise<string | null> {
224 if (this.extUri.isEqual(this.remoteResource, uri)
225 || this.extUri.isEqual(this.baseResource, uri)
231 return null;
232 }
234 > private merge(originalLocalContent: string | null, originalRemoteContent: string | null, baseContent: string | null): {
235 content: string | null;
236 hasLocalChanged: boolean;
274 return { content: originalLocalContent, hasLocalChanged: true, hasRemoteChanged: true, hasConflicts: true };
275 }
src/vs/platform/userDataSync/common/userDataSyncAccount.ts 45 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataSyncAccount.ts
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable } from '../../../base/common/lifecycle.js';
8 > import { createDecorator } from '../../instantiation/common/instantiation.js';
9 > import { IUserDataSyncLogService, IUserDataSyncStoreService, UserDataSyncErrorCode } from './userDataSync.js';
10 >
11 > export interface IUserDataSyncAccount {
12 > readonly authenticationProviderId: string;
13 > readonly token: string;
14 > }
15 >
16 > export const IUserDataSyncAccountService = createDecorator<IUserDataSyncAccountService>('IUserDataSyncAccountService');
17 > export interface IUserDataSyncAccountService {
18 > readonly _serviceBrand: undefined;
19 >
20 > readonly onTokenFailed: Event<boolean/*bail out*/>;
21 > readonly account: IUserDataSyncAccount | undefined;
22 > readonly onDidChangeAccount: Event<IUserDataSyncAccount | undefined>;
23 > updateAccount(account: IUserDataSyncAccount | undefined): Promise<void>;
24 >
25 > }
26 >
27 > export class UserDataSyncAccountService extends Disposable implements IUserDataSyncAccountService {
28 >
29 > _serviceBrand: undefined;
30 >
31 > private _account: IUserDataSyncAccount | undefined;
32 > get account(): IUserDataSyncAccount | undefined { return this._account; }
33 > private _onDidChangeAccount = this._register(new Emitter<IUserDataSyncAccount | undefined>());
34 > readonly onDidChangeAccount = this._onDidChangeAccount.event;
35 >
36 > private _onTokenFailed: Emitter<boolean> = this._register(new Emitter<boolean>());
37 > readonly onTokenFailed: Event<boolean> = this._onTokenFailed.event;
38 >
39 > private wasTokenFailed: boolean = false;
40 >
41 > constructor(
42 @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
43 @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
56 this._register(userDataSyncStoreService.onTokenSucceed(() => this.wasTokenFailed = false));
57 }
59 > async updateAccount(account: IUserDataSyncAccount | undefined): Promise<void> {
60 if (account && this._account ? account.token !== this._account.token || account.authenticationProviderId !== this._account.authenticationProviderId : account !== this._account) {
61 this._account = account;
src/vs/platform/userDataSync/common/tasksSync.ts 44 introduced LOC · 8 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- tasksSync.ts
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 { IConfigurationService } from '../../configuration/common/configuration.js';
8 > import { IEnvironmentService } from '../../environment/common/environment.js';
9 > import { IFileService } from '../../files/common/files.js';
10 > import { ILogService } from '../../log/common/log.js';
11 > import { IStorageService } from '../../storage/common/storage.js';
12 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
13 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
14 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
15 > import { AbstractJsonSynchronizer } from './abstractJsonSynchronizer.js';
16 > import { AbstractInitializer } from './abstractSynchronizer.js';
17 > import { IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource } from './userDataSync.js';
18 >
19 > interface ITasksSyncContent {
20 > tasks?: string;
21 > }
22 >
23 > export function getTasksContentFromSyncContent(syncContent: string, logService: ILogService): string | null {
24 try {
25 const parsed = <ITasksSyncContent>JSON.parse(syncContent);
30 }
31 }
33 > export class TasksSynchroniser extends AbstractJsonSynchronizer implements IUserDataSynchroniser {
34 >
35 > constructor(
36 profile: IUserDataProfile,
37 collection: string | undefined,
49 super(profile.tasksResource, { syncResource: SyncResource.Tasks, profile }, collection, 'tasks.json', fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
50 }
52 > protected getContentFromSyncContent(syncContent: string): string | null {
53 return getTasksContentFromSyncContent(syncContent, this.logService);
54 }
56 > protected toSyncContent(tasks: string | null): ITasksSyncContent {
57 return tasks ? { tasks } : {};
58 }
59 > } tasksSync.ts
60 >
61 > export class TasksInitializer extends AbstractInitializer {
62 >
63 > private tasksResource = this.userDataProfilesService.defaultProfile.tasksResource;
64 >
65 > constructor(
66 @IFileService fileService: IFileService,
67 @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
73 super(SyncResource.Tasks, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
74 }
76 > protected async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
77 const tasksContent = remoteUserData.syncData ? getTasksContentFromSyncContent(remoteUserData.syncData.content, this.logService) : null;
78 if (!tasksContent) {
91 await this.updateLastSyncUserData(remoteUserData);
92 }
94 > private async isEmpty(): Promise<boolean> {
95 return this.fileService.exists(this.tasksResource);
96 }
98 > }
src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts 44 introduced LOC · 11 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- userDataSyncEnablementService.ts
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable } from '../../../base/common/lifecycle.js';
8 > import { isWeb } from '../../../base/common/platform.js';
9 > import { IEnvironmentService } from '../../environment/common/environment.js';
10 > import { IApplicationStorageValueChangeEvent, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
11 > import { ALL_SYNC_RESOURCES, getEnablementKey, IUserDataSyncEnablementService, IUserDataSyncStoreManagementService, SyncResource } from './userDataSync.js';
12 >
13 > const enablementKey = 'sync.enable';
14 >
15 > export class UserDataSyncEnablementService extends Disposable implements IUserDataSyncEnablementService {
16 >
17 > _serviceBrand: undefined;
18 >
19 > private _onDidChangeEnablement = this._register(new Emitter<boolean>());
20 > readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
21 >
22 > private _onDidChangeResourceEnablement = this._register(new Emitter<[SyncResource, boolean]>());
23 > readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event;
24 >
25 > constructor(
26 @IStorageService private readonly storageService: IStorageService,
27 @IEnvironmentService protected readonly environmentService: IEnvironmentService,
31 this._register(storageService.onDidChangeValue(StorageScope.APPLICATION, undefined, this._store)(e => this.onDidStorageChange(e)));
32 }
34 > isEnabled(): boolean {
35 switch (this.environmentService.sync) {
36 case 'on':
41 return this.storageService.getBoolean(enablementKey, StorageScope.APPLICATION, false);
42 }
44 > canToggleEnablement(): boolean {
45 return this.userDataSyncStoreManagementService.userDataSyncStore !== undefined && this.environmentService.sync === undefined;
46 }
48 > setEnablement(enabled: boolean): void {
49 if (enabled && !this.canToggleEnablement()) {
50 return;
52 this.storageService.store(enablementKey, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE);
53 }
55 > isResourceEnabled(resource: SyncResource, defaultValue?: boolean): boolean {
56 const storedValue = this.storageService.getBoolean(getEnablementKey(resource), StorageScope.APPLICATION);
57 defaultValue = defaultValue ?? resource !== SyncResource.Prompts;
58 return storedValue ?? defaultValue;
59 }
61 > isResourceEnablementConfigured(resource: SyncResource): boolean {
62 const storedValue = this.storageService.getBoolean(getEnablementKey(resource), StorageScope.APPLICATION);
63
64 return (storedValue !== undefined);
65 }
67 > setResourceEnablement(resource: SyncResource, enabled: boolean): void {
68 if (this.isResourceEnabled(resource) !== enabled) {
69 const resourceEnablementKey = getEnablementKey(resource);
71 }
72 }
74 > getResourceSyncStateVersion(resource: SyncResource): string | undefined {
75 return undefined;
76 }
78 > private storeResourceEnablement(resourceEnablementKey: string, enabled: boolean): void {
79 this.storageService.store(resourceEnablementKey, enabled, StorageScope.APPLICATION, isWeb ? StorageTarget.USER /* sync in web */ : StorageTarget.MACHINE);
80 }
82 > private onDidStorageChange(storageChangeEvent: IApplicationStorageValueChangeEvent): void {
83 if (enablementKey === storageChangeEvent.key) {
84 this._onDidChangeEnablement.fire(this.isEnabled());
src/vs/platform/userDataSync/common/keybindingsMerge.ts 43 introduced LOC · 13 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- keybindingsMerge.ts
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 { equals } from '../../../base/common/arrays.js';
7 > import { IStringDictionary } from '../../../base/common/collections.js';
8 > import { parse } from '../../../base/common/json.js';
9 > import { FormattingOptions } from '../../../base/common/jsonFormatter.js';
10 > import * as objects from '../../../base/common/objects.js';
11 > import { ContextKeyExpr } from '../../contextkey/common/contextkey.js';
12 > import { IUserFriendlyKeybinding } from '../../keybinding/common/keybinding.js';
13 > import * as contentUtil from './content.js';
14 > import { IUserDataSyncUtilService } from './userDataSync.js';
15 >
16 > interface ICompareResult {
17 > added: Set<string>;
18 > removed: Set<string>;
19 > updated: Set<string>;
20 > }
21 >
22 > interface IMergeResult {
23 > hasLocalForwarded: boolean;
24 > hasRemoteForwarded: boolean;
25 > added: Set<string>;
26 > removed: Set<string>;
27 > updated: Set<string>;
28 > conflicts: Set<string>;
29 > }
30 >
31 function parseKeybindings(content: string): IUserFriendlyKeybinding[] {
32 return parse(content) || [];
33 }
35 export async function merge(localContent: string, remoteContent: string, baseContent: string | null, formattingOptions: FormattingOptions, userDataSyncUtilService: IUserDataSyncUtilService): Promise<{ mergeContent: string; hasChanges: boolean; hasConflicts: boolean }> {
36 const local = parseKeybindings(localContent);
105 return { mergeContent, hasChanges: true, hasConflicts: commandsMergeResult.conflicts.size > 0 };
106 }
108 function computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): { added: Set<string>; removed: Set<string>; updated: Set<string>; conflicts: Set<string> } {
109 const added: Set<string> = new Set<string>();
196 return { added, removed, updated, conflicts };
197 }
199 function computeMergeResultByKeybinding(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null, normalizedKeys: IStringDictionary<string>): IMergeResult {
200 const empty = new Set<string>();
222 return { hasLocalForwarded: true, hasRemoteForwarded: true, added, removed, updated, conflicts };
223 }
225 function byKeybinding(keybindings: IUserFriendlyKeybinding[], keys: IStringDictionary<string>) {
226 const map: Map<string, IUserFriendlyKeybinding[]> = new Map<string, IUserFriendlyKeybinding[]>();
237 return map;
238 }
240 function byCommand(keybindings: IUserFriendlyKeybinding[]): Map<string, IUserFriendlyKeybinding[]> {
241 const map: Map<string, IUserFriendlyKeybinding[]> = new Map<string, IUserFriendlyKeybinding[]>();
251 return map;
252 }
254 >
255 function compareByKeybinding(from: Map<string, IUserFriendlyKeybinding[]>, to: Map<string, IUserFriendlyKeybinding[]>): ICompareResult {
256 const fromKeys = [...from.keys()];
273 return { added, removed, updated };
274 }
276 function compareByCommand(from: Map<string, IUserFriendlyKeybinding[]>, to: Map<string, IUserFriendlyKeybinding[]>, normalizedKeys: IStringDictionary<string>): ICompareResult {
277 const fromKeys = [...from.keys()];
294 return { added, removed, updated };
295 }
297 function areSameKeybindingsWithSameCommand(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean {
298 // Compare entries adding keybindings
306 return true;
307 }
309 function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean {
310 if (a.command !== b.command) {
327 return true;
328 }
330 function addKeybindings(content: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string {
331 for (const keybinding of keybindings) {
334 return content;
335 }
337 function removeKeybindings(content: string, command: string, formattingOptions: FormattingOptions): string {
338 const keybindings = parseKeybindings(content);
344 return content;
345 }
347 function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string {
348 const allKeybindings = parseKeybindings(content);