mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-04 17:23:45 -05:00
Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f
This commit is contained in:
@@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import {
|
||||
SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService,
|
||||
IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview,
|
||||
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change
|
||||
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
|
||||
@@ -57,11 +57,11 @@ export interface IMergableResourcePreview extends IBaseResourcePreview {
|
||||
readonly remoteContent: string | null;
|
||||
readonly localContent: string | null;
|
||||
readonly previewContent: string | null;
|
||||
readonly acceptedContent: string | null;
|
||||
readonly hasConflicts: boolean;
|
||||
merged: boolean;
|
||||
}
|
||||
|
||||
export type IResourcePreview = Omit<IMergableResourcePreview, 'merged'>;
|
||||
export type IResourcePreview = Omit<IMergableResourcePreview, 'mergeState'>;
|
||||
|
||||
export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
|
||||
readonly remoteUserData: IRemoteUserData;
|
||||
@@ -217,6 +217,19 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
return this._sync(manifest, false, headers);
|
||||
}
|
||||
|
||||
async apply(force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
|
||||
try {
|
||||
this.syncHeaders = { ...headers };
|
||||
|
||||
const status = await this.doApply(force);
|
||||
this.setStatus(status);
|
||||
|
||||
return this.syncPreviewPromise;
|
||||
} finally {
|
||||
this.syncHeaders = {};
|
||||
}
|
||||
}
|
||||
|
||||
private async _sync(manifest: IUserDataManifest | null, apply: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null> {
|
||||
try {
|
||||
this.syncHeaders = { ...headers };
|
||||
@@ -350,14 +363,18 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, apply, token));
|
||||
}
|
||||
|
||||
if (apply) {
|
||||
const preview = await this.syncPreviewPromise;
|
||||
const newConflicts = preview.resourcePreviews.filter(({ hasConflicts }) => hasConflicts);
|
||||
return await this.updateConflictsAndApply(newConflicts, false);
|
||||
} else {
|
||||
return SyncStatus.Syncing;
|
||||
const preview = await this.syncPreviewPromise;
|
||||
this.updateConflicts(preview.resourcePreviews);
|
||||
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
|
||||
return SyncStatus.HasConflicts;
|
||||
}
|
||||
|
||||
if (apply) {
|
||||
return await this.doApply(false);
|
||||
}
|
||||
|
||||
return SyncStatus.Syncing;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
// reset preview on error
|
||||
@@ -367,76 +384,91 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
|
||||
async accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null> {
|
||||
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
|
||||
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content);
|
||||
return {
|
||||
...updatedResourcePreview,
|
||||
mergeState: MergeState.Accepted
|
||||
};
|
||||
});
|
||||
return this.syncPreviewPromise;
|
||||
}
|
||||
|
||||
async merge(resource: URI): Promise<ISyncResourcePreview | null> {
|
||||
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
|
||||
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
|
||||
return {
|
||||
...updatedResourcePreview,
|
||||
mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted
|
||||
};
|
||||
});
|
||||
return this.syncPreviewPromise;
|
||||
}
|
||||
|
||||
async discard(resource: URI): Promise<ISyncResourcePreview | null> {
|
||||
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
|
||||
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
|
||||
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
|
||||
return {
|
||||
...updatedResourcePreview,
|
||||
mergeState: MergeState.Preview
|
||||
};
|
||||
});
|
||||
return this.syncPreviewPromise;
|
||||
}
|
||||
|
||||
private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise<IMergableResourcePreview>): Promise<void> {
|
||||
if (!this.syncPreviewPromise) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.syncHeaders = { ...headers };
|
||||
const preview = await this.syncPreviewPromise;
|
||||
this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token));
|
||||
return this.merge(resource, force, headers);
|
||||
} finally {
|
||||
this.syncHeaders = {};
|
||||
let preview = await this.syncPreviewPromise;
|
||||
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
|
||||
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncPreviewPromise = createCancelablePromise(async token => {
|
||||
const resourcePreviews = [...preview.resourcePreviews];
|
||||
resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]);
|
||||
return {
|
||||
...preview,
|
||||
resourcePreviews
|
||||
};
|
||||
});
|
||||
|
||||
preview = await this.syncPreviewPromise;
|
||||
this.updateConflicts(preview.resourcePreviews);
|
||||
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
|
||||
this.setStatus(SyncStatus.HasConflicts);
|
||||
} else {
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
}
|
||||
}
|
||||
|
||||
async merge(resource: URI, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
|
||||
if (!this.syncPreviewPromise) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
this.syncHeaders = { ...headers };
|
||||
const preview = await this.syncPreviewPromise;
|
||||
const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) =>
|
||||
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
|
||||
if (!resourcePreview) {
|
||||
return preview;
|
||||
}
|
||||
|
||||
/* mark merged */
|
||||
resourcePreview.merged = true;
|
||||
|
||||
/* Add or remove the preview from conflicts */
|
||||
const newConflicts = [...this._conflicts];
|
||||
const index = newConflicts.findIndex(({ localResource, remoteResource, previewResource }) =>
|
||||
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
|
||||
if (resourcePreview.hasConflicts) {
|
||||
if (newConflicts.indexOf(resourcePreview) === -1) {
|
||||
newConflicts.push(resourcePreview);
|
||||
}
|
||||
} else {
|
||||
if (index !== -1) {
|
||||
newConflicts.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const status = await this.updateConflictsAndApply(newConflicts, force);
|
||||
this.setStatus(status);
|
||||
return this.syncPreviewPromise;
|
||||
|
||||
} finally {
|
||||
this.syncHeaders = {};
|
||||
}
|
||||
protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise<IResourcePreview> {
|
||||
return {
|
||||
...resourcePreview,
|
||||
acceptedContent
|
||||
};
|
||||
}
|
||||
|
||||
private async updateConflictsAndApply(conflicts: IMergableResourcePreview[], force: boolean): Promise<SyncStatus> {
|
||||
private async doApply(force: boolean): Promise<SyncStatus> {
|
||||
if (!this.syncPreviewPromise) {
|
||||
return SyncStatus.Idle;
|
||||
}
|
||||
|
||||
const preview = await this.syncPreviewPromise;
|
||||
|
||||
// update conflicts
|
||||
this.updateConflicts(conflicts);
|
||||
if (this._conflicts.length) {
|
||||
// check for conflicts
|
||||
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
|
||||
return SyncStatus.HasConflicts;
|
||||
}
|
||||
|
||||
// check if all are merged
|
||||
if (preview.resourcePreviews.some(r => !r.merged)) {
|
||||
// check if all are accepted
|
||||
if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) {
|
||||
return SyncStatus.Syncing;
|
||||
}
|
||||
|
||||
@@ -452,39 +484,14 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
return SyncStatus.Idle;
|
||||
}
|
||||
|
||||
private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<ISyncResourcePreview> {
|
||||
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
|
||||
(localChange !== Change.None || remoteChange !== Change.None)
|
||||
&& (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)));
|
||||
if (index !== -1) {
|
||||
const resourcePreviews = [...preview.resourcePreviews];
|
||||
const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token);
|
||||
resourcePreviews[index] = { ...resourcePreview, merged: resourcePreviews[index].merged };
|
||||
preview = {
|
||||
...preview,
|
||||
resourcePreviews
|
||||
};
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IResourcePreview> {
|
||||
return {
|
||||
...resourcePreview,
|
||||
previewContent,
|
||||
hasConflicts: false,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
|
||||
private async clearPreviewFolder(): Promise<void> {
|
||||
try {
|
||||
await this.fileService.del(this.syncPreviewFolder, { recursive: true });
|
||||
} catch (error) { /* Ignore */ }
|
||||
}
|
||||
|
||||
private updateConflicts(conflicts: IMergableResourcePreview[]): void {
|
||||
private updateConflicts(previews: IMergableResourcePreview[]): void {
|
||||
const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict);
|
||||
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
|
||||
this._conflicts = conflicts;
|
||||
this._onDidChangeConflicts.fire(conflicts);
|
||||
@@ -542,14 +549,14 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
|
||||
if (syncPreview) {
|
||||
for (const resourcePreview of syncPreview.resourcePreviews) {
|
||||
if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) {
|
||||
return resourcePreview.previewContent || '';
|
||||
if (isEqual(resourcePreview.acceptedResource, uri)) {
|
||||
return resourcePreview.acceptedContent;
|
||||
}
|
||||
if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) {
|
||||
return resourcePreview.remoteContent || '';
|
||||
if (isEqual(resourcePreview.remoteResource, uri)) {
|
||||
return resourcePreview.remoteContent;
|
||||
}
|
||||
if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) {
|
||||
return resourcePreview.localContent || '';
|
||||
if (isEqual(resourcePreview.localResource, uri)) {
|
||||
return resourcePreview.localContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,21 +569,31 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, merge: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
|
||||
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
|
||||
const machineId = await this.currentMachineIdPromise;
|
||||
const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
|
||||
|
||||
// For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine
|
||||
const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData;
|
||||
const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
|
||||
const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
|
||||
|
||||
// Mark merge
|
||||
const mergableResourcePreviews = resourcePreviews.map(r => ({
|
||||
...r,
|
||||
merged: merge || (r.localChange === Change.None && r.remoteChange === Change.None) /* Mark previews with no changes as merged */
|
||||
}));
|
||||
const resourcePreviews: IMergableResourcePreview[] = [];
|
||||
for (const resourcePreview of result) {
|
||||
if (token.isCancellationRequested) {
|
||||
break;
|
||||
}
|
||||
if (!apply) {
|
||||
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
|
||||
}
|
||||
resourcePreviews.push({
|
||||
...resourcePreview,
|
||||
mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */
|
||||
: apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted)
|
||||
: MergeState.Preview
|
||||
});
|
||||
}
|
||||
|
||||
return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine };
|
||||
return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine };
|
||||
}
|
||||
|
||||
async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {
|
||||
|
||||
@@ -46,8 +46,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
*/
|
||||
protected readonly version: number = 3;
|
||||
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
|
||||
private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
|
||||
private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
|
||||
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
|
||||
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
|
||||
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
|
||||
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@@ -94,12 +96,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const mergeResult = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions);
|
||||
const { added, removed, updated } = mergeResult;
|
||||
return [{
|
||||
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
@@ -131,12 +135,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
|
||||
return [{
|
||||
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
@@ -177,14 +183,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
}
|
||||
|
||||
protected async updateResourcePreviewContent(resourcePreview: IExtensionResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IExtensionResourcePreview> {
|
||||
if (isEqual(resource, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
|
||||
protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string | null): Promise<IExtensionResourcePreview> {
|
||||
if (isEqual(resource, this.localResource)) {
|
||||
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
|
||||
return this.getPushPreview(remoteExtensions);
|
||||
}
|
||||
return {
|
||||
...resourcePreview,
|
||||
previewContent,
|
||||
acceptedContent,
|
||||
hasConflicts: false,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.Modified,
|
||||
@@ -195,10 +201,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
|
||||
const localResource = ExtensionsSynchroniser.EXTENSIONS_DATA_URI;
|
||||
const localResource = this.localResource;
|
||||
const localContent = this.format(localExtensions);
|
||||
const remoteResource = this.remotePreviewResource;
|
||||
const previewResource = this.localPreviewResource;
|
||||
const remoteResource = this.remoteResource;
|
||||
const previewResource = this.previewResource;
|
||||
const acceptedResource = this.acceptedResource;
|
||||
const previewContent = null;
|
||||
if (remoteExtensions !== null) {
|
||||
const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions);
|
||||
@@ -210,6 +217,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
remoteContent: this.format(remoteExtensions),
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
@@ -228,6 +237,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
remoteContent: null,
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [],
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None,
|
||||
@@ -243,12 +254,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const mergeResult = merge(localExtensions, null, null, [], ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
return {
|
||||
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
@@ -266,13 +279,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
return this.format(localExtensions);
|
||||
}
|
||||
|
||||
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
|
||||
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
|
||||
return this.resolvePreviewContent(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
|
||||
private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` });
|
||||
protected readonly version: number = 1;
|
||||
private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
|
||||
private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
|
||||
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
|
||||
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
|
||||
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
|
||||
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -93,12 +95,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
const mergeResult = merge(localUserData.storage, syncGlobalState.storage, localUserData.storage, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
|
||||
const { local, skipped } = mergeResult;
|
||||
return [{
|
||||
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localUserData),
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
local,
|
||||
remote: syncGlobalState.storage,
|
||||
localUserData,
|
||||
@@ -125,12 +129,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
const { local, remote, skipped } = mergeResult;
|
||||
|
||||
return [{
|
||||
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localGloablState),
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
local,
|
||||
remote,
|
||||
localUserData: localGloablState,
|
||||
@@ -172,11 +178,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
}
|
||||
|
||||
protected async updateResourcePreviewContent(resourcePreview: IGlobalStateResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IGlobalStateResourcePreview> {
|
||||
if (GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, resource) {
|
||||
protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise<IGlobalStateResourcePreview> {
|
||||
if (isEqual(this.localResource, resource)) {
|
||||
return this.getPushPreview(resourcePreview.remoteContent);
|
||||
}
|
||||
if (this.remotePreviewResource, resource) {
|
||||
if (isEqual(this.remoteResource, resource)) {
|
||||
return this.getPullPreview(resourcePreview.remoteContent, resourcePreview.skippedStorageKeys);
|
||||
}
|
||||
return resourcePreview;
|
||||
@@ -184,10 +190,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
|
||||
private async getPullPreview(remoteContent: string | null, skippedStorageKeys: string[]): Promise<IGlobalStateResourcePreview> {
|
||||
const localGlobalState = await this.getLocalGlobalState();
|
||||
const localResource = GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI;
|
||||
const localResource = this.localResource;
|
||||
const localContent = this.format(localGlobalState);
|
||||
const remoteResource = this.remotePreviewResource;
|
||||
const previewResource = this.localPreviewResource;
|
||||
const remoteResource = this.remoteResource;
|
||||
const previewResource = this.previewResource;
|
||||
const acceptedResource = this.acceptedResource;
|
||||
const previewContent = null;
|
||||
if (remoteContent !== null) {
|
||||
const remoteGlobalState: IGlobalState = JSON.parse(remoteContent);
|
||||
@@ -200,6 +207,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
remoteContent: this.format(remoteGlobalState),
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
local,
|
||||
remote,
|
||||
localUserData: localGlobalState,
|
||||
@@ -216,6 +225,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
remoteContent: null,
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
local: { added: {}, removed: [], updated: {} },
|
||||
remote: null,
|
||||
localUserData: localGlobalState,
|
||||
@@ -231,12 +242,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
const localUserData = await this.getLocalGlobalState();
|
||||
const remoteGlobalState: IGlobalState = remoteContent ? JSON.parse(remoteContent) : null;
|
||||
return {
|
||||
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localUserData),
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
local: { added: {}, removed: [], updated: {} },
|
||||
remote: localUserData.storage,
|
||||
localUserData,
|
||||
@@ -252,12 +265,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
|
||||
const localGlobalState = await this.getLocalGlobalState();
|
||||
return this.format(localGlobalState);
|
||||
}
|
||||
|
||||
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
|
||||
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
|
||||
return this.resolvePreviewContent(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,10 @@ interface ISyncContent {
|
||||
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
protected readonly version: number = 1;
|
||||
protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
|
||||
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
|
||||
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
|
||||
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
|
||||
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
|
||||
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@@ -58,13 +60,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
const previewContent = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: previewContent,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: false,
|
||||
@@ -76,13 +80,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
const previewContent: string | null = fileContent ? fileContent.value.toString() : null;
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
@@ -94,13 +100,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
const previewContent = this.getKeybindingsContentFromSyncContent(syncData.content);
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
@@ -150,17 +158,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
}
|
||||
|
||||
if (previewContent && !token.isCancellationRequested) {
|
||||
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(previewContent));
|
||||
await this.fileService.writeFile(this.previewResource, VSBuffer.fromString(previewContent));
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
hasConflicts,
|
||||
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
|
||||
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
|
||||
@@ -168,7 +178,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0];
|
||||
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
|
||||
|
||||
if (content !== null) {
|
||||
if (this.hasErrors(content)) {
|
||||
@@ -193,7 +203,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
// Delete the preview
|
||||
try {
|
||||
await this.fileService.del(this.localPreviewResource);
|
||||
await this.fileService.del(this.previewResource);
|
||||
} catch (e) { /* ignore */ }
|
||||
} else {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
|
||||
@@ -230,11 +240,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqual(this.file, uri)) {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
return fileContent ? fileContent.value.toString() : '';
|
||||
}
|
||||
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
|
||||
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
|
||||
return this.resolvePreviewContent(uri);
|
||||
}
|
||||
let content = await super.resolveContent(uri);
|
||||
|
||||
@@ -39,8 +39,10 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
|
||||
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
protected readonly version: number = 1;
|
||||
protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
|
||||
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
|
||||
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
|
||||
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
|
||||
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
|
||||
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -66,19 +68,21 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
|
||||
let previewContent: string | null = null;
|
||||
if (remoteSettingsSyncContent !== null) {
|
||||
if (remoteSettingsSyncContent) {
|
||||
// Update ignored settings from local file content
|
||||
previewContent = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: false,
|
||||
@@ -92,20 +96,22 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
|
||||
let previewContent: string | null = null;
|
||||
if (fileContent !== null) {
|
||||
let previewContent: string | null = fileContent?.value.toString() || null;
|
||||
if (previewContent) {
|
||||
// Remove ignored settings
|
||||
previewContent = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils);
|
||||
previewContent = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
@@ -126,13 +132,15 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
@@ -146,6 +154,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
|
||||
let acceptedContent: string | null = null;
|
||||
let previewContent: string | null = null;
|
||||
let hasLocalChanged: boolean = false;
|
||||
let hasRemoteChanged: boolean = false;
|
||||
@@ -156,7 +165,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
this.validateContent(localContent);
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`);
|
||||
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions);
|
||||
previewContent = result.localContent || result.remoteContent;
|
||||
acceptedContent = result.localContent || result.remoteContent;
|
||||
hasLocalChanged = result.localContent !== null;
|
||||
hasRemoteChanged = result.remoteContent !== null;
|
||||
hasConflicts = result.hasConflicts;
|
||||
@@ -165,40 +174,43 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
// First time syncing to remote
|
||||
else if (fileContent) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`);
|
||||
previewContent = fileContent.value.toString();
|
||||
acceptedContent = fileContent.value.toString();
|
||||
hasRemoteChanged = true;
|
||||
}
|
||||
|
||||
if (previewContent && !token.isCancellationRequested) {
|
||||
if (acceptedContent && !token.isCancellationRequested) {
|
||||
// Remove the ignored settings from the preview.
|
||||
const content = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formattingOptions);
|
||||
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content));
|
||||
previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.file,
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remotePreviewResource,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.localPreviewResource,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent,
|
||||
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
|
||||
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
|
||||
hasConflicts,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IFileResourcePreview> {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Add ignored settings from local file content
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
previewContent = updateIgnoredSettings(previewContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
return super.updateResourcePreviewContent(resourcePreview, resource, previewContent, token) as Promise<IFileResourcePreview>;
|
||||
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
|
||||
if (acceptedContent && (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource))) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Add ignored settings from local file content
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
return super.updateResourcePreview(resourcePreview, resource, acceptedContent) as Promise<IFileResourcePreview>;
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0];
|
||||
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
|
||||
|
||||
if (content !== null) {
|
||||
|
||||
@@ -225,7 +237,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
|
||||
// Delete the preview
|
||||
try {
|
||||
await this.fileService.del(this.localPreviewResource);
|
||||
await this.fileService.del(this.previewResource);
|
||||
} catch (e) { /* ignore */ }
|
||||
} else {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
|
||||
@@ -260,11 +272,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqual(this.file, uri)) {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
return fileContent ? fileContent.value.toString() : '';
|
||||
}
|
||||
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
|
||||
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
|
||||
return this.resolvePreviewContent(uri);
|
||||
}
|
||||
let content = await super.resolveContent(uri);
|
||||
@@ -289,7 +297,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
|
||||
protected async resolvePreviewContent(resource: URI): Promise<string | null> {
|
||||
let content = await super.resolvePreviewContent(resource);
|
||||
if (content !== null) {
|
||||
if (content) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// remove ignored settings from the preview content
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import {
|
||||
IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService,
|
||||
USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, UserDataSyncError, UserDataSyncErrorCode, Change
|
||||
USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, Change
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
@@ -20,7 +20,6 @@ import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/sn
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
@@ -94,34 +93,93 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
}
|
||||
|
||||
const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets);
|
||||
const resourcePreviews = this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
|
||||
|
||||
for (const resourcePreview of resourcePreviews) {
|
||||
if (resourcePreview.hasConflicts) {
|
||||
if (!token.isCancellationRequested) {
|
||||
await this.fileService.writeFile(resourcePreview.previewResource!, VSBuffer.fromString(resourcePreview.previewContent || ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resourcePreviews;
|
||||
return this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
|
||||
}
|
||||
|
||||
protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IFileResourcePreview> {
|
||||
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
|
||||
return {
|
||||
...resourcePreview,
|
||||
previewContent: previewContent || null,
|
||||
hasConflicts: false,
|
||||
localChange: previewContent ? Change.Modified : Change.Deleted,
|
||||
remoteChange: previewContent ? Change.Modified : Change.Deleted,
|
||||
acceptedContent,
|
||||
localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent),
|
||||
remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent),
|
||||
};
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
|
||||
throw new UserDataSyncError(localize('unresolved conflicts', "Error while syncing {0}. Please resolve conflicts first.", this.syncResourceLogLabel), UserDataSyncErrorCode.UnresolvedConflicts, this.resource);
|
||||
private computeLocalChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
|
||||
const isRemoteResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }));
|
||||
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
|
||||
|
||||
const previewExists = acceptedContent !== null;
|
||||
const remoteExists = resourcePreview.remoteContent !== null;
|
||||
const localExists = resourcePreview.fileContent !== null;
|
||||
|
||||
if (isRemoteResourceAccepted) {
|
||||
if (remoteExists && localExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (remoteExists && !localExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
if (!remoteExists && localExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
if (isPreviewResourceAccepted) {
|
||||
if (previewExists && localExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (previewExists && !localExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
if (!previewExists && localExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
private computeRemoteChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
|
||||
const isLocalResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }));
|
||||
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
|
||||
|
||||
const previewExists = acceptedContent !== null;
|
||||
const remoteExists = resourcePreview.remoteContent !== null;
|
||||
const localExists = resourcePreview.fileContent !== null;
|
||||
|
||||
if (isLocalResourceAccepted) {
|
||||
if (remoteExists && localExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (remoteExists && !localExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
if (!remoteExists && localExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
if (isPreviewResourceAccepted) {
|
||||
if (previewExists && remoteExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (previewExists && !remoteExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
if (!previewExists && remoteExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`);
|
||||
}
|
||||
@@ -158,13 +216,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets added remotely -> add locally */
|
||||
for (const key of Object.keys(mergeResult.local.added)) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: null,
|
||||
localContent: null,
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.local.added[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.local.added[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.Added,
|
||||
remoteChange: Change.None
|
||||
@@ -174,13 +234,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets updated remotely -> update locally */
|
||||
for (const key of Object.keys(mergeResult.local.updated)) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
localContent: localFileContent[key].value.toString(),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.local.updated[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.local.updated[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None
|
||||
@@ -190,13 +252,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets removed remotely -> remove locally */
|
||||
for (const key of mergeResult.local.removed) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
localContent: localFileContent[key].value.toString(),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.Deleted,
|
||||
remoteChange: Change.None
|
||||
@@ -206,13 +270,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets added locally -> add remotely */
|
||||
for (const key of Object.keys(mergeResult.remote.added)) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
localContent: localFileContent[key].value.toString(),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.remote.added[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.remote.added[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Added
|
||||
@@ -222,13 +288,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets updated locally -> update remotely */
|
||||
for (const key of Object.keys(mergeResult.remote.updated)) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
localContent: localFileContent[key].value.toString(),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.remote.updated[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.remote.updated[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified
|
||||
@@ -238,13 +306,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets removed locally -> remove remotely */
|
||||
for (const key of mergeResult.remote.removed) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: null,
|
||||
localContent: null,
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Deleted
|
||||
@@ -254,13 +324,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
/* Snippets with conflicts */
|
||||
for (const key of mergeResult.conflicts) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key] || null,
|
||||
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key] || null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
hasConflicts: true,
|
||||
localChange: localFileContent[key] ? Change.Modified : Change.Added,
|
||||
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
|
||||
@@ -271,13 +343,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
for (const key of Object.keys(localFileContent)) {
|
||||
if (!resourcePreviews.has(key)) {
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.snippetsFolder, key),
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key] || null,
|
||||
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }),
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key] || null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None
|
||||
@@ -308,17 +382,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqualOrParent(uri, this.snippetsFolder)) {
|
||||
try {
|
||||
const content = await this.fileService.readFile(uri);
|
||||
return content ? content.value.toString() : null;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (isEqualOrParent(uri.with({ scheme: this.syncPreviewFolder.scheme }), this.syncPreviewFolder)
|
||||
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME }))) {
|
||||
if (isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
|
||||
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
|
||||
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
|
||||
return this.resolvePreviewContent(uri);
|
||||
}
|
||||
|
||||
@@ -362,12 +428,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
}
|
||||
|
||||
private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
|
||||
// Do not update if there are conflicts
|
||||
return;
|
||||
}
|
||||
|
||||
for (const { fileContent, previewContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
|
||||
for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
|
||||
if (localChange !== Change.None) {
|
||||
const key = remoteResource ? basename(remoteResource) : basename(localResource!);
|
||||
const resource = joinPath(this.snippetsFolder, key);
|
||||
@@ -397,15 +458,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
}
|
||||
|
||||
private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
|
||||
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
|
||||
// Do not update if there are conflicts
|
||||
return remoteUserData;
|
||||
}
|
||||
|
||||
const currentSnippets: IStringDictionary<string> = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {};
|
||||
const newSnippets: IStringDictionary<string> = deepClone(currentSnippets);
|
||||
|
||||
for (const { previewContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
|
||||
for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
|
||||
if (remoteChange !== Change.None) {
|
||||
const key = localResource ? basename(localResource) : basename(remoteResource!);
|
||||
if (remoteChange === Change.Deleted) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { localize } from 'vs/nls';
|
||||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
|
||||
type AutoSyncClassification = {
|
||||
sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
@@ -96,18 +97,24 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
|
||||
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
|
||||
|
||||
if (userDataSyncStoreService.userDataSyncStore) {
|
||||
if (this.isEnabled()) {
|
||||
this.logService.info('Auto Sync is enabled.');
|
||||
} else {
|
||||
this.logService.info('Auto Sync is disabled.');
|
||||
}
|
||||
this.updateAutoSync();
|
||||
if (this.hasToDisableMachineEventually()) {
|
||||
this.disableMachineEventually();
|
||||
}
|
||||
this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync()));
|
||||
this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync()));
|
||||
this._register(Event.debounce<string, string[]>(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false)));
|
||||
this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false)));
|
||||
}
|
||||
}
|
||||
|
||||
private updateAutoSync(): void {
|
||||
const { enabled, reason } = this.isAutoSyncEnabled();
|
||||
const { enabled, message } = this.isAutoSyncEnabled();
|
||||
if (enabled) {
|
||||
if (this.autoSync.value === undefined) {
|
||||
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncStoreService, this.userDataSyncService, this.userDataSyncMachinesService, this.logService, this.storageService);
|
||||
@@ -120,21 +127,31 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
|
||||
} else {
|
||||
this.syncTriggerDelayer.cancel();
|
||||
if (this.autoSync.value !== undefined) {
|
||||
this.logService.info('Auto Sync: Disabled because', reason);
|
||||
if (message) {
|
||||
this.logService.info(message);
|
||||
}
|
||||
this.autoSync.clear();
|
||||
}
|
||||
|
||||
/* log message when auto sync is not disabled by user */
|
||||
else if (message && this.isEnabled()) {
|
||||
this.logService.info(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For tests purpose only
|
||||
protected startAutoSync(): boolean { return true; }
|
||||
|
||||
private isAutoSyncEnabled(): { enabled: boolean, reason?: string } {
|
||||
private isAutoSyncEnabled(): { enabled: boolean, message?: string } {
|
||||
if (!this.isEnabled()) {
|
||||
return { enabled: false, reason: 'sync is disabled' };
|
||||
return { enabled: false, message: 'Auto Sync: Disabled.' };
|
||||
}
|
||||
if (!this.userDataSyncAccountService.account) {
|
||||
return { enabled: false, reason: 'token is not avaialable' };
|
||||
return { enabled: false, message: 'Auto Sync: Suspended until auth token is available.' };
|
||||
}
|
||||
if (this.userDataSyncStoreService.donotMakeRequestsUntil) {
|
||||
return { enabled: false, message: `Auto Sync: Suspended until ${toLocalISOString(this.userDataSyncStoreService.donotMakeRequestsUntil)} because server is not accepting requests until then.` };
|
||||
}
|
||||
return { enabled: true };
|
||||
}
|
||||
|
||||
@@ -122,16 +122,19 @@ export function isAuthenticationProvider(thing: any): thing is IAuthenticationPr
|
||||
}
|
||||
|
||||
export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined {
|
||||
const value = configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY) || productService[CONFIGURATION_SYNC_STORE_KEY];
|
||||
const value = {
|
||||
...(productService[CONFIGURATION_SYNC_STORE_KEY] || {}),
|
||||
...(configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY) || {})
|
||||
};
|
||||
if (value
|
||||
&& isString(value.url)
|
||||
&& isObject(value.authenticationProviders)
|
||||
&& Object.keys(value.authenticationProviders).every(authenticationProviderId => isArray(value.authenticationProviders[authenticationProviderId].scopes))
|
||||
&& isString((value as any).url) // {{SQL CARBON EDIT}} strict-nulls
|
||||
&& isObject((value as any).authenticationProviders) // {{SQL CARBON EDIT}} strict-nulls
|
||||
&& Object.keys((value as any).authenticationProviders).every(authenticationProviderId => isArray((value as any).authenticationProviders[authenticationProviderId].scopes)) // {{SQL CARBON EDIT}} strict-nulls
|
||||
) {
|
||||
return {
|
||||
url: joinPath(URI.parse(value.url), 'v1'),
|
||||
authenticationProviders: Object.keys(value.authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => {
|
||||
result.push({ id, scopes: value.authenticationProviders[id].scopes });
|
||||
url: joinPath(URI.parse((value as any).url), 'v1'), // {{SQL CARBON EDIT}} strict-nulls
|
||||
authenticationProviders: Object.keys((value as any).authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => { // {{SQL CARBON EDIT}} strict-nulls
|
||||
result.push({ id, scopes: (value as any).authenticationProviders[id].scopes }); // {{SQL CARBON EDIT}} strict-nulls
|
||||
return result;
|
||||
}, [])
|
||||
};
|
||||
@@ -164,6 +167,9 @@ export interface IUserDataSyncStoreService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly userDataSyncStore: IUserDataSyncStore | undefined;
|
||||
|
||||
readonly onDidChangeDonotMakeRequestsUntil: Event<void>;
|
||||
readonly donotMakeRequestsUntil: Date | undefined;
|
||||
|
||||
readonly onTokenFailed: Event<void>;
|
||||
readonly onTokenSucceed: Event<void>;
|
||||
setAuthToken(token: string, type: string): void;
|
||||
@@ -207,6 +213,7 @@ export enum UserDataSyncErrorCode {
|
||||
UpgradeRequired = 'UpgradeRequired', /* 426 */
|
||||
PreconditionRequired = 'PreconditionRequired', /* 428 */
|
||||
TooManyRequests = 'RemoteTooManyRequests', /* 429 */
|
||||
TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */
|
||||
|
||||
// Local Errors
|
||||
ConnectionRefused = 'ConnectionRefused',
|
||||
@@ -317,13 +324,20 @@ export const enum Change {
|
||||
Deleted,
|
||||
}
|
||||
|
||||
export const enum MergeState {
|
||||
Preview = 'preview',
|
||||
Conflict = 'conflict',
|
||||
Accepted = 'accepted',
|
||||
}
|
||||
|
||||
export interface IResourcePreview {
|
||||
readonly remoteResource: URI;
|
||||
readonly localResource: URI;
|
||||
readonly previewResource: URI;
|
||||
readonly acceptedResource: URI;
|
||||
readonly localChange: Change;
|
||||
readonly remoteChange: Change;
|
||||
readonly merged: boolean;
|
||||
readonly mergeState: MergeState;
|
||||
}
|
||||
|
||||
export interface ISyncResourcePreview {
|
||||
@@ -345,18 +359,20 @@ export interface IUserDataSynchroniser {
|
||||
pull(): Promise<void>;
|
||||
push(): Promise<void>;
|
||||
sync(manifest: IUserDataManifest | null, headers: IHeaders): Promise<void>;
|
||||
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
replace(uri: URI): Promise<boolean>;
|
||||
stop(): Promise<void>;
|
||||
|
||||
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null>;
|
||||
merge(resource: URI): Promise<ISyncResourcePreview | null>;
|
||||
discard(resource: URI): Promise<ISyncResourcePreview | null>;
|
||||
apply(force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
|
||||
hasPreviouslySynced(): Promise<boolean>;
|
||||
hasLocalData(): Promise<boolean>;
|
||||
resetLocal(): Promise<void>;
|
||||
|
||||
resolveContent(resource: URI): Promise<string | null>;
|
||||
acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
merge(resource: URI, force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
|
||||
getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
|
||||
getLocalSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
|
||||
getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>;
|
||||
@@ -387,8 +403,10 @@ export interface IManualSyncTask extends IDisposable {
|
||||
readonly manifest: IUserDataManifest | null;
|
||||
readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>;
|
||||
preview(): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
accept(uri: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
merge(uri?: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
apply(): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
pull(): Promise<void>;
|
||||
push(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
@@ -422,7 +440,7 @@ export interface IUserDataSyncService {
|
||||
hasLocalData(): Promise<boolean>;
|
||||
hasPreviouslySynced(): Promise<boolean>;
|
||||
resolveContent(resource: URI): Promise<string | null>;
|
||||
acceptPreviewContent(resource: SyncResource, conflictResource: URI, content: string): Promise<void>;
|
||||
accept(resource: SyncResource, conflictResource: URI, content: string | null, apply: boolean): Promise<void>;
|
||||
|
||||
getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
|
||||
@@ -53,7 +53,7 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
case 'resetLocal': return this.service.resetLocal();
|
||||
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
|
||||
case 'hasLocalData': return this.service.hasLocalData();
|
||||
case 'acceptPreviewContent': return this.service.acceptPreviewContent(args[0], URI.revive(args[1]), args[2]);
|
||||
case 'accept': return this.service.accept(args[0], URI.revive(args[1]), args[2], args[3]);
|
||||
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
|
||||
case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]);
|
||||
case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]);
|
||||
@@ -65,7 +65,7 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
private async createManualSyncTask(): Promise<{ id: string, manifest: IUserDataManifest | null }> {
|
||||
const manualSyncTask = await this.service.createManualSyncTask();
|
||||
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask);
|
||||
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask, this.logService);
|
||||
this.server.registerChannel(`manualSyncTask-${manualSyncTask.id}`, manualSyncTaskChannel);
|
||||
return { id: manualSyncTask.id, manifest: manualSyncTask.manifest };
|
||||
}
|
||||
@@ -73,7 +73,10 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
class ManualSyncTaskChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly manualSyncTask: IManualSyncTask) { }
|
||||
constructor(
|
||||
private readonly manualSyncTask: IManualSyncTask,
|
||||
private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
@@ -83,10 +86,22 @@ class ManualSyncTaskChannel implements IServerChannel {
|
||||
}
|
||||
|
||||
async call(context: any, command: string, args?: any): Promise<any> {
|
||||
try {
|
||||
const result = await this._call(context, command, args);
|
||||
return result;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async _call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'preview': return this.manualSyncTask.preview();
|
||||
case 'accept': return this.manualSyncTask.accept(URI.revive(args[0]), args[1]);
|
||||
case 'merge': return this.manualSyncTask.merge(URI.revive(args[0]));
|
||||
case 'discard': return this.manualSyncTask.discard(URI.revive(args[0]));
|
||||
case 'apply': return this.manualSyncTask.apply();
|
||||
case 'pull': return this.manualSyncTask.pull();
|
||||
case 'push': return this.manualSyncTask.push();
|
||||
case 'stop': return this.manualSyncTask.stop();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import {
|
||||
IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode,
|
||||
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID
|
||||
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -102,11 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
await this.checkEnablement();
|
||||
try {
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
try {
|
||||
await synchroniser.pull();
|
||||
} catch (e) {
|
||||
this.handleSynchronizerError(e, synchroniser.resource);
|
||||
}
|
||||
await synchroniser.pull();
|
||||
}
|
||||
this.updateLastSyncTime();
|
||||
} catch (error) {
|
||||
@@ -121,11 +117,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
await this.checkEnablement();
|
||||
try {
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
try {
|
||||
await synchroniser.push();
|
||||
} catch (e) {
|
||||
this.handleSynchronizerError(e, synchroniser.resource);
|
||||
}
|
||||
await synchroniser.push();
|
||||
}
|
||||
this.updateLastSyncTime();
|
||||
} catch (error) {
|
||||
@@ -264,10 +256,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
}
|
||||
|
||||
async acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string, executionId: string = generateUuid()): Promise<void> {
|
||||
async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
const synchroniser = this.getSynchroniser(syncResource);
|
||||
await synchroniser.acceptPreviewContent(resource, content, false, createSyncHeaders(executionId));
|
||||
await synchroniser.accept(resource, content);
|
||||
if (apply) {
|
||||
await synchroniser.apply(false, createSyncHeaders(generateUuid()));
|
||||
}
|
||||
}
|
||||
|
||||
async resolveContent(resource: URI): Promise<string | null> {
|
||||
@@ -399,6 +394,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
throw new UserDataSyncError(e.message, e.code, source);
|
||||
|
||||
case UserDataSyncErrorCode.TooManyRequests:
|
||||
case UserDataSyncErrorCode.TooManyRequestsAndRetryAfter:
|
||||
case UserDataSyncErrorCode.LocalTooManyRequests:
|
||||
case UserDataSyncErrorCode.Gone:
|
||||
case UserDataSyncErrorCode.UpgradeRequired:
|
||||
@@ -460,21 +456,21 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
||||
return this.previews;
|
||||
}
|
||||
|
||||
async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.acceptPreviewContent(resource, content, force, this.syncHeaders));
|
||||
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
return this.performAction(resource, sychronizer => sychronizer.accept(resource, content));
|
||||
}
|
||||
|
||||
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
if (resource) {
|
||||
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.merge(resource, force, this.syncHeaders));
|
||||
} else {
|
||||
return this.mergeAll();
|
||||
}
|
||||
async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
return this.performAction(resource, sychronizer => sychronizer.merge(resource));
|
||||
}
|
||||
|
||||
private async mergeOrAccept(resource: URI, mergeOrAccept: (synchroniser: IUserDataSynchroniser, force: boolean) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
return this.performAction(resource, sychronizer => sychronizer.discard(resource));
|
||||
}
|
||||
|
||||
private async performAction(resource: URI, action: (synchroniser: IUserDataSynchroniser) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
if (!this.previews) {
|
||||
throw new Error('You need to create preview before merging or accepting');
|
||||
throw new Error('Missing preview. Create preview and try again.');
|
||||
}
|
||||
|
||||
const index = this.previews.findIndex(([, preview]) => preview.resourcePreviews.some(({ localResource, previewResource, remoteResource }) =>
|
||||
@@ -500,9 +496,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
||||
}
|
||||
|
||||
const synchroniser = this.synchronisers.find(s => s.resource === this.previews![index][0])!;
|
||||
/* force only if the resource is local or remote resource */
|
||||
const force = isEqual(resource, resourcePreview.localResource) || isEqual(resource, resourcePreview.remoteResource);
|
||||
const preview = await mergeOrAccept(synchroniser, force);
|
||||
const preview = await action(synchroniser);
|
||||
preview ? this.previews.splice(index, 1, this.toSyncResourcePreview(synchroniser.resource, preview)) : this.previews.splice(index, 1);
|
||||
|
||||
const i = this.synchronizingResources.findIndex(s => s[0] === syncResource);
|
||||
@@ -515,25 +509,33 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
||||
return this.previews;
|
||||
}
|
||||
|
||||
private async mergeAll(): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
if (!this.previews) {
|
||||
throw new Error('You need to create preview before merging');
|
||||
throw new Error('You need to create preview before applying');
|
||||
}
|
||||
if (this.synchronizingResources.length) {
|
||||
throw new Error('Cannot merge while synchronizing resources');
|
||||
throw new Error('Cannot pull while synchronizing resources');
|
||||
}
|
||||
const previews: [SyncResource, ISyncResourcePreview][] = [];
|
||||
for (const [syncResource, preview] of this.previews) {
|
||||
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
|
||||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
|
||||
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
|
||||
let syncResourcePreview = null;
|
||||
|
||||
/* merge those which are not yet merged */
|
||||
for (const resourcePreview of preview.resourcePreviews) {
|
||||
syncResourcePreview = await synchroniser.merge(resourcePreview.remoteResource, false, this.syncHeaders);
|
||||
if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) {
|
||||
await synchroniser.merge(resourcePreview.previewResource);
|
||||
}
|
||||
}
|
||||
if (syncResourcePreview) {
|
||||
previews.push([syncResource, syncResourcePreview]);
|
||||
|
||||
/* apply */
|
||||
const newPreview = await synchroniser.apply(false, this.syncHeaders);
|
||||
if (newPreview) {
|
||||
previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview));
|
||||
}
|
||||
|
||||
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
|
||||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
}
|
||||
@@ -553,9 +555,10 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
||||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
|
||||
for (const resourcePreview of preview.resourcePreviews) {
|
||||
const content = await synchroniser.resolveContent(resourcePreview.remoteResource) || '';
|
||||
await synchroniser.acceptPreviewContent(resourcePreview.remoteResource, content, true, this.syncHeaders);
|
||||
const content = await synchroniser.resolveContent(resourcePreview.remoteResource);
|
||||
await synchroniser.accept(resourcePreview.remoteResource, content);
|
||||
}
|
||||
await synchroniser.apply(true, this.syncHeaders);
|
||||
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
|
||||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
}
|
||||
@@ -574,9 +577,10 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
||||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
|
||||
for (const resourcePreview of preview.resourcePreviews) {
|
||||
const content = await synchroniser.resolveContent(resourcePreview.localResource) || '';
|
||||
await synchroniser.acceptPreviewContent(resourcePreview.localResource, content, true, this.syncHeaders);
|
||||
const content = await synchroniser.resolveContent(resourcePreview.localResource);
|
||||
await synchroniser.accept(resourcePreview.localResource, content);
|
||||
}
|
||||
await synchroniser.apply(true, this.syncHeaders);
|
||||
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
|
||||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
}
|
||||
@@ -641,8 +645,9 @@ function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePr
|
||||
localResource: resourcePreview.localResource,
|
||||
previewResource: resourcePreview.previewResource,
|
||||
remoteResource: resourcePreview.remoteResource,
|
||||
acceptedResource: resourcePreview.acceptedResource,
|
||||
localChange: resourcePreview.localChange,
|
||||
remoteChange: resourcePreview.remoteChange,
|
||||
merged: resourcePreview.merged,
|
||||
mergeState: resourcePreview.mergeState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ import { assign } from 'vs/base/common/objects';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { createCancelablePromise, timeout, CancelablePromise } from 'vs/base/common/async';
|
||||
|
||||
const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until';
|
||||
const USER_SESSION_ID_KEY = 'sync.user-session-id';
|
||||
const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id';
|
||||
const REQUEST_SESSION_LIMIT = 100;
|
||||
@@ -40,6 +42,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
private _onTokenSucceed: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onTokenSucceed: Event<void> = this._onTokenSucceed.event;
|
||||
|
||||
private _donotMakeRequestsUntil: Date | undefined = undefined;
|
||||
get donotMakeRequestsUntil() { return this._donotMakeRequestsUntil; }
|
||||
private _onDidChangeDonotMakeRequestsUntil = this._register(new Emitter<void>());
|
||||
readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
|
||||
|
||||
constructor(
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@@ -66,12 +73,41 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
|
||||
/* A requests session that limits requests per sessions */
|
||||
this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, this.logService);
|
||||
this.initDonotMakeRequestsUntil();
|
||||
}
|
||||
|
||||
setAuthToken(token: string, type: string): void {
|
||||
this.authToken = { token, type };
|
||||
}
|
||||
|
||||
private initDonotMakeRequestsUntil(): void {
|
||||
const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
|
||||
if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) {
|
||||
this.setDonotMakeRequestsUntil(new Date(donotMakeRequestsUntil));
|
||||
}
|
||||
}
|
||||
|
||||
private resetDonotMakeRequestsUntilPromise: CancelablePromise<void> | undefined = undefined;
|
||||
private setDonotMakeRequestsUntil(donotMakeRequestsUntil: Date | undefined): void {
|
||||
if (this._donotMakeRequestsUntil?.getTime() !== donotMakeRequestsUntil?.getTime()) {
|
||||
this._donotMakeRequestsUntil = donotMakeRequestsUntil;
|
||||
|
||||
if (this.resetDonotMakeRequestsUntilPromise) {
|
||||
this.resetDonotMakeRequestsUntilPromise.cancel();
|
||||
this.resetDonotMakeRequestsUntilPromise = undefined;
|
||||
}
|
||||
|
||||
if (this._donotMakeRequestsUntil) {
|
||||
this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL);
|
||||
this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined)));
|
||||
} else {
|
||||
this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
this._onDidChangeDonotMakeRequestsUntil.fire();
|
||||
}
|
||||
}
|
||||
|
||||
async getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]> {
|
||||
if (!this.userDataSyncStore) {
|
||||
throw new Error('No settings sync store url configured.');
|
||||
@@ -244,6 +280,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, undefined);
|
||||
}
|
||||
|
||||
if (this._donotMakeRequestsUntil && Date.now() < this._donotMakeRequestsUntil.getTime()) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined);
|
||||
}
|
||||
this.setDonotMakeRequestsUntil(undefined);
|
||||
|
||||
const commonHeaders = await this.commonHeadersPromise;
|
||||
options.headers = assign(options.headers || {}, commonHeaders, {
|
||||
'X-Account-Type': this.authToken.type,
|
||||
@@ -299,7 +340,13 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 429) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
|
||||
const retryAfter = context.res.headers['retry-after'];
|
||||
if (retryAfter) {
|
||||
this.setDonotMakeRequestsUntil(new Date(Date.now() + (parseInt(retryAfter) * 1000)));
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId);
|
||||
} else {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
|
||||
Reference in New Issue
Block a user