提交 49852131 编写于 作者: S Sandeep Somavarapu

make user data provider a full fledged filesystem provider

上级 60dcb1ac
......@@ -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<IChannel>(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) {
......
......@@ -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<ISnippetsService>('snippetService');
Registry.as<IUserDataContainerRegistry>(Extensions.UserDataContainers).registerContainer('snippets');
export interface ISnippetsService {
_serviceBrand: any;
......
......@@ -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<IChannel>(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);
......
......@@ -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 = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>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(() => {
......
......@@ -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(<IWindowConfiguration>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(<IWindowConfiguration>{}, environmentService, new RemoteAuthorityResolverService(), new SignService()));
return (<WorkspaceService>workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir)));
});
......@@ -158,12 +167,13 @@ suite('WorkspaceContextService - Workspace', () => {
parentResource = parentDir;
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>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 = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>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 = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>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 = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>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 = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>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 = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new WorkbenchEnvironmentService(<IWindowConfiguration>parseArgs(process.argv), process.execPath);
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const remoteEnvironmentPromise = new Promise<Partial<IRemoteAgentEnvironment>>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) }));
const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<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);
......
......@@ -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(<IWindowConfiguration>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));
......
......@@ -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<FileChangeEvent[]> = this._register(new Emitter<FileChangeEvent[]>());
readonly onDidChangeFile: Event<FileChangeEvent[]> = this._onDidChangeFile.event;
readonly capabilities: FileSystemProviderCapabilities = this.fileSystemProvider.capabilities;
readonly onDidChangeCapabilities: Event<void> = Event.None;
private readonly _onDidChangeFile: Emitter<IFileChange[]> = this._register(new Emitter<IFileChange[]>());
readonly onDidChangeFile: Event<IFileChange[]> = 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<IUserDataContainerRegistry>(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<IUserDataContainerRegistry>(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<IStat> {
return this.fileSystemProvider.stat(this.toFileSystemResource(resource));
}
mkdir(resource: URI): Promise<void> {
return this.fileSystemProvider.mkdir(this.toFileSystemResource(resource));
}
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
return this.fileSystemProvider.rename(this.toFileSystemResource(from), this.toFileSystemResource(to), opts);
}
readFile(resource: URI): Promise<Uint8Array> {
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<void> {
if (this.isBackUpsPath(container)) {
return;
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
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<number> {
if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.open(this.toFileSystemResource(resource), opts);
}
throw new Error('not supported');
}
async readFile(path: string): Promise<Uint8Array> {
const resource = this.toResource(path);
const content = await this.fileService.readFile(resource);
return content.value.buffer;
close(fd: number): Promise<void> {
if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.close(fd);
}
throw new Error('not supported');
}
writeFile(path: string, value: Uint8Array): Promise<void> {
return this.fileService.writeFile(this.toResource(path), VSBuffer.wrap(value)).then(() => undefined);
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.read(fd, pos, data, offset, length);
}
throw new Error('not supported');
}
async listFiles(path: string): Promise<string[]> {
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<number> {
if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.write(fd, pos, data, offset, length);
}
return [];
throw new Error('not supported');
}
deleteFile(path: string): Promise<void> {
return this.fileService.del(this.toResource(path));
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
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
......@@ -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<string[]>;
}
export interface IUserDataContainerRegistry {
/**
* An event to signal that a container has been registered.
*/
readonly onDidRegisterContainer: Event<string>;
/**
* 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<string> = TernarySearchTree.forStrings();
private _onDidRegisterContainer: Emitter<string> = new Emitter<string>();
readonly onDidRegisterContainer: Event<string> = 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
/*---------------------------------------------------------------------------------------------
* 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<string, number> = new Map<string, number>();
readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite;
readonly onDidChangeCapabilities: Event<void> = Event.None;
private readonly _onDidChangeFile: Emitter<IFileChange[]> = this._register(new Emitter<IFileChange[]>());
readonly onDidChangeFile: Event<IFileChange[]> = 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<IStat> {
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<void> { throw new Error('not supported'); }
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { throw new Error('not supported'); }
async readFile(resource: URI): Promise<Uint8Array> {
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<void> {
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<void> {
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<IUserDataContainerRegistry>(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
......@@ -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;
}
/**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册