src/vs/platform/userDataSync/common/userDataSyncMachines.ts
219 LOC · 184 covered · 35 uncovered · 37 ranges · 585 concepts · 8 introducers · 348 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
abstractSynchronizer.ts ×49
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { isAndroid, isChrome, isEdge, isFirefox, isSafari, isWeb, Platform, platform, PlatformToString } from '../../../base/common/platform.js';
import { escapeRegExpCharacters } from '../../../base/common/strings.js';
import { localize } from '../../../nls.js';
import { IEnvironmentService } from '../../environment/common/environment.js';
import { IFileService } from '../../files/common/files.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { IProductService } from '../../product/common/productService.js';
import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
import { IUserData, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncStoreService } from './userDataSync.js';
export interface IMachineData {
id: string;
name: string;
disabled?: boolean;
platform?: string;
}
export interface IMachinesData {
version: number;
machines: IMachineData[];
}
export type IUserDataSyncMachine = Readonly<IMachineData> & { readonly isCurrent: boolean };
export const IUserDataSyncMachinesService = createDecorator<IUserDataSyncMachinesService>('IUserDataSyncMachinesService');
export interface IUserDataSyncMachinesService {
_serviceBrand: undefined;
readonly onDidChange: Event<void>;
getMachines(manifest?: IUserDataManifest): Promise<IUserDataSyncMachine[]>;
addCurrentMachine(manifest?: IUserDataManifest): Promise<void>;
removeCurrentMachine(manifest?: IUserDataManifest): Promise<void>;
renameMachine(machineId: string, name: string): Promise<void>;
setEnablements(enbalements: [string, boolean][]): Promise<void>;
}
const currentMachineNameKey = 'sync.currentMachineName';
const Safari = 'Safari';
const Chrome = 'Chrome';
const Edge = 'Edge';
const Firefox = 'Firefox';
const Android = 'Android';
export function isWebPlatform(platform: string) {
switch (platform) {
case Safari:
case Chrome:
case Edge:
case Firefox:
case Android:
case PlatformToString(Platform.Web):
return true;
}
return false;
}
if (isSafari) { return Safari; }
if (isChrome) { return Chrome; }
if (isEdge) { return Edge; }
if (isFirefox) { return Firefox; }
if (isAndroid) { return Android; }
return PlatformToString(isWeb ? Platform.Web : platform);
}
export class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService {
private static readonly VERSION = 1;
private static readonly RESOURCE = 'machines';
_serviceBrand: undefined;
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
private readonly currentMachineIdPromise: Promise<string>;
private userData: IUserData | null = null;
constructor(
@IFileService fileService: IFileService,
@IStorageService private readonly 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(manifest?: IUserDataManifest): Promise<IUserDataSyncMachine[]> {
const machineData = await this.readMachinesData(manifest);
return machineData.machines.map<IUserDataSyncMachine>(machine => ({ ...machine, ...{ isCurrent: machine.id === currentMachineId } }));
}
async addCurrentMachine(manifest?: IUserDataManifest): Promise<void> {
const machineData = await this.readMachinesData(manifest);
if (!machineData.machines.some(({ id }) => id === currentMachineId)) {
machineData.machines.push({ id: currentMachineId, name: this.computeCurrentMachineName(machineData.machines), platform: getPlatformName() });
await this.writeMachinesData(machineData);
}
}
async removeCurrentMachine(manifest?: IUserDataManifest): Promise<void> {
const machineData = await this.readMachinesData(manifest);
const updatedMachines = machineData.machines.filter(({ id }) => id !== currentMachineId);
if (updatedMachines.length !== machineData.machines.length) {
await this.writeMachinesData(machineData);
}
async renameMachine(machineId: string, name: string, manifest?: IUserDataManifest): Promise<void> {
const machineData = await this.readMachinesData(manifest);
const machine = machineData.machines.find(({ id }) => id === machineId);
if (machine) {
machine.name = name;
await this.writeMachinesData(machineData);
const currentMachineId = await this.currentMachineIdPromise;
if (machineId === currentMachineId) {
this.storageService.store(currentMachineNameKey, name, StorageScope.APPLICATION, StorageTarget.MACHINE);
}
}
}
async setEnablements(enablements: [string, boolean][]): Promise<void> {
for (const [machineId, enabled] of enablements) {
const machine = machineData.machines.find(machine => machine.id === machineId);
if (machine) {
machine.disabled = enabled ? undefined : true;
}
}
await this.writeMachinesData(machineData);
}
private computeCurrentMachineName(machines: IMachineData[]): string {
const previousName = this.storageService.get(currentMachineNameKey, StorageScope.APPLICATION);
userDataSyncMachines.ts ×8
if (previousName) {
if (!machines.some(machine => machine.name === previousName)) {
return previousName;
}
this.storageService.remove(currentMachineNameKey, StorageScope.APPLICATION);
}
const namePrefix = `${this.productService.embedderIdentifier ? `${this.productService.embedderIdentifier} - ` : ''}${getPlatformName()} (${this.productService.nameShort})`;
const nameRegEx = new RegExp(`${escapeRegExpCharacters(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;
}
}
private async readMachinesData(manifest?: IUserDataManifest): Promise<IMachinesData> {
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));
}
}
private async writeMachinesData(machinesData: IMachinesData): Promise<void> {
const ref = await this.userDataSyncStoreService.writeResource(UserDataSyncMachinesService.RESOURCE, content, this.userData?.ref || null);
this.userData = { ref, content };
this._onDidChange.fire();
}
private async readUserData(manifest?: IUserDataManifest): Promise<IUserData> {
const latestRef = manifest && manifest.latest ? manifest.latest[UserDataSyncMachinesService.RESOURCE] : undefined;
// Last time synced resource and latest resource on server are same
if (this.userData.ref === latestRef) {
}
// There is no resource on server and last time it was synced with no resource
if (latestRef === undefined && this.userData.content === null) {
return this.userData;
}
}
return this.userDataSyncStoreService.readResource(UserDataSyncMachinesService.RESOURCE, this.userData);
}
private parse(userData: IUserData): IMachinesData {
return JSON.parse(userData.content);
} catch (e) {
this.logService.error(e);
}
version: UserDataSyncMachinesService.VERSION,
machines: []
};
}