mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 01:25:37 -05:00
Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5
This commit is contained in:
@@ -54,7 +54,8 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
}
|
||||
|
||||
async hasRemoteData(): Promise<boolean> {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
return remoteUserData.content !== null;
|
||||
}
|
||||
|
||||
@@ -77,8 +78,8 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
|
||||
}
|
||||
|
||||
protected getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
|
||||
return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData || null, this.source);
|
||||
protected async getRemoteUserData(lastSyncData: IUserData | null): Promise<IUserData> {
|
||||
return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData, this.source);
|
||||
}
|
||||
|
||||
protected async updateRemoteUserData(content: string, ref: string | null): Promise<string> {
|
||||
|
||||
@@ -23,6 +23,7 @@ interface ISyncPreviewResult {
|
||||
readonly remote: ISyncExtension[] | null;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly skippedExtensions: ISyncExtension[];
|
||||
readonly lastSyncUserData: ILastSyncUserData | null;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IUserData {
|
||||
@@ -44,9 +45,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService);
|
||||
this._register(
|
||||
Event.debounce(
|
||||
Event.any(
|
||||
Event.any<any>(
|
||||
Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)),
|
||||
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error))),
|
||||
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)),
|
||||
this.extensionEnablementService.onDidChangeEnablement),
|
||||
() => undefined, 500)(() => this._onDidChangeLocal.fire()));
|
||||
}
|
||||
|
||||
@@ -64,13 +66,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
this.logService.info('Extensions: Started pulling extensions...');
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.content !== null) {
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.content);
|
||||
const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions());
|
||||
await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [] });
|
||||
await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData });
|
||||
}
|
||||
|
||||
// No remote exists to pull
|
||||
@@ -98,8 +101,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions());
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
await this.apply({ added, removed, updated, remote, remoteUserData, skippedExtensions: [] }, true);
|
||||
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
await this.apply({ added, removed, updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData }, true);
|
||||
|
||||
this.logService.info('Extensions: Finished pushing extensions.');
|
||||
} finally {
|
||||
@@ -132,7 +136,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronizing again...');
|
||||
this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
@@ -148,7 +152,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
throw new Error('Extensions: Conflicts should not occur');
|
||||
}
|
||||
|
||||
resolveConflicts(content: string): Promise<void> {
|
||||
accept(content: string): Promise<void> {
|
||||
throw new Error('Extensions: Conflicts should not occur');
|
||||
}
|
||||
|
||||
@@ -169,11 +173,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
|
||||
private async getPreview(): Promise<ISyncPreviewResult> {
|
||||
const lastSyncData = await this.getLastSyncUserData<ILastSyncUserData>();
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : [];
|
||||
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null;
|
||||
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
@@ -181,40 +185,44 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
if (remoteExtensions) {
|
||||
this.logService.trace('Extensions: Merging remote extensions with local extensions...');
|
||||
} else {
|
||||
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
|
||||
this.logService.trace('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
|
||||
}
|
||||
|
||||
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions());
|
||||
|
||||
return { added, removed, updated, remote, skippedExtensions, remoteUserData };
|
||||
return { added, removed, updated, remote, skippedExtensions, remoteUserData, lastSyncUserData };
|
||||
}
|
||||
|
||||
private getIgnoredExtensions() {
|
||||
return this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
|
||||
}
|
||||
|
||||
private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
|
||||
if (!added.length && !removed.length && !updated.length && !remote) {
|
||||
private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
|
||||
|
||||
const hasChanges = added.length || removed.length || updated.length || remote;
|
||||
|
||||
if (!hasChanges) {
|
||||
this.logService.trace('Extensions: No changes found during synchronizing extensions.');
|
||||
}
|
||||
|
||||
if (added.length || removed.length || updated.length) {
|
||||
this.logService.info('Extensions: Updating local extensions...');
|
||||
skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions);
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
// update remote
|
||||
this.logService.info('Extensions: Updating remote extensions...');
|
||||
this.logService.trace('Extensions: Updating remote extensions...');
|
||||
const content = JSON.stringify(remote);
|
||||
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
remoteUserData = { ref, content };
|
||||
this.logService.info('Extensions: Updated remote extensions');
|
||||
}
|
||||
|
||||
if (remoteUserData.content) {
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
// update last sync
|
||||
this.logService.info('Extensions: Updating last synchronised extensions...');
|
||||
this.logService.trace('Extensions: Updating last synchronized extensions...');
|
||||
await this.updateLastSyncUserData<ILastSyncUserData>({ ...remoteUserData, skippedExtensions });
|
||||
this.logService.info('Extensions: Updated last synchronized extensions');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,29 +234,56 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
|
||||
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
|
||||
this.logService.info('Extensions: Removing local extension.', extensionToRemove.identifier.id);
|
||||
this.logService.trace('Extensions: Uninstalling local extension...', extensionToRemove.identifier.id);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove);
|
||||
this.logService.info('Extensions: Uninstalled local extension.', extensionToRemove.identifier.id);
|
||||
removeFromSkipped.push(extensionToRemove.identifier);
|
||||
}));
|
||||
}
|
||||
|
||||
if (added.length || updated.length) {
|
||||
await Promise.all([...added, ...updated].map(async e => {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const installedExtension = installedExtensions.filter(installed => areSameExtensions(installed.identifier, e.identifier))[0];
|
||||
|
||||
// Builtin Extension: Sync only enablement state
|
||||
if (installedExtension && installedExtension.type === ExtensionType.System) {
|
||||
if (e.enabled) {
|
||||
this.logService.trace('Extensions: Enabling extension...', e.identifier.id);
|
||||
await this.extensionEnablementService.enableExtension(e.identifier);
|
||||
this.logService.info('Extensions: Enabled extension', e.identifier.id);
|
||||
} else {
|
||||
this.logService.trace('Extensions: Disabling extension...', e.identifier.id);
|
||||
await this.extensionEnablementService.disableExtension(e.identifier);
|
||||
this.logService.info('Extensions: Disabled extension.', e.identifier.id);
|
||||
}
|
||||
removeFromSkipped.push(e.identifier);
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version);
|
||||
if (extension) {
|
||||
this.logService.info('Extensions: Installing local extension.', e.identifier.id, extension.version);
|
||||
try {
|
||||
await this.extensionManagementService.installFromGallery(extension);
|
||||
removeFromSkipped.push(extension.identifier);
|
||||
if (e.enabled) {
|
||||
this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version);
|
||||
await this.extensionEnablementService.enableExtension(extension.identifier);
|
||||
this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version);
|
||||
} else {
|
||||
this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version);
|
||||
await this.extensionEnablementService.disableExtension(extension.identifier);
|
||||
this.logService.info('Extensions: Disabled extension', e.identifier.id, extension.version);
|
||||
}
|
||||
// Install only if the extension does not exist
|
||||
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
|
||||
this.logService.trace('Extensions: Installing extension...', e.identifier.id, extension.version);
|
||||
await this.extensionManagementService.installFromGallery(extension);
|
||||
this.logService.info('Extensions: Installed extension.', e.identifier.id, extension.version);
|
||||
removeFromSkipped.push(extension.identifier);
|
||||
}
|
||||
} catch (error) {
|
||||
addToSkipped.push(e);
|
||||
this.logService.error(error);
|
||||
this.logService.info(localize('skip extension', "Skipping synchronising extension {0}", extension.displayName || extension.identifier.id));
|
||||
this.logService.info(localize('skip extension', "Skipped synchronizing extension {0}", extension.displayName || extension.identifier.id));
|
||||
}
|
||||
} else {
|
||||
addToSkipped.push(e);
|
||||
@@ -271,7 +306,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
|
||||
private async getLocalExtensions(): Promise<ISyncExtension[]> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const disabledExtensions = await this.extensionEnablementService.getDisabledExtensionsAsync();
|
||||
return installedExtensions
|
||||
.map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) }));
|
||||
|
||||
@@ -22,6 +22,7 @@ interface ISyncPreviewResult {
|
||||
readonly local: IGlobalState | undefined;
|
||||
readonly remote: IGlobalState | undefined;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly lastSyncUserData: IUserData | null;
|
||||
}
|
||||
|
||||
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
@@ -52,11 +53,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.info('UI State: Started pulling ui state...');
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.content !== null) {
|
||||
const local: IGlobalState = JSON.parse(remoteUserData.content);
|
||||
await this.apply({ local, remote: undefined, remoteUserData });
|
||||
await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData });
|
||||
}
|
||||
|
||||
// No remote exists to pull
|
||||
@@ -83,8 +85,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const remote = await this.getLocalGlobalState();
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
await this.apply({ local: undefined, remote, remoteUserData }, true);
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
await this.apply({ local: undefined, remote, remoteUserData, lastSyncUserData }, true);
|
||||
|
||||
this.logService.info('UI State: Finished pushing UI State.');
|
||||
} finally {
|
||||
@@ -115,7 +118,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...');
|
||||
this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
@@ -130,7 +133,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
throw new Error('UI State: Conflicts should not occur');
|
||||
}
|
||||
|
||||
resolveConflicts(content: string): Promise<void> {
|
||||
accept(content: string): Promise<void> {
|
||||
throw new Error('UI State: Conflicts should not occur');
|
||||
}
|
||||
|
||||
@@ -151,38 +154,54 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
|
||||
private async getPreview(): Promise<ISyncPreviewResult> {
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null;
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null;
|
||||
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null;
|
||||
|
||||
const localGloablState = await this.getLocalGlobalState();
|
||||
|
||||
if (remoteGlobalState) {
|
||||
this.logService.trace('UI State: Merging remote ui state with local ui state...');
|
||||
} else {
|
||||
this.logService.trace('UI State: Remote ui state does not exist. Synchronizing ui state for the first time.');
|
||||
}
|
||||
|
||||
const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState);
|
||||
|
||||
return { local, remote, remoteUserData };
|
||||
return { local, remote, remoteUserData, lastSyncUserData };
|
||||
}
|
||||
|
||||
private async apply({ local, remote, remoteUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
|
||||
private async apply({ local, remote, remoteUserData, lastSyncUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
|
||||
|
||||
const hasChanges = local || remote;
|
||||
|
||||
if (!hasChanges) {
|
||||
this.logService.trace('UI State: No changes found during synchronizing ui state.');
|
||||
}
|
||||
|
||||
if (local) {
|
||||
// update local
|
||||
this.logService.info('UI State: Updating local ui state...');
|
||||
this.logService.trace('UI State: Updating local ui state...');
|
||||
await this.writeLocalGlobalState(local);
|
||||
this.logService.info('UI State: Updated local ui state');
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
// update remote
|
||||
this.logService.info('UI State: Updating remote ui state...');
|
||||
this.logService.trace('UI State: Updating remote ui state...');
|
||||
const content = JSON.stringify(remote);
|
||||
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('UI State: Updated remote ui state');
|
||||
remoteUserData = { ref, content };
|
||||
}
|
||||
|
||||
if (remoteUserData.content) {
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
// update last sync
|
||||
this.logService.info('UI State: Updating last synchronised ui state...');
|
||||
this.logService.trace('UI State: Updating last synchronized ui state...');
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
this.logService.info('UI State: Updated last synchronized ui state');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ interface ISyncContent {
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly lastSyncUserData: IUserData | null;
|
||||
readonly hasLocalChanged: boolean;
|
||||
readonly hasRemoteChanged: boolean;
|
||||
readonly hasConflicts: boolean;
|
||||
@@ -63,7 +64,8 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
this.logService.info('Keybindings: Started pulling keybindings...');
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const remoteContent = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null;
|
||||
|
||||
if (remoteContent !== null) {
|
||||
@@ -74,7 +76,8 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
hasConflicts: false,
|
||||
hasLocalChanged: true,
|
||||
hasRemoteChanged: false,
|
||||
remoteUserData
|
||||
remoteUserData,
|
||||
lastSyncUserData
|
||||
}));
|
||||
await this.apply();
|
||||
}
|
||||
@@ -106,14 +109,16 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
|
||||
if (fileContent !== null) {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, fileContent.value);
|
||||
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
|
||||
fileContent,
|
||||
hasConflicts: false,
|
||||
hasLocalChanged: false,
|
||||
hasRemoteChanged: true,
|
||||
remoteUserData
|
||||
remoteUserData,
|
||||
lastSyncUserData
|
||||
}));
|
||||
await this.apply(undefined, true);
|
||||
}
|
||||
@@ -151,7 +156,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
if (this.syncPreviewResultPromise) {
|
||||
this.syncPreviewResultPromise.cancel();
|
||||
this.syncPreviewResultPromise = null;
|
||||
this.logService.info('Keybindings: Stopped synchronizing keybindings.');
|
||||
this.logService.trace('Keybindings: Stopped synchronizing keybindings.');
|
||||
}
|
||||
await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource);
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
@@ -165,16 +170,15 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
}
|
||||
}
|
||||
|
||||
async resolveConflicts(content: string): Promise<void> {
|
||||
async accept(content: string): Promise<void> {
|
||||
if (this.status === SyncStatus.HasConflicts) {
|
||||
try {
|
||||
await this.apply(content, true);
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
|
||||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
|
||||
throw new Error('Failed to resolve conflicts as there is a new local version available.');
|
||||
throw new UserDataSyncError('Failed to resolve conflicts as there is a new local version available.', UserDataSyncErrorCode.NewLocal);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
@@ -204,7 +208,8 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
const preview = await this.syncPreviewResultPromise;
|
||||
content = preview.remoteUserData?.content;
|
||||
} else {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
content = remoteUserData.content;
|
||||
}
|
||||
return content ? this.getKeybindingsContentFromSyncContent(content) : null;
|
||||
@@ -229,13 +234,13 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...');
|
||||
this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
|
||||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
|
||||
// Rejected as there is a new local version. Syncing again.
|
||||
this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...');
|
||||
this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
@@ -247,6 +252,8 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
return;
|
||||
}
|
||||
|
||||
let { fileContent, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
|
||||
|
||||
if (content === undefined) {
|
||||
if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) {
|
||||
const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource);
|
||||
@@ -261,24 +268,22 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
throw error;
|
||||
}
|
||||
|
||||
let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
|
||||
if (!hasLocalChanged && !hasRemoteChanged) {
|
||||
this.logService.trace('Keybindings: No changes found during synchronizing keybindings.');
|
||||
}
|
||||
|
||||
if (hasLocalChanged) {
|
||||
this.logService.info('Keybindings: Updating local keybindings');
|
||||
this.logService.trace('Keybindings: Updating local keybindings...');
|
||||
await this.updateLocalFileContent(content, fileContent);
|
||||
this.logService.info('Keybindings: Updated local keybindings');
|
||||
}
|
||||
|
||||
if (hasRemoteChanged) {
|
||||
this.logService.info('Keybindings: Updating remote keybindings');
|
||||
this.logService.trace('Keybindings: Updating remote keybindings...');
|
||||
const remoteContents = this.updateSyncContent(content, remoteUserData.content);
|
||||
const ref = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref);
|
||||
remoteUserData = { ref, content: remoteContents };
|
||||
}
|
||||
if (remoteUserData?.content) {
|
||||
this.logService.info('Keybindings: Updating last synchronised keybindings');
|
||||
const lastSyncContent = this.updateSyncContent(content, null);
|
||||
await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent });
|
||||
this.logService.info('Keybindings: Updated remote keybindings');
|
||||
}
|
||||
|
||||
// Delete the preview
|
||||
@@ -287,6 +292,13 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
this.logService.trace('Keybindings: No changes found during synchronizing keybindings.');
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== undefined || fileContent !== null)) {
|
||||
this.logService.trace('Keybindings: Updating last synchronized keybindings...');
|
||||
const lastSyncContent = this.updateSyncContent(content !== undefined ? content : fileContent!.value.toString(), null);
|
||||
await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent });
|
||||
this.logService.info('Keybindings: Updated last synchronized keybindings');
|
||||
}
|
||||
|
||||
this.syncPreviewResultPromise = null;
|
||||
}
|
||||
|
||||
@@ -304,9 +316,9 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
}
|
||||
|
||||
private async generatePreview(token: CancellationToken): Promise<ISyncPreviewResult> {
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const lastSyncContent = lastSyncData && lastSyncData.content ? this.getKeybindingsContentFromSyncContent(lastSyncData.content) : null;
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null;
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null;
|
||||
// Get file content last to get the latest
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
@@ -319,7 +331,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
const localContent: string = fileContent ? fileContent.value.toString() : '[]';
|
||||
if (this.hasErrors(localContent)) {
|
||||
this.logService.error('Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.');
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
return { fileContent, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
if (!lastSyncContent // First time sync
|
||||
@@ -341,7 +353,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
|
||||
// First time syncing to remote
|
||||
else if (fileContent) {
|
||||
this.logService.info('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.');
|
||||
this.logService.trace('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.');
|
||||
hasRemoteChanged = true;
|
||||
previewContent = fileContent.value.toString();
|
||||
}
|
||||
@@ -350,7 +362,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
|
||||
await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(previewContent));
|
||||
}
|
||||
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
return { fileContent, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
|
||||
|
||||
@@ -10,8 +10,10 @@ import { values } from 'vs/base/common/map';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions, Edit, getEOL } from 'vs/base/common/jsonFormatter';
|
||||
import * as contentUtil from 'vs/platform/userDataSync/common/content';
|
||||
import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IConflictSetting, DEFAULT_IGNORED_SETTINGS } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export interface IMergeResult {
|
||||
localContent: string | null;
|
||||
@@ -20,6 +22,30 @@ export interface IMergeResult {
|
||||
conflictsSettings: IConflictSetting[];
|
||||
}
|
||||
|
||||
export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] {
|
||||
let value: string[] = [];
|
||||
if (settingsContent) {
|
||||
const setting = parse(settingsContent);
|
||||
if (setting) {
|
||||
value = setting['sync.ignoredSettings'];
|
||||
}
|
||||
} else {
|
||||
value = configurationService.getValue<string[]>('sync.ignoredSettings');
|
||||
}
|
||||
const added: string[] = [], removed: string[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (const key of value) {
|
||||
if (startsWith(key, '-')) {
|
||||
removed.push(key.substring(1));
|
||||
} else {
|
||||
added.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1);
|
||||
}
|
||||
|
||||
|
||||
export function updateIgnoredSettings(targetContent: string, sourceContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string {
|
||||
if (ignoredSettings.length) {
|
||||
const sourceTree = parseSettings(sourceContent);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse, ParseError } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -12,22 +12,22 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { isEmptyObject, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly lastSyncUserData: IUserData | null;
|
||||
readonly content: string | null;
|
||||
readonly hasLocalChanged: boolean;
|
||||
readonly hasRemoteChanged: boolean;
|
||||
readonly remoteContent: string | null;
|
||||
readonly hasConflicts: boolean;
|
||||
readonly conflictSettings: IConflictSetting[];
|
||||
}
|
||||
@@ -84,23 +84,23 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
this.logService.info('Settings: Started pulling settings...');
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.content !== null) {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Update ignored settings
|
||||
// Update ignored settings from local file content
|
||||
const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content));
|
||||
|
||||
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
|
||||
hasConflicts: false,
|
||||
conflictSettings: [],
|
||||
fileContent,
|
||||
remoteUserData,
|
||||
lastSyncUserData,
|
||||
content,
|
||||
hasLocalChanged: true,
|
||||
hasRemoteChanged: false,
|
||||
remoteContent: content,
|
||||
remoteUserData,
|
||||
hasConflicts: false,
|
||||
conflictSettings: [],
|
||||
}));
|
||||
|
||||
await this.apply();
|
||||
@@ -135,20 +135,21 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Remove ignored settings
|
||||
const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content));
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
|
||||
conflictSettings: [],
|
||||
hasConflicts: false,
|
||||
fileContent,
|
||||
hasLocalChanged: false,
|
||||
hasRemoteChanged: true,
|
||||
remoteContent: content,
|
||||
remoteUserData,
|
||||
lastSyncUserData,
|
||||
content,
|
||||
hasRemoteChanged: true,
|
||||
hasLocalChanged: false,
|
||||
hasConflicts: false,
|
||||
conflictSettings: [],
|
||||
}));
|
||||
|
||||
await this.apply(undefined, true);
|
||||
await this.apply(true);
|
||||
}
|
||||
|
||||
// No local exists to push
|
||||
@@ -182,7 +183,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
if (this.syncPreviewResultPromise) {
|
||||
this.syncPreviewResultPromise.cancel();
|
||||
this.syncPreviewResultPromise = null;
|
||||
this.logService.info('Settings: Stopped synchronizing settings.');
|
||||
this.logService.trace('Settings: Stopped synchronizing settings.');
|
||||
}
|
||||
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
@@ -207,15 +208,21 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
return false;
|
||||
}
|
||||
|
||||
async getRemoteContent(): Promise<string | null> {
|
||||
async getRemoteContent(preview?: boolean): Promise<string | null> {
|
||||
let content: string | null | undefined = null;
|
||||
if (this.syncPreviewResultPromise) {
|
||||
const preview = await this.syncPreviewResultPromise;
|
||||
content = preview.remoteUserData?.content;
|
||||
} else {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
content = remoteUserData.content;
|
||||
}
|
||||
if (preview && !isUndefinedOrNull(content)) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// remove ignored settings from the remote content for preview
|
||||
content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
}
|
||||
return content !== undefined ? content : null;
|
||||
}
|
||||
|
||||
@@ -227,16 +234,20 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
}
|
||||
}
|
||||
|
||||
async resolveConflicts(content: string): Promise<void> {
|
||||
async accept(content: string): Promise<void> {
|
||||
if (this.status === SyncStatus.HasConflicts) {
|
||||
try {
|
||||
await this.apply(content, true);
|
||||
const preview = await this.syncPreviewResultPromise!;
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Add ignored settings from local file content
|
||||
content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
|
||||
await this.apply(true);
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
|
||||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
|
||||
throw new Error('New local version available.');
|
||||
throw new UserDataSyncError('Failed to resolve conflicts as there is a new local version available.', UserDataSyncErrorCode.NewLocal);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
@@ -270,32 +281,27 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...');
|
||||
this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
|
||||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
|
||||
// Rejected as there is a new local version. Syncing again.
|
||||
this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronizing again...');
|
||||
this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async apply(content?: string, forcePush?: boolean): Promise<void> {
|
||||
private async apply(forcePush?: boolean): Promise<void> {
|
||||
if (!this.syncPreviewResultPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (content === undefined) {
|
||||
if (await this.fileService.exists(this.environmentService.settingsSyncPreviewResource)) {
|
||||
const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource);
|
||||
content = settingsPreivew.value.toString();
|
||||
}
|
||||
}
|
||||
let { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
|
||||
|
||||
if (content !== undefined) {
|
||||
if (content !== null) {
|
||||
|
||||
if (this.hasErrors(content)) {
|
||||
const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
|
||||
@@ -303,25 +309,20 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
throw error;
|
||||
}
|
||||
|
||||
let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
|
||||
if (!hasLocalChanged && !hasRemoteChanged) {
|
||||
this.logService.trace('Settings: No changes found during synchronizing settings.');
|
||||
}
|
||||
if (hasLocalChanged) {
|
||||
this.logService.info('Settings: Updating local settings');
|
||||
this.logService.trace('Settings: Updating local settings...');
|
||||
await this.updateLocalFileContent(content, fileContent);
|
||||
this.logService.info('Settings: Updated local settings');
|
||||
}
|
||||
if (hasRemoteChanged) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const remoteContent = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
|
||||
this.logService.info('Settings: Updating remote settings');
|
||||
const ref = await this.updateRemoteUserData(remoteContent, forcePush ? null : remoteUserData.ref);
|
||||
// Update ignored settings from remote
|
||||
content = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
|
||||
this.logService.trace('Settings: Updating remote settings...');
|
||||
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('Settings: Updated remote settings');
|
||||
remoteUserData = { ref, content };
|
||||
}
|
||||
if (remoteUserData.content) {
|
||||
this.logService.info('Settings: Updating last synchronised sttings');
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
// Delete the preview
|
||||
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
|
||||
@@ -329,6 +330,12 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
this.logService.trace('Settings: No changes found during synchronizing settings.');
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
this.logService.trace('Settings: Updating last synchronized settings...');
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
this.logService.info('Settings: Updated last synchronized settings');
|
||||
}
|
||||
|
||||
this.syncPreviewResultPromise = null;
|
||||
}
|
||||
|
||||
@@ -346,16 +353,17 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
}
|
||||
|
||||
private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<ISyncPreviewResult> {
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
// Get file content last to get the latest
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
|
||||
let content: string | null = null;
|
||||
let hasLocalChanged: boolean = false;
|
||||
let hasRemoteChanged: boolean = false;
|
||||
let hasConflicts: boolean = false;
|
||||
let conflictSettings: IConflictSetting[] = [];
|
||||
let previewContent: string | null = null;
|
||||
let remoteContent: string | null = null;
|
||||
|
||||
if (remoteUserData.content) {
|
||||
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
|
||||
@@ -367,31 +375,30 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
|
||||
else {
|
||||
this.logService.trace('Settings: Merging remote settings with local settings...');
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const result = merge(localContent, remoteUserData.content, lastSyncData ? lastSyncData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formatUtils);
|
||||
hasConflicts = result.hasConflicts;
|
||||
const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formatUtils);
|
||||
content = result.localContent || result.remoteContent;
|
||||
hasLocalChanged = result.localContent !== null;
|
||||
hasRemoteChanged = result.remoteContent !== null;
|
||||
hasConflicts = result.hasConflicts;
|
||||
conflictSettings = result.conflictsSettings;
|
||||
remoteContent = result.remoteContent;
|
||||
previewContent = result.localContent || result.remoteContent;
|
||||
}
|
||||
}
|
||||
|
||||
// First time syncing to remote
|
||||
else if (fileContent) {
|
||||
this.logService.info('Settings: Remote settings does not exist. Synchronizing settings for the first time.');
|
||||
this.logService.trace('Settings: Remote settings does not exist. Synchronizing settings for the first time.');
|
||||
content = fileContent.value.toString();
|
||||
hasRemoteChanged = true;
|
||||
previewContent = fileContent.value.toString();
|
||||
remoteContent = fileContent.value.toString();
|
||||
}
|
||||
|
||||
if (previewContent && !token.isCancellationRequested) {
|
||||
if (content && !token.isCancellationRequested) {
|
||||
// Remove the ignored settings from the preview.
|
||||
const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
|
||||
}
|
||||
|
||||
this.setConflicts(conflictSettings);
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, remoteContent, conflictSettings, hasConflicts };
|
||||
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, conflictSettings, hasConflicts };
|
||||
}
|
||||
|
||||
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
|
||||
@@ -403,26 +410,3 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] {
|
||||
let value: string[] = [];
|
||||
if (settingsContent) {
|
||||
const setting = parse(settingsContent);
|
||||
if (setting) {
|
||||
value = setting['sync.ignoredSettings'];
|
||||
}
|
||||
} else {
|
||||
value = configurationService.getValue<string[]>('sync.ignoredSettings');
|
||||
}
|
||||
const added: string[] = [], removed: string[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (const key of value) {
|
||||
if (startsWith(key, '-')) {
|
||||
removed.push(key.substring(1));
|
||||
} else {
|
||||
added.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { timeout, Delayer } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService, UserDataSyncError, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncService {
|
||||
export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private enabled: boolean = false;
|
||||
private successiveFailures: number = 0;
|
||||
private readonly syncDelayer: Delayer<void>;
|
||||
|
||||
private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>());
|
||||
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event;
|
||||
@@ -28,6 +29,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
|
||||
) {
|
||||
super();
|
||||
this.updateEnablement(false, true);
|
||||
this.syncDelayer = this._register(new Delayer<void>(0));
|
||||
this._register(Event.any<any>(userDataAuthTokenService.onDidChangeToken)(() => this.updateEnablement(true, true)));
|
||||
this._register(Event.any<any>(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true)));
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true, false)));
|
||||
@@ -41,14 +43,14 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
|
||||
|
||||
this.enabled = enabled;
|
||||
if (this.enabled) {
|
||||
this.logService.info('Auto sync started');
|
||||
this.logService.info('Auto Sync: Started');
|
||||
this.sync(true, auto);
|
||||
return;
|
||||
} else {
|
||||
this.successiveFailures = 0;
|
||||
this.resetFailures();
|
||||
if (stopIfDisabled) {
|
||||
this.userDataSyncService.stop();
|
||||
this.logService.info('Auto sync stopped.');
|
||||
this.logService.info('Auto Sync: stopped.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,29 +62,29 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
|
||||
if (auto) {
|
||||
if (await this.isTurnedOffEverywhere()) {
|
||||
// Turned off everywhere. Reset & Stop Sync.
|
||||
this.logService.info('Auto Sync: Turning off sync as it is turned off everywhere.');
|
||||
await this.userDataSyncService.resetLocal();
|
||||
await this.userDataSyncUtilService.updateConfigurationValue('sync.enable', false);
|
||||
return;
|
||||
}
|
||||
if (this.userDataSyncService.status !== SyncStatus.Idle) {
|
||||
this.logService.info('Skipped auto sync as sync is happening');
|
||||
this.logService.trace('Auto Sync: Skipped once as it is syncing already');
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.userDataSyncService.sync();
|
||||
this.successiveFailures = 0;
|
||||
this.resetFailures();
|
||||
} catch (e) {
|
||||
this.successiveFailures++;
|
||||
this.logService.error(e);
|
||||
this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown });
|
||||
}
|
||||
if (this.successiveFailures > 5) {
|
||||
this._onError.fire({ code: UserDataSyncErrorCode.TooManyFailures });
|
||||
}
|
||||
if (loop) {
|
||||
await timeout(1000 * 60 * 5 * (this.successiveFailures + 1)); // Loop sync for every (successive failures count + 1) times 5 mins interval.
|
||||
await timeout(1000 * 60 * 5);
|
||||
this.sync(loop, true);
|
||||
}
|
||||
} else {
|
||||
this.logService.trace('Auto Sync: Not syncing as it is disabled.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +100,21 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
|
||||
&& !!(await this.userDataAuthTokenService.getToken());
|
||||
}
|
||||
|
||||
triggerAutoSync(): Promise<void> {
|
||||
return this.sync(false, true);
|
||||
private resetFailures(): void {
|
||||
this.successiveFailures = 0;
|
||||
}
|
||||
|
||||
async triggerAutoSync(): Promise<void> {
|
||||
if (this.enabled) {
|
||||
return this.syncDelayer.trigger(() => {
|
||||
this.logService.info('Auto Sync: Triggerred.');
|
||||
return this.sync(false, true);
|
||||
}, this.successiveFailures
|
||||
? 1000 * 1 * Math.min(this.successiveFailures, 60) /* Delay by number of seconds as number of failures up to 1 minute */
|
||||
: 1000);
|
||||
} else {
|
||||
this.syncDelayer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -124,22 +124,38 @@ export interface IUserData {
|
||||
}
|
||||
|
||||
export enum UserDataSyncErrorCode {
|
||||
TooLarge = 'TooLarge',
|
||||
Unauthroized = 'Unauthroized',
|
||||
Unauthorized = 'Unauthorized',
|
||||
Forbidden = 'Forbidden',
|
||||
ConnectionRefused = 'ConnectionRefused',
|
||||
Rejected = 'Rejected',
|
||||
TooLarge = 'TooLarge',
|
||||
NoRef = 'NoRef',
|
||||
NewLocal = 'NewLocal',
|
||||
Unknown = 'Unknown',
|
||||
TooManyFailures = 'TooManyFailures',
|
||||
ConnectionRefused = 'ConnectionRefused'
|
||||
}
|
||||
|
||||
export class UserDataSyncError extends Error {
|
||||
|
||||
constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) {
|
||||
super(message);
|
||||
this.name = `${this.code} (UserDataSyncError) ${this.source}`;
|
||||
}
|
||||
|
||||
static toUserDataSyncError(error: Error): UserDataSyncError {
|
||||
if (error instanceof UserDataSyncStoreError) {
|
||||
return error;
|
||||
}
|
||||
const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name);
|
||||
if (match && match[1]) {
|
||||
return new UserDataSyncError(error.message, <UserDataSyncErrorCode>match[1], <SyncSource>match[2]);
|
||||
}
|
||||
return new UserDataSyncError(error.message, UserDataSyncErrorCode.Unknown);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreError extends UserDataSyncError { }
|
||||
|
||||
export interface IUserDataSyncStore {
|
||||
url: string;
|
||||
authenticationProviderId: string;
|
||||
@@ -201,8 +217,8 @@ export interface ISynchroniser {
|
||||
|
||||
export interface IUserDataSynchroniser extends ISynchroniser {
|
||||
readonly source: SyncSource;
|
||||
getRemoteContent(): Promise<string | null>;
|
||||
resolveConflicts(content: string): Promise<void>;
|
||||
getRemoteContent(preivew?: boolean): Promise<string | null>;
|
||||
accept(content: string): Promise<void>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
|
||||
@@ -212,14 +228,14 @@ export interface IUserDataSyncService extends ISynchroniser {
|
||||
isFirstTimeSyncAndHasUserData(): Promise<boolean>;
|
||||
reset(): Promise<void>;
|
||||
resetLocal(): Promise<void>;
|
||||
getRemoteContent(source: SyncSource): Promise<string | null>;
|
||||
resolveConflictsAndContinueSync(content: string): Promise<void>;
|
||||
getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null>;
|
||||
accept(source: SyncSource, content: string): Promise<void>;
|
||||
}
|
||||
|
||||
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
|
||||
export interface IUserDataAutoSyncService {
|
||||
_serviceBrand: any;
|
||||
onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>;
|
||||
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>;
|
||||
triggerAutoSync(): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'sync': return this.service.sync();
|
||||
case 'resolveConflictsAndContinueSync': return this.service.resolveConflictsAndContinueSync(args[0]);
|
||||
case 'accept': return this.service.accept(args[0], args[1]);
|
||||
case 'pull': return this.service.pull();
|
||||
case 'push': return this.service.push();
|
||||
case '_getInitialStatus': return Promise.resolve(this.service.status);
|
||||
@@ -37,7 +37,7 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
|
||||
case 'hasRemoteData': return this.service.hasRemoteData();
|
||||
case 'hasLocalData': return this.service.hasLocalData();
|
||||
case 'getRemoteContent': return this.service.getRemoteContent(args[0]);
|
||||
case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]);
|
||||
case 'isFirstTimeSyncAndHasUserData': return this.service.isFirstTimeSyncAndHasUserData();
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
@@ -60,7 +60,7 @@ export class SettingsSyncChannel implements IServerChannel {
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'sync': return this.service.sync();
|
||||
case 'resolveConflicts': return this.service.resolveConflicts(args[0]);
|
||||
case 'accept': return this.service.accept(args[0]);
|
||||
case 'pull': return this.service.pull();
|
||||
case 'push': return this.service.push();
|
||||
case 'restart': return this.service.restart().then(() => this.service.status);
|
||||
@@ -72,7 +72,7 @@ export class SettingsSyncChannel implements IServerChannel {
|
||||
case 'hasRemoteData': return this.service.hasRemoteData();
|
||||
case 'hasLocalData': return this.service.hasLocalData();
|
||||
case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]);
|
||||
case 'getRemoteContent': return this.service.getRemoteContent();
|
||||
case 'getRemoteContent': return this.service.getRemoteContent(args[0]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync';
|
||||
import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
@@ -19,6 +18,10 @@ type SyncConflictsClassification = {
|
||||
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
type SyncErrorClassification = {
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
@@ -73,7 +76,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
try {
|
||||
await synchroniser.pull();
|
||||
} catch (e) {
|
||||
this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`);
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +92,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
try {
|
||||
await synchroniser.push();
|
||||
} catch (e) {
|
||||
this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`);
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,25 +107,25 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
if (this.status === SyncStatus.HasConflicts) {
|
||||
throw new Error(localize('resolve conflicts', "Please resolve conflicts before resuming sync."));
|
||||
}
|
||||
const startTime = new Date().getTime();
|
||||
this.logService.trace('Started Syncing...');
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
try {
|
||||
await synchroniser.sync();
|
||||
// do not continue if synchroniser has conflicts
|
||||
if (synchroniser.status === SyncStatus.HasConflicts) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`);
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
}
|
||||
}
|
||||
this.logService.trace(`Finished Syncing. Took ${new Date().getTime() - startTime}ms`);
|
||||
}
|
||||
|
||||
async resolveConflictsAndContinueSync(content: string): Promise<void> {
|
||||
const synchroniser = this.getSynchroniserInConflicts();
|
||||
if (!synchroniser) {
|
||||
throw new Error(localize('no synchroniser with conflicts', "No conflicts detected."));
|
||||
}
|
||||
await synchroniser.resolveConflicts(content);
|
||||
async accept(source: SyncSource, content: string): Promise<void> {
|
||||
const synchroniser = this.getSynchroniser(source);
|
||||
await synchroniser.accept(content);
|
||||
if (synchroniser.status !== SyncStatus.HasConflicts) {
|
||||
await this.sync();
|
||||
}
|
||||
@@ -193,10 +196,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return false;
|
||||
}
|
||||
|
||||
async getRemoteContent(source: SyncSource): Promise<string | null> {
|
||||
async getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null> {
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (synchroniser.source === source) {
|
||||
return synchroniser.getRemoteContent();
|
||||
return synchroniser.getRemoteContent(preview);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -246,7 +249,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
try {
|
||||
await synchroniser.resetLocal();
|
||||
} catch (e) {
|
||||
this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`);
|
||||
this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`);
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
this.logService.info('Completed resetting local cache');
|
||||
@@ -284,9 +288,21 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return SyncStatus.Idle;
|
||||
}
|
||||
|
||||
private handleSyncError(e: Error, source: SyncSource): void {
|
||||
if (e instanceof UserDataSyncStoreError) {
|
||||
switch (e.code) {
|
||||
case UserDataSyncErrorCode.TooLarge:
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncErrorClassification>('sync/errorTooLarge', { source });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
this.logService.error(e);
|
||||
this.logService.error(`${source}: ${toErrorMessage(e)}`);
|
||||
}
|
||||
|
||||
private computeConflictsSource(): SyncSource | null {
|
||||
const synchroniser = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0];
|
||||
return synchroniser ? this.getSyncSource(synchroniser) : null;
|
||||
return synchroniser ? synchroniser.source : null;
|
||||
}
|
||||
|
||||
private getSynchroniserInConflicts(): IUserDataSynchroniser | null {
|
||||
@@ -294,17 +310,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return synchroniser || null;
|
||||
}
|
||||
|
||||
private getSyncSource(synchroniser: ISynchroniser): SyncSource {
|
||||
if (synchroniser instanceof SettingsSynchroniser) {
|
||||
return SyncSource.Settings;
|
||||
private getSynchroniser(source: SyncSource): IUserDataSynchroniser {
|
||||
switch (source) {
|
||||
case SyncSource.Settings: return this.settingsSynchroniser;
|
||||
case SyncSource.Keybindings: return this.keybindingsSynchroniser;
|
||||
case SyncSource.Extensions: return this.extensionsSynchroniser;
|
||||
case SyncSource.GlobalState: return this.globalStateSynchroniser;
|
||||
}
|
||||
if (synchroniser instanceof KeybindingsSynchroniser) {
|
||||
return SyncSource.Keybindings;
|
||||
}
|
||||
if (synchroniser instanceof ExtensionsSynchroniser) {
|
||||
return SyncSource.Extensions;
|
||||
}
|
||||
return SyncSource.GlobalState;
|
||||
}
|
||||
|
||||
private onDidChangeAuthTokenStatus(token: string | undefined): void {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, } from 'vs/base/common/lifecycle';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
@@ -22,6 +22,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IUserDataAuthTokenService private readonly authTokenService: IUserDataAuthTokenService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super();
|
||||
this.userDataSyncStore = getUserDataSyncStore(configurationService);
|
||||
@@ -48,12 +49,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
}
|
||||
|
||||
if (!isSuccess(context)) {
|
||||
throw new Error('Server returned ' + context.res.statusCode);
|
||||
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source);
|
||||
}
|
||||
|
||||
const ref = context.res.headers['etag'];
|
||||
if (!ref) {
|
||||
throw new Error('Server did not return the ref');
|
||||
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source);
|
||||
}
|
||||
const content = await asText(context);
|
||||
return { ref, content };
|
||||
@@ -73,12 +74,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None);
|
||||
|
||||
if (!isSuccess(context)) {
|
||||
throw new Error('Server returned ' + context.res.statusCode);
|
||||
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source);
|
||||
}
|
||||
|
||||
const newRef = context.res.headers['etag'];
|
||||
if (!newRef) {
|
||||
throw new Error('Server did not return the ref');
|
||||
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source);
|
||||
}
|
||||
return newRef;
|
||||
}
|
||||
@@ -94,39 +95,42 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None);
|
||||
|
||||
if (!isSuccess(context)) {
|
||||
throw new Error('Server returned ' + context.res.statusCode);
|
||||
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise<IRequestContext> {
|
||||
const authToken = await this.authTokenService.getToken();
|
||||
if (!authToken) {
|
||||
throw new Error('No Auth Token Available.');
|
||||
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source);
|
||||
}
|
||||
options.headers = options.headers || {};
|
||||
options.headers['authorization'] = `Bearer ${authToken}`;
|
||||
|
||||
let context;
|
||||
this.logService.trace('Sending request to server', { url: options.url, headers: { ...options.headers, ...{ authorization: undefined } } });
|
||||
|
||||
let context;
|
||||
try {
|
||||
context = await this.requestService.request(options, token);
|
||||
this.logService.trace('Request finished', { url: options.url, status: context.res.statusCode });
|
||||
} catch (e) {
|
||||
throw new UserDataSyncError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source);
|
||||
throw new UserDataSyncStoreError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 401) {
|
||||
// Throw Unauthorized Error
|
||||
throw new UserDataSyncError(`Request '${options.url?.toString()}' is not authorized.`, UserDataSyncErrorCode.Unauthroized, source);
|
||||
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized, source);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 403) {
|
||||
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' is Forbidden (403).`, UserDataSyncErrorCode.Forbidden, source);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 412) {
|
||||
// There is a new value. Throw Rejected Error
|
||||
throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed with precondition. There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Precondition Failed (412). There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 413) {
|
||||
// Throw Too Large Payload Error
|
||||
throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed because data is too large.`, UserDataSyncErrorCode.TooLarge, source);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too large payload (413).`, UserDataSyncErrorCode.TooLarge, source);
|
||||
}
|
||||
|
||||
return context;
|
||||
|
||||
Reference in New Issue
Block a user