mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 9bc92b48d945144abb405b9e8df05e18accb9148
This commit is contained in:
@@ -353,7 +353,7 @@ export class UserSettings extends Disposable {
|
||||
super();
|
||||
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes);
|
||||
this._register(this.fileService.watch(dirname(this.userSettingsResource)));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire()));
|
||||
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire()));
|
||||
}
|
||||
|
||||
async loadConfiguration(): Promise<ConfigurationModel> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { testFile } from 'vs/base/test/node/utils';
|
||||
|
||||
@@ -55,7 +55,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
// Forward events from provider
|
||||
const providerDisposables = new DisposableStore();
|
||||
providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes))));
|
||||
providerDisposables.add(provider.onDidChangeFile(changes => this._onDidFilesChange.fire(new FileChangesEvent(changes))));
|
||||
providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme })));
|
||||
if (typeof provider.onDidErrorOccur === 'function') {
|
||||
providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error))));
|
||||
@@ -147,11 +147,11 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
//#endregion
|
||||
|
||||
private _onAfterOperation: Emitter<FileOperationEvent> = this._register(new Emitter<FileOperationEvent>());
|
||||
readonly onAfterOperation: Event<FileOperationEvent> = this._onAfterOperation.event;
|
||||
private _onDidRunOperation = this._register(new Emitter<FileOperationEvent>());
|
||||
readonly onDidRunOperation = this._onDidRunOperation.event;
|
||||
|
||||
private _onError: Emitter<Error> = this._register(new Emitter<Error>());
|
||||
readonly onError: Event<Error> = this._onError.event;
|
||||
private _onError = this._register(new Emitter<Error>());
|
||||
readonly onError = this._onError.event;
|
||||
|
||||
//#region File Metadata Resolving
|
||||
|
||||
@@ -299,7 +299,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
const fileStat = await this.writeFile(resource, bufferOrReadableOrStream);
|
||||
|
||||
// events
|
||||
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
|
||||
this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
|
||||
|
||||
return fileStat;
|
||||
}
|
||||
@@ -549,7 +549,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
// resolve and send events
|
||||
const fileStat = await this.resolve(target, { resolveMetadata: true });
|
||||
this._onAfterOperation.fire(new FileOperationEvent(source, mode === 'move' ? FileOperation.MOVE : FileOperation.COPY, fileStat));
|
||||
this._onDidRunOperation.fire(new FileOperationEvent(source, mode === 'move' ? FileOperation.MOVE : FileOperation.COPY, fileStat));
|
||||
|
||||
return fileStat;
|
||||
}
|
||||
@@ -563,7 +563,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
// resolve and send events
|
||||
const fileStat = await this.resolve(target, { resolveMetadata: true });
|
||||
this._onAfterOperation.fire(new FileOperationEvent(source, mode === 'copy' ? FileOperation.COPY : FileOperation.MOVE, fileStat));
|
||||
this._onDidRunOperation.fire(new FileOperationEvent(source, mode === 'copy' ? FileOperation.COPY : FileOperation.MOVE, fileStat));
|
||||
|
||||
return fileStat;
|
||||
}
|
||||
@@ -717,7 +717,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
// events
|
||||
const fileStat = await this.resolve(resource, { resolveMetadata: true });
|
||||
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
|
||||
this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
|
||||
|
||||
return fileStat;
|
||||
}
|
||||
@@ -799,15 +799,15 @@ export class FileService extends Disposable implements IFileService {
|
||||
await provider.delete(resource, { recursive, useTrash });
|
||||
|
||||
// Events
|
||||
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
|
||||
this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region File Watching
|
||||
|
||||
private _onFileChanges: Emitter<FileChangesEvent> = this._register(new Emitter<FileChangesEvent>());
|
||||
readonly onFileChanges: Event<FileChangesEvent> = this._onFileChanges.event;
|
||||
private _onDidFilesChange = this._register(new Emitter<FileChangesEvent>());
|
||||
readonly onDidFilesChange = this._onDidFilesChange.event;
|
||||
|
||||
private activeWatchers = new Map<string, { disposable: IDisposable, count: number }>();
|
||||
|
||||
|
||||
@@ -63,12 +63,12 @@ export interface IFileService {
|
||||
* Allows to listen for file changes. The event will fire for every file within the opened workspace
|
||||
* (if any) as well as all files that have been watched explicitly using the #watch() API.
|
||||
*/
|
||||
readonly onFileChanges: Event<FileChangesEvent>;
|
||||
readonly onDidFilesChange: Event<FileChangesEvent>;
|
||||
|
||||
/**
|
||||
* An event that is fired upon successful completion of a certain file operation.
|
||||
*/
|
||||
readonly onAfterOperation: Event<FileOperationEvent>;
|
||||
readonly onDidRunOperation: Event<FileOperationEvent>;
|
||||
|
||||
/**
|
||||
* Resolve the properties of a file/folder identified by the resource.
|
||||
@@ -471,15 +471,7 @@ export interface IFileChange {
|
||||
|
||||
export class FileChangesEvent {
|
||||
|
||||
private readonly _changes: readonly IFileChange[];
|
||||
|
||||
constructor(changes: readonly IFileChange[]) {
|
||||
this._changes = changes;
|
||||
}
|
||||
|
||||
get changes() {
|
||||
return this._changes;
|
||||
}
|
||||
constructor(public readonly changes: readonly IFileChange[]) { }
|
||||
|
||||
/**
|
||||
* Returns true if this change event contains the provided file with the given change type (if provided). In case of
|
||||
@@ -493,7 +485,7 @@ export class FileChangesEvent {
|
||||
|
||||
const checkForChangeType = !isUndefinedOrNull(type);
|
||||
|
||||
return this._changes.some(change => {
|
||||
return this.changes.some(change => {
|
||||
if (checkForChangeType && change.type !== type) {
|
||||
return false;
|
||||
}
|
||||
@@ -550,11 +542,11 @@ export class FileChangesEvent {
|
||||
}
|
||||
|
||||
private getOfType(type: FileChangeType): IFileChange[] {
|
||||
return this._changes.filter(change => change.type === type);
|
||||
return this.changes.filter(change => change.type === type);
|
||||
}
|
||||
|
||||
private hasType(type: FileChangeType): boolean {
|
||||
return this._changes.some(change => {
|
||||
return this.changes.some(change => {
|
||||
return change.type === type;
|
||||
});
|
||||
}
|
||||
|
||||
228
src/vs/platform/files/common/inMemoryFilesystemProvider.ts
Normal file
228
src/vs/platform/files/common/inMemoryFilesystemProvider.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { FileChangeType, FileType, IWatchOptions, IStat, FileSystemProviderErrorCode, FileSystemProviderError, FileWriteOptions, IFileChange, FileDeleteOptions, FileSystemProviderCapabilities, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
class File implements IStat {
|
||||
|
||||
type: FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
|
||||
name: string;
|
||||
data?: Uint8Array;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = FileType.File;
|
||||
this.ctime = Date.now();
|
||||
this.mtime = Date.now();
|
||||
this.size = 0;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
class Directory implements IStat {
|
||||
|
||||
type: FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
|
||||
name: string;
|
||||
entries: Map<string, File | Directory>;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = FileType.Directory;
|
||||
this.ctime = Date.now();
|
||||
this.mtime = Date.now();
|
||||
this.size = 0;
|
||||
this.name = name;
|
||||
this.entries = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
export type Entry = File | Directory;
|
||||
|
||||
export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
|
||||
|
||||
readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite;
|
||||
readonly onDidChangeCapabilities: Event<void> = Event.None;
|
||||
|
||||
root = new Directory('');
|
||||
|
||||
// --- manage file metadata
|
||||
|
||||
async stat(resource: URI): Promise<IStat> {
|
||||
return this._lookup(resource, false);
|
||||
}
|
||||
|
||||
async readdir(resource: URI): Promise<[string, FileType][]> {
|
||||
const entry = this._lookupAsDirectory(resource, false);
|
||||
let result: [string, FileType][] = [];
|
||||
entry.entries.forEach((child, name) => result.push([name, child.type]));
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- manage file contents
|
||||
|
||||
async readFile(resource: URI): Promise<Uint8Array> {
|
||||
const data = this._lookupAsFile(resource, false).data;
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
throw new FileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
|
||||
}
|
||||
|
||||
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
|
||||
let basename = resources.basename(resource);
|
||||
let parent = this._lookupParentDirectory(resource);
|
||||
let entry = parent.entries.get(basename);
|
||||
if (entry instanceof Directory) {
|
||||
throw new FileSystemProviderError('file is directory', FileSystemProviderErrorCode.FileIsADirectory);
|
||||
}
|
||||
if (!entry && !opts.create) {
|
||||
throw new FileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
|
||||
}
|
||||
if (entry && opts.create && !opts.overwrite) {
|
||||
throw new FileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
|
||||
}
|
||||
if (!entry) {
|
||||
entry = new File(basename);
|
||||
parent.entries.set(basename, entry);
|
||||
this._fireSoon({ type: FileChangeType.ADDED, resource });
|
||||
}
|
||||
entry.mtime = Date.now();
|
||||
entry.size = content.byteLength;
|
||||
entry.data = content;
|
||||
|
||||
this._fireSoon({ type: FileChangeType.UPDATED, resource });
|
||||
}
|
||||
|
||||
// --- manage files/folders
|
||||
|
||||
async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||
if (!opts.overwrite && this._lookup(to, true)) {
|
||||
throw new FileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
|
||||
}
|
||||
|
||||
let entry = this._lookup(from, false);
|
||||
let oldParent = this._lookupParentDirectory(from);
|
||||
|
||||
let newParent = this._lookupParentDirectory(to);
|
||||
let newName = resources.basename(to);
|
||||
|
||||
oldParent.entries.delete(entry.name);
|
||||
entry.name = newName;
|
||||
newParent.entries.set(newName, entry);
|
||||
|
||||
this._fireSoon(
|
||||
{ type: FileChangeType.DELETED, resource: from },
|
||||
{ type: FileChangeType.ADDED, resource: to }
|
||||
);
|
||||
}
|
||||
|
||||
async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
||||
let dirname = resources.dirname(resource);
|
||||
let basename = resources.basename(resource);
|
||||
let parent = this._lookupAsDirectory(dirname, false);
|
||||
if (parent.entries.has(basename)) {
|
||||
parent.entries.delete(basename);
|
||||
parent.mtime = Date.now();
|
||||
parent.size -= 1;
|
||||
this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { resource, type: FileChangeType.DELETED });
|
||||
}
|
||||
}
|
||||
|
||||
async mkdir(resource: URI): Promise<void> {
|
||||
let basename = resources.basename(resource);
|
||||
let dirname = resources.dirname(resource);
|
||||
let parent = this._lookupAsDirectory(dirname, false);
|
||||
|
||||
let entry = new Directory(basename);
|
||||
parent.entries.set(entry.name, entry);
|
||||
parent.mtime = Date.now();
|
||||
parent.size += 1;
|
||||
this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { type: FileChangeType.ADDED, resource });
|
||||
}
|
||||
|
||||
// --- lookup
|
||||
|
||||
private _lookup(uri: URI, silent: false): Entry;
|
||||
private _lookup(uri: URI, silent: boolean): Entry | undefined;
|
||||
private _lookup(uri: URI, silent: boolean): Entry | undefined {
|
||||
let parts = uri.path.split('/');
|
||||
let entry: Entry = this.root;
|
||||
for (const part of parts) {
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
let child: Entry | undefined;
|
||||
if (entry instanceof Directory) {
|
||||
child = entry.entries.get(part);
|
||||
}
|
||||
if (!child) {
|
||||
if (!silent) {
|
||||
throw new FileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
entry = child;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private _lookupAsDirectory(uri: URI, silent: boolean): Directory {
|
||||
let entry = this._lookup(uri, silent);
|
||||
if (entry instanceof Directory) {
|
||||
return entry;
|
||||
}
|
||||
throw new FileSystemProviderError('file not a directory', FileSystemProviderErrorCode.FileNotADirectory);
|
||||
}
|
||||
|
||||
private _lookupAsFile(uri: URI, silent: boolean): File {
|
||||
let entry = this._lookup(uri, silent);
|
||||
if (entry instanceof File) {
|
||||
return entry;
|
||||
}
|
||||
throw new FileSystemProviderError('file is a directory', FileSystemProviderErrorCode.FileIsADirectory);
|
||||
}
|
||||
|
||||
private _lookupParentDirectory(uri: URI): Directory {
|
||||
const dirname = resources.dirname(uri);
|
||||
return this._lookupAsDirectory(dirname, false);
|
||||
}
|
||||
|
||||
// --- manage file events
|
||||
|
||||
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
|
||||
|
||||
private _bufferedChanges: IFileChange[] = [];
|
||||
private _fireSoonHandle?: any;
|
||||
|
||||
watch(resource: URI, opts: IWatchOptions): IDisposable {
|
||||
// ignore, fires for all changes...
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
private _fireSoon(...changes: IFileChange[]): void {
|
||||
this._bufferedChanges.push(...changes);
|
||||
|
||||
if (this._fireSoonHandle) {
|
||||
clearTimeout(this._fireSoonHandle);
|
||||
}
|
||||
|
||||
this._fireSoonHandle = setTimeout(() => {
|
||||
this._onDidChangeFile.fire(this._bufferedChanges);
|
||||
this._bufferedChanges.length = 0;
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class FileWatcher extends Disposable {
|
||||
|
||||
constructor(
|
||||
private path: string,
|
||||
private onFileChanges: (changes: IDiskFileChange[]) => void,
|
||||
private onDidFilesChange: (changes: IDiskFileChange[]) => void,
|
||||
private onLogMessage: (msg: ILogMessage) => void,
|
||||
private verboseLogging: boolean
|
||||
) {
|
||||
@@ -101,7 +101,7 @@ export class FileWatcher extends Disposable {
|
||||
|
||||
// Fire
|
||||
if (normalizedFileChanges.length > 0) {
|
||||
this.onFileChanges(normalizedFileChanges);
|
||||
this.onDidFilesChange(normalizedFileChanges);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
|
||||
@@ -21,7 +21,7 @@ export class FileWatcher extends Disposable {
|
||||
|
||||
constructor(
|
||||
private folders: IWatcherRequest[],
|
||||
private onFileChanges: (changes: IDiskFileChange[]) => void,
|
||||
private onDidFilesChange: (changes: IDiskFileChange[]) => void,
|
||||
private onLogMessage: (msg: ILogMessage) => void,
|
||||
private verboseLogging: boolean,
|
||||
) {
|
||||
@@ -68,7 +68,7 @@ export class FileWatcher extends Disposable {
|
||||
this.service.setVerboseLogging(this.verboseLogging);
|
||||
|
||||
const options = {};
|
||||
this._register(this.service.watch(options)(e => !this.isDisposed && this.onFileChanges(e)));
|
||||
this._register(this.service.watch(options)(e => !this.isDisposed && this.onDidFilesChange(e)));
|
||||
|
||||
this._register(this.service.onLogMessage(m => this.onLogMessage(m)));
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export class FileWatcher extends Disposable {
|
||||
|
||||
constructor(
|
||||
private folders: IWatcherRequest[],
|
||||
private onFileChanges: (changes: IDiskFileChange[]) => void,
|
||||
private onDidFilesChange: (changes: IDiskFileChange[]) => void,
|
||||
private onLogMessage: (msg: ILogMessage) => void,
|
||||
private verboseLogging: boolean,
|
||||
private watcherOptions: IWatcherOptions = {}
|
||||
@@ -67,7 +67,7 @@ export class FileWatcher extends Disposable {
|
||||
|
||||
this.service.setVerboseLogging(this.verboseLogging);
|
||||
|
||||
this._register(this.service.watch(this.watcherOptions)(e => !this.isDisposed && this.onFileChanges(e)));
|
||||
this._register(this.service.watch(this.watcherOptions)(e => !this.isDisposed && this.onDidFilesChange(e)));
|
||||
|
||||
this._register(this.service.onLogMessage(m => this.onLogMessage(m)));
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export class FileWatcher implements IDisposable {
|
||||
|
||||
constructor(
|
||||
folders: { path: string, excludes: string[] }[],
|
||||
private onFileChanges: (changes: IDiskFileChange[]) => void,
|
||||
private onDidFilesChange: (changes: IDiskFileChange[]) => void,
|
||||
private onLogMessage: (msg: ILogMessage) => void,
|
||||
private verboseLogging: boolean
|
||||
) {
|
||||
@@ -62,7 +62,7 @@ export class FileWatcher implements IDisposable {
|
||||
|
||||
// Emit through event emitter
|
||||
if (events.length > 0) {
|
||||
this.onFileChanges(events);
|
||||
this.onDidFilesChange(events);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@ export class FileWatcher implements IDisposable {
|
||||
this.service = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('createFolder', async () => {
|
||||
let event: FileOperationEvent | undefined;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const parent = await service.resolve(URI.file(testDir));
|
||||
|
||||
@@ -185,7 +185,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('createFolder: creating multiple folders at once', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
|
||||
const parent = await service.resolve(URI.file(testDir));
|
||||
@@ -460,7 +460,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('deleteFile', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const resource = URI.file(join(testDir, 'deep', 'conway.js'));
|
||||
const source = await service.resolve(resource);
|
||||
@@ -496,7 +496,7 @@ suite('Disk File Service', function () {
|
||||
const source = await service.resolve(link);
|
||||
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
await service.del(source.resource);
|
||||
|
||||
@@ -519,7 +519,7 @@ suite('Disk File Service', function () {
|
||||
await symlink(target.fsPath, link.fsPath);
|
||||
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
await service.del(link);
|
||||
|
||||
@@ -532,7 +532,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('deleteFolder (recursive)', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const resource = URI.file(join(testDir, 'deep'));
|
||||
const source = await service.resolve(resource);
|
||||
@@ -561,7 +561,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'index.html'));
|
||||
const sourceContents = readFileSync(source.fsPath);
|
||||
@@ -641,7 +641,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise<void> {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, sourceFile));
|
||||
const sourceContents = readFileSync(source.fsPath);
|
||||
@@ -665,7 +665,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - multi folder', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
|
||||
const renameToPath = join(...multiFolderPaths, 'other.html');
|
||||
@@ -684,7 +684,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - directory', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'deep'));
|
||||
|
||||
@@ -728,7 +728,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
async function testMoveFolderAcrossProviders(): Promise<void> {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'deep'));
|
||||
const sourceChildren = readdirSync(source.fsPath);
|
||||
@@ -753,7 +753,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - MIX CASE', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
assert.ok(source.size > 0);
|
||||
@@ -774,7 +774,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - same file', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
assert.ok(source.size > 0);
|
||||
@@ -794,7 +794,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - same file #2', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
assert.ok(source.size > 0);
|
||||
@@ -817,7 +817,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - source parent of target', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
let source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
const originalSize = source.size;
|
||||
@@ -839,7 +839,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('move - FILE_MOVE_CONFLICT', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
let source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
const originalSize = source.size;
|
||||
@@ -863,7 +863,7 @@ suite('Disk File Service', function () {
|
||||
let createEvent: FileOperationEvent;
|
||||
let moveEvent: FileOperationEvent;
|
||||
let deleteEvent: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => {
|
||||
disposables.add(service.onDidRunOperation(e => {
|
||||
if (e.operation === FileOperation.CREATE) {
|
||||
createEvent = e;
|
||||
} else if (e.operation === FileOperation.DELETE) {
|
||||
@@ -927,7 +927,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
async function doTestCopy(sourceName: string = 'index.html') {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, sourceName)));
|
||||
const target = URI.file(join(testDir, 'other.html'));
|
||||
@@ -952,7 +952,7 @@ suite('Disk File Service', function () {
|
||||
let createEvent: FileOperationEvent;
|
||||
let copyEvent: FileOperationEvent;
|
||||
let deleteEvent: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => {
|
||||
disposables.add(service.onDidRunOperation(e => {
|
||||
if (e.operation === FileOperation.CREATE) {
|
||||
createEvent = e;
|
||||
} else if (e.operation === FileOperation.DELETE) {
|
||||
@@ -1057,7 +1057,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('copy - same file', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
assert.ok(source.size > 0);
|
||||
@@ -1077,7 +1077,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('copy - same file #2', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true });
|
||||
assert.ok(source.size > 0);
|
||||
@@ -1567,7 +1567,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<void> {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const contents = 'Hello World';
|
||||
const resource = URI.file(join(testDir, 'test.txt'));
|
||||
@@ -1600,7 +1600,7 @@ suite('Disk File Service', function () {
|
||||
|
||||
test('createFile (allows to overwrite existing)', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
|
||||
const contents = 'Hello World';
|
||||
const resource = URI.file(join(testDir, 'test.txt'));
|
||||
@@ -2152,7 +2152,7 @@ suite('Disk File Service', function () {
|
||||
return event.changes.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n');
|
||||
}
|
||||
|
||||
const listenerDisposable = service.onFileChanges(event => {
|
||||
const listenerDisposable = service.onDidFilesChange(event => {
|
||||
watcherDisposable.dispose();
|
||||
listenerDisposable.dispose();
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ function toFileChangesEvent(changes: IDiskFileChange[]): FileChangesEvent {
|
||||
}
|
||||
|
||||
class TestFileWatcher {
|
||||
private readonly _onFileChanges: Emitter<FileChangesEvent>;
|
||||
private readonly _onDidFilesChange: Emitter<FileChangesEvent>;
|
||||
|
||||
constructor() {
|
||||
this._onFileChanges = new Emitter<FileChangesEvent>();
|
||||
this._onDidFilesChange = new Emitter<FileChangesEvent>();
|
||||
}
|
||||
|
||||
get onFileChanges(): Event<FileChangesEvent> {
|
||||
return this._onFileChanges.event;
|
||||
get onDidFilesChange(): Event<FileChangesEvent> {
|
||||
return this._onDidFilesChange.event;
|
||||
}
|
||||
|
||||
report(changes: IDiskFileChange[]): void {
|
||||
@@ -36,7 +36,7 @@ class TestFileWatcher {
|
||||
|
||||
// Emit through event emitter
|
||||
if (normalizedEvents.length > 0) {
|
||||
this._onFileChanges.fire(toFileChangesEvent(normalizedEvents));
|
||||
this._onDidFilesChange.fire(toFileChangesEvent(normalizedEvents));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ suite('Normalizer', () => {
|
||||
{ path: deleted.fsPath, type: FileChangeType.DELETED },
|
||||
];
|
||||
|
||||
watch.onFileChanges(e => {
|
||||
watch.onDidFilesChange(e => {
|
||||
assert.ok(e);
|
||||
assert.equal(e.changes.length, 3);
|
||||
assert.ok(e.contains(added, FileChangeType.ADDED));
|
||||
@@ -101,7 +101,7 @@ suite('Normalizer', () => {
|
||||
{ path: updatedFile.fsPath, type: FileChangeType.UPDATED }
|
||||
];
|
||||
|
||||
watch.onFileChanges(e => {
|
||||
watch.onDidFilesChange(e => {
|
||||
assert.ok(e);
|
||||
assert.equal(e.changes.length, 5);
|
||||
|
||||
@@ -131,7 +131,7 @@ suite('Normalizer', () => {
|
||||
{ path: unrelated.fsPath, type: FileChangeType.UPDATED },
|
||||
];
|
||||
|
||||
watch.onFileChanges(e => {
|
||||
watch.onDidFilesChange(e => {
|
||||
assert.ok(e);
|
||||
assert.equal(e.changes.length, 1);
|
||||
|
||||
@@ -156,7 +156,7 @@ suite('Normalizer', () => {
|
||||
{ path: unrelated.fsPath, type: FileChangeType.UPDATED },
|
||||
];
|
||||
|
||||
watch.onFileChanges(e => {
|
||||
watch.onDidFilesChange(e => {
|
||||
assert.ok(e);
|
||||
assert.equal(e.changes.length, 2);
|
||||
|
||||
@@ -182,7 +182,7 @@ suite('Normalizer', () => {
|
||||
{ path: unrelated.fsPath, type: FileChangeType.UPDATED },
|
||||
];
|
||||
|
||||
watch.onFileChanges(e => {
|
||||
watch.onDidFilesChange(e => {
|
||||
assert.ok(e);
|
||||
assert.equal(e.changes.length, 2);
|
||||
|
||||
@@ -211,7 +211,7 @@ suite('Normalizer', () => {
|
||||
{ path: updated.fsPath, type: FileChangeType.DELETED }
|
||||
];
|
||||
|
||||
watch.onFileChanges(e => {
|
||||
watch.onDidFilesChange(e => {
|
||||
assert.ok(e);
|
||||
assert.equal(e.changes.length, 2);
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ interface IServiceMock<T> {
|
||||
service: any;
|
||||
}
|
||||
|
||||
const isSinonSpyLike = (fn: Function): fn is sinon.SinonSpy => fn && 'callCount' in fn;
|
||||
|
||||
export class TestInstantiationService extends InstantiationService {
|
||||
|
||||
private _servciesMap: Map<ServiceIdentifier<any>, any>;
|
||||
@@ -37,10 +39,10 @@ export class TestInstantiationService extends InstantiationService {
|
||||
|
||||
public stub<T>(service: ServiceIdentifier<T>, ctor: Function): T;
|
||||
public stub<T>(service: ServiceIdentifier<T>, obj: Partial<T>): T;
|
||||
public stub<T>(service: ServiceIdentifier<T>, ctor: Function, property: string, value: any): sinon.SinonStub;
|
||||
public stub<T>(service: ServiceIdentifier<T>, obj: Partial<T>, property: string, value: any): sinon.SinonStub;
|
||||
public stub<T>(service: ServiceIdentifier<T>, property: string, value: any): sinon.SinonStub;
|
||||
public stub<T>(serviceIdentifier: ServiceIdentifier<T>, arg2: any, arg3?: string, arg4?: any): sinon.SinonStub {
|
||||
public stub<T, V>(service: ServiceIdentifier<T>, ctor: Function, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;
|
||||
public stub<T, V>(service: ServiceIdentifier<T>, obj: Partial<T>, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;
|
||||
public stub<T, V>(service: ServiceIdentifier<T>, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;
|
||||
public stub<T>(serviceIdentifier: ServiceIdentifier<T>, arg2: any, arg3?: string, arg4?: any): sinon.SinonStub | sinon.SinonSpy {
|
||||
let service = typeof arg2 !== 'string' ? arg2 : undefined;
|
||||
let serviceMock: IServiceMock<any> = { id: serviceIdentifier, service: service };
|
||||
let property = typeof arg2 === 'string' ? arg2 : arg3;
|
||||
@@ -53,9 +55,11 @@ export class TestInstantiationService extends InstantiationService {
|
||||
stubObject[property].restore();
|
||||
}
|
||||
if (typeof value === 'function') {
|
||||
stubObject[property] = value;
|
||||
const spy = isSinonSpyLike(value) ? value : sinon.spy(value);
|
||||
stubObject[property] = spy;
|
||||
return spy;
|
||||
} else {
|
||||
let stub = value ? sinon.stub().returns(value) : sinon.stub();
|
||||
const stub = value ? sinon.stub().returns(value) : sinon.stub();
|
||||
stubObject[property] = stub;
|
||||
return stub;
|
||||
}
|
||||
@@ -67,9 +71,9 @@ export class TestInstantiationService extends InstantiationService {
|
||||
}
|
||||
|
||||
public stubPromise<T>(service?: ServiceIdentifier<T>, fnProperty?: string, value?: any): T | sinon.SinonStub;
|
||||
public stubPromise<T>(service?: ServiceIdentifier<T>, ctor?: any, fnProperty?: string, value?: any): sinon.SinonStub;
|
||||
public stubPromise<T>(service?: ServiceIdentifier<T>, obj?: any, fnProperty?: string, value?: any): sinon.SinonStub;
|
||||
public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub {
|
||||
public stubPromise<T, V>(service?: ServiceIdentifier<T>, ctor?: any, fnProperty?: string, value?: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;
|
||||
public stubPromise<T, V>(service?: ServiceIdentifier<T>, obj?: any, fnProperty?: string, value?: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;
|
||||
public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub | sinon.SinonSpy {
|
||||
arg3 = typeof arg2 === 'string' ? Promise.resolve(arg3) : arg3;
|
||||
arg4 = typeof arg2 !== 'string' && typeof arg3 === 'string' ? Promise.resolve(arg4) : arg4;
|
||||
return this.stub(arg1, arg2, arg3, arg4);
|
||||
@@ -124,4 +128,4 @@ export class TestInstantiationService extends InstantiationService {
|
||||
interface SinonOptions {
|
||||
mock?: boolean;
|
||||
stub?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +135,15 @@ export namespace IMarkerData {
|
||||
export function makeKeyOptionalMessage(markerData: IMarkerData, useMessage: boolean): string {
|
||||
let result: string[] = [emptyString];
|
||||
if (markerData.source) {
|
||||
result.push(markerData.source.replace('¦', '\¦'));
|
||||
result.push(markerData.source.replace('¦', '\\¦'));
|
||||
} else {
|
||||
result.push(emptyString);
|
||||
}
|
||||
if (markerData.code) {
|
||||
if (typeof markerData.code === 'string') {
|
||||
result.push(markerData.code.replace('¦', '\¦'));
|
||||
result.push(markerData.code.replace('¦', '\\¦'));
|
||||
} else {
|
||||
result.push(markerData.code.value.replace('¦', '\¦'));
|
||||
result.push(markerData.code.value.replace('¦', '\\¦'));
|
||||
}
|
||||
} else {
|
||||
result.push(emptyString);
|
||||
@@ -157,7 +157,7 @@ export namespace IMarkerData {
|
||||
// Modifed to not include the message as part of the marker key to work around
|
||||
// https://github.com/microsoft/vscode/issues/77475
|
||||
if (markerData.message && useMessage) {
|
||||
result.push(markerData.message.replace('¦', '\¦'));
|
||||
result.push(markerData.message.replace('¦', '\\¦'));
|
||||
} else {
|
||||
result.push(emptyString);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import BaseSeverity from 'vs/base/common/severity';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export import Severity = BaseSeverity;
|
||||
@@ -318,16 +318,14 @@ export class NoOpNotification implements INotificationHandle {
|
||||
|
||||
readonly progress = new NoOpProgress();
|
||||
|
||||
private readonly _onDidClose: Emitter<void> = new Emitter();
|
||||
readonly onDidClose: Event<void> = this._onDidClose.event;
|
||||
readonly onDidClose = Event.None;
|
||||
readonly onDidChangeVisibility = Event.None;
|
||||
|
||||
updateSeverity(severity: Severity): void { }
|
||||
updateMessage(message: NotificationMessage): void { }
|
||||
updateActions(actions?: INotificationActions): void { }
|
||||
|
||||
close(): void {
|
||||
this._onDidClose.dispose();
|
||||
}
|
||||
close(): void { }
|
||||
}
|
||||
|
||||
export class NoOpProgress implements INotificationProgress {
|
||||
|
||||
@@ -17,7 +17,11 @@ export interface IProgressService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
withProgress<R = any>(options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: (choice?: number) => void): Promise<R>;
|
||||
withProgress<R = any>(
|
||||
options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
|
||||
task: (progress: IProgress<IProgressStep>) => Promise<R>,
|
||||
onDidCancel?: (choice?: number) => void
|
||||
): Promise<R>;
|
||||
}
|
||||
|
||||
export interface IProgressIndicator {
|
||||
@@ -45,19 +49,19 @@ export const enum ProgressLocation {
|
||||
}
|
||||
|
||||
export interface IProgressOptions {
|
||||
location: ProgressLocation | string;
|
||||
title?: string;
|
||||
source?: string;
|
||||
total?: number;
|
||||
cancellable?: boolean;
|
||||
buttons?: string[];
|
||||
readonly location: ProgressLocation | string;
|
||||
readonly title?: string;
|
||||
readonly source?: string;
|
||||
readonly total?: number;
|
||||
readonly cancellable?: boolean;
|
||||
readonly buttons?: string[];
|
||||
}
|
||||
|
||||
export interface IProgressNotificationOptions extends IProgressOptions {
|
||||
readonly location: ProgressLocation.Notification;
|
||||
readonly primaryActions?: ReadonlyArray<IAction>;
|
||||
readonly secondaryActions?: ReadonlyArray<IAction>;
|
||||
delay?: number;
|
||||
readonly delay?: number;
|
||||
}
|
||||
|
||||
export interface IProgressWindowOptions extends IProgressOptions {
|
||||
@@ -66,8 +70,8 @@ export interface IProgressWindowOptions extends IProgressOptions {
|
||||
}
|
||||
|
||||
export interface IProgressCompositeOptions extends IProgressOptions {
|
||||
location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string;
|
||||
delay?: number;
|
||||
readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string;
|
||||
readonly delay?: number;
|
||||
}
|
||||
|
||||
export interface IProgressStep {
|
||||
@@ -96,20 +100,14 @@ export interface IProgress<T> {
|
||||
|
||||
export class Progress<T> implements IProgress<T> {
|
||||
|
||||
private _callback: (data: T) => void;
|
||||
private _value?: T;
|
||||
get value(): T | undefined { return this._value; }
|
||||
|
||||
constructor(callback: (data: T) => void) {
|
||||
this._callback = callback;
|
||||
}
|
||||
|
||||
get value(): T | undefined {
|
||||
return this._value;
|
||||
}
|
||||
constructor(private callback: (data: T) => void) { }
|
||||
|
||||
report(item: T) {
|
||||
this._value = item;
|
||||
this._callback(this._value);
|
||||
this.callback(this._value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase
|
||||
this.isWatching = true;
|
||||
|
||||
this._register(this.fileService.watch(this.file));
|
||||
this._register(this.fileService.onFileChanges(e => {
|
||||
this._register(this.fileService.onDidFilesChange(e => {
|
||||
if (document.hasFocus()) {
|
||||
return; // optimization: ignore changes from ourselves by checking for focus
|
||||
}
|
||||
|
||||
@@ -428,6 +428,10 @@ export const minimapError = registerColor('minimap.errorHighlight', { dark: new
|
||||
export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.'));
|
||||
export const minimapBackground = registerColor('minimap.background', { dark: null, light: null, hc: null }, nls.localize('minimapBackground', "Minimap background color."));
|
||||
|
||||
export const minimapSliderBackground = registerColor('minimapSlider.background', { light: transparent(scrollbarSliderBackground, 0.5), dark: transparent(scrollbarSliderBackground, 0.5), hc: transparent(scrollbarSliderBackground, 0.5) }, nls.localize('minimapSliderBackground', "Minimap slider background color."));
|
||||
export const minimapSliderHoverBackground = registerColor('minimapSlider.hoverBackground', { light: transparent(scrollbarSliderHoverBackground, 0.5), dark: transparent(scrollbarSliderHoverBackground, 0.5), hc: transparent(scrollbarSliderHoverBackground, 0.5) }, nls.localize('minimapSliderHoverBackground', "Minimap slider background color when hovering."));
|
||||
export const minimapSliderActiveBackground = registerColor('minimapSlider.activeBackground', { light: transparent(scrollbarSliderActiveBackground, 0.5), dark: transparent(scrollbarSliderActiveBackground, 0.5), hc: transparent(scrollbarSliderActiveBackground, 0.5) }, nls.localize('minimapSliderActiveBackground', "Minimap slider background color when clicked on."));
|
||||
|
||||
export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon."));
|
||||
export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground }, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon."));
|
||||
export const problemsInfoIconForeground = registerColor('problemsInfoIcon.foreground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoForeground }, nls.localize('problemsInfoIconForeground', "The color used for the problems info icon."));
|
||||
|
||||
73
src/vs/platform/undoRedo/common/undoRedo.ts
Normal file
73
src/vs/platform/undoRedo/common/undoRedo.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const IUndoRedoService = createDecorator<IUndoRedoService>('undoRedoService');
|
||||
|
||||
export interface IUndoRedoContext {
|
||||
replaceCurrentElement(others: IUndoRedoElement[]): void;
|
||||
}
|
||||
|
||||
export interface IUndoRedoElement {
|
||||
/**
|
||||
* None, one or multiple resources that this undo/redo element impacts.
|
||||
*/
|
||||
readonly resources: URI[];
|
||||
|
||||
/**
|
||||
* The label of the undo/redo element.
|
||||
*/
|
||||
readonly label: string;
|
||||
|
||||
/**
|
||||
* Undo.
|
||||
* Will always be called before `redo`.
|
||||
* Can be called multiple times.
|
||||
* e.g. `undo` -> `redo` -> `undo` -> `redo`
|
||||
*/
|
||||
undo(ctx: IUndoRedoContext): void;
|
||||
|
||||
/**
|
||||
* Redo.
|
||||
* Will always be called after `undo`.
|
||||
* Can be called multiple times.
|
||||
* e.g. `undo` -> `redo` -> `undo` -> `redo`
|
||||
*/
|
||||
redo(ctx: IUndoRedoContext): void;
|
||||
|
||||
/**
|
||||
* Invalidate the edits concerning `resource`.
|
||||
* i.e. the undo/redo stack for that particular resource has been destroyed.
|
||||
*/
|
||||
invalidate(resource: URI): boolean;
|
||||
}
|
||||
|
||||
export interface IUndoRedoService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Add a new element to the `undo` stack.
|
||||
* This will destroy the `redo` stack.
|
||||
*/
|
||||
pushElement(element: IUndoRedoElement): void;
|
||||
|
||||
/**
|
||||
* Get the last pushed element. If the last pushed element has been undone, returns null.
|
||||
*/
|
||||
getLastElement(resource: URI): IUndoRedoElement | null;
|
||||
|
||||
/**
|
||||
* Remove elements that target `resource`.
|
||||
*/
|
||||
removeElements(resource: URI): void;
|
||||
|
||||
canUndo(resource: URI): boolean;
|
||||
undo(resource: URI): void;
|
||||
|
||||
redo(resource: URI): void;
|
||||
canRedo(resource: URI): boolean;
|
||||
}
|
||||
241
src/vs/platform/undoRedo/common/undoRedoService.ts
Normal file
241
src/vs/platform/undoRedo/common/undoRedoService.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
class StackElement {
|
||||
public readonly actual: IUndoRedoElement;
|
||||
public readonly label: string;
|
||||
public readonly resources: URI[];
|
||||
public readonly strResources: string[];
|
||||
|
||||
constructor(actual: IUndoRedoElement) {
|
||||
this.actual = actual;
|
||||
this.label = actual.label;
|
||||
this.resources = actual.resources;
|
||||
this.strResources = this.resources.map(resource => uriGetComparisonKey(resource));
|
||||
}
|
||||
|
||||
public invalidate(resource: URI): void {
|
||||
if (this.resources.length > 1) {
|
||||
this.actual.invalidate(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ResourceEditStack {
|
||||
public resource: URI;
|
||||
public past: StackElement[];
|
||||
public future: StackElement[];
|
||||
|
||||
constructor(resource: URI) {
|
||||
this.resource = resource;
|
||||
this.past = [];
|
||||
this.future = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class UndoRedoService implements IUndoRedoService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _editStacks: Map<string, ResourceEditStack>;
|
||||
|
||||
constructor() {
|
||||
this._editStacks = new Map<string, ResourceEditStack>();
|
||||
}
|
||||
|
||||
public pushElement(_element: IUndoRedoElement): void {
|
||||
const element = new StackElement(_element);
|
||||
for (let i = 0, len = element.resources.length; i < len; i++) {
|
||||
const resource = element.resources[i];
|
||||
const strResource = element.strResources[i];
|
||||
|
||||
let editStack: ResourceEditStack;
|
||||
if (this._editStacks.has(strResource)) {
|
||||
editStack = this._editStacks.get(strResource)!;
|
||||
} else {
|
||||
editStack = new ResourceEditStack(resource);
|
||||
this._editStacks.set(strResource, editStack);
|
||||
}
|
||||
|
||||
// remove the future
|
||||
for (const futureElement of editStack.future) {
|
||||
futureElement.invalidate(resource);
|
||||
}
|
||||
editStack.future = [];
|
||||
editStack.past.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
public getLastElement(resource: URI): IUndoRedoElement | null {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
if (editStack.future.length > 0) {
|
||||
return null;
|
||||
}
|
||||
if (editStack.past.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return editStack.past[editStack.past.length - 1].actual;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public removeElements(resource: URI): void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (const pastElement of editStack.past) {
|
||||
pastElement.invalidate(resource);
|
||||
}
|
||||
for (const futureElement of editStack.future) {
|
||||
futureElement.invalidate(resource);
|
||||
}
|
||||
this._editStacks.delete(strResource);
|
||||
}
|
||||
}
|
||||
|
||||
public canUndo(resource: URI): boolean {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
return (editStack.past.length > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public undo(resource: URI): void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (!this._editStacks.has(strResource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
if (editStack.past.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = editStack.past[editStack.past.length - 1];
|
||||
|
||||
let replaceCurrentElement: IUndoRedoElement[] | null = null as IUndoRedoElement[] | null;
|
||||
try {
|
||||
element.actual.undo({
|
||||
replaceCurrentElement: (others: IUndoRedoElement[]): void => {
|
||||
replaceCurrentElement = others;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
if (replaceCurrentElement === null) {
|
||||
// regular case
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const replaceCurrentElementMap = new Map<string, StackElement>();
|
||||
for (const _replace of replaceCurrentElement) {
|
||||
const replace = new StackElement(_replace);
|
||||
for (const strResource of replace.strResources) {
|
||||
replaceCurrentElementMap.set(strResource, replace);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = element.strResources.length; i < len; i++) {
|
||||
const strResource = element.strResources[i];
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (let j = editStack.past.length - 1; j >= 0; j--) {
|
||||
if (editStack.past[j] === element) {
|
||||
if (replaceCurrentElementMap.has(strResource)) {
|
||||
editStack.past[j] = replaceCurrentElementMap.get(strResource)!;
|
||||
} else {
|
||||
editStack.past.splice(j, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public canRedo(resource: URI): boolean {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
return (editStack.future.length > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
redo(resource: URI): void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (!this._editStacks.has(strResource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
if (editStack.future.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = editStack.future[editStack.future.length - 1];
|
||||
|
||||
let replaceCurrentElement: IUndoRedoElement[] | null = null as IUndoRedoElement[] | null;
|
||||
try {
|
||||
element.actual.redo({
|
||||
replaceCurrentElement: (others: IUndoRedoElement[]): void => {
|
||||
replaceCurrentElement = others;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
if (replaceCurrentElement === null) {
|
||||
// regular case
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const replaceCurrentElementMap = new Map<string, StackElement>();
|
||||
for (const _replace of replaceCurrentElement) {
|
||||
const replace = new StackElement(_replace);
|
||||
for (const strResource of replace.strResources) {
|
||||
replaceCurrentElementMap.set(strResource, replace);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = element.strResources.length; i < len; i++) {
|
||||
const strResource = element.strResources[i];
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (let j = editStack.future.length - 1; j >= 0; j--) {
|
||||
if (editStack.future[j] === element) {
|
||||
if (replaceCurrentElementMap.has(strResource)) {
|
||||
editStack.future[j] = replaceCurrentElementMap.get(strResource)!;
|
||||
} else {
|
||||
editStack.future.splice(j, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
|
||||
) {
|
||||
super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService);
|
||||
this._register(this.fileService.watch(dirname(file)));
|
||||
this._register(this.fileService.onFileChanges(e => this.onFileChanges(e)));
|
||||
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
|
||||
@@ -39,7 +39,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
) {
|
||||
super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService);
|
||||
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
|
||||
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
|
||||
}
|
||||
|
||||
async pull(): Promise<void> {
|
||||
|
||||
@@ -14,6 +14,9 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut
|
||||
private _onDidChangeToken: Emitter<string | undefined> = this._register(new Emitter<string | undefined>());
|
||||
readonly onDidChangeToken: Event<string | undefined> = this._onDidChangeToken.event;
|
||||
|
||||
private _onTokenFailed: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onTokenFailed: Event<void> = this._onTokenFailed.event;
|
||||
|
||||
private _token: string | undefined;
|
||||
|
||||
constructor() {
|
||||
@@ -30,4 +33,8 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut
|
||||
this._onDidChangeToken.fire(token);
|
||||
}
|
||||
}
|
||||
|
||||
sendTokenFailed(): void {
|
||||
this._onTokenFailed.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,13 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
return this.sync(loop, auto);
|
||||
}
|
||||
}
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.SessionExpired) {
|
||||
this.logService.info('Auto Sync: Cloud has new session');
|
||||
this.logService.info('Auto Sync: Resetting the local sync state.');
|
||||
await this.userDataSyncService.resetLocal();
|
||||
this.logService.info('Auto Sync: Completed resetting the local sync state.');
|
||||
return this.sync(loop, auto);
|
||||
}
|
||||
this.logService.error(e);
|
||||
this.successiveFailures++;
|
||||
this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown });
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { isEqual, joinPath } from 'vs/base/common/resources';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
|
||||
@@ -120,23 +120,27 @@ export interface IUserData {
|
||||
}
|
||||
|
||||
export interface IUserDataSyncStore {
|
||||
url: string;
|
||||
url: URI;
|
||||
authenticationProviderId: string;
|
||||
}
|
||||
|
||||
export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined {
|
||||
const value = configurationService.getValue<IUserDataSyncStore>(CONFIGURATION_SYNC_STORE_KEY);
|
||||
return value && value.url && value.authenticationProviderId ? value : undefined;
|
||||
const value = configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY);
|
||||
if (value && value.url && value.authenticationProviderId) {
|
||||
return {
|
||||
url: joinPath(URI.parse(value.url), 'v1'),
|
||||
authenticationProviderId: value.authenticationProviderId
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const ALL_RESOURCE_KEYS: ResourceKey[] = ['settings', 'keybindings', 'extensions', 'globalState'];
|
||||
export type ResourceKey = 'settings' | 'keybindings' | 'extensions' | 'globalState';
|
||||
|
||||
export interface IUserDataManifest {
|
||||
settings: string;
|
||||
keybindings: string;
|
||||
extensions: string;
|
||||
globalState: string;
|
||||
latest?: Record<ResourceKey, string>
|
||||
session: string;
|
||||
}
|
||||
|
||||
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
|
||||
@@ -162,6 +166,7 @@ export enum UserDataSyncErrorCode {
|
||||
TooLarge = 'TooLarge',
|
||||
NoRef = 'NoRef',
|
||||
TurnedOff = 'TurnedOff',
|
||||
SessionExpired = 'SessionExpired',
|
||||
|
||||
// Local Errors
|
||||
LocalPreconditionFailed = 'LocalPreconditionFailed',
|
||||
@@ -303,9 +308,11 @@ export interface IUserDataAuthTokenService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
readonly onDidChangeToken: Event<string | undefined>;
|
||||
readonly onTokenFailed: Event<void>;
|
||||
|
||||
getToken(): Promise<string | undefined>;
|
||||
setToken(accessToken: string | undefined): Promise<void>;
|
||||
sendTokenFailed(): void;
|
||||
}
|
||||
|
||||
export const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('IUserDataSyncLogService');
|
||||
|
||||
@@ -96,6 +96,7 @@ export class UserDataAuthTokenServiceChannel implements IServerChannel {
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onDidChangeToken': return this.service.onDidChangeToken;
|
||||
case 'onTokenFailed': return this.service.onTokenFailed;
|
||||
}
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,14 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
|
||||
type SyncErrorClassification = {
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
const SESSION_ID_KEY = 'sync.sessionId';
|
||||
|
||||
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
@@ -48,6 +51,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser));
|
||||
@@ -96,7 +100,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
}
|
||||
|
||||
const manifest = await this.userDataSyncStoreService.manifest();
|
||||
let manifest = await this.userDataSyncStoreService.manifest();
|
||||
|
||||
// Server has no data but this machine was synced before
|
||||
if (manifest === null && await this.hasPreviouslySynced()) {
|
||||
@@ -104,14 +108,30 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
|
||||
}
|
||||
|
||||
const sessionId = this.storageService.get(SESSION_ID_KEY, StorageScope.GLOBAL);
|
||||
// Server session is different from client session
|
||||
if (sessionId && manifest && sessionId !== manifest.session) {
|
||||
throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
|
||||
}
|
||||
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
try {
|
||||
await synchroniser.sync(manifest ? manifest[synchroniser.resourceKey] : undefined);
|
||||
await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined);
|
||||
} catch (e) {
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
}
|
||||
}
|
||||
|
||||
// After syncing, get the manifest if it was not available before
|
||||
if (manifest === null) {
|
||||
manifest = await this.userDataSyncStoreService.manifest();
|
||||
}
|
||||
|
||||
// Update local session id
|
||||
if (manifest && manifest.session !== sessionId) {
|
||||
this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
|
||||
|
||||
} finally {
|
||||
@@ -140,26 +160,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return synchroniser.accept(content);
|
||||
}
|
||||
|
||||
private async hasPreviouslySynced(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasPreviouslySynced()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async hasLocalData(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasLocalData()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
@@ -189,6 +189,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
|
||||
async resetLocal(): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
try {
|
||||
synchroniser.resetLocal();
|
||||
@@ -199,6 +200,26 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
}
|
||||
|
||||
private async hasPreviouslySynced(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasPreviouslySynced()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async hasLocalData(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasLocalData()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async resetRemote(): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
try {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { Disposable, } from 'vs/base/common/lifecycle';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request';
|
||||
@@ -33,7 +32,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
throw new Error('No settings sync store url configured.');
|
||||
}
|
||||
|
||||
const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key, 'latest').toString();
|
||||
const url = joinPath(this.userDataSyncStore.url, 'resource', key, 'latest').toString();
|
||||
const headers: IHeaders = {};
|
||||
// Disable caching as they are cached by synchronisers
|
||||
headers['Cache-Control'] = 'no-cache';
|
||||
@@ -65,7 +64,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
throw new Error('No settings sync store url configured.');
|
||||
}
|
||||
|
||||
const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key).toString();
|
||||
const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString();
|
||||
const headers: IHeaders = { 'Content-Type': 'text/plain' };
|
||||
if (ref) {
|
||||
headers['If-Match'] = ref;
|
||||
@@ -89,7 +88,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
throw new Error('No settings sync store url configured.');
|
||||
}
|
||||
|
||||
const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', 'latest').toString();
|
||||
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);
|
||||
@@ -105,7 +104,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
throw new Error('No settings sync store url configured.');
|
||||
}
|
||||
|
||||
const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource').toString();
|
||||
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);
|
||||
@@ -134,6 +133,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 401) {
|
||||
this.authTokenService.sendTokenFailed();
|
||||
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized, source);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import type { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { TestUserDataSyncUtilService } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
|
||||
|
||||
suite('KeybindingsMerge - No Conflicts', () => {
|
||||
|
||||
@@ -613,7 +609,7 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
});
|
||||
|
||||
async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) {
|
||||
const userDataSyncUtilService = new MockUserDataSyncUtilService();
|
||||
const userDataSyncUtilService = new TestUserDataSyncUtilService();
|
||||
const formattingOptions = await userDataSyncUtilService.resolveFormattingOptions();
|
||||
return merge(localContent, remoteContent, baseContent, formattingOptions, userDataSyncUtilService);
|
||||
}
|
||||
@@ -621,22 +617,3 @@ async function mergeKeybindings(localContent: string, remoteContent: string, bas
|
||||
function stringify(value: any): string {
|
||||
return JSON.stringify(value, null, '\t');
|
||||
}
|
||||
|
||||
class MockUserDataSyncUtilService implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
const keys: IStringDictionary<string> = {};
|
||||
for (const keybinding of userbindings) {
|
||||
keys[keybinding] = keybinding;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> {
|
||||
return { eol: '\n', insertSpaces: false, tabSize: 4 };
|
||||
}
|
||||
|
||||
async ignoreExtensionsToSync(extensions: IExtensionIdentifier[]): Promise<void> { }
|
||||
}
|
||||
|
||||
246
src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
Normal file
246
src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataAuthTokenService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { NullLogService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService';
|
||||
import { IGlobalExtensionEnablementService, IExtensionManagementService, IExtensionGalleryService, DidInstallExtensionEvent, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class UserDataSyncClient extends Disposable {
|
||||
|
||||
readonly instantiationService: TestInstantiationService;
|
||||
|
||||
constructor(readonly testServer: UserDataSyncTestServer = new UserDataSyncTestServer()) {
|
||||
super();
|
||||
this.instantiationService = new TestInstantiationService();
|
||||
}
|
||||
|
||||
async setUp(empty: boolean = false): Promise<void> {
|
||||
const userDataDirectory = URI.file('userdata').with({ scheme: Schemas.inMemory });
|
||||
const userDataSyncHome = joinPath(userDataDirectory, '.sync');
|
||||
const environmentService = this.instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{
|
||||
userDataSyncHome,
|
||||
settingsResource: joinPath(userDataDirectory, 'settings.json'),
|
||||
settingsSyncPreviewResource: joinPath(userDataSyncHome, 'settings.json'),
|
||||
keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'),
|
||||
keybindingsSyncPreviewResource: joinPath(userDataSyncHome, 'keybindings.json'),
|
||||
argvResource: joinPath(userDataDirectory, 'argv.json'),
|
||||
});
|
||||
|
||||
const logService = new NullLogService();
|
||||
this.instantiationService.stub(ILogService, logService);
|
||||
|
||||
const fileService = this._register(new FileService(logService));
|
||||
fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider());
|
||||
this.instantiationService.stub(IFileService, fileService);
|
||||
|
||||
this.instantiationService.stub(IStorageService, new InMemoryStorageService());
|
||||
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({
|
||||
'configurationSync.store': {
|
||||
url: this.testServer.url,
|
||||
authenticationProviderId: 'test'
|
||||
}
|
||||
})));
|
||||
|
||||
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
||||
await configurationService.initialize();
|
||||
this.instantiationService.stub(IConfigurationService, configurationService);
|
||||
|
||||
this.instantiationService.stub(IRequestService, this.testServer);
|
||||
this.instantiationService.stub(IUserDataAuthTokenService, <Partial<IUserDataAuthTokenService>>{
|
||||
onDidChangeToken: new Emitter<string | undefined>().event,
|
||||
async getToken() { return 'token'; }
|
||||
});
|
||||
|
||||
this.instantiationService.stub(IUserDataSyncLogService, logService);
|
||||
this.instantiationService.stub(ITelemetryService, NullTelemetryService);
|
||||
this.instantiationService.stub(IUserDataSyncStoreService, this.instantiationService.createInstance(UserDataSyncStoreService));
|
||||
this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService());
|
||||
this.instantiationService.stub(IUserDataSyncEnablementService, this.instantiationService.createInstance(UserDataSyncEnablementService));
|
||||
|
||||
this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService));
|
||||
this.instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
||||
async getInstalled() { return []; },
|
||||
onDidInstallExtension: new Emitter<DidInstallExtensionEvent>().event,
|
||||
onDidUninstallExtension: new Emitter<DidUninstallExtensionEvent>().event,
|
||||
});
|
||||
this.instantiationService.stub(IExtensionGalleryService, <Partial<IExtensionGalleryService>>{
|
||||
isEnabled() { return true; },
|
||||
async getCompatibleExtension() { return null; }
|
||||
});
|
||||
|
||||
this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser));
|
||||
this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService));
|
||||
|
||||
if (empty) {
|
||||
await fileService.del(environmentService.settingsResource);
|
||||
} else {
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([])));
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' })));
|
||||
}
|
||||
await configurationService.reloadConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncTestServer implements IRequestService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly url: string = 'http://host:3000';
|
||||
private session: string | null = null;
|
||||
private readonly data: Map<ResourceKey, IUserData> = new Map<ResourceKey, IUserData>();
|
||||
|
||||
private _requests: { url: string, type: string, headers?: IHeaders }[] = [];
|
||||
get requests(): { url: string, type: string, headers?: IHeaders }[] { return this._requests; }
|
||||
|
||||
private _responses: { status: number }[] = [];
|
||||
get responses(): { status: number }[] { return this._responses; }
|
||||
reset(): void { this._requests = []; this._responses = []; }
|
||||
|
||||
async resolveProxy(url: string): Promise<string | undefined> { return url; }
|
||||
|
||||
async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
|
||||
const headers: IHeaders = {};
|
||||
if (options.headers) {
|
||||
if (options.headers['If-None-Match']) {
|
||||
headers['If-None-Match'] = options.headers['If-None-Match'];
|
||||
}
|
||||
if (options.headers['If-Match']) {
|
||||
headers['If-Match'] = options.headers['If-Match'];
|
||||
}
|
||||
}
|
||||
this._requests.push({ url: options.url!, type: options.type!, headers });
|
||||
const requestContext = await this.doRequest(options);
|
||||
this._responses.push({ status: requestContext.res.statusCode! });
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
private async doRequest(options: IRequestOptions): Promise<IRequestContext> {
|
||||
const versionUrl = `${this.url}/v1/`;
|
||||
const relativePath = options.url!.indexOf(versionUrl) === 0 ? options.url!.substring(versionUrl.length) : undefined;
|
||||
const segments = relativePath ? relativePath.split('/') : [];
|
||||
if (options.type === 'GET' && segments.length === 1 && segments[0] === 'manifest') {
|
||||
return this.getManifest(options.headers);
|
||||
}
|
||||
if (options.type === 'GET' && segments.length === 3 && segments[0] === 'resource' && segments[2] === 'latest') {
|
||||
return this.getLatestData(segments[1], options.headers);
|
||||
}
|
||||
if (options.type === 'POST' && segments.length === 2 && segments[0] === 'resource') {
|
||||
return this.writeData(segments[1], options.data, options.headers);
|
||||
}
|
||||
if (options.type === 'DELETE' && segments.length === 1 && segments[0] === 'resource') {
|
||||
return this.clear(options.headers);
|
||||
}
|
||||
return this.toResponse(501);
|
||||
}
|
||||
|
||||
private async getManifest(headers?: IHeaders): Promise<IRequestContext> {
|
||||
if (this.session) {
|
||||
const latest: Record<ResourceKey, string> = Object.create({});
|
||||
const manifest: IUserDataManifest = { session: this.session, latest };
|
||||
this.data.forEach((value, key) => latest[key] = value.ref);
|
||||
return this.toResponse(200, { 'Content-Type': 'application/json' }, JSON.stringify(manifest));
|
||||
}
|
||||
return this.toResponse(204);
|
||||
}
|
||||
|
||||
private async getLatestData(resource: string, headers: IHeaders = {}): Promise<IRequestContext> {
|
||||
const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource);
|
||||
if (resourceKey) {
|
||||
const data = this.data.get(resourceKey);
|
||||
if (!data) {
|
||||
return this.toResponse(204, { etag: '0' });
|
||||
}
|
||||
if (headers['If-None-Match'] === data.ref) {
|
||||
return this.toResponse(304);
|
||||
}
|
||||
return this.toResponse(200, { etag: data.ref }, data.content || '');
|
||||
}
|
||||
return this.toResponse(204);
|
||||
}
|
||||
|
||||
private async writeData(resource: string, content: string = '', headers: IHeaders = {}): Promise<IRequestContext> {
|
||||
if (!headers['If-Match']) {
|
||||
return this.toResponse(428);
|
||||
}
|
||||
if (!this.session) {
|
||||
this.session = generateUuid();
|
||||
}
|
||||
const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource);
|
||||
if (resourceKey) {
|
||||
const data = this.data.get(resourceKey);
|
||||
if (headers['If-Match'] !== (data ? data.ref : '0')) {
|
||||
return this.toResponse(412);
|
||||
}
|
||||
const ref = `${parseInt(data?.ref || '0') + 1}`;
|
||||
this.data.set(resourceKey, { ref, content });
|
||||
return this.toResponse(200, { etag: ref });
|
||||
}
|
||||
return this.toResponse(204);
|
||||
}
|
||||
|
||||
private async clear(headers?: IHeaders): Promise<IRequestContext> {
|
||||
this.data.clear();
|
||||
this.session = null;
|
||||
return this.toResponse(204);
|
||||
}
|
||||
|
||||
private toResponse(statusCode: number, headers?: IHeaders, data?: string): IRequestContext {
|
||||
return {
|
||||
res: {
|
||||
headers: headers || {},
|
||||
statusCode
|
||||
},
|
||||
stream: bufferToStream(VSBuffer.fromString(data || ''))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class TestUserDataSyncUtilService implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
const keys: IStringDictionary<string> = {};
|
||||
for (const keybinding of userbindings) {
|
||||
keys[keybinding] = keybinding;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> {
|
||||
return { eol: '\n', insertSpaces: false, tabSize: 4 };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,555 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
suite('UserDataSyncService', () => {
|
||||
|
||||
const disposableStore = new DisposableStore();
|
||||
|
||||
teardown(() => disposableStore.clear());
|
||||
|
||||
test('test first time sync ever', async () => {
|
||||
// Setup the client
|
||||
const target = new UserDataSyncTestServer();
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
const testObject = client.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// Sync for first time
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '0' } },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } },
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync ever with no data', async () => {
|
||||
// Setup the client
|
||||
const target = new UserDataSyncTestServer();
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp(true);
|
||||
const testObject = client.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// Sync for first time
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } },
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with no changes - pull', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// Sync (pull) from the test client
|
||||
target.reset();
|
||||
await testObject.isFirstTimeSyncWithMerge();
|
||||
await testObject.pull();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with changes - pull', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client with changes
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
const fileService = testClient.instantiationService.get(IFileService);
|
||||
const environmentService = testClient.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
|
||||
|
||||
// Sync (pull) from the test client
|
||||
target.reset();
|
||||
await testObject.isFirstTimeSyncWithMerge();
|
||||
await testObject.pull();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with no changes - merge', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// Sync (merge) from the test client
|
||||
target.reset();
|
||||
await testObject.isFirstTimeSyncWithMerge();
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with changes - merge', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client with changes
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const fileService = testClient.instantiationService.get(IFileService);
|
||||
const environmentService = testClient.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// Sync (merge) from the test client
|
||||
target.reset();
|
||||
await testObject.isFirstTimeSyncWithMerge();
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test sync when there are no changes', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
const testObject = client.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// sync from the client again
|
||||
target.reset();
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
]);
|
||||
});
|
||||
|
||||
test('test sync when there are local changes', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
const testObject = client.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
target.reset();
|
||||
|
||||
// Do changes in the client
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
|
||||
|
||||
// Sync from the client
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } },
|
||||
// Keybindings
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } },
|
||||
// Global state
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } },
|
||||
]);
|
||||
});
|
||||
|
||||
test('test sync when there are remote changes', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Sync from first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Sync from test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// Do changes in first client and sync
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Sync from test client
|
||||
target.reset();
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: { 'If-None-Match': '1' } },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: { 'If-None-Match': '1' } },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: { 'If-None-Match': '1' } },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test delete', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Sync from the client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// Reset from the client
|
||||
target.reset();
|
||||
await testObject.reset();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'DELETE', url: `${target.url}/v1/resource`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test delete and sync', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Sync from the client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// Reset from the client
|
||||
await testObject.reset();
|
||||
|
||||
// Sync again
|
||||
target.reset();
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Settings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '0' } },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } },
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test delete on one client throws turned off error on other client while syncing', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Set up and sync from the client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Set up and sync from the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// Reset from the first client
|
||||
await client.instantiationService.get(IUserDataSyncService).reset();
|
||||
|
||||
// Sync from the test client
|
||||
target.reset();
|
||||
try {
|
||||
await testObject.sync();
|
||||
} catch (e) {
|
||||
assert.ok(e instanceof UserDataSyncError);
|
||||
assert.deepEqual((<UserDataSyncError>e).code, UserDataSyncErrorCode.TurnedOff);
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
]);
|
||||
return;
|
||||
}
|
||||
throw assert.fail('Should fail with turned off error');
|
||||
});
|
||||
|
||||
test('test creating new session from one client throws session expired error on another client while syncing', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Set up and sync from the client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Set up and sync from the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// Reset from the first client
|
||||
await client.instantiationService.get(IUserDataSyncService).reset();
|
||||
|
||||
// Sync again from the first client to create new session
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Sync from the test client
|
||||
target.reset();
|
||||
try {
|
||||
await testObject.sync();
|
||||
} catch (e) {
|
||||
assert.ok(e instanceof UserDataSyncError);
|
||||
assert.deepEqual((<UserDataSyncError>e).code, UserDataSyncErrorCode.SessionExpired);
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
]);
|
||||
return;
|
||||
}
|
||||
throw assert.fail('Should fail with turned off error');
|
||||
});
|
||||
|
||||
test('test sync status', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup the client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
const testObject = client.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// sync from the client
|
||||
const actualStatuses: SyncStatus[] = [];
|
||||
const disposable = testObject.onDidChangeStatus(status => actualStatuses.push(status));
|
||||
await testObject.sync();
|
||||
|
||||
disposable.dispose();
|
||||
assert.deepEqual(actualStatuses, [SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle]);
|
||||
});
|
||||
|
||||
test('test sync conflicts status', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
let fileService = client.instantiationService.get(IFileService);
|
||||
let environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
fileService = testClient.instantiationService.get(IFileService);
|
||||
environmentService = testClient.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 })));
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// sync from the client
|
||||
await testObject.sync();
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
|
||||
assert.deepEqual(testObject.conflictsSources, [SyncSource.Settings]);
|
||||
});
|
||||
|
||||
test('test sync will sync other non conflicted areas', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
let fileService = client.instantiationService.get(IFileService);
|
||||
let environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client and get conflicts in settings
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
let testFileService = testClient.instantiationService.get(IFileService);
|
||||
let testEnvironmentService = testClient.instantiationService.get(IEnvironmentService);
|
||||
await testFileService.writeFile(testEnvironmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 })));
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// sync from the first client with changes in keybindings
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// sync from the test client
|
||||
target.reset();
|
||||
const actualStatuses: SyncStatus[] = [];
|
||||
const disposable = testObject.onDidChangeStatus(status => actualStatuses.push(status));
|
||||
await testObject.sync();
|
||||
|
||||
disposable.dispose();
|
||||
assert.deepEqual(actualStatuses, []);
|
||||
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
// Manifest
|
||||
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
|
||||
// Keybindings
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: { 'If-None-Match': '1' } },
|
||||
]);
|
||||
});
|
||||
|
||||
test('test stop sync reset status', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
let fileService = client.instantiationService.get(IFileService);
|
||||
let environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await client.instantiationService.get(IUserDataSyncService).sync();
|
||||
|
||||
// Setup the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
fileService = testClient.instantiationService.get(IFileService);
|
||||
environmentService = testClient.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 })));
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
await testObject.sync();
|
||||
|
||||
// sync from the client
|
||||
await testObject.stop();
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflictsSources, []);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user