mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-18 22:29:56 +01:00
#93960 Show sycned machines
This commit is contained in:
@@ -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) +
|
||||
|
||||
+43
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user