Merge from vscode ad407028575a77ea387eb7cc219b323dc017b686

This commit is contained in:
ADS Merger
2020-08-22 06:06:52 +00:00
committed by Anthony Dresser
parent 404260b8a0
commit 4ad73d381c
480 changed files with 14360 additions and 14122 deletions

View File

@@ -53,6 +53,10 @@ function isSyncData(thing: any): thing is ISyncData {
return false;
}
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService): URI {
return joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
}
export interface IResourcePreview {
readonly remoteResource: URI;
@@ -133,7 +137,7 @@ export abstract class AbstractSynchroniser extends Disposable {
this.syncResourceLogLabel = uppercaseFirstLetter(this.resource);
this.syncFolder = joinPath(environmentService.userDataSyncHome, resource);
this.syncPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME);
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`);
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService);
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
}
@@ -324,6 +328,7 @@ export abstract class AbstractSynchroniser extends Disposable {
this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize ${this.syncResourceLogLabel} as there is a new local version available. Synchronizing again...`);
return this.performSync(remoteUserData, lastSyncUserData, apply);
case UserDataSyncErrorCode.Conflict:
case UserDataSyncErrorCode.PreconditionFailed:
// Rejected as there is a new remote version. Syncing again...
this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`);
@@ -796,3 +801,62 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
}
}
export abstract class AbstractInitializer {
private readonly lastSyncResource: URI;
constructor(
readonly resource: SyncResource,
@IEnvironmentService protected readonly environmentService: IEnvironmentService,
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
@IFileService protected readonly fileService: IFileService,
) {
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService);
}
async initialize({ ref, content }: IUserData): Promise<void> {
if (!content) {
this.logService.info('Remote content does not exist.', this.resource);
return;
}
const syncData = this.parseSyncData(content);
if (!syncData) {
return;
}
const isPreviouslySynced = await this.fileService.exists(this.lastSyncResource);
if (isPreviouslySynced) {
this.logService.info('Remote content does not exist.', this.resource);
return;
}
try {
await this.doInitialize({ ref, syncData });
} catch (error) {
this.logService.error(error);
}
}
private parseSyncData(content: string): ISyncData | undefined {
try {
const syncData: ISyncData = JSON.parse(content);
if (isSyncData(syncData)) {
return syncData;
}
} catch (error) {
this.logService.error(error);
}
this.logService.info('Cannot parse sync data as it is not compatible with the current version.', this.resource);
return undefined;
}
protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: lastSyncRemoteUserData.syncData ? JSON.stringify(lastSyncRemoteUserData.syncData) : null, ...additionalProps };
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
}
protected abstract doInitialize(remoteUserData: IRemoteUserData): Promise<void>;
}

View File

@@ -15,7 +15,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { IFileService } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources';
@@ -42,6 +42,34 @@ interface ILastSyncUserData extends IRemoteUserData {
skippedExtensions: ISyncExtension[] | undefined;
}
async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagementService: IExtensionManagementService): Promise<ISyncExtension[]> {
const extensions = JSON.parse(syncData.content);
if (syncData.version === 1
|| syncData.version === 2
) {
const systemExtensions = await extensionManagementService.getInstalled(ExtensionType.System);
for (const extension of extensions) {
// #region Migration from v1 (enabled -> disabled)
if (syncData.version === 1) {
if ((<any>extension).enabled === false) {
extension.disabled = true;
}
delete (<any>extension).enabled;
}
// #endregion
// #region Migration from v2 (set installed property on extension)
if (syncData.version === 2) {
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
extension.installed = true;
}
}
// #endregion
}
}
return extensions;
}
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` });
@@ -84,9 +112,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null;
const installedExtensions = await this.extensionManagementService.getInstalled();
const localExtensions = this.getLocalExtensions(installedExtensions);
@@ -385,34 +413,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return newSkippedExtensions;
}
private async parseAndMigrateExtensions(syncData: ISyncData): Promise<ISyncExtension[]> {
const extensions = this.parseExtensions(syncData);
if (syncData.version === 1
|| syncData.version === 2
) {
const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System);
for (const extension of extensions) {
// #region Migration from v1 (enabled -> disabled)
if (syncData.version === 1) {
if ((<any>extension).enabled === false) {
extension.disabled = true;
}
delete (<any>extension).enabled;
}
// #endregion
// #region Migration from v2 (set installed property on extension)
if (syncData.version === 2) {
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
extension.installed = true;
}
}
// #endregion
}
}
return extensions;
}
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
return JSON.parse(syncData.content);
}
@@ -433,3 +433,68 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
}
export class ExtensionsInitializer extends AbstractInitializer {
constructor(
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
) {
super(SyncResource.Extensions, environmentService, logService, fileService);
}
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
if (!remoteExtensions) {
this.logService.info('Skipping initializing extensions because remote extensions does not exist.');
return;
}
const installedExtensions = await this.extensionManagementService.getInstalled();
const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] };
const toDisable: IExtensionIdentifier[] = [];
for (const extension of remoteExtensions) {
if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) {
if (extension.disabled) {
toDisable.push(extension.identifier);
}
} else {
if (extension.installed) {
if (extension.identifier.uuid) {
toInstall.uuids.push(extension.identifier.uuid);
} else {
toInstall.names.push(extension.identifier.id);
}
}
}
}
if (toInstall.names.length || toInstall.uuids.length) {
const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage;
for (const galleryExtension of galleryExtensions) {
try {
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
await this.extensionManagementService.installFromGallery(galleryExtension);
this.logService.info(`Installed extension.`, galleryExtension.identifier.id);
} catch (error) {
this.logService.error(error);
}
}
}
if (toDisable.length) {
for (const identifier of toDisable) {
this.logService.trace(`Enabling extension...`, identifier.id);
await this.extensionEnablementService.disableExtension(identifier);
this.logService.info(`Enabled extension`, identifier.id);
}
}
}
}

View File

@@ -16,7 +16,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
import { edit } from 'vs/platform/userDataSync/common/content';
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
import { parse } from 'vs/base/common/json';
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
@@ -341,3 +341,55 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => (<IStorageKey>{ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))];
}
}
export class GlobalStateInitializer extends AbstractInitializer {
constructor(
@IStorageService private readonly storageService: IStorageService,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
) {
super(SyncResource.GlobalState, environmentService, logService, fileService);
}
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
if (!remoteGlobalState) {
this.logService.info('Skipping initializing global state because remote global state does not exist.');
return;
}
const argv: IStringDictionary<any> = {};
const storage: IStringDictionary<any> = {};
for (const key of Object.keys(remoteGlobalState.storage)) {
if (key.startsWith(argvStoragePrefx)) {
argv[key.substring(argvStoragePrefx.length)] = remoteGlobalState.storage[key].value;
} else {
if (this.storageService.get(key, StorageScope.GLOBAL) === undefined) {
storage[key] = remoteGlobalState.storage[key].value;
}
}
}
if (Object.keys(argv).length) {
let content = '{}';
try {
const fileContent = await this.fileService.readFile(this.environmentService.argvResource);
content = fileContent.value.toString();
} catch (error) { }
for (const argvProperty of Object.keys(argv)) {
content = edit(content, [argvProperty], argv[argvProperty], {});
}
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
}
if (Object.keys(storage).length) {
for (const key of Object.keys(storage)) {
this.storageService.store(key, storage[key], StorageScope.GLOBAL);
}
}
}
}

View File

@@ -28,10 +28,14 @@ interface IMergeResult {
conflicts: Set<string>;
}
export function parseKeybindings(content: string): IUserFriendlyKeybinding[] {
return parse(content) || [];
}
export async function merge(localContent: string, remoteContent: string, baseContent: string | null, formattingOptions: FormattingOptions, userDataSyncUtilService: IUserDataSyncUtilService): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
const local = <IUserFriendlyKeybinding[]>parse(localContent);
const remote = <IUserFriendlyKeybinding[]>parse(remoteContent);
const base = baseContent ? <IUserFriendlyKeybinding[]>parse(baseContent) : null;
const local = parseKeybindings(localContent);
const remote = parseKeybindings(remoteContent);
const base = baseContent ? parseKeybindings(baseContent) : null;
const userbindings: string[] = [...local, ...remote, ...(base || [])].map(keybinding => keybinding.key);
const normalizedKeys = await userDataSyncUtilService.resolveUserBindings(userbindings);
@@ -331,7 +335,7 @@ function addKeybindings(content: string, keybindings: IUserFriendlyKeybinding[],
}
function removeKeybindings(content: string, command: string, formattingOptions: FormattingOptions): string {
const keybindings = <IUserFriendlyKeybinding[]>parse(content);
const keybindings = parseKeybindings(content);
for (let index = keybindings.length - 1; index >= 0; index--) {
if (keybindings[index].command === command || keybindings[index].command === `-${command}`) {
content = contentUtil.edit(content, [index], undefined, formattingOptions);
@@ -341,7 +345,7 @@ function removeKeybindings(content: string, command: string, formattingOptions:
}
function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string {
const allKeybindings = <IUserFriendlyKeybinding[]>parse(content);
const allKeybindings = parseKeybindings(content);
const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`);
// Remove all entries with this command
for (let index = allKeybindings.length - 1; index >= 0; index--) {

View File

@@ -18,11 +18,12 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { OS, OperatingSystem } from 'vs/base/common/platform';
import { isUndefined } from 'vs/base/common/types';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { VSBuffer } from 'vs/base/common/buffer';
interface ISyncContent {
mac?: string;
@@ -35,6 +36,21 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview {
previewResult: IMergeResult;
}
export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null {
const parsed = <ISyncContent>JSON.parse(syncContent);
if (platformSpecific) {
return isUndefined(parsed.all) ? null : parsed.all;
}
switch (OS) {
case OperatingSystem.Macintosh:
return isUndefined(parsed.mac) ? null : parsed.mac;
case OperatingSystem.Linux:
return isUndefined(parsed.linux) ? null : parsed.linux;
case OperatingSystem.Windows:
return isUndefined(parsed.windows) ? null : parsed.windows;
}
}
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
@@ -209,7 +225,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (lastSyncUserData?.ref !== remoteUserData.ref) {
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`);
const lastSyncContent = content !== null ? this.toSyncContent(content, null) : null;
const lastSyncContent = content !== null ? this.toSyncContent(content, null) : remoteUserData.syncData?.content;
await this.updateLastSyncUserData({
ref: remoteUserData.ref,
syncData: lastSyncContent ? {
@@ -266,20 +282,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
return null;
}
getKeybindingsContentFromSyncContent(syncContent: string): string | null {
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
try {
const parsed = <ISyncContent>JSON.parse(syncContent);
if (!this.syncKeybindingsPerPlatform()) {
return isUndefined(parsed.all) ? null : parsed.all;
}
switch (OS) {
case OperatingSystem.Macintosh:
return isUndefined(parsed.mac) ? null : parsed.mac;
case OperatingSystem.Linux:
return isUndefined(parsed.linux) ? null : parsed.linux;
case OperatingSystem.Windows:
return isUndefined(parsed.windows) ? null : parsed.windows;
}
return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform());
} catch (e) {
this.logService.error(e);
return null;
@@ -325,3 +330,52 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
}
export class KeybindingsInitializer extends AbstractInitializer {
constructor(
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
) {
super(SyncResource.Keybindings, environmentService, logService, fileService);
}
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
const keybindingsContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
if (!keybindingsContent) {
this.logService.info('Skipping initializing keybindings because remote keybindings does not exist.');
return;
}
const isEmpty = await this.isEmpty();
if (!isEmpty) {
this.logService.info('Skipping initializing keybindings because local keybindings exist.');
return;
}
await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(keybindingsContent));
await this.updateLastSyncUserData(remoteUserData);
}
private async isEmpty(): Promise<boolean> {
try {
const fileContent = await this.fileService.readFile(this.environmentService.settingsResource);
const keybindings = parse(fileContent.value.toString());
return !isNonEmptyArray(keybindings);
} catch (error) {
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
}
}
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
try {
return getKeybindingsContentFromSyncContent(syncContent, true);
} catch (e) {
this.logService.error(e);
return null;
}
}
}

View File

@@ -275,8 +275,11 @@ export function areSame(localContent: string, remoteContent: string, ignoredSett
}
export function isEmpty(content: string): boolean {
const nodes = parseSettings(content);
return nodes.length === 0;
if (content) {
const nodes = parseSettings(content);
return nodes.length === 0;
}
return true;
}
function compare(from: IStringDictionary<any> | null, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {

View File

@@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { CancellationToken } from 'vs/base/common/cancellation';
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
import { edit } from 'vs/platform/userDataSync/common/content';
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -40,6 +40,11 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
&& Object.keys(thing).length === 1;
}
export function parseSettingsSyncContent(syncContent: string): ISettingsSyncContent {
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
}
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
@@ -281,10 +286,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
}
parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
try {
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
return parseSettingsSyncContent(syncContent);
} catch (e) {
this.logService.error(e);
}
@@ -350,6 +354,54 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
}
export class SettingsInitializer extends AbstractInitializer {
constructor(
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
) {
super(SyncResource.Settings, environmentService, logService, fileService);
}
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
const settingsSyncContent = remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
if (!settingsSyncContent) {
this.logService.info('Skipping initializing settings because remote settings does not exist.');
return;
}
const isEmpty = await this.isEmpty();
if (!isEmpty) {
this.logService.info('Skipping initializing settings because local settings exist.');
return;
}
await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(settingsSyncContent.settings));
await this.updateLastSyncUserData(remoteUserData);
}
private async isEmpty(): Promise<boolean> {
try {
const fileContent = await this.fileService.readFile(this.environmentService.settingsResource);
return isEmpty(fileContent.value.toString().trim());
} catch (error) {
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
}
}
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
try {
return parseSettingsSyncContent(syncContent);
} catch (e) {
this.logService.error(e);
}
return null;
}
}
function isSyncData(thing: any): thing is ISyncData {
if (thing
&& (thing.version !== undefined && typeof thing.version === 'number')

View File

@@ -10,7 +10,7 @@ import {
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';
import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStringDictionary } from 'vs/base/common/collections';
import { URI } from 'vs/base/common/uri';
@@ -499,3 +499,49 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
return snippets;
}
}
export class SnippetsInitializer extends AbstractInitializer {
constructor(
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
) {
super(SyncResource.Snippets, environmentService, logService, fileService);
}
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
if (!remoteSnippets) {
this.logService.info('Skipping initializing snippets because remote snippets does not exist.');
return;
}
const isEmpty = await this.isEmpty();
if (!isEmpty) {
this.logService.info('Skipping initializing snippets because local snippets exist.');
return;
}
for (const key of Object.keys(remoteSnippets)) {
const content = remoteSnippets[key];
if (content) {
const resource = joinPath(this.environmentService.snippetsHome, key);
await this.fileService.createFile(resource, VSBuffer.fromString(content));
this.logService.info('Created snippet', basename(resource));
}
}
await this.updateLastSyncUserData(remoteUserData);
}
private async isEmpty(): Promise<boolean> {
try {
const stat = await this.fileService.resolve(this.environmentService.snippetsHome);
return !stat.children?.length;
} catch (error) {
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
}
}
}

View File

@@ -58,6 +58,7 @@ export class UserDataAutoSyncEnablementService extends Disposable {
case 'off':
return false;
}
//@ts-expect-error
return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, this.environmentService.enableSyncByDefault);
}

View File

@@ -164,10 +164,7 @@ export interface IUserDataSyncStoreManagementService {
getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined>;
}
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
export interface IUserDataSyncStoreService {
readonly _serviceBrand: undefined;
export interface IUserDataSyncStoreClient {
readonly onDidChangeDonotMakeRequestsUntil: Event<void>;
readonly donotMakeRequestsUntil: Date | undefined;
@@ -186,6 +183,11 @@ export interface IUserDataSyncStoreService {
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
}
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
export interface IUserDataSyncStoreService extends IUserDataSyncStoreClient {
readonly _serviceBrand: undefined;
}
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
export interface IUserDataSyncBackupStoreService {
readonly _serviceBrand: undefined;
@@ -208,6 +210,7 @@ export const HEADER_EXECUTION_ID = 'X-Execution-Id';
export enum UserDataSyncErrorCode {
// Client Errors (>= 400 )
Unauthorized = 'Unauthorized', /* 401 */
Conflict = 'Conflict', /* 409 */
Gone = 'Gone', /* 410 */
PreconditionFailed = 'PreconditionFailed', /* 412 */
TooLarge = 'TooLarge', /* 413 */

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSync';
import { IRequestService, asText, isSuccess as isSuccessContext, asJson } from 'vs/platform/request/common/request';
import { joinPath, relativePath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -125,9 +125,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
}
}
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
_serviceBrand: any;
export class UserDataSyncStoreClient extends Disposable implements IUserDataSyncStoreClient {
private readonly userDataSyncStoreUrl: URI | undefined;
@@ -147,16 +145,16 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
constructor(
userDataSyncStoreUrl: URI | undefined,
@IProductService productService: IProductService,
@IRequestService private readonly requestService: IRequestService,
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IStorageService private readonly storageService: IStorageService,
) {
super();
this.userDataSyncStoreUrl = this.userDataSyncStoreManagementService.userDataSyncStore ? joinPath(this.userDataSyncStoreManagementService.userDataSyncStore.url, 'v1') : undefined;
this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined;
this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService)
.then(uuid => {
const headers: IHeaders = {
@@ -395,6 +393,10 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
this._onTokenSucceed.fire();
if (context.res.statusCode === 409) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Conflict (409). There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Conflict, operationId);
}
if (context.res.statusCode === 410) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because the requested resource is not longer available (410).`, UserDataSyncErrorCode.Gone, operationId);
}
@@ -444,6 +446,23 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
}
export class UserDataSyncStoreService extends UserDataSyncStoreClient implements IUserDataSyncStoreService {
_serviceBrand: any;
constructor(
@IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
@IProductService productService: IProductService,
@IRequestService requestService: IRequestService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IStorageService storageService: IStorageService,
) {
super(userDataSyncStoreManagementService.userDataSyncStore?.url, productService, requestService, logService, environmentService, fileService, storageService);
}
}
export class RequestsSession {
private requests: string[] = [];

View File

@@ -10,7 +10,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
import { getKeybindingsContentFromSyncContent, KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
import { VSBuffer } from 'vs/base/common/buffer';
suite('KeybindingsSync', () => {
@@ -70,8 +70,8 @@ suite('KeybindingsSync', () => {
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), '[]');
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]');
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), '[]');
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), '');
});
@@ -95,11 +95,75 @@ suite('KeybindingsSync', () => {
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content);
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content);
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content);
});
test('when keybindings file is empty with comment and remote has no changes', async () => {
const fileService = client.instantiationService.get(IFileService);
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
const expectedContent = '// Empty Keybindings';
await fileService.writeFile(keybindingsResource, VSBuffer.fromString(expectedContent));
await testObject.sync(await client.manifest());
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), expectedContent);
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), expectedContent);
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent);
});
test('when keybindings file is empty and remote has keybindings', async () => {
const client2 = disposableStore.add(new UserDataSyncClient(server));
await client2.setUp(true);
const content = JSON.stringify([
{
'key': 'shift+cmd+w',
'command': 'workbench.action.closeAllEditors',
}
]);
await client2.instantiationService.get(IFileService).writeFile(client2.instantiationService.get(IEnvironmentService).keybindingsResource, VSBuffer.fromString(content));
await client2.sync();
const fileService = client.instantiationService.get(IFileService);
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
await fileService.writeFile(keybindingsResource, VSBuffer.fromString('// Empty Keybindings'));
await testObject.sync(await client.manifest());
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content);
});
test('when keybindings file is empty and remote has empty array', async () => {
const client2 = disposableStore.add(new UserDataSyncClient(server));
await client2.setUp(true);
const content =
`// Place your key bindings in this file to override the defaults
[
]`;
await client2.instantiationService.get(IFileService).writeFile(client2.instantiationService.get(IEnvironmentService).keybindingsResource, VSBuffer.fromString(content));
await client2.sync();
const fileService = client.instantiationService.get(IFileService);
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
const expectedLocalContent = '// Empty Keybindings';
await fileService.writeFile(keybindingsResource, VSBuffer.fromString(expectedLocalContent));
await testObject.sync(await client.manifest());
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent);
});
test('when keybindings file is created after first sync', async () => {
const fileService = client.instantiationService.get(IFileService);
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
@@ -119,7 +183,7 @@ suite('KeybindingsSync', () => {
const remoteUserData = await testObject.getRemoteUserData(null);
assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref);
assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData);
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]');
});
test('test apply remote when keybindings file does not exist', async () => {

View File

@@ -7,7 +7,7 @@ import * as assert from 'assert';
import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, ISyncData, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
import { SettingsSynchroniser, ISettingsSyncContent, parseSettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -88,8 +88,8 @@ suite('SettingsSync - Auto', () => {
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}');
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}');
assert.equal((await fileService.readFile(settingsResource)).value.toString(), '');
});
@@ -129,8 +129,8 @@ suite('SettingsSync - Auto', () => {
const lastSyncUserData = await testObject.getLastSyncUserData();
const remoteUserData = await testObject.getRemoteUserData(null);
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content);
assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content);
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content);
assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content);
assert.equal((await fileService.readFile(settingsResource)).value.toString(), content);
});
@@ -154,7 +154,7 @@ suite('SettingsSync - Auto', () => {
const remoteUserData = await testObject.getRemoteUserData(null);
assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref);
assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData);
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
});
test('sync for first time to the server', async () => {