#93960 Show sycned machines

This commit is contained in:
Sandeep Somavarapu
2020-05-20 12:00:55 +02:00
parent 025dc8a948
commit 175fddc4ee
10 changed files with 364 additions and 43 deletions
@@ -53,7 +53,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, 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, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel } 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';
@@ -70,6 +70,7 @@ import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/co
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -200,6 +201,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService));
services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService));
services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService));
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
@@ -225,6 +227,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
server.registerChannel('extensionTipsService', extensionTipsChannel);
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
const authTokenService = accessor.get(IAuthenticationTokenService);
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel);
@@ -159,16 +159,17 @@ export interface IResourceRefHandle {
}
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
export type ServerResource = SyncResource | 'machines';
export interface IUserDataSyncStoreService {
_serviceBrand: undefined;
readonly userDataSyncStore: IUserDataSyncStore | undefined;
read(resource: SyncResource, oldValue: IUserData | null): Promise<IUserData>;
write(resource: SyncResource, content: string, ref: string | null): Promise<string>;
read(resource: ServerResource, oldValue: IUserData | null): Promise<IUserData>;
write(resource: ServerResource, content: string, ref: string | null): Promise<string>;
manifest(): Promise<IUserDataManifest | null>;
clear(): Promise<void>;
getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]>;
resolveContent(resource: SyncResource, ref: string): Promise<string | null>;
delete(resource: SyncResource): Promise<void>;
getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]>;
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
delete(resource: ServerResource): Promise<void>;
}
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
@@ -225,7 +226,11 @@ export class UserDataSyncError extends Error {
}
export class UserDataSyncStoreError extends UserDataSyncError { }
export class UserDataSyncStoreError extends UserDataSyncError {
constructor(message: string, code: UserDataSyncErrorCode) {
super(message, code);
}
}
//#endregion
@@ -12,6 +12,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
export class UserDataSyncChannel implements IServerChannel {
@@ -163,3 +164,23 @@ export class StorageKeysSyncRegistryChannelClient extends Disposable implements
}
}
export class UserDataSyncMachinesServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncMachinesService) { }
listen(_: unknown, event: string): Event<any> {
throw new Error(`Event not found: ${event}`);
}
async call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'getMachines': return this.service.getMachines();
case 'updateName': return this.service.updateName(args[0]);
case 'unset': return this.service.unset();
case 'disable': return this.service.disable(args[0]);
}
throw new Error('Invalid call');
}
}
@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Disposable } from 'vs/base/common/lifecycle';
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IUserDataSyncStoreService, IUserData, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
import { localize } from 'vs/nls';
import { IProductService } from 'vs/platform/product/common/productService';
interface IMachineData {
id: string;
name: string;
disabled?: boolean;
}
interface IMachinesData {
version: number;
machines: IMachineData[];
}
export type IUserDataSyncMachine = Readonly<IMachineData> & { readonly isCurrent: boolean };
export const IUserDataSyncMachinesService = createDecorator<IUserDataSyncMachinesService>('IUserDataSyncMachinesService');
export interface IUserDataSyncMachinesService {
_serviceBrand: any;
getMachines(): Promise<IUserDataSyncMachine[]>;
updateName(name: string): Promise<void>;
unset(): Promise<void>;
disable(id: string): Promise<void>
}
export class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService {
private static readonly VERSION = 1;
private static readonly RESOURCE = 'machines';
_serviceBrand: any;
private readonly currentMachineIdPromise: Promise<string>;
private userData: IUserData | null = null;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IStorageService storageService: IStorageService,
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IProductService private readonly productService: IProductService,
) {
super();
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
}
async getMachines(): Promise<IUserDataSyncMachine[]> {
const currentMachineId = await this.currentMachineIdPromise;
const machineData = await this.readMachinesData();
return machineData.machines.map<IUserDataSyncMachine>(machine => ({ ...machine, ...{ isCurrent: machine.id === currentMachineId } }));
}
async updateName(name: string): Promise<void> {
const currentMachineId = await this.currentMachineIdPromise;
const machineData = await this.readMachinesData();
let currentMachine = machineData.machines.find(({ id }) => id === currentMachineId);
if (currentMachine) {
currentMachine.name = name;
} else {
machineData.machines.push({ id: currentMachineId, name });
}
await this.writeMachinesData(machineData);
}
async unset(): Promise<void> {
const currentMachineId = await this.currentMachineIdPromise;
const machineData = await this.readMachinesData();
const updatedMachines = machineData.machines.filter(({ id }) => id !== currentMachineId);
if (updatedMachines.length !== machineData.machines.length) {
machineData.machines = updatedMachines;
await this.writeMachinesData(machineData);
}
}
async disable(machineId: string): Promise<void> {
const machineData = await this.readMachinesData();
const machine = machineData.machines.find(({ id }) => id === machineId);
if (machine) {
machine.disabled = true;
await this.writeMachinesData(machineData);
}
}
private async readMachinesData(): Promise<IMachinesData> {
this.userData = await this.userDataSyncStoreService.read(UserDataSyncMachinesService.RESOURCE, this.userData);
const machinesData = this.parse(this.userData);
if (machinesData.version !== UserDataSyncMachinesService.VERSION) {
throw new Error(localize('error incompatible', "Cannot read machines data as the current version is incompatible. Please update {0} and try again.", this.productService.nameLong));
}
return machinesData;
}
private async writeMachinesData(machinesData: IMachinesData): Promise<void> {
const content = JSON.stringify(machinesData);
const ref = await this.userDataSyncStoreService.write(UserDataSyncMachinesService.RESOURCE, content, this.userData?.ref || null);
this.userData = { ref, content };
}
private parse(userData: IUserData): IMachinesData {
if (userData.content !== null) {
try {
return JSON.parse(userData.content);
} catch (e) {
this.logService.error(e);
}
}
return {
version: UserDataSyncMachinesService.VERSION,
machines: []
};
}
}
@@ -20,6 +20,9 @@ import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSy
import { isEqual } from 'vs/base/common/resources';
import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync';
import { Throttler } from 'vs/base/common/async';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IProductService } from 'vs/platform/product/common/productService';
import { isWeb } from 'vs/base/common/platform';
type SyncClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -67,7 +70,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService
@IStorageService private readonly storageService: IStorageService,
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
@IProductService private readonly productService: IProductService
) {
super();
this.syncThrottler = new Throttler();
@@ -131,7 +136,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
// Server has no data but this machine was synced before
if (manifest === null && await this.hasPreviouslySynced()) {
// Sync was turned off from other machine
// Sync was turned off in the cloud
throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
}
@@ -141,6 +146,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
}
const machines = await this.userDataSyncMachinesService.getMachines();
const currentMachine = machines.find(machine => machine.isCurrent);
// Check if sync was turned off from other machine
if (currentMachine?.disabled) {
// Unset the current machine
await this.userDataSyncMachinesService.unset();
// Throw TurnedOff error
throw new UserDataSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
}
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.sync(manifest);
@@ -160,6 +176,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
}
if (!currentMachine) {
// add current machine to sync server
const namePrefix = `${this.productService.nameLong}${isWeb ? ' (Web)' : ''}`;
const nameRegEx = new RegExp(`${namePrefix}\\s#(\\d)`);
let nameIndex = 0;
for (const machine of machines) {
const matches = nameRegEx.exec(machine.name);
const index = matches ? parseInt(matches[1]) : 0;
nameIndex = index > nameIndex ? index : nameIndex;
}
await this.userDataSyncMachinesService.updateName(`${namePrefix} #${nameIndex + 1}`);
}
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
this.updateLastSyncTime();
@@ -247,14 +277,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async reset(): Promise<void> {
await this.checkEnablement();
await this.resetRemote();
await this.resetLocal();
await this.resetRemote();
}
async resetLocal(): Promise<void> {
await this.checkEnablement();
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
await this.userDataSyncMachinesService.unset();
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.resetLocal();
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request';
import { joinPath, relativePath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -64,7 +64,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, telemetryService);
}
async getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]> {
async getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
}
@@ -72,17 +72,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const uri = joinPath(this.userDataSyncStore.url, 'resource', resource);
const headers: IHeaders = {};
const context = await this.request({ type: 'GET', url: uri.toString(), headers }, undefined, CancellationToken.None);
const context = await this.request({ type: 'GET', url: uri.toString(), headers }, CancellationToken.None);
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined);
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
}
const result = await asJson<{ url: string, created: number }[]>(context) || [];
return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
}
async resolveContent(resource: SyncResource, ref: string): Promise<string | null> {
async resolveContent(resource: ServerResource, ref: string): Promise<string | null> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
}
@@ -91,17 +91,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const headers: IHeaders = {};
headers['Cache-Control'] = 'no-cache';
const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None);
const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None);
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined);
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
}
const content = await asText(context);
return content;
}
async delete(resource: SyncResource): Promise<void> {
async delete(resource: ServerResource): Promise<void> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
}
@@ -109,14 +109,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString();
const headers: IHeaders = {};
const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None);
const context = await this.request({ type: 'DELETE', url, headers }, CancellationToken.None);
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined);
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
}
}
async read(resource: SyncResource, oldValue: IUserData | null): Promise<IUserData> {
async read(resource: ServerResource, oldValue: IUserData | null): Promise<IUserData> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
}
@@ -129,7 +129,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
headers['If-None-Match'] = oldValue.ref;
}
const context = await this.request({ type: 'GET', url, headers }, resource, CancellationToken.None);
const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None);
if (context.res.statusCode === 304) {
// There is no new value. Hence return the old value.
@@ -137,18 +137,18 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
}
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource);
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
}
const ref = context.res.headers['etag'];
if (!ref) {
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource);
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef);
}
const content = await asText(context);
return { ref, content };
}
async write(resource: SyncResource, data: string, ref: string | null): Promise<string> {
async write(resource: ServerResource, data: string, ref: string | null): Promise<string> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
}
@@ -159,15 +159,15 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
headers['If-Match'] = ref;
}
const context = await this.request({ type: 'POST', url, data, headers }, resource, CancellationToken.None);
const context = await this.request({ type: 'POST', url, data, headers }, CancellationToken.None);
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource);
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
}
const newRef = context.res.headers['etag'];
if (!newRef) {
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource);
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef);
}
return newRef;
}
@@ -180,7 +180,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const url = joinPath(this.userDataSyncStore.url, 'manifest').toString();
const headers: IHeaders = { 'Content-Type': 'application/json' };
const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None);
const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None);
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
}
@@ -214,7 +214,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const url = joinPath(this.userDataSyncStore.url, 'resource').toString();
const headers: IHeaders = { 'Content-Type': 'text/plain' };
const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None);
const context = await this.request({ type: 'DELETE', url, headers }, CancellationToken.None);
if (!isSuccess(context)) {
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown);
@@ -229,10 +229,10 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
this.storageService.remove(MACHINE_SESSION_ID_KEY, StorageScope.GLOBAL);
}
private async request(options: IRequestOptions, source: SyncResource | undefined, token: CancellationToken): Promise<IRequestContext> {
private async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
const authToken = this.authTokenService.token;
if (!authToken) {
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source);
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized);
}
const commonHeaders = await this.commonHeadersPromise;
@@ -252,30 +252,30 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
this.logService.trace('Request finished', { url: options.url, status: context.res.statusCode });
} catch (e) {
if (!(e instanceof UserDataSyncStoreError)) {
e = new UserDataSyncStoreError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source);
e = new UserDataSyncStoreError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused);
}
throw e;
}
if (context.res.statusCode === 401) {
this.authTokenService.sendTokenFailed();
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized, source);
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized);
}
if (context.res.statusCode === 403) {
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' is Forbidden (403).`, UserDataSyncErrorCode.Forbidden, source);
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' is Forbidden (403).`, UserDataSyncErrorCode.Forbidden);
}
if (context.res.statusCode === 412) {
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.RemotePreconditionFailed, 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.RemotePreconditionFailed);
}
if (context.res.statusCode === 413) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too large payload (413).`, UserDataSyncErrorCode.TooLarge, source);
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too large payload (413).`, UserDataSyncErrorCode.TooLarge);
}
if (context.res.statusCode === 429) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, source);
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests);
}
return context;
@@ -32,6 +32,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IAction, Action } from 'vs/base/common/actions';
import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSyncWorkbenchService';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
export class UserDataSyncViewPaneContainer extends ViewPaneContainer {
@@ -69,19 +71,21 @@ export class UserDataSyncDataViews extends Disposable {
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
) {
super();
this.registerViews(container);
}
private registerViews(container: ViewContainer): void {
const remoteView = this.registerView(container, true, true);
const remoteView = this.registerDataView(container, true, true);
this.registerRemoteViewActions(remoteView);
this.registerView(container, false, false);
this.registerDataView(container, false, false);
this.registerMachinesView(container);
}
private registerView(container: ViewContainer, remote: boolean, showByDefault: boolean): TreeView {
private registerDataView(container: ViewContainer, remote: boolean, showByDefault: boolean): TreeView {
const id = `workbench.views.sync.${remote ? 'remote' : 'local'}DataView`;
const showByDefaultContext = new RawContextKey<boolean>(id, showByDefault);
const viewEnablementContext = showByDefaultContext.bindTo(this.contextKeyService);
@@ -148,11 +152,69 @@ export class UserDataSyncDataViews extends Disposable {
}
});
this.registerActions(id);
this.registerDataViewActions(id);
return treeView;
}
private registerActions(viewId: string) {
private registerMachinesView(container: ViewContainer): void {
const that = this;
const id = `workbench.views.sync.machines`;
const name = localize('synced machines', "Synced Machines");
const treeView = this.instantiationService.createInstance(TreeView, id, name);
treeView.showRefreshAction = true;
const disposable = treeView.onDidChangeVisibility(visible => {
if (visible && !treeView.dataProvider) {
disposable.dispose();
treeView.dataProvider = new UserDataSyncMachinesViewDataProvider(treeView, this.userDataSyncMachinesService);
}
});
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViews([<ITreeViewDescriptor>{
id,
name,
ctorDescriptor: new SyncDescriptor(TreeViewPane),
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
canToggleVisibility: true,
canMoveView: true,
treeView,
collapsed: false,
order: 200,
}], container);
registerAction2(class extends Action2 {
constructor() {
super({
id: `workbench.actions.sync.editCurrentMachineName`,
title: localize('workbench.actions.sync.editCurrentMachineName', "Edit Name"),
icon: Codicon.edit,
menu: {
id: MenuId.ViewItemContext,
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', id), ContextKeyEqualsExpr.create('viewItem', 'current-machine')),
group: 'inline',
},
});
}
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
const quickInputService = accessor.get(IQuickInputService);
const inputBox = quickInputService.createInputBox();
inputBox.placeholder = localize('placeholder', "Enter the name of the machine");
inputBox.show();
return new Promise((c, e) => {
inputBox.onDidAccept(async () => {
const name = inputBox.value;
if (name) {
await that.userDataSyncMachinesService.updateName(name);
await treeView.refresh();
}
inputBox.dispose();
c();
});
});
}
});
}
private registerDataViewActions(viewId: string) {
registerAction2(class extends Action2 {
constructor() {
super({
@@ -317,6 +379,28 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
}
}
class UserDataSyncMachinesViewDataProvider implements ITreeViewDataProvider {
constructor(
private readonly treeView: TreeView,
private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
) { }
async getChildren(): Promise<ITreeItem[]> {
let machines = await this.userDataSyncMachinesService.getMachines();
machines = machines.filter(m => !m.disabled).sort((m1, m2) => m1.isCurrent ? -1 : 1);
this.treeView.message = machines.length ? undefined : localize('no machines', "No Machines");
return machines.map(({ id, name, isCurrent }) => ({
handle: id,
collapsibleState: TreeItemCollapsibleState.None,
label: { label: name },
description: isCurrent ? localize({ key: 'current', comment: ['Current machine'] }, "Current") : undefined,
themeIcon: Codicon.vm,
contextValue: isCurrent ? 'current-machine' : 'other-machine'
}));
}
}
function label(date: Date): string {
return date.toLocaleDateString() +
' ' + pad(date.getHours(), 2) +
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines';
class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService {
_serviceBrand: undefined;
private readonly channel: IChannel;
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService
) {
super();
this.channel = sharedProcessService.getChannel('userDataSyncMachines');
}
getMachines(): Promise<IUserDataSyncMachine[]> {
return this.channel.call<IUserDataSyncMachine[]>('getMachines');
}
updateName(name: string): Promise<void> {
return this.channel.call('updateName', [name]);
}
unset(): Promise<void> {
return this.channel.call('unset');
}
disable(id: string): Promise<void> {
return this.channel.call('disable', [id]);
}
}
registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService);
@@ -51,6 +51,7 @@ import 'vs/workbench/services/url/electron-browser/urlService';
import 'vs/workbench/services/workspaces/electron-browser/workspacesService';
import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService';
import 'vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncMachinesService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService';
import 'vs/workbench/services/authentication/electron-browser/authenticationTokenService';
import 'vs/workbench/services/authentication/browser/authenticationService';
+2
View File
@@ -64,6 +64,7 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService';
import { ILoggerService } from 'vs/platform/log/common/log';
import { FileLoggerService } from 'vs/platform/log/common/fileLogService';
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { StorageKeysSyncRegistryService, IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
@@ -86,6 +87,7 @@ registerSingleton(ILoggerService, FileLoggerService);
registerSingleton(IAuthenticationService, AuthenticationService);
registerSingleton(IUserDataSyncLogService, UserDataSyncLogService);
registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService);
registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService);
registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService);
registerSingleton(IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService);
registerSingleton(IAuthenticationTokenService, AuthenticationTokenService);