From 498521311cab45ab3f3c1aea851599ec4afb8c63 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 4 Jul 2019 17:59:11 +0200 Subject: [PATCH] make user data provider a full fledged filesystem provider --- src/vs/workbench/browser/web.main.ts | 34 +- .../snippets/browser/snippets.contribution.ts | 3 - src/vs/workbench/electron-browser/main.ts | 6 +- .../configurationEditingService.test.ts | 19 +- .../configurationService.test.ts | 56 +- .../keybindingEditing.test.ts | 19 +- .../userData/common/fileUserDataProvider.ts | 174 +++--- .../services/userData/common/userData.ts | 60 +- .../common/userDataFileSystemProvider.ts | 156 ----- .../fileUserDataProvider.test.ts | 545 ------------------ src/vs/workbench/workbench.web.api.ts | 4 +- 11 files changed, 178 insertions(+), 898 deletions(-) delete mode 100644 src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts delete mode 100644 src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index f1961c05b50..35d76e44b47 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -19,7 +19,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, IFileSystemProvider } from 'vs/platform/files/common/files'; import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -35,10 +35,7 @@ import { hash } from 'vs/base/common/hash'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { ProductService } from 'vs/platform/product/browser/productService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; -import { joinPath } from 'vs/base/common/resources'; -import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; +import { joinPath, dirname } from 'vs/base/common/resources'; class CodeRendererMain extends Disposable { @@ -110,16 +107,26 @@ class CodeRendererMain extends Disposable { const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); + let userDataProvider: IFileSystemProvider | undefined = this.configuration.userDataProvider; const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); + + if (!userDataProvider) { + const remoteUserDataUri = this.getRemoteUserDataUri(); + if (remoteUserDataUri) { + userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, dirname(remoteUserDataUri), remoteFileSystemProvider)); + } + } } // User Data Provider - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, this.getUserDataPovider(fileService))); + if (userDataProvider) { + fileService.registerProvider(Schemas.userData, userDataProvider); + } const payload = await this.resolveWorkspaceInitializationPayload(); @@ -169,21 +176,6 @@ class CodeRendererMain extends Disposable { return { id: 'empty-window' }; } - private getUserDataPovider(fileService: IFileService): IUserDataProvider { - if (this.configuration.userDataProvider) { - return this.configuration.userDataProvider; - } - - if (this.configuration.remoteAuthority) { - const remoteUserDataUri = this.getRemoteUserDataUri(); - if (remoteUserDataUri) { - return this._register(new FileUserDataProvider(remoteUserDataUri, fileService)); - } - } - - return this._register(new InMemoryUserDataProvider()); - } - private getRemoteUserDataUri(): URI | null { const element = document.getElementById('vscode-remote-user-data-uri'); if (element) { diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 6ee3f0b24f8..ee158fb00bf 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -10,12 +10,9 @@ import * as nls from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { LanguageId } from 'vs/editor/common/modes'; import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; -import { IUserDataContainerRegistry, Extensions } from 'vs/workbench/services/userData/common/userData'; export const ISnippetsService = createDecorator('snippetService'); -Registry.as(Extensions.UserDataContainers).registerContainer('snippets'); - export interface ISnippetsService { _serviceBrand: any; diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 9394c30bb0f..c7542c63abe 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -51,7 +51,6 @@ import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; class CodeRendererMain extends Disposable { @@ -200,6 +199,9 @@ class CodeRendererMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // User Data Provider + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, URI.file(environmentService.backupHome), diskFileSystemProvider)); + const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); @@ -207,8 +209,6 @@ class CodeRendererMain extends Disposable { fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } - // User Data Provider - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(environmentService.appSettingsHome, fileService))); const payload = await this.resolveWorkspaceInitializationPayload(environmentService); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 12704b9a215..be0cfec3420 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -39,10 +39,20 @@ import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFil import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; +import { dirname } from 'vs/base/common/resources'; + +class TestBackupEnvironmentService extends WorkbenchEnvironmentService { + + constructor(private _appSettingsHome: URI) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + } + + get appSettingsHome() { return this._appSettingsHome; } + +} suite('ConfigurationEditingService', () => { @@ -95,14 +105,15 @@ suite('ConfigurationEditingService', () => { clearServices(); instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestBackupEnvironmentService(URI.file(workspaceDir)); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(URI.file(workspaceDir), dirname(URI.file(workspaceDir)), diskFileSystemProvider)); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(workspaceDir), fileService))); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 75945ef6e1b..308b976db11 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -30,7 +30,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { createHash } from 'crypto'; import { Schemas } from 'vs/base/common/network'; -import { originalFSPath } from 'vs/base/common/resources'; +import { originalFSPath, dirname } from 'vs/base/common/resources'; import { isLinux } from 'vs/base/common/platform'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; @@ -47,9 +47,18 @@ import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +class TestEnvironmentService extends WorkbenchEnvironmentService { + + constructor(private _appSettingsHome: URI) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + } + + get appSettingsHome() { return this._appSettingsHome; } + +} + function setUpFolderWorkspace(folderName: string): Promise<{ parentDir: string, folderDir: string }> { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); @@ -96,9 +105,9 @@ suite('WorkspaceContextService - Folder', () => { .then(({ parentDir, folderDir }) => { parentResource = parentDir; workspaceResource = folderDir; - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), new DiskFileSystemProvider(new NullLogService()))); workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService({}, environmentService, new RemoteAuthorityResolverService(), new SignService())); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); @@ -158,12 +167,13 @@ suite('WorkspaceContextService - Workspace', () => { parentResource = parentDir; instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -217,12 +227,13 @@ suite('WorkspaceContextService - Workspace Editing', () => { parentResource = parentDir; instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -477,12 +488,13 @@ suite('WorkspaceService - Initialization', () => { globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -740,12 +752,13 @@ suite('WorkspaceConfigurationService - Folder', () => { globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -1069,12 +1082,13 @@ suite('WorkspaceConfigurationService-Multiroot', () => { globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -1471,12 +1485,12 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { remoteSettingsFile = path.join(parentDir, 'remote-settings.json'); instantiationService = workbenchInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) })); const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(parentDir), fileService))); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() }; testObject = new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, testObject); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 8e0c6f8d0db..dc8b05ca6f8 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -45,11 +45,21 @@ import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { URI } from 'vs/base/common/uri'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { parseArgs } from 'vs/platform/environment/node/argv'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { dirname } from 'vs/base/common/resources'; + +class TestBackupEnvironmentService extends WorkbenchEnvironmentService { + + constructor(private _appSettingsHome: URI) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + } + + get appSettingsHome() { return this._appSettingsHome; } + +} interface Modifiers { metaKey?: boolean; @@ -71,7 +81,7 @@ suite('KeybindingsEditing', () => { instantiationService = new TestInstantiationService(); - const environmentService = new WorkbenchEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestBackupEnvironmentService(URI.file(testDir)); instantiationService.stub(IEnvironmentService, environmentService); instantiationService.stub(IConfigurationService, ConfigurationService); instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' }); @@ -89,8 +99,9 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.userRoamingDataHome, new FileUserDataProvider(URI.file(testDir), fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index 3b8ad172fe3..d4dd8d7abb7 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -4,120 +4,134 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataProvider, FileChangeEvent, IUserDataContainerRegistry, Extensions } from 'vs/workbench/services/userData/common/userData'; -import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; import { startsWith } from 'vs/base/common/strings'; import { BACKUPS } from 'vs/platform/environment/common/environment'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { Schemas } from 'vs/base/common/network'; -export class FileUserDataProvider extends Disposable implements IUserDataProvider { +export class FileUserDataProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability { - private _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + readonly capabilities: FileSystemProviderCapabilities = this.fileSystemProvider.capabilities; + readonly onDidChangeCapabilities: Event = Event.None; + + private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; constructor( private readonly userDataHome: URI, - @IFileService private readonly fileService: IFileService + private readonly backupsHome: URI, + private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, ) { super(); + // Assumption: This path always exists - this._register(this.fileService.watch(this.userDataHome)); - this._register(this.fileService.onFileChanges(e => this.handleFileChanges(e))); - - const userDataContainersRegistry = Registry.as(Extensions.UserDataContainers); - userDataContainersRegistry.containers.forEach(c => this.watchContainer(c)); - this._register(userDataContainersRegistry.onDidRegisterContainer(c => this.watchContainer(c))); - } - - private handleFileChanges(event: FileChangesEvent): void { - const changedPaths: FileChangeEvent[] = []; - const userDataContainersRegistry = Registry.as(Extensions.UserDataContainers); - for (const change of event.changes) { - if (change.resource.scheme === this.userDataHome.scheme) { - const path = this.toPath(change.resource); - if (path) { - changedPaths.push({ - path, - type: change.type - }); - if (userDataContainersRegistry.isContainer(path)) { - if (change.type === FileChangeType.ADDED) { - this.watchContainer(path); - } - } - } - } - } - if (changedPaths.length) { - this._onDidChangeFile.fire(changedPaths); + this._register(this.fileSystemProvider.watch(this.userDataHome, { recursive: false, excludes: [] })); + this._register(this.fileSystemProvider.onDidChangeFile(e => this.handleFileChanges(e))); + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { + return this.fileSystemProvider.watch(this.toFileSystemResource(resource), opts); + } + + stat(resource: URI): Promise { + return this.fileSystemProvider.stat(this.toFileSystemResource(resource)); + } + + mkdir(resource: URI): Promise { + return this.fileSystemProvider.mkdir(this.toFileSystemResource(resource)); + } + + rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + return this.fileSystemProvider.rename(this.toFileSystemResource(from), this.toFileSystemResource(to), opts); + } + + readFile(resource: URI): Promise { + if (hasReadWriteCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.readFile(this.toFileSystemResource(resource)); } + throw new Error('not supported'); + } + + readdir(resource: URI): Promise<[string, FileType][]> { + return this.fileSystemProvider.readdir(this.toFileSystemResource(resource)); } - private async watchContainer(container: string): Promise { - if (this.isBackUpsPath(container)) { - return; + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + if (hasReadWriteCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.writeFile(this.toFileSystemResource(resource), content, opts); } - const resource = this.toResource(container); - const exists = await this.fileService.exists(resource); - if (exists) { - this._register(this.fileService.watch(resource)); + throw new Error('not supported'); + } + + open(resource: URI, opts: FileOpenOptions): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.open(this.toFileSystemResource(resource), opts); } + throw new Error('not supported'); } - async readFile(path: string): Promise { - const resource = this.toResource(path); - const content = await this.fileService.readFile(resource); - return content.value.buffer; + close(fd: number): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.close(fd); + } + throw new Error('not supported'); } - writeFile(path: string, value: Uint8Array): Promise { - return this.fileService.writeFile(this.toResource(path), VSBuffer.wrap(value)).then(() => undefined); + read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.read(fd, pos, data, offset, length); + } + throw new Error('not supported'); } - async listFiles(path: string): Promise { - const resource = this.toResource(path); - try { - const result = await this.fileService.resolve(resource); - if (result.children) { - return result.children - .filter(c => !c.isDirectory) - .map(c => this.toRelativePath(c.resource, resource)!); - } - } catch (error) { + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.write(fd, pos, data, offset, length); } - return []; + throw new Error('not supported'); } - deleteFile(path: string): Promise { - return this.fileService.del(this.toResource(path)); + delete(resource: URI, opts: FileDeleteOptions): Promise { + return this.fileSystemProvider.delete(this.toFileSystemResource(resource), opts); } - private toResource(path: string): URI { - if (this.isBackUpsPath(path)) { - return resources.joinPath(resources.dirname(this.userDataHome), path); + private handleFileChanges(changes: IFileChange[]): void { + const userDataChanges: IFileChange[] = []; + for (const change of changes) { + const userDataResource = this.toUserDataResource(change.resource); + if (userDataResource) { + userDataChanges.push({ + resource: userDataResource, + type: change.type + }); + } + } + if (userDataChanges.length) { + this._onDidChangeFile.fire(userDataChanges); } - return resources.joinPath(this.userDataHome, path); } - private isBackUpsPath(path: string): boolean { - return path === BACKUPS || startsWith(path, `${BACKUPS}/`); + private toFileSystemResource(userDataResource: URI): URI { + const fileSystemResource = userDataResource.with({ scheme: this.userDataHome.scheme }); + const relativePath = resources.relativePath(this.userDataHome, fileSystemResource); + if (relativePath && startsWith(relativePath, BACKUPS)) { + return resources.joinPath(resources.dirname(this.backupsHome), relativePath); + } + return fileSystemResource; } - private toPath(resource: URI): string | undefined { - let result = this.toRelativePath(resource, this.userDataHome); - if (result === undefined) { - result = this.toRelativePath(resource, resources.joinPath(resources.dirname(this.userDataHome), BACKUPS)); + private toUserDataResource(fileSystemResource: URI): URI | null { + if (resources.relativePath(this.userDataHome, fileSystemResource)) { + return fileSystemResource.with({ scheme: Schemas.userData }); + } + const relativePath = resources.relativePath(this.backupsHome, fileSystemResource); + if (relativePath) { + return resources.joinPath(this.userDataHome, BACKUPS, relativePath).with({ scheme: Schemas.userData }); } - return result; + return null; } - private toRelativePath(fromResource: URI, toResource: URI): string | undefined { - const fromPath = fromResource.toString(); - const toPath = toResource.toString(); - return startsWith(fromPath, toPath) ? fromPath.substr(toPath.length + 1) : undefined; - } } \ No newline at end of file diff --git a/src/vs/workbench/services/userData/common/userData.ts b/src/vs/workbench/services/userData/common/userData.ts index 438d6339e30..6ce93879fc3 100644 --- a/src/vs/workbench/services/userData/common/userData.ts +++ b/src/vs/workbench/services/userData/common/userData.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter } from 'vs/base/common/event'; -import { TernarySearchTree } from 'vs/base/common/map'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { Event } from 'vs/base/common/event'; import { FileChangeType } from 'vs/platform/files/common/files'; /** @@ -80,59 +78,3 @@ export interface IUserDataProvider { */ listFiles(path: string): Promise; } - -export interface IUserDataContainerRegistry { - - /** - * An event to signal that a container has been registered. - */ - readonly onDidRegisterContainer: Event; - - /** - * Registered containers - */ - readonly containers: string[]; - - /** - * Register the given path as an user data container if user data files are stored under this path. - * - * It is required to register the container to access the user data files under the container. - */ - registerContainer(path: string): void; - - /** - * Returns true if the given path is an user data container or sub container of user data container - */ - isContainer(path: string): boolean; -} - -class UserDataContainerRegistry implements IUserDataContainerRegistry { - - private _containers: TernarySearchTree = TernarySearchTree.forStrings(); - - private _onDidRegisterContainer: Emitter = new Emitter(); - readonly onDidRegisterContainer: Event = this._onDidRegisterContainer.event; - - get containers(): string[] { - const containers: string[] = []; - this._containers.forEach(c => containers.push(c)); - return containers; - } - - public registerContainer(path: string): void { - if (!this._containers.get(path)) { - this._containers.set(path, path); - this._onDidRegisterContainer.fire(path); - } - } - - isContainer(path: string): boolean { - return !!this._containers.get(path) || !!this._containers.findSuperstr(path); - } -} - -export const Extensions = { - UserDataContainers: 'workbench.contributions.userDataContainers' -}; - -Registry.add(Extensions.UserDataContainers, new UserDataContainerRegistry()); \ No newline at end of file diff --git a/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts b/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts deleted file mode 100644 index 3eb3e76ea3e..00000000000 --- a/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts +++ /dev/null @@ -1,156 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { FileSystemProviderCapabilities, FileWriteOptions, IStat, FileType, FileDeleteOptions, IWatchOptions, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileChange, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; -import { IUserDataProvider, IUserDataContainerRegistry, Extensions, FileChangeEvent } from 'vs/workbench/services/userData/common/userData'; -import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; -import { startsWith } from 'vs/base/common/strings'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { joinPath } from 'vs/base/common/resources'; - -export class UserDataFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { - - private readonly versions: Map = new Map(); - - readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; - readonly onDidChangeCapabilities: Event = Event.None; - - private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; - - constructor( - private readonly userDataHome: URI, - private readonly userDataProvider: IUserDataProvider - ) { - super(); - this._register(this.userDataProvider.onDidChangeFile(changes => this.onDidChangeUserData(changes))); - } - - private onDidChangeUserData(changes: FileChangeEvent[]): void { - const fileChanges: IFileChange[] = []; - for (const { path, type } of changes) { - if (type === FileChangeType.DELETED) { - this.versions.delete(path); - } else { - this.versions.set(path, this.getOrSetVersion(path) + 1); - } - fileChanges.push({ - resource: this.toResource(path), - type - }); - } - if (fileChanges.length) { - this._onDidChangeFile.fire(new FileChangesEvent(fileChanges).changes); - } - } - - watch(resource: URI, opts: IWatchOptions): IDisposable { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalid user data resource ${resource}`); - } - return Disposable.None; - } - - async stat(resource: URI): Promise { - const path = this.toPath(resource); - if (path === undefined) { - throw new Error(`Invalid user data resource ${resource}`); - } - if (this.isContainer(path)) { - return { - type: FileType.Directory, - ctime: 0, - mtime: this.getOrSetVersion(path), - size: 0 - }; - } - const result = await this.readFile(resource); - return { - type: FileType.File, - ctime: 0, - mtime: this.getOrSetVersion(path), - size: result.byteLength - }; - } - - mkdir(resource: URI): Promise { throw new Error('not supported'); } - rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { throw new Error('not supported'); } - - async readFile(resource: URI): Promise { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalid user data resource ${resource}`); - } - if (this.isContainer(path)) { - throw new Error(`Invalid user data file ${resource}`); - } - return this.userDataProvider.readFile(path); - } - - async readdir(resource: URI): Promise<[string, FileType][]> { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalid user data resource ${resource}`); - } - if (!this.isContainer(path)) { - throw new Error(`Invalid user data container ${resource}`); - } - const children = await this.userDataProvider.listFiles(path); - return children.map(c => [c, FileType.File]); - } - - writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalid user data resource ${resource}`); - } - if (this.isContainer(path)) { - throw new Error(`Invalid user data file ${resource}`); - } - return this.userDataProvider.writeFile(path, content); - } - - delete(resource: URI, opts: FileDeleteOptions): Promise { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalid user data resource ${resource}`); - } - if (this.isContainer(path)) { - throw new Error(`Invalid user data file ${resource}`); - } - return this.userDataProvider.deleteFile(path); - } - - private getOrSetVersion(path: string): number { - if (!this.versions.has(path)) { - this.versions.set(path, 1); - } - return this.versions.get(path)!; - } - - private isContainer(path: string): boolean { - if (path === '') { - return true; // Root - } - return Registry.as(Extensions.UserDataContainers).isContainer(path); - } - - private toResource(path: string): URI { - return joinPath(this.userDataHome, path); - } - - private toPath(resource: URI): string | undefined { - return this.toRelativePath(resource, this.userDataHome); - } - - private toRelativePath(fromResource: URI, toResource: URI): string | undefined { - const fromPath = fromResource.toString(); - const toPath = toResource.toString(); - return startsWith(fromPath, toPath) ? fromPath.substr(toPath.length + 1) : undefined; - } -} \ No newline at end of file diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts deleted file mode 100644 index 807e4c40423..00000000000 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ /dev/null @@ -1,545 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as os from 'os'; -import * as path from 'vs/base/common/path'; -import * as uuid from 'vs/base/common/uuid'; -import * as pfs from 'vs/base/node/pfs'; -import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; -import { FileService } from 'vs/workbench/services/files/common/fileService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { Schemas } from 'vs/base/common/network'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; -import { URI } from 'vs/base/common/uri'; -import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { joinPath } from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-browser/diskFileSystemProvider'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IUserDataContainerRegistry, Extensions } from 'vs/workbench/services/userData/common/userData'; -import { BACKUPS } from 'vs/platform/environment/common/environment'; -import { DisposableStore } from 'vs/base/common/lifecycle'; - -suite('FileUserDataProvider', () => { - - let testObject: IFileService; - let rootPath: string; - let userDataPath: string; - let backupsPath: string; - let userDataResource: URI; - const userDataContainersRegistry = Registry.as(Extensions.UserDataContainers); - const disposables = new DisposableStore(); - - setup(async () => { - const logService = new NullLogService(); - testObject = new FileService(logService); - disposables.add(testObject); - - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - disposables.add(diskFileSystemProvider); - disposables.add(testObject.registerProvider(Schemas.file, diskFileSystemProvider)); - - rootPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid()); - userDataPath = path.join(rootPath, 'user'); - backupsPath = path.join(rootPath, BACKUPS); - userDataResource = URI.from({ scheme: Schemas.userData, path: '/user' }); - await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - - const fileUserDataProvider = new FileUserDataProvider(URI.file(userDataPath), testObject); - disposables.add(fileUserDataProvider); - const userDataFileSystemProvider = new UserDataFileSystemProvider(userDataResource, fileUserDataProvider); - disposables.add(userDataFileSystemProvider); - disposables.add(testObject.registerProvider(Schemas.userData, userDataFileSystemProvider)); - - userDataContainersRegistry.registerContainer('testContainer'); - userDataContainersRegistry.registerContainer('testContainer/subContainer'); - userDataContainersRegistry.registerContainer(BACKUPS); - }); - - teardown(async () => { - disposables.clear(); - await pfs.rimraf(rootPath, pfs.RimRafMode.MOVE); - }); - - test('exists return false when file does not exist', async () => { - const exists = await testObject.exists(joinPath(userDataResource, 'settings.json')); - assert.equal(exists, false); - }); - - test('read file throws error if not exist', async () => { - try { - await testObject.readFile(joinPath(userDataResource, 'settings.json')); - assert.fail('Should fail since file does not exist'); - } catch (e) { } - }); - - test('read existing file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}'); - const result = await testObject.readFile(joinPath(userDataResource, 'settings.json')); - assert.equal(result.value, '{}'); - }); - - test('create file', async () => { - await testObject.createFile(joinPath(userDataResource, 'settings.json'), VSBuffer.fromString('{}')); - const result = await pfs.readFile(path.join(userDataPath, 'settings.json')); - assert.equal(result, '{}'); - }); - - test('write file creates the file if not exist', async () => { - await testObject.writeFile(joinPath(userDataResource, 'settings.json'), VSBuffer.fromString('{}')); - const result = await pfs.readFile(path.join(userDataPath, 'settings.json')); - assert.equal(result, '{}'); - }); - - test('write to existing file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}'); - await testObject.writeFile(joinPath(userDataResource, 'settings.json'), VSBuffer.fromString('{a:1}')); - const result = await pfs.readFile(path.join(userDataPath, 'settings.json')); - assert.equal(result, '{a:1}'); - }); - - test('delete file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), ''); - await testObject.del(joinPath(userDataResource, 'settings.json')); - const result = await pfs.exists(path.join(userDataPath, 'settings.json')); - assert.equal(false, result); - }); - - test('resolve file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), ''); - const result = await testObject.resolve(joinPath(userDataResource, 'settings.json')); - assert.ok(!result.isDirectory); - assert.ok(result.children === undefined); - }); - - test('exists return true for container', async () => { - const exists = await testObject.exists(joinPath(userDataResource, 'testContainer')); - assert.equal(exists, true); - }); - - test('exists return false for non registered container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'container')); - const exists = await testObject.exists(joinPath(userDataResource, 'container')); - assert.equal(exists, false); - }); - - test('read file throws error for container', async () => { - try { - await testObject.readFile(joinPath(userDataResource, 'testContainer')); - assert.fail('Should fail since read file is not supported for container'); - } catch (e) { } - }); - - test('read file under container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - const actual = await testObject.readFile(joinPath(userDataResource, 'testContainer/settings.json')); - assert.equal(actual.value, '{}'); - }); - - test('read file under sub container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer', 'subContainer')); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'subContainer', 'settings.json'), '{}'); - const actual = await testObject.readFile(joinPath(userDataResource, 'testContainer/subContainer/settings.json')); - assert.equal(actual.value, '{}'); - }); - - test('create file throws error for container', async () => { - try { - await testObject.createFile(joinPath(userDataResource, 'testContainer'), VSBuffer.fromString('{}')); - assert.fail('Should fail since create file is not supported for container'); - } catch (e) { } - }); - - test('create file under container that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await testObject.createFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}')); - const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json')); - assert.equal(actual, '{}'); - }); - - test('create file under container that does not exist', async () => { - await testObject.createFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}')); - const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json')); - assert.equal(actual, '{}'); - }); - - test('write file throws error for container', async () => { - try { - await testObject.writeFile(joinPath(userDataResource, 'testContainer'), VSBuffer.fromString('{}')); - assert.fail('Should fail since write file is not supported for container'); - } catch (e) { } - }); - - test('write to not existing file under container that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}')); - const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json')); - assert.equal(actual, '{}'); - }); - - test('write to not existing file under container that does not exists', async () => { - await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}')); - const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json')); - assert.equal(actual, '{}'); - }); - - test('write to existing file under container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{a:1}')); - const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json')); - assert.equal(actual.toString(), '{a:1}'); - }); - - test('write file under sub container', async () => { - await testObject.writeFile(joinPath(userDataResource, 'testContainer/subContainer/settings.json'), VSBuffer.fromString('{}')); - const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'subContainer', 'settings.json')); - assert.equal(actual, '{}'); - }); - - test('delete file throws error for container that does not exist', async () => { - try { - await testObject.del(joinPath(userDataResource, 'testContainer')); - assert.fail('Should fail since delete file is not supported for container'); - } catch (e) { } - }); - - test('delete file throws error for container that exist', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - try { - await testObject.del(joinPath(userDataResource, 'testContainer')); - assert.fail('Should fail since delete file is not supported for container'); - } catch (e) { } - }); - - test('delete not existing file under container that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - try { - await testObject.del(joinPath(userDataResource, 'testContainer')); - assert.fail('Should fail since file does not exist'); - } catch (e) { } - }); - - test('delete not existing file under container that does not exists', async () => { - try { - await testObject.del(joinPath(userDataResource, 'testContainer/settings.json')); - assert.fail('Should fail since file does not exist'); - } catch (e) { } - }); - - test('delete existing file under container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - await testObject.del(joinPath(userDataResource, 'testContainer/settings.json')); - const exists = await pfs.exists(path.join(userDataPath, 'testContainer', 'settings.json')); - assert.equal(exists, false); - }); - - test('resolve container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - const result = await testObject.resolve(joinPath(userDataResource, 'testContainer')); - assert.ok(result.isDirectory); - assert.ok(result.children !== undefined); - assert.equal(result.children!.length, 1); - assert.equal(result.children![0].resource.toString(), joinPath(userDataResource, 'testContainer/settings.json').toString()); - }); - - test('read backup file', async () => { - await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}'); - const result = await testObject.readFile(joinPath(userDataResource, `${BACKUPS}/backup.json`)); - assert.equal(result.value, '{}'); - }); - - test('create backup file', async () => { - await testObject.createFile(joinPath(userDataResource, `${BACKUPS}/backup.json`), VSBuffer.fromString('{}')); - const result = await pfs.readFile(path.join(backupsPath, 'backup.json')); - assert.equal(result, '{}'); - }); - - test('write backup file', async () => { - await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}'); - await testObject.writeFile(joinPath(userDataResource, `${BACKUPS}/backup.json`), VSBuffer.fromString('{a:1}')); - const result = await pfs.readFile(path.join(backupsPath, 'backup.json')); - assert.equal(result, '{a:1}'); - }); - - test('resolve backups container', async () => { - pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}'); - const result = await testObject.resolve(joinPath(userDataResource, BACKUPS)); - assert.ok(result.isDirectory); - assert.ok(result.children !== undefined); - assert.equal(result.children!.length, 1); - assert.equal(result.children![0].resource.toString(), joinPath(userDataResource, `${BACKUPS}/backup.json`).toString()); - }); -}); - -// Watch tests are flaky -if (false) { - - suite('FileUserDataProvider - Watching', () => { - - let testObject: IFileService; - let rootPath: string; - let userDataPath: string; - let backupsPath: string; - let userDataResource: URI; - const userDataContainersRegistry = Registry.as(Extensions.UserDataContainers); - const disposables = new DisposableStore(); - - setup(async () => { - const logService = new NullLogService(); - testObject = new FileService(logService); - disposables.add(testObject); - - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - disposables.add(diskFileSystemProvider); - disposables.add(testObject.registerProvider(Schemas.file, diskFileSystemProvider)); - - rootPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid()); - userDataPath = path.join(rootPath, 'user'); - backupsPath = path.join(rootPath, BACKUPS); - userDataResource = URI.from({ scheme: Schemas.userData, path: '/user' }); - await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - - const fileUserDataProvider = new FileUserDataProvider(URI.file(userDataPath), testObject); - disposables.add(fileUserDataProvider); - const userDataFileSystemProvider = new UserDataFileSystemProvider(userDataResource, fileUserDataProvider); - disposables.add(userDataFileSystemProvider); - disposables.add(testObject.registerProvider(Schemas.userData, userDataFileSystemProvider)); - - userDataContainersRegistry.registerContainer('testContainer'); - userDataContainersRegistry.registerContainer('testContainer/subContainer'); - userDataContainersRegistry.registerContainer(BACKUPS); - }); - - teardown(async () => { - disposables.clear(); - await pfs.rimraf(rootPath, pfs.RimRafMode.MOVE); - }); - - test('watch file - event is triggerred when file is created', async (done) => { - const resource = joinPath(userDataResource, 'settings.json'); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.ADDED)) { - done(); - } - }); - await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - }); - - test('watch file - event is triggerred when file is created externally', async (done) => { - const resource = joinPath(userDataResource, 'settings.json'); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.ADDED)) { - done(); - } - }); - await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}'); - }); - - test('watch file - event is triggerred when file is updated', async (done) => { - const resource = joinPath(userDataResource, 'settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.UPDATED)) { - done(); - } - }); - await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - }); - - test('watch file - event is triggerred when file is update externally', async (done) => { - const resource = joinPath(userDataResource, 'settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.UPDATED)) { - done(); - } - }); - await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{a:1}'); - }); - - test('watch file - event is triggerred when file is deleted', async (done) => { - const resource = joinPath(userDataResource, 'settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.DELETED)) { - done(); - } - }); - await testObject.del(resource); - }); - - test('watch file - event is triggerred when file is deleted externally', async (done) => { - const resource = joinPath(userDataResource, 'settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.DELETED)) { - done(); - } - }); - await pfs.unlink(path.join(userDataPath, 'settings.json')); - }); - - test('watch file under container - event is triggerred when file is created', async (done) => { - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.ADDED)) { - done(); - } - }); - await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{a:1}')); - }); - - test('watch file under container - event is triggerred when file is created externally', async (done) => { - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.ADDED)) { - done(); - } - }); - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - }); - - test('watch file under container - event is triggerred when file is updated', async (done) => { - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.UPDATED)) { - done(); - } - }); - await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - }); - - test('watch file under container - event is triggerred when file is updated externally', async (done) => { - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.UPDATED)) { - done(); - } - }); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{a:1}'); - }); - - test('watch file under container - event is triggerred when file is deleted', async (done) => { - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.DELETED)) { - done(); - } - }); - await testObject.del(resource); - }); - - test('watch file under container - event is triggerred when file is deleted externally', async (done) => { - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(resource)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.DELETED)) { - done(); - } - }); - await pfs.unlink(path.join(userDataPath, 'testContainer', 'settings.json')); - }); - - test('watch container - event is triggerred when file under container is created', async (done) => { - const container = joinPath(userDataResource, 'testContainer'); - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - disposables.add(testObject.watch(container)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.ADDED)) { - done(); - } - }); - await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - }); - - test('watch container - event is triggerred when file under container is created externally', async (done) => { - await pfs.mkdirp(path.join(userDataPath, 'testContainer')); - const container = joinPath(userDataResource, 'testContainer'); - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - disposables.add(testObject.watch(container)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.ADDED)) { - done(); - } - }); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}'); - }); - - test('watch container - event is triggerred when file under container is updated', async (done) => { - const container = joinPath(userDataResource, 'testContainer'); - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(container)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.UPDATED)) { - done(); - } - }); - await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - }); - - test('watch container - event is triggerred when file under container is updated externally ', async (done) => { - const container = joinPath(userDataResource, 'testContainer'); - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(container)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.UPDATED)) { - done(); - } - }); - await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{a:1}'); - }); - - test('watch container - event is triggerred when file under container is deleted', async (done) => { - const container = joinPath(userDataResource, 'testContainer'); - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(container)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.DELETED)) { - done(); - } - }); - await testObject.del(resource); - }); - - test('watch container - event is triggerred when file under container is deleted externally ', async (done) => { - const container = joinPath(userDataResource, 'testContainer'); - const resource = joinPath(userDataResource, 'testContainer/settings.json'); - await testObject.writeFile(resource, VSBuffer.fromString('{}')); - disposables.add(testObject.watch(container)); - testObject.onFileChanges(e => { - if (e.contains(resource, FileChangeType.DELETED)) { - done(); - } - }); - await pfs.unlink(path.join(userDataPath, 'testContainer', 'settings.json')); - }); - }); -} \ No newline at end of file diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index a0615457e0a..e0b3b1ed9ad 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -6,7 +6,7 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents } from 'vs/base/common/uri'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; export interface IWorkbenchConstructionOptions { @@ -36,7 +36,7 @@ export interface IWorkbenchConstructionOptions { * Experimental: The userDataProvider is used to handle user specific application * state like settings, keybindings, UI state (e.g. opened editors) and snippets. */ - userDataProvider?: IUserDataProvider; + userDataProvider?: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability; } /** -- GitLab