提交 c7a2ac36 编写于 作者: S Sandeep Somavarapu

FIrst cut - sync history view

上级 99cc1f22
......@@ -18,6 +18,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { IStringDictionary } from 'vs/base/common/collections';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isString } from 'vs/base/common/types';
type SyncSourceClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
......@@ -157,6 +158,11 @@ export abstract class AbstractSynchroniser extends Disposable {
return syncData ? syncData.content : null;
}
async resolveContent(ref: string): Promise<string | null> {
const { syncData } = await this.getRemoteUserData(ref);
return syncData ? syncData.content : null;
}
async resetLocal(): Promise<void> {
try {
await this.fileService.del(this.lastSyncResource);
......@@ -189,9 +195,20 @@ export abstract class AbstractSynchroniser extends Disposable {
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
}
protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData> {
const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null;
const { ref, content } = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source);
protected async getRemoteUserData(ref: string): Promise<IRemoteUserData>
protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData>
protected async getRemoteUserData(arg: string | IRemoteUserData | null): Promise<IRemoteUserData> {
let ref: string;
let content: string | null = null;
if (isString(arg)) {
ref = arg;
content = await this.userDataSyncStoreService.resolveContent(this.resourceKey, ref);
} else {
const lastSyncUserData: IUserData | null = arg ? { ref: arg.ref, content: arg.syncData ? JSON.stringify(arg.syncData) : null } : null;
const userData = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source);
ref = userData.ref;
content = userData.content;
}
let syncData: ISyncData | null = null;
if (content !== null) {
try {
......
......@@ -161,6 +161,11 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
}
async resolveContent(ref: string): Promise<string | null> {
let content = await super.resolveContent(ref);
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
try {
const result = await this.getPreview(remoteUserData, lastSyncUserData);
......
......@@ -202,6 +202,15 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return content;
}
async resolveContent(ref: string): Promise<string | null> {
let content = await super.resolveContent(ref);
if (content !== null) {
const settingsSyncContent = this.parseSettingsSyncContent(content);
content = settingsSyncContent ? settingsSyncContent.settings : null;
}
return content;
}
async accept(content: string): Promise<void> {
if (this.status === SyncStatus.HasConflicts) {
const preview = await this.syncPreviewResultPromise!;
......
......@@ -255,6 +255,7 @@ export interface IUserDataSynchroniser {
resetLocal(): Promise<void>;
getRemoteContent(preivew?: boolean): Promise<string | null>;
resolveContent(ref: string): Promise<string | null>;
accept(content: string): Promise<void>;
}
......@@ -300,6 +301,7 @@ export interface IUserDataSyncService {
isFirstTimeSyncWithMerge(): Promise<boolean>;
getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null>;
resolveContent(resource: ResourceKey, ref: string): Promise<string | null>;
accept(source: SyncSource, content: string): Promise<void>;
}
......
......@@ -35,6 +35,7 @@ export class UserDataSyncChannel implements IServerChannel {
case 'reset': return this.service.reset();
case 'resetLocal': return this.service.resetLocal();
case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]);
case 'resolveContent': return this.service.resolveContent(args[0], args[1]);
case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge();
}
throw new Error('Invalid call');
......@@ -68,6 +69,7 @@ export class SettingsSyncChannel implements IServerChannel {
case 'hasLocalData': return this.service.hasLocalData();
case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]);
case 'getRemoteContent': return this.service.getRemoteContent(args[0]);
case 'resolveContent': return this.service.resolveContent(args[0]);
}
throw new Error('Invalid call');
}
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, ResourceKey } from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
......@@ -186,6 +186,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return null;
}
async resolveContent(resourceKey: ResourceKey, ref: string): Promise<string | null> {
const synchronizer = this.synchronisers.filter(s => s.resourceKey === resourceKey)[0];
if (synchronizer) {
return synchronizer.resolveContent(ref);
}
return null;
}
async isFirstTimeSyncWithMerge(): Promise<boolean> {
await this.checkEnablement();
if (!await this.userDataSyncStoreService.manifest()) {
......
......@@ -7,6 +7,8 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
import { UserDataSyncHistoryViewContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncHistory';
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready);
workbenchRegistry.registerWorkbenchContribution(UserDataSyncHistoryViewContribution, LifecyclePhase.Ready);
......@@ -9,7 +9,7 @@ import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { isWeb } from 'vs/base/common/platform';
import { isEqual } from 'vs/base/common/resources';
import { isEqual, dirname, basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import type { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
......@@ -1059,9 +1059,14 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider {
let promise: Promise<string | null> | undefined;
if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) {
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings, true);
}
if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) {
} else if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) {
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings, true);
} else {
const resourceKey: ResourceKey = basename(dirname(uri)) as ResourceKey;
const ref = basename(uri);
if (resourceKey && ref) {
promise = this.userDataSyncService.resolveContent(resourceKey, ref);
}
}
if (promise) {
return promise.then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri));
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg } from 'vs/workbench/common/views';
import { localize } from 'vs/nls';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView';
import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ALL_RESOURCE_KEYS, CONTEXT_SYNC_ENABLEMENT, IUserDataSyncStoreService, USER_DATA_SYNC_SCHEME, ResourceKey } from 'vs/platform/userDataSync/common/userDataSync';
import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
import { URI } from 'vs/base/common/uri';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { FolderThemeIcon, FileThemeIcon } from 'vs/platform/theme/common/themeService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
const CONTEXT_SHOW_USER_DATA_SYNC_HISTORY_VIEW = new RawContextKey<boolean>('showUserDataSyncHistoryView', false);
export class UserDataSyncHistoryViewContribution implements IWorkbenchContribution {
private readonly viewId = 'workbench.views.syncHistory';
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
this.registerView();
this.registerActions();
}
private registerView(): void {
const that = this;
const name = localize('title', "Sync History");
const viewEnablementContext = CONTEXT_SHOW_USER_DATA_SYNC_HISTORY_VIEW.bindTo(this.contextKeyService);
const treeView = this.instantiationService.createInstance(CustomTreeView, this.viewId, name);
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViews([<ITreeViewDescriptor>{
id: this.viewId,
name,
ctorDescriptor: new SyncDescriptor(CustomTreeViewPane),
when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_SHOW_USER_DATA_SYNC_HISTORY_VIEW),
canToggleVisibility: false,
canMoveView: true,
treeView,
collapsed: false,
order: 100,
}], VIEW_CONTAINER);
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.actions.showSyncHistory',
title: { value: localize('workbench.action.showRemoteUserDatraView', "Show Sync History"), original: `Show Sync History` },
category: { value: localize('sync', "Sync"), original: `Sync` },
menu: {
id: MenuId.CommandPalette,
when: CONTEXT_SYNC_ENABLEMENT
},
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const instantiationService = accessor.get(IInstantiationService);
const viewsService = accessor.get(IViewsService);
if (!treeView.dataProvider) {
treeView.dataProvider = instantiationService.createInstance(UserDataSyncHistoryViewDataProvider);
}
viewEnablementContext.set(true);
viewsService.openView(that.viewId, true);
}
});
}
private registerActions() {
const that = this;
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.actions.openRef',
title: localize('workbench.action.openRef', "Open Ref"),
});
}
async run(accessor: ServicesAccessor, resource: URI): Promise<void> {
const editorService = accessor.get(IEditorService);
await editorService.openEditor({ resource });
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.actions.deleteRef',
title: localize('workbench.action.deleteRef', "Delete"),
menu: {
id: MenuId.ViewItemContext,
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.viewId), ContextKeyExpr.regex('viewItem', /sync-.*/i))
},
});
}
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
const dialogService = accessor.get(IDialogService);
const userDataSyncStoreService = accessor.get(IUserDataSyncStoreService);
const result = await dialogService.confirm({
message: 'Would you like to delete'
});
if (result) {
return userDataSyncStoreService.delete(handle.$treeItemHandle as ResourceKey);
}
}
});
}
}
class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
constructor(
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
) {
}
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
if (element) {
return this.getResources(element.handle);
}
return ALL_RESOURCE_KEYS.map(resourceKey => ({
handle: resourceKey,
collapsibleState: TreeItemCollapsibleState.Collapsed,
label: { label: resourceKey },
themeIcon: FolderThemeIcon,
contextValue: `sync-${resourceKey}`
}));
}
private async getResources(handle: string): Promise<ITreeItem[]> {
const resourceKey = ALL_RESOURCE_KEYS.filter(key => key === handle)[0];
if (resourceKey) {
const refs = await this.userDataSyncStoreService.getAllRefs(resourceKey);
return refs.map(ref => {
const resourceUri = URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${resourceKey}/${ref}` });
return {
handle: `${resourceKey}/${ref}`,
collapsibleState: TreeItemCollapsibleState.None,
label: { label: ref },
resourceUri,
command: { id: 'workbench.actions.openRef', title: '', arguments: [resourceUri] },
themeIcon: FileThemeIcon,
contextValue: `syncref-${resourceKey}`
};
});
}
return [];
}
}
......@@ -88,6 +88,10 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ
return this.channel.call('getRemoteContent', [!!preview]);
}
resolveContent(ref: string): Promise<string | null> {
return this.channel.call('resolveContent', [ref]);
}
private async updateStatus(status: SyncStatus): Promise<void> {
this._status = status;
this._onDidChangeStatus.fire(status);
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, SyncSource, IUserDataSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, SyncSource, IUserDataSyncService, UserDataSyncError, ResourceKey } from 'vs/platform/userDataSync/common/userDataSync';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
......@@ -92,6 +92,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('getRemoteContent', [source, preview]);
}
resolveContent(key: ResourceKey, ref: string): Promise<string | null> {
return this.channel.call('resolveContent', [key, ref]);
}
isFirstTimeSyncWithMerge(): Promise<boolean> {
return this.channel.call('isFirstTimeSyncWithMerge');
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册