Skip to content

Commit 175fddc

Browse files
committed
microsoft#93960 Show sycned machines
1 parent 025dc8a commit 175fddc

10 files changed

Lines changed: 364 additions & 43 deletions

File tree

src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
5353
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
5454
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
5555
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
56-
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
56+
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
5757
import { IElectronService } from 'vs/platform/electron/node/electron';
5858
import { LoggerService } from 'vs/platform/log/node/loggerService';
5959
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
@@ -70,6 +70,7 @@ import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/co
7070
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
7171
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
7272
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
73+
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
7374

7475
export interface ISharedProcessConfiguration {
7576
readonly machineId: string;
@@ -200,6 +201,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
200201
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
201202
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
202203
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
204+
services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService));
203205
services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService));
204206
services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService));
205207
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
@@ -225,6 +227,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
225227
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
226228
server.registerChannel('extensionTipsService', extensionTipsChannel);
227229

230+
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
231+
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
232+
server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
233+
228234
const authTokenService = accessor.get(IAuthenticationTokenService);
229235
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
230236
server.registerChannel('authToken', authTokenChannel);

src/vs/platform/userDataSync/common/userDataSync.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,16 +159,17 @@ export interface IResourceRefHandle {
159159
}
160160

161161
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
162+
export type ServerResource = SyncResource | 'machines';
162163
export interface IUserDataSyncStoreService {
163164
_serviceBrand: undefined;
164165
readonly userDataSyncStore: IUserDataSyncStore | undefined;
165-
read(resource: SyncResource, oldValue: IUserData | null): Promise<IUserData>;
166-
write(resource: SyncResource, content: string, ref: string | null): Promise<string>;
166+
read(resource: ServerResource, oldValue: IUserData | null): Promise<IUserData>;
167+
write(resource: ServerResource, content: string, ref: string | null): Promise<string>;
167168
manifest(): Promise<IUserDataManifest | null>;
168169
clear(): Promise<void>;
169-
getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]>;
170-
resolveContent(resource: SyncResource, ref: string): Promise<string | null>;
171-
delete(resource: SyncResource): Promise<void>;
170+
getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]>;
171+
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
172+
delete(resource: ServerResource): Promise<void>;
172173
}
173174

174175
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
@@ -225,7 +226,11 @@ export class UserDataSyncError extends Error {
225226

226227
}
227228

228-
export class UserDataSyncStoreError extends UserDataSyncError { }
229+
export class UserDataSyncStoreError extends UserDataSyncError {
230+
constructor(message: string, code: UserDataSyncErrorCode) {
231+
super(message, code);
232+
}
233+
}
229234

230235
//#endregion
231236

src/vs/platform/userDataSync/common/userDataSyncIpc.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter';
1212
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
1313
import { Disposable } from 'vs/base/common/lifecycle';
1414
import { ILogService } from 'vs/platform/log/common/log';
15+
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
1516

1617
export class UserDataSyncChannel implements IServerChannel {
1718

@@ -163,3 +164,23 @@ export class StorageKeysSyncRegistryChannelClient extends Disposable implements
163164
}
164165

165166
}
167+
168+
export class UserDataSyncMachinesServiceChannel implements IServerChannel {
169+
170+
constructor(private readonly service: IUserDataSyncMachinesService) { }
171+
172+
listen(_: unknown, event: string): Event<any> {
173+
throw new Error(`Event not found: ${event}`);
174+
}
175+
176+
async call(context: any, command: string, args?: any): Promise<any> {
177+
switch (command) {
178+
case 'getMachines': return this.service.getMachines();
179+
case 'updateName': return this.service.updateName(args[0]);
180+
case 'unset': return this.service.unset();
181+
case 'disable': return this.service.disable(args[0]);
182+
}
183+
throw new Error('Invalid call');
184+
}
185+
186+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
7+
import { Disposable } from 'vs/base/common/lifecycle';
8+
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
9+
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
10+
import { IFileService } from 'vs/platform/files/common/files';
11+
import { IStorageService } from 'vs/platform/storage/common/storage';
12+
import { IUserDataSyncStoreService, IUserData, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
13+
import { localize } from 'vs/nls';
14+
import { IProductService } from 'vs/platform/product/common/productService';
15+
16+
interface IMachineData {
17+
id: string;
18+
name: string;
19+
disabled?: boolean;
20+
}
21+
22+
interface IMachinesData {
23+
version: number;
24+
machines: IMachineData[];
25+
}
26+
27+
export type IUserDataSyncMachine = Readonly<IMachineData> & { readonly isCurrent: boolean };
28+
29+
30+
export const IUserDataSyncMachinesService = createDecorator<IUserDataSyncMachinesService>('IUserDataSyncMachinesService');
31+
export interface IUserDataSyncMachinesService {
32+
_serviceBrand: any;
33+
34+
getMachines(): Promise<IUserDataSyncMachine[]>;
35+
updateName(name: string): Promise<void>;
36+
unset(): Promise<void>;
37+
38+
disable(id: string): Promise<void>
39+
}
40+
41+
export class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService {
42+
43+
private static readonly VERSION = 1;
44+
private static readonly RESOURCE = 'machines';
45+
46+
_serviceBrand: any;
47+
48+
private readonly currentMachineIdPromise: Promise<string>;
49+
private userData: IUserData | null = null;
50+
51+
constructor(
52+
@IEnvironmentService environmentService: IEnvironmentService,
53+
@IFileService fileService: IFileService,
54+
@IStorageService storageService: IStorageService,
55+
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
56+
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
57+
@IProductService private readonly productService: IProductService,
58+
) {
59+
super();
60+
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
61+
}
62+
63+
async getMachines(): Promise<IUserDataSyncMachine[]> {
64+
const currentMachineId = await this.currentMachineIdPromise;
65+
const machineData = await this.readMachinesData();
66+
return machineData.machines.map<IUserDataSyncMachine>(machine => ({ ...machine, ...{ isCurrent: machine.id === currentMachineId } }));
67+
}
68+
69+
async updateName(name: string): Promise<void> {
70+
const currentMachineId = await this.currentMachineIdPromise;
71+
const machineData = await this.readMachinesData();
72+
let currentMachine = machineData.machines.find(({ id }) => id === currentMachineId);
73+
if (currentMachine) {
74+
currentMachine.name = name;
75+
} else {
76+
machineData.machines.push({ id: currentMachineId, name });
77+
}
78+
await this.writeMachinesData(machineData);
79+
}
80+
81+
async unset(): Promise<void> {
82+
const currentMachineId = await this.currentMachineIdPromise;
83+
const machineData = await this.readMachinesData();
84+
const updatedMachines = machineData.machines.filter(({ id }) => id !== currentMachineId);
85+
if (updatedMachines.length !== machineData.machines.length) {
86+
machineData.machines = updatedMachines;
87+
await this.writeMachinesData(machineData);
88+
}
89+
}
90+
91+
async disable(machineId: string): Promise<void> {
92+
const machineData = await this.readMachinesData();
93+
const machine = machineData.machines.find(({ id }) => id === machineId);
94+
if (machine) {
95+
machine.disabled = true;
96+
await this.writeMachinesData(machineData);
97+
}
98+
}
99+
100+
private async readMachinesData(): Promise<IMachinesData> {
101+
this.userData = await this.userDataSyncStoreService.read(UserDataSyncMachinesService.RESOURCE, this.userData);
102+
const machinesData = this.parse(this.userData);
103+
if (machinesData.version !== UserDataSyncMachinesService.VERSION) {
104+
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));
105+
}
106+
return machinesData;
107+
}
108+
109+
private async writeMachinesData(machinesData: IMachinesData): Promise<void> {
110+
const content = JSON.stringify(machinesData);
111+
const ref = await this.userDataSyncStoreService.write(UserDataSyncMachinesService.RESOURCE, content, this.userData?.ref || null);
112+
this.userData = { ref, content };
113+
}
114+
115+
private parse(userData: IUserData): IMachinesData {
116+
if (userData.content !== null) {
117+
try {
118+
return JSON.parse(userData.content);
119+
} catch (e) {
120+
this.logService.error(e);
121+
}
122+
}
123+
return {
124+
version: UserDataSyncMachinesService.VERSION,
125+
machines: []
126+
};
127+
}
128+
}

src/vs/platform/userDataSync/common/userDataSyncService.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSy
2020
import { isEqual } from 'vs/base/common/resources';
2121
import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync';
2222
import { Throttler } from 'vs/base/common/async';
23+
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
24+
import { IProductService } from 'vs/platform/product/common/productService';
25+
import { isWeb } from 'vs/base/common/platform';
2326

2427
type SyncClassification = {
2528
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -67,7 +70,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
6770
@IInstantiationService private readonly instantiationService: IInstantiationService,
6871
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
6972
@ITelemetryService private readonly telemetryService: ITelemetryService,
70-
@IStorageService private readonly storageService: IStorageService
73+
@IStorageService private readonly storageService: IStorageService,
74+
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
75+
@IProductService private readonly productService: IProductService
7176
) {
7277
super();
7378
this.syncThrottler = new Throttler();
@@ -131,7 +136,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
131136

132137
// Server has no data but this machine was synced before
133138
if (manifest === null && await this.hasPreviouslySynced()) {
134-
// Sync was turned off from other machine
139+
// Sync was turned off in the cloud
135140
throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
136141
}
137142

@@ -141,6 +146,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
141146
throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
142147
}
143148

149+
const machines = await this.userDataSyncMachinesService.getMachines();
150+
const currentMachine = machines.find(machine => machine.isCurrent);
151+
152+
// Check if sync was turned off from other machine
153+
if (currentMachine?.disabled) {
154+
// Unset the current machine
155+
await this.userDataSyncMachinesService.unset();
156+
// Throw TurnedOff error
157+
throw new UserDataSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
158+
}
159+
144160
for (const synchroniser of this.synchronisers) {
145161
try {
146162
await synchroniser.sync(manifest);
@@ -160,6 +176,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
160176
this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
161177
}
162178

179+
if (!currentMachine) {
180+
// add current machine to sync server
181+
const namePrefix = `${this.productService.nameLong}${isWeb ? ' (Web)' : ''}`;
182+
const nameRegEx = new RegExp(`${namePrefix}\\s#(\\d)`);
183+
184+
let nameIndex = 0;
185+
for (const machine of machines) {
186+
const matches = nameRegEx.exec(machine.name);
187+
const index = matches ? parseInt(matches[1]) : 0;
188+
nameIndex = index > nameIndex ? index : nameIndex;
189+
}
190+
await this.userDataSyncMachinesService.updateName(`${namePrefix} #${nameIndex + 1}`);
191+
}
192+
163193
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
164194
this.updateLastSyncTime();
165195

@@ -247,14 +277,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
247277

248278
async reset(): Promise<void> {
249279
await this.checkEnablement();
250-
await this.resetRemote();
251280
await this.resetLocal();
281+
await this.resetRemote();
252282
}
253283

254284
async resetLocal(): Promise<void> {
255285
await this.checkEnablement();
256286
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
257287
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
288+
await this.userDataSyncMachinesService.unset();
258289
for (const synchroniser of this.synchronisers) {
259290
try {
260291
await synchroniser.resetLocal();

0 commit comments

Comments
 (0)