From c584c5c1775c319b5333742f3bbed27743a9695d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Mar 2020 11:54:09 +0100 Subject: [PATCH] Implement getAllRefs and resolveContent for backup store --- .../sharedProcess/sharedProcessMain.ts | 6 +- .../common/abstractSynchronizer.ts | 4 ++ .../userDataSync/common/extensionsSync.ts | 28 +++++++--- .../userDataSync/common/globalStateSync.ts | 28 +++++++--- .../userDataSync/common/keybindingsSync.ts | 28 +++++++--- .../userDataSync/common/settingsSync.ts | 32 +++++++---- .../userDataSync/common/userDataSync.ts | 6 ++ .../common/userDataSyncBackupStoreService.ts | 55 ++++++++++++++----- .../userDataSync/common/userDataSyncIpc.ts | 20 ++++++- .../common/userDataSyncService.ts | 2 +- .../electron-browser/settingsSyncService.ts | 4 ++ .../userDataSyncBackupStoreService.ts | 37 +++++++++++++ src/vs/workbench/workbench.desktop.main.ts | 1 + 13 files changed, 201 insertions(+), 50 deletions(-) create mode 100644 src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 79e8473bcc6..0e6c2358199 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -52,7 +52,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; @@ -225,6 +225,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const userDataSyncStoreServiceChannel = new UserDataSyncStoreServiceChannel(userDataSyncStoreService); server.registerChannel('userDataSyncStoreService', userDataSyncStoreServiceChannel); + const userDataSyncBackupStoreService = accessor.get(IUserDataSyncBackupStoreService); + const userDataSyncBackupStoreServiceChannel = new UserDataSyncBackupStoreServiceChannel(userDataSyncBackupStoreService); + server.registerChannel('userDataSyncBackupStoreService', userDataSyncBackupStoreServiceChannel); + const settingsSyncService = accessor.get(ISettingsSyncService); const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); server.registerChannel('settingsSync', settingsSyncChannel); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 5d8e7648c1d..8f79415fe07 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -162,6 +162,10 @@ export abstract class AbstractSynchroniser extends Disposable { return content; } + async getLocalBackupContent(ref?: string): Promise { + return this.userDataSyncBackupStoreService.resolveContent(this.resourceKey, ref); + } + async resetLocal(): Promise { try { await this.fileService.del(this.lastSyncResource); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 4a108b0814e..cdc3d6faab5 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -123,18 +123,30 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async getRemoteContent(ref?: string, fragment?: string): Promise { const content = await super.getRemoteContent(ref); if (content !== null && fragment) { - const syncData = this.parseSyncData(content); - if (syncData) { - switch (fragment) { - case 'extensions': - return syncData.content; - } - } - return null; + return this.getFragment(content, fragment); } return content; } + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (fragment) { + case 'extensions': + return syncData.content; + } + } + return null; + } + accept(content: string): Promise { throw new Error('Extensions: Conflicts should not occur'); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index d125cd05e4e..a095a09b482 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -107,18 +107,30 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async getRemoteContent(ref?: string, fragment?: string): Promise { let content = await super.getRemoteContent(ref); if (content !== null && fragment) { - const syncData = this.parseSyncData(content); - if (syncData) { - switch (fragment) { - case 'globalState': - return syncData.content; - } - } - return null; + return this.getFragment(content, fragment); + } + return content; + } + + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); } return content; } + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (fragment) { + case 'globalState': + return syncData.content; + } + } + return null; + } + accept(content: string): Promise { throw new Error('UI State: Conflicts should not occur'); } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 33f5eb92dd7..c3f02bac0da 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -164,18 +164,30 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem async getRemoteContent(ref?: string, fragment?: string): Promise { const content = await super.getRemoteContent(ref); if (content !== null && fragment) { - const syncData = this.parseSyncData(content); - if (syncData) { - switch (fragment) { - case 'keybindings': - return this.getKeybindingsContentFromSyncContent(syncData.content); - } - } - return null; + return this.getFragment(content, fragment); + } + return content; + } + + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); } return content; } + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (fragment) { + case 'keybindings': + return this.getKeybindingsContentFromSyncContent(syncData.content); + } + } + return null; + } + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { try { const result = await this.getPreview(remoteUserData, lastSyncUserData); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 79d0b2494e8..219f2c2a698 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -205,19 +205,31 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement async getRemoteContent(ref?: string, fragment?: string): Promise { let content = await super.getRemoteContent(ref); if (content !== null && fragment) { - const syncData = this.parseSyncData(content); - if (syncData) { - const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); - if (settingsSyncContent) { - switch (fragment) { - case 'settings': - return settingsSyncContent.settings; - } + return this.getFragment(content, fragment); + } + return content; + } + + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); + if (settingsSyncContent) { + switch (fragment) { + case 'settings': + return settingsSyncContent.settings; } } - return null; } - return content; + return null; } async accept(content: string): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 2518c3e365b..0744514d2f5 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -165,6 +165,8 @@ export const IUserDataSyncBackupStoreService = createDecorator; + getAllRefs(key: ResourceKey): Promise; + resolveContent(key: ResourceKey, ref?: string): Promise; } //#endregion @@ -261,6 +263,7 @@ export interface IUserDataSynchroniser { getRemoteContentFromPreview(): Promise; getRemoteContent(ref?: string, fragment?: string): Promise; + getLocalBackupContent(ref?: string, fragment?: string): Promise; accept(content: string): Promise; } @@ -354,6 +357,9 @@ export function toRemoteSyncResourceFromSource(source: SyncSource, ref?: string) export function toRemoteSyncResource(resourceKey: ResourceKey, ref?: string): URI { return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); } +export function toLocalBackupSyncResource(resourceKey: ResourceKey, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); +} export function resolveSyncResource(resource: URI): { remote: boolean, resourceKey: ResourceKey, ref?: string } | null { const remote = resource.authority === 'remote'; diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts index 8875ec93336..b439de0bc02 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserDataSyncLogService, ResourceKey, ALL_RESOURCE_KEYS, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, ResourceKey, ALL_RESOURCE_KEYS, IUserDataSyncBackupStoreService, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { joinPath } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { toLocalISOString } from 'vs/base/common/date'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -26,6 +26,34 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD ALL_RESOURCE_KEYS.forEach(resourceKey => this.cleanUpBackup(resourceKey)); } + async getAllRefs(resourceKey: ResourceKey): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + const stat = await this.fileService.resolve(folder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); + return all.map(stat => ({ + ref: stat.name, + created: this.getCreationTime(stat) + })); + } + return []; + } + + async resolveContent(resourceKey: ResourceKey, ref?: string): Promise { + if (!ref) { + const refs = await this.getAllRefs(resourceKey); + if (refs.length) { + ref = refs[refs.length - 1].ref; + } + } + if (ref) { + const file = joinPath(this.environmentService.userDataSyncHome, resourceKey, ref); + const content = await this.fileService.readFile(file); + return content.value.toString(); + } + return null; + } + async backup(resourceKey: ResourceKey, content: string): Promise { const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); @@ -53,17 +81,7 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD if (stat.children) { const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort(); const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); - let toDelete = all.filter(stat => { - const ctime = stat.ctime || new Date( - parseInt(stat.name.substring(0, 4)), - parseInt(stat.name.substring(4, 6)) - 1, - parseInt(stat.name.substring(6, 8)), - parseInt(stat.name.substring(9, 11)), - parseInt(stat.name.substring(11, 13)), - parseInt(stat.name.substring(13, 15)) - ).getTime(); - return Date.now() - ctime > backUpMaxAge; - }); + let toDelete = all.filter(stat => Date.now() - this.getCreationTime(stat) > backUpMaxAge); const remaining = all.length - toDelete.length; if (remaining < 10) { toDelete = toDelete.slice(10 - remaining); @@ -77,4 +95,15 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD this.logService.error(e); } } + + private getCreationTime(stat: IFileStat) { + return stat.ctime || new Date( + parseInt(stat.name.substring(0, 4)), + parseInt(stat.name.substring(4, 6)) - 1, + parseInt(stat.name.substring(6, 8)), + parseInt(stat.name.substring(9, 11)), + parseInt(stat.name.substring(11, 13)), + parseInt(stat.name.substring(13, 15)) + ).getTime(); + } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 08e0a4c9aa3..de2736a2fe3 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -69,6 +69,7 @@ export class SettingsSyncChannel implements IServerChannel { case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); case 'getRemoteContentFromPreview': return this.service.getRemoteContentFromPreview(); case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); + case 'getLocalBackupContent': return this.service.getLocalBackupContent(args[0], args[1]); } throw new Error('Invalid call'); } @@ -149,3 +150,20 @@ export class UserDataSyncStoreServiceChannel implements IServerChannel { throw new Error('Invalid call'); } } + +export class UserDataSyncBackupStoreServiceChannel implements IServerChannel { + + constructor(private readonly service: IUserDataSyncBackupStoreService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'getAllRefs': return this.service.getAllRefs(args[0]); + case 'resolveContent': return this.service.resolveContent(args[0], args[1]); + } + throw new Error('Invalid call'); + } +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 5c7f3950cfe..4be50d8387a 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -185,7 +185,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (PREVIEW_QUERY === resource.query) { return result.remote ? synchronizer.getRemoteContentFromPreview() : null; } - return result.remote ? synchronizer.getRemoteContent(result.ref, resource.fragment) : null; + return result.remote ? synchronizer.getRemoteContent(result.ref, resource.fragment) : synchronizer.getLocalBackupContent(result.ref, resource.fragment); } } return null; diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 8fe78318d4c..7d48e03e84c 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -88,6 +88,10 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ return this.channel.call('getRemoteContent', [ref, fragment]); } + getLocalBackupContent(ref?: string, fragment?: string): Promise { + return this.channel.call('getLocalBackupContent', [ref, fragment]); + } + getRemoteContentFromPreview(): Promise { return this.channel.call('getRemoteContentFromPreview', []); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts new file mode 100644 index 00000000000..3a395b1c5b4 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ResourceKey, IResourceRefHandle, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class UserDataSyncBackupStoreService implements IUserDataSyncBackupStoreService { + + _serviceBrand: undefined; + private readonly channel: IChannel; + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService, + ) { + this.channel = sharedProcessService.getChannel('userDataSyncBackupStoreService'); + } + + backup(key: ResourceKey, content: string): Promise { + return this.channel.call('backup', [key, content]); + } + + + getAllRefs(key: ResourceKey): Promise { + return this.channel.call('getAllRefs', [key]); + } + + resolveContent(key: ResourceKey, ref: string): Promise { + return this.channel.call('resolveContent', [key, ref]); + } + +} + +registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 33ba47dadd6..a8f926566d4 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -53,6 +53,7 @@ import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService' import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService'; +import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService'; import 'vs/workbench/services/authentication/electron-browser/authenticationTokenService'; import 'vs/workbench/services/authentication/browser/authenticationService'; import 'vs/workbench/services/host/electron-browser/desktopHostService'; -- GitLab