Merge from vscode 2f984aad710215f4e4684a035bb02f55d1a9e2cc (#9819)

This commit is contained in:
Anthony Dresser
2020-04-01 00:44:39 -07:00
committed by GitHub
parent 0e27aaa61f
commit 0bfbdc62ed
247 changed files with 5402 additions and 3311 deletions

View File

@@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
import { CancelablePromise } from 'vs/base/common/async';
@@ -144,6 +144,16 @@ export abstract class AbstractSynchroniser extends Disposable {
}
}
async getSyncPreview(): Promise<ISyncPreviewResult> {
if (!this.isEnabled()) {
return { hasLocalChanged: false, hasRemoteChanged: false };
}
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
return this.generatePreview(remoteUserData, lastSyncUserData);
}
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
// current version is not compatible with cloud version
@@ -285,15 +295,14 @@ export abstract class AbstractSynchroniser extends Disposable {
protected abstract readonly version: number;
protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus>;
protected abstract generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult>;
}
export interface IFileSyncPreviewResult {
export interface IFileSyncPreviewResult extends ISyncPreviewResult {
readonly fileContent: IFileContent | null;
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null;
readonly content: string | null;
readonly hasLocalChanged: boolean;
readonly hasRemoteChanged: boolean;
readonly hasConflicts: boolean;
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -20,7 +20,7 @@ import { joinPath, dirname, basename } from 'vs/base/common/resources';
import { format } from 'vs/base/common/jsonFormatter';
import { applyEdits } from 'vs/base/common/jsonEdit';
interface ISyncPreviewResult {
interface IExtensionsSyncPreviewResult extends ISyncPreviewResult {
readonly localExtensions: ISyncExtension[];
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: ILastSyncUserData | null;
@@ -82,7 +82,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const localExtensions = await this.getLocalExtensions();
const remoteExtensions = this.parseExtensions(remoteUserData.syncData);
const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions());
await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData });
await this.apply({
added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData,
hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0,
hasRemoteChanged: remote !== null
});
}
// No remote exists to pull
@@ -112,7 +116,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions());
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true);
await this.apply({
added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData,
hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0,
hasRemoteChanged: remote !== null
}, true);
this.logService.info(`${this.syncResourceLogLabel}: Finished pushing extensions.`);
} finally {
@@ -163,12 +171,12 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<SyncStatus> {
const previewResult = await this.getPreview(remoteUserData, lastSyncUserData);
const previewResult = await this.generatePreview(remoteUserData, lastSyncUserData);
await this.apply(previewResult);
return SyncStatus.Idle;
}
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> {
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionsSyncPreviewResult> {
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null;
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
@@ -183,22 +191,31 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions());
return { added, removed, updated, remote, skippedExtensions, remoteUserData, localExtensions, lastSyncUserData };
return {
added,
removed,
updated,
remote,
skippedExtensions,
remoteUserData,
localExtensions,
lastSyncUserData,
hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0,
hasRemoteChanged: remote !== null
};
}
private getIgnoredExtensions() {
return this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
}
private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions, hasLocalChanged, hasRemoteChanged }: IExtensionsSyncPreviewResult, forcePush?: boolean): Promise<void> {
const hasChanges = added.length || removed.length || updated.length || remote;
if (!hasChanges) {
if (!hasLocalChanged && !hasRemoteChanged) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`);
}
if (added.length || removed.length || updated.length) {
if (hasLocalChanged) {
// back up all disabled or market place extensions
const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid);
await this.backupLocal(JSON.stringify(backUpExtensions));

View File

@@ -13,17 +13,18 @@ import { ILogService } from 'vs/platform/log/common/log';
export interface IMergeResult {
local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
remote: IStringDictionary<IStorageValue> | null;
skipped: string[];
}
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: ReadonlyArray<IStorageKey>, logService: ILogService): IMergeResult {
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: ReadonlyArray<IStorageKey>, previouslySkipped: string[], logService: ILogService): IMergeResult {
if (!remoteStorage) {
return { remote: localStorage, local: { added: {}, removed: [], updated: {} } };
return { remote: localStorage, local: { added: {}, removed: [], updated: {} }, skipped: [] };
}
const localToRemote = compare(localStorage, remoteStorage);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return { remote: null, local: { added: {}, removed: [], updated: {} } };
return { remote: null, local: { added: {}, removed: [], updated: {} }, skipped: [] };
}
const baseToRemote = baseStorage ? compare(baseStorage, remoteStorage) : { added: Object.keys(remoteStorage).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
@@ -31,17 +32,19 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
const local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> } = { added: {}, removed: [], updated: {} };
const remote: IStringDictionary<IStorageValue> = objects.deepClone(remoteStorage);
const skipped: string[] = [];
// Added in remote
for (const key of values(baseToRemote.added)) {
const remoteValue = remoteStorage[key];
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
if (!storageKey) {
logService.info(`GlobalState: Skipped updating ${key} in storage. It is not registered to sync.`);
skipped.push(key);
logService.info(`GlobalState: Skipped adding ${key} in local storage as it is not registered.`);
continue;
}
if (storageKey.version !== remoteValue.version) {
logService.info(`GlobalState: Skipped updating ${key} in storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
logService.info(`GlobalState: Skipped adding ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
continue;
}
const localValue = localStorage[key];
@@ -60,11 +63,12 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
const remoteValue = remoteStorage[key];
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
if (!storageKey) {
logService.info(`GlobalState: Skipped updating ${key} in storage. It is not registered to sync.`);
skipped.push(key);
logService.info(`GlobalState: Skipped updating ${key} in local storage as is not registered.`);
continue;
}
if (storageKey.version !== remoteValue.version) {
logService.info(`GlobalState: Skipped updating ${key} in storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
logService.info(`GlobalState: Skipped updating ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
continue;
}
const localValue = localStorage[key];
@@ -78,7 +82,7 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
for (const key of values(baseToRemote.removed)) {
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
if (!storageKey) {
logService.info(`GlobalState: Skipped updating ${key} in storage. It is not registered to sync.`);
logService.info(`GlobalState: Skipped removing ${key} in local storage. It is not registered to sync.`);
continue;
}
local.removed.push(key);
@@ -99,6 +103,7 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
const remoteValue = remote[key];
const localValue = localStorage[key];
if (localValue.version < remoteValue.version) {
logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${localValue.version}' and remote version '${remoteValue.version} are not same.`);
continue;
}
remote[key] = localValue;
@@ -106,18 +111,36 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
// Removed in local
for (const key of values(baseToLocal.removed)) {
// do not remove from remote if it is updated in remote
if (baseToRemote.updated.has(key)) {
continue;
}
const remoteValue = remote[key];
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
if (storageKey && storageKey.version < remoteValue.version) {
// do not remove from remote if storage key is not found
if (!storageKey) {
skipped.push(key);
logService.info(`GlobalState: Skipped removing ${key} in remote storage. It is not registered to sync.`);
continue;
}
const remoteValue = remote[key];
// do not remove from remote if local data version is old
if (storageKey.version < remoteValue.version) {
logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
continue;
}
// add to local if it was skipped before
if (previouslySkipped.indexOf(key) !== -1) {
local.added[key] = remote[key];
continue;
}
delete remote[key];
}
return { local, remote: areSame(remote, remoteStorage) ? null : remote };
return { local, remote: areSame(remote, remoteStorage) ? null : remote, skipped };
}
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -21,16 +21,22 @@ import { format } from 'vs/base/common/jsonFormatter';
import { applyEdits } from 'vs/base/common/jsonEdit';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
import { equals } from 'vs/base/common/arrays';
const argvStoragePrefx = 'globalState.argv.';
const argvProperties: string[] = ['locale'];
interface ISyncPreviewResult {
interface IGlobalSyncPreviewResult extends ISyncPreviewResult {
readonly local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
readonly remote: IStringDictionary<IStorageValue> | null;
readonly skippedStorageKeys: string[];
readonly localUserData: IGlobalState;
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null;
readonly lastSyncUserData: ILastSyncUserData | null;
}
interface ILastSyncUserData extends IRemoteUserData {
skippedStorageKeys: string[] | undefined;
}
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
@@ -51,8 +57,16 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
) {
super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
this._register(Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key))(() => this._onDidChangeLocal.fire()));
this._register(
Event.any(
/* Locale change */
Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
/* Storage change */
Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key)),
/* Storage key registered */
this.storageKeysSyncRegistryService.onDidChangeStorageKeys
)((() => this._onDidChangeLocal.fire()))
);
}
async pull(): Promise<void> {
@@ -67,14 +81,19 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
this.logService.info(`${this.syncResourceLogLabel}: Started pulling ui state...`);
this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData();
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.syncData !== null) {
const localGlobalState = await this.getLocalGlobalState();
const remoteGlobalState: IGlobalState = JSON.parse(remoteUserData.syncData.content);
const { local, remote } = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), this.logService);
await this.apply({ local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData });
const { local, remote, skipped } = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
await this.apply({
local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData,
skippedStorageKeys: skipped,
hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0,
hasRemoteChanged: remote !== null
});
}
// No remote exists to pull
@@ -101,9 +120,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
this.setStatus(SyncStatus.Syncing);
const localUserData = await this.getLocalGlobalState();
const lastSyncUserData = await this.getLastSyncUserData();
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
await this.apply({ local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData }, true);
await this.apply({
local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData,
skippedStorageKeys: [],
hasLocalChanged: false,
hasRemoteChanged: true
}, true);
this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`);
} finally {
@@ -153,13 +177,13 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return false;
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
const result = await this.getPreview(remoteUserData, lastSyncUserData);
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<SyncStatus> {
const result = await this.generatePreview(remoteUserData, lastSyncUserData);
await this.apply(result);
return SyncStatus.Idle;
}
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult> {
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IGlobalSyncPreviewResult> {
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
const lastSyncGlobalState: IGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
@@ -171,15 +195,17 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
}
const { local, remote } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), this.logService);
const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData };
return {
local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData,
skippedStorageKeys: skipped,
hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0,
hasRemoteChanged: remote !== null
};
}
private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
const hasLocalChanged = Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0;
const hasRemoteChanged = remote !== null;
private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData, hasLocalChanged, hasRemoteChanged, skippedStorageKeys }: IGlobalSyncPreviewResult, forcePush?: boolean): Promise<void> {
if (!hasLocalChanged && !hasRemoteChanged) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`);
@@ -201,10 +227,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`);
}
if (lastSyncUserData?.ref !== remoteUserData.ref) {
if (lastSyncUserData?.ref !== remoteUserData.ref || !equals(lastSyncUserData.skippedStorageKeys, skippedStorageKeys)) {
// update last sync
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`);
await this.updateLastSyncUserData(remoteUserData);
await this.updateLastSyncUserData(remoteUserData, { skippedStorageKeys });
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`);
}
}

View File

@@ -265,7 +265,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
return this.syncPreviewResultPromise;
}
private async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileSyncPreviewResult> {
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken = CancellationToken.None): Promise<IFileSyncPreviewResult> {
const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
const lastSyncContent = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null;
// Get file content last to get the latest

View File

@@ -309,14 +309,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
this.syncPreviewResultPromise = null;
}
private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise<IFileSyncPreviewResult> {
private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[] = []): Promise<IFileSyncPreviewResult> {
if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token));
}
return this.syncPreviewResultPromise;
}
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<IFileSyncPreviewResult> {
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[] = [], token: CancellationToken = CancellationToken.None): Promise<IFileSyncPreviewResult> {
const fileContent = await this.getLocalFileContent();
const formattingOptions = await this.getFormattingOptions();
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode, ISyncResourceHandle, ISyncPreviewResult } 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';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -17,7 +17,7 @@ import { merge } from 'vs/platform/userDataSync/common/snippetsMerge';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
interface ISyncPreviewResult {
interface ISinppetsSyncPreviewResult extends ISyncPreviewResult {
readonly local: IStringDictionary<IFileContent>;
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null;
@@ -34,7 +34,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
protected readonly version: number = 1;
private readonly snippetsFolder: URI;
private readonly snippetsPreviewFolder: URI;
private syncPreviewResultPromise: CancelablePromise<ISyncPreviewResult> | null = null;
private syncPreviewResultPromise: CancelablePromise<ISinppetsSyncPreviewResult> | null = null;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@@ -94,8 +94,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
const localSnippets = this.toSnippetsContents(local);
const remoteSnippets = this.parseSnippets(remoteUserData.syncData);
const { added, updated, remote, removed } = merge(localSnippets, remoteSnippets, localSnippets);
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISinppetsSyncPreviewResult>({
added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {},
hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0,
hasRemoteChanged: remote !== null
}));
await this.apply();
}
@@ -128,8 +130,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
const { added, removed, updated, remote } = merge(localSnippets, null, null);
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISinppetsSyncPreviewResult>({
added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {},
hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0,
hasRemoteChanged: remote !== null
}));
await this.apply(true);
@@ -207,7 +211,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
let previewResult = await this.syncPreviewResultPromise!;
this.cancel();
previewResult.resolvedConflicts[key] = content || null;
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(previewResult.local, previewResult.remoteUserData, previewResult.lastSyncUserData, previewResult.resolvedConflicts, token));
this.syncPreviewResultPromise = createCancelablePromise(token => this.doGeneratePreview(previewResult.local, previewResult.remoteUserData, previewResult.lastSyncUserData, previewResult.resolvedConflicts, token));
previewResult = await this.syncPreviewResultPromise;
this.setConflicts(previewResult.conflicts);
if (!this.conflicts.length) {
@@ -252,10 +256,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
}
private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult> {
protected getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISinppetsSyncPreviewResult> {
if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.getSnippetsFileContents()
.then(local => this.generatePreview(local, remoteUserData, lastSyncUserData, {}, token)));
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token));
}
return this.syncPreviewResultPromise;
}
@@ -274,7 +277,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
}
private async generatePreview(local: IStringDictionary<IFileContent>, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary<string | null>, token: CancellationToken): Promise<ISyncPreviewResult> {
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken = CancellationToken.None): Promise<ISinppetsSyncPreviewResult> {
return this.getSnippetsFileContents()
.then(local => this.doGeneratePreview(local, remoteUserData, lastSyncUserData, {}, token));
}
private async doGeneratePreview(local: IStringDictionary<IFileContent>, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary<string | null> = {}, token: CancellationToken = CancellationToken.None): Promise<ISinppetsSyncPreviewResult> {
const localSnippets = this.toSnippetsContents(local);
const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null;
const lastSyncSnippets: IStringDictionary<string> | null = lastSyncUserData ? this.parseSnippets(lastSyncUserData.syncData!) : null;
@@ -309,7 +317,18 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
}
return { remoteUserData, local, lastSyncUserData, added: mergeResult.added, removed: mergeResult.removed, updated: mergeResult.updated, conflicts, remote: mergeResult.remote, resolvedConflicts };
return {
remoteUserData, local,
lastSyncUserData,
added: mergeResult.added,
removed: mergeResult.removed,
updated: mergeResult.updated,
conflicts,
remote: mergeResult.remote,
resolvedConflicts,
hasLocalChanged: Object.keys(mergeResult.added).length > 0 || mergeResult.removed.length > 0 || Object.keys(mergeResult.updated).length > 0,
hasRemoteChanged: mergeResult.remote !== null
};
}
private async apply(forcePush?: boolean): Promise<void> {
@@ -317,15 +336,13 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
return;
}
let { added, removed, updated, local, remote, remoteUserData, lastSyncUserData } = await this.syncPreviewResultPromise;
let { added, removed, updated, local, remote, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
const hasChanges = Object.keys(added).length || removed.length || Object.keys(updated).length || remote;
if (!hasChanges) {
if (!hasLocalChanged && !hasRemoteChanged) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`);
}
if (Object.keys(added).length || removed.length || Object.keys(updated).length) {
if (hasLocalChanged) {
// back up all snippets
await this.backupLocal(JSON.stringify(this.toSnippetsContents(local)));
await this.updateLocalSnippets(added, removed, updated, local);

View File

@@ -254,6 +254,11 @@ export interface ISyncResourceHandle {
export type Conflict = { remote: URI, local: URI };
export interface ISyncPreviewResult {
readonly hasLocalChanged: boolean;
readonly hasRemoteChanged: boolean;
}
export interface IUserDataSynchroniser {
readonly resource: SyncResource;
@@ -268,6 +273,7 @@ export interface IUserDataSynchroniser {
sync(ref?: string): Promise<void>;
stop(): Promise<void>;
getSyncPreview(): Promise<ISyncPreviewResult>
hasPreviouslySynced(): Promise<boolean>
hasLocalData(): Promise<boolean>;
resetLocal(): Promise<void>;

View File

@@ -217,7 +217,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (await this.hasPreviouslySynced()) {
return false;
}
return await this.hasLocalData();
if (!(await this.hasLocalData())) {
return false;
}
for (const synchroniser of [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.extensionsSynchroniser]) {
const preview = await synchroniser.getSyncPreview();
if (preview.hasLocalChanged || preview.hasRemoteChanged) {
return true;
}
}
return false;
}
async reset(): Promise<void> {

View File

@@ -12,7 +12,6 @@ import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/reques
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { IProductService } from 'vs/platform/product/common/productService';
import { URI } from 'vs/base/common/uri';
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
@@ -66,7 +65,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
}
const result = await asJson<{ url: string, created: number }[]>(context) || [];
return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url).with({ scheme: uri.scheme, authority: uri.authority }))!, created: created * 1000 /* Server returns in seconds */ }));
return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
}
async resolveContent(resource: SyncResource, ref: string): Promise<string | null> {
@@ -76,6 +75,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const url = joinPath(this.userDataSyncStore.url, 'resource', resource, ref).toString();
const headers: IHeaders = {};
headers['Cache-Control'] = 'no-cache';
const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None);