未验证 提交 524f2e20 编写于 作者: S Sandeep Somavarapu 提交者: GitHub

Adopt user data provider (#76674)

Adopt user data provider
......@@ -90,6 +90,8 @@ export interface IExtensionHostDebugParams extends IDebugParams {
debugId?: string;
}
export const BACKUPS = 'Backups';
export interface IEnvironmentService {
_serviceBrand: any;
......@@ -105,9 +107,13 @@ export interface IEnvironmentService {
appNameLong: string;
appQuality?: string;
appSettingsHome: URI;
// user roaming data
userRoamingDataHome: URI;
settingsResource: URI;
keybindingsResource: URI;
keyboardLayoutResource: URI;
localeResource: URI;
machineSettingsHome: URI;
machineSettingsResource: URI;
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentService, ParsedArgs, IDebugParams, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment';
import { IEnvironmentService, ParsedArgs, IDebugParams, IExtensionHostDebugParams, BACKUPS } from 'vs/platform/environment/common/environment';
import * as crypto from 'crypto';
import * as paths from 'vs/base/node/paths';
import * as os from 'os';
......@@ -115,7 +115,10 @@ export class EnvironmentService implements IEnvironmentService {
get appSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'User')); }
@memoize
get settingsResource(): URI { return resources.joinPath(this.appSettingsHome, 'settings.json'); }
get userRoamingDataHome(): URI { return this.appSettingsHome; }
@memoize
get settingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'settings.json'); }
@memoize
get machineSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'Machine')); }
......@@ -136,16 +139,19 @@ export class EnvironmentService implements IEnvironmentService {
get settingsSearchUrl(): string | undefined { return product.settingsSearchUrl; }
@memoize
get keybindingsResource(): URI { return resources.joinPath(this.appSettingsHome, 'keybindings.json'); }
get keybindingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'keybindings.json'); }
@memoize
get keyboardLayoutResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); }
@memoize
get keyboardLayoutResource(): URI { return resources.joinPath(this.appSettingsHome, 'keyboardLayout.json'); }
get localeResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'locale.json'); }
@memoize
get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; }
@memoize
get backupHome(): string { return path.join(this.userDataPath, 'Backups'); }
get backupHome(): string { return path.join(this.userDataPath, BACKUPS); }
@memoize
get backupWorkspacesPath(): string { return path.join(this.backupHome, 'workspaces.json'); }
......
......@@ -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, dirname } from 'vs/base/common/resources';
import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider';
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
class CodeRendererMain extends Disposable {
......@@ -87,8 +84,7 @@ class CodeRendererMain extends Disposable {
serviceCollection.set(ILogService, logService);
// Environment
const remoteUserDataUri = this.getRemoteUserDataUri();
const environmentService = new BrowserWorkbenchEnvironmentService(this.configuration, remoteUserDataUri);
const environmentService = new BrowserWorkbenchEnvironmentService(this.configuration);
serviceCollection.set(IWorkbenchEnvironmentService, environmentService);
// Product
......@@ -111,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(dirname(environmentService.settingsResource), this.getUserDataPovider(fileService, remoteUserDataUri)));
if (userDataProvider) {
fileService.registerProvider(Schemas.userData, userDataProvider);
}
const payload = await this.resolveWorkspaceInitializationPayload();
......@@ -170,15 +176,6 @@ class CodeRendererMain extends Disposable {
return { id: 'empty-window' };
}
private getUserDataPovider(fileService: IFileService, remoteUserDataUri: URI | null): IUserDataProvider {
if (this.configuration.userDataProvider) {
return this.configuration.userDataProvider;
} else if (this.configuration.remoteAuthority && 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) {
......
......@@ -28,7 +28,6 @@ import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/bro
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { joinPath } from 'vs/base/common/resources';
// Register action to configure locale and related settings
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
......@@ -81,8 +80,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
[{
label: updateAndRestart ? localize('yes', "Yes") : localize('restart now', "Restart Now"),
run: () => {
const file = joinPath(this.environmentService.appSettingsHome, 'locale.json');
const updatePromise = updateAndRestart ? this.jsonEditingService.write(file, { key: 'locale', value: locale }, true) : Promise.resolve(undefined);
const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: locale }, true) : Promise.resolve(undefined);
updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e));
}
}, {
......
......@@ -16,7 +16,6 @@ import { firstIndex } from 'vs/base/common/arrays';
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { joinPath } from 'vs/base/common/resources';
export class ConfigureLocaleAction extends Action {
public static readonly ID = 'workbench.action.configureLocale';
......@@ -66,8 +65,7 @@ export class ConfigureLocaleAction extends Action {
}
if (selectedLanguage) {
const file = joinPath(this.environmentService.appSettingsHome, 'locale.json');
await this.jsonEditingService.write(file, { key: 'locale', value: selectedLanguage.label }, true);
await this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: selectedLanguage.label }, true);
const restart = await this.dialogService.confirm({
type: 'info',
message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
......
......@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IModeService } from 'vs/editor/common/services/modeService';
import { basename, extname } from 'vs/base/common/path';
import { extname } from 'vs/base/common/path';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
......@@ -19,7 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IFileService } from 'vs/platform/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { isValidBasename } from 'vs/base/common/extpath';
import { joinPath } from 'vs/base/common/resources';
import { joinPath, basename } from 'vs/base/common/resources';
const id = 'workbench.action.openSnippets';
......@@ -69,7 +69,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir
}
existing.push({
label: basename(file.location.fsPath),
label: basename(file.location),
filepath: file.location,
description: names.size === 0
? nls.localize('global.scope', "(global)")
......@@ -78,9 +78,9 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir
} else {
// language snippet
const mode = basename(file.location.fsPath).replace(/\.json$/, '');
const mode = basename(file.location).replace(/\.json$/, '');
existing.push({
label: basename(file.location.fsPath),
label: basename(file.location),
description: `(${modeService.getLanguageName(mode)})`,
filepath: file.location
});
......@@ -88,7 +88,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir
}
}
const dir = joinPath(envService.appSettingsHome, 'snippets');
const dir = joinPath(envService.userRoamingDataHome, 'snippets');
for (const mode of modeService.getRegisteredModes()) {
const label = modeService.getLanguageName(mode);
if (label && !seen.has(mode)) {
......@@ -220,7 +220,7 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise<any> => {
const globalSnippetPicks: SnippetPick[] = [{
scope: nls.localize('new.global_scope', 'global'),
label: nls.localize('new.global', "New Global Snippets file..."),
uri: joinPath(envService.appSettingsHome, 'snippets')
uri: joinPath(envService.userRoamingDataHome, 'snippets')
}];
const workspaceSnippetPicks: SnippetPick[] = [];
......
......@@ -287,7 +287,7 @@ class SnippetsService implements ISnippetsService {
}
private _initUserSnippets(): Promise<any> {
const userSnippetsFolder = resources.joinPath(this._environmentService.appSettingsHome, 'snippets');
const userSnippetsFolder = resources.joinPath(this._environmentService.userRoamingDataHome, 'snippets');
return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables));
}
......
......@@ -51,8 +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';
import { dirname } from 'vs/base/common/resources';
class CodeRendererMain extends Disposable {
......@@ -201,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);
......@@ -208,8 +209,6 @@ class CodeRendererMain extends Disposable {
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
}
// User Data Provider
fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(dirname(environmentService.settingsResource), new FileUserDataProvider(environmentService.appSettingsHome, fileService)));
const payload = await this.resolveWorkspaceInitializationPayload(environmentService);
......
......@@ -40,7 +40,6 @@ export class UserConfiguration extends Disposable {
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes);
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
this._register(this.fileService.watch(this.userSettingsResource));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this.reloadConfigurationScheduler.schedule()));
}
......
......@@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path';
import * as fs from 'fs';
import * as json from 'vs/base/common/json';
import { Registry } from 'vs/platform/registry/common/platform';
import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices';
......@@ -38,20 +38,20 @@ import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { dirname } from 'vs/base/common/resources';
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 SettingsTestEnvironmentService extends WorkbenchEnvironmentService {
class TestBackupEnvironmentService extends WorkbenchEnvironmentService {
constructor(args: ParsedArgs, _execPath: string, private _settingsPath: string) {
super(<IWindowConfiguration>args, _execPath);
constructor(private _appSettingsHome: URI) {
super(parseArgs(process.argv) as IWindowConfiguration, process.execPath);
}
get appSettingsHome(): URI { return dirname(URI.file(this._settingsPath)); }
get appSettingsHome() { return this._appSettingsHome; }
}
suite('ConfigurationEditingService', () => {
......@@ -105,14 +105,15 @@ suite('ConfigurationEditingService', () => {
clearServices();
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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(() => {
......
......@@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path';
import * as os from 'os';
import { URI } from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform';
import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { parseArgs } from 'vs/platform/environment/node/argv';
import * as pfs from 'vs/base/node/pfs';
import * as uuid from 'vs/base/common/uuid';
......@@ -47,16 +47,16 @@ 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 SettingsTestEnvironmentService extends WorkbenchEnvironmentService {
class TestEnvironmentService extends WorkbenchEnvironmentService {
constructor(args: ParsedArgs, _execPath: string, private _settingsPath: string) {
super(<IWindowConfiguration>args, _execPath);
constructor(private _appSettingsHome: URI) {
super(parseArgs(process.argv) as IWindowConfiguration, process.execPath);
}
get appSettingsHome(): URI { return dirname(URI.file(this._settingsPath)); }
get appSettingsHome() { return this._appSettingsHome; }
}
function setUpFolderWorkspace(folderName: string): Promise<{ parentDir: string, folderDir: string }> {
......@@ -105,10 +105,9 @@ suite('WorkspaceContextService - Folder', () => {
.then(({ parentDir, folderDir }) => {
parentResource = parentDir;
workspaceResource = folderDir;
const globalSettingsFile = path.join(parentDir, 'settings.json');
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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)));
});
......@@ -168,12 +167,13 @@ suite('WorkspaceContextService - Workspace', () => {
parentResource = parentDir;
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json'));
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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);
......@@ -227,12 +227,13 @@ suite('WorkspaceContextService - Workspace Editing', () => {
parentResource = parentDir;
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json'));
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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);
......@@ -487,12 +488,13 @@ suite('WorkspaceService - Initialization', () => {
globalSettingsFile = path.join(parentDir, 'settings.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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);
......@@ -750,12 +752,13 @@ suite('WorkspaceConfigurationService - Folder', () => {
globalSettingsFile = path.join(parentDir, 'settings.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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);
......@@ -1040,7 +1043,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
suite('WorkspaceConfigurationService-Multiroot', () => {
let parentResource: string, workspaceContextService: IWorkspaceContextService, environmentService: IWorkbenchEnvironmentService, jsonEditingServce: IJSONEditingService, testObject: IConfigurationService, globalSettingsFile: string;
let parentResource: string, workspaceContextService: IWorkspaceContextService, jsonEditingServce: IJSONEditingService, testObject: IConfigurationService, globalSettingsFile: string;
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
suiteSetup(() => {
......@@ -1079,12 +1082,13 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
globalSettingsFile = path.join(parentDir, 'settings.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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);
......@@ -1481,12 +1485,12 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
remoteSettingsFile = path.join(parentDir, 'remote-settings.json');
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
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.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, 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);
......
......@@ -63,24 +63,17 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration();
constructor(configuration: IWorkbenchConstructionOptions, remoteUserDataUri: URI | null) {
constructor(configuration: IWorkbenchConstructionOptions) {
this.args = { _: [] };
this.appRoot = '/web/';
this.appNameLong = 'Visual Studio Code - Web';
this.configuration.remoteAuthority = configuration.remoteAuthority;
if (remoteUserDataUri) {
this.appSettingsHome = remoteUserDataUri;
this.settingsResource = joinPath(this.appSettingsHome, 'settings.json').with({ scheme: Schemas.userData });
this.keybindingsResource = joinPath(this.appSettingsHome, 'keybindings.json').with({ scheme: Schemas.userData });
} else {
const appSettingsHome = URI.file('/User').with({ scheme: Schemas.userData });
this.settingsResource = joinPath(appSettingsHome, 'settings.json');
this.keybindingsResource = joinPath(appSettingsHome, 'keybindings.json');
}
this.keyboardLayoutResource = joinPath(this.appSettingsHome, 'keyboardLayout.json');
this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData });
this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json');
this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json');
this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json');
this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json');
this.logsPath = '/web/logs';
......@@ -104,9 +97,11 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
appNameLong: string;
appQuality?: string;
appSettingsHome: URI;
userRoamingDataHome: URI;
settingsResource: URI;
keybindingsResource: URI;
keyboardLayoutResource: URI;
localeResource: URI;
machineSettingsHome: URI;
machineSettingsResource: URI;
settingsSearchBuildId?: number;
......
......@@ -8,7 +8,6 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { memoize } from 'vs/base/common/decorators';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService {
......@@ -27,8 +26,5 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I
}
@memoize
get settingsResource(): URI { return joinPath(this.appSettingsHome, 'settings.json').with({ scheme: Schemas.userData }); }
@memoize
get keybindingsResource(): URI { return joinPath(this.appSettingsHome, 'keybindings.json').with({ scheme: Schemas.userData }); }
get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); }
}
......@@ -579,7 +579,6 @@ class UserKeybindings extends Disposable {
this._onDidChange.fire();
}
}), 50));
this._register(this.fileService.watch(this.keybindingsResource));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keybindingsResource))(() => this.reloadConfigurationScheduler.schedule()));
}
......
......@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, toDisposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping, IWindowsKeyboardMapping, KeymapInfo, IRawMixedKeyboardMapping, getKeyboardLayoutId, IKeymapInfo } from 'vs/workbench/services/keybinding/common/keymapInfo';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig';
......@@ -17,9 +17,8 @@ import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { URI } from 'vs/base/common/uri';
import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { IFileService } from 'vs/platform/files/common/files';
import { RunOnceScheduler } from 'vs/base/common/async';
import { dirname, isEqual } from 'vs/base/common/resources';
import { parse } from 'vs/base/common/json';
import * as objects from 'vs/base/common/objects';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -440,13 +439,11 @@ export class BrowserKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBa
}
class UserKeyboardLayout extends Disposable {
private readonly reloadConfigurationScheduler: RunOnceScheduler;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private fileWatcherDisposable: IDisposable = Disposable.None;
private directoryWatcherDisposable: IDisposable = Disposable.None;
private _keyboardLayout: KeymapInfo | null;
get keyboardLayout(): KeymapInfo | null { return this._keyboardLayout; }
......@@ -458,22 +455,16 @@ class UserKeyboardLayout extends Disposable {
this._keyboardLayout = null;
this._register(fileService.onFileChanges(e => this.handleFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(changed => {
if (changed) {
this._onDidChange.fire();
}
}), 50));
this._register(toDisposable(() => {
this.stopWatchingResource();
this.stopWatchingDirectory();
}));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keyboardLayoutResource))(() => this.reloadConfigurationScheduler.schedule()));
}
async initialize(): Promise<void> {
const exists = await this.fileService.exists(this.keyboardLayoutResource);
this.onResourceExists(exists);
await this.reload();
}
......@@ -492,57 +483,6 @@ class UserKeyboardLayout extends Disposable {
return existing ? !objects.equals(existing, this._keyboardLayout) : true;
}
private watchResource(): void {
this.fileWatcherDisposable = this.fileService.watch(this.keyboardLayoutResource);
}
private watchDirectory(): void {
const directory = dirname(this.keyboardLayoutResource);
this.directoryWatcherDisposable = this.fileService.watch(directory);
}
private stopWatchingResource(): void {
this.fileWatcherDisposable.dispose();
this.fileWatcherDisposable = Disposable.None;
}
private stopWatchingDirectory(): void {
this.directoryWatcherDisposable.dispose();
this.directoryWatcherDisposable = Disposable.None;
}
private async handleFileEvents(event: FileChangesEvent): Promise<void> {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect the resource
for (const event of events) {
affectedByChanges = isEqual(this.keyboardLayoutResource, event.resource);
if (affectedByChanges) {
if (event.type === FileChangeType.ADDED) {
this.onResourceExists(true);
} else if (event.type === FileChangeType.DELETED) {
this.onResourceExists(false);
}
break;
}
}
if (affectedByChanges) {
this.reloadConfigurationScheduler.schedule();
}
}
private onResourceExists(exists: boolean): void {
if (exists) {
this.stopWatchingDirectory();
this.watchResource();
} else {
this.stopWatchingResource();
this.watchDirectory();
}
}
}
class BrowserKeymapService extends Disposable implements IKeymapService {
......
......@@ -21,7 +21,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/resour
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
......@@ -45,13 +45,22 @@ 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 { parseArgs } from 'vs/platform/environment/node/argv';
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;
ctrlKey?: boolean;
......@@ -59,14 +68,6 @@ interface Modifiers {
shiftKey?: boolean;
}
class SettingsTestEnvironmentService extends WorkbenchEnvironmentService {
constructor(args: ParsedArgs, _execPath: string, private _appSettingsHome: URI) {
super(<IWindowConfiguration>args, _execPath);
}
get appSettingsHome(): URI { return this._appSettingsHome; }
}
suite('KeybindingsEditing', () => {
let instantiationService: TestInstantiationService;
......@@ -80,7 +81,7 @@ suite('KeybindingsEditing', () => {
instantiationService = new TestInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, URI.file(testDir));
const environmentService = new TestBackupEnvironmentService(URI.file(testDir));
instantiationService.stub(IEnvironmentService, environmentService);
instantiationService.stub(IConfigurationService, ConfigurationService);
instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' });
......@@ -98,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(dirname(environmentService.keybindingsResource), new FileUserDataProvider(environmentService.appSettingsHome, 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));
......
......@@ -785,12 +785,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// Check for locale file
if (isEqual(this.resource, joinPath(this.environmentService.appSettingsHome, 'locale.json'), !isLinux)) {
if (isEqual(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'locale.json'), !isLinux)) {
return 'locale';
}
// Check for snippets
if (isEqualOrParent(this.resource, joinPath(this.environmentService.appSettingsHome, 'snippets'))) {
if (isEqualOrParent(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'snippets'))) {
return 'snippets';
}
......
......@@ -390,7 +390,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
const defaultEncodingOverrides: IEncodingOverride[] = [];
// Global settings
defaultEncodingOverrides.push({ parent: this.environmentService.appSettingsHome, encoding: UTF8 });
defaultEncodingOverrides.push({ parent: this.environmentService.userRoamingDataHome, encoding: UTF8 });
// Workspace files
defaultEncodingOverrides.push({ extension: WORKSPACE_EXTENSION, encoding: UTF8 });
......
......@@ -4,79 +4,134 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
import { IFileService, FileChangesEvent } 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 { Schemas } from 'vs/base/common/network';
export class FileUserDataProvider extends Disposable implements IUserDataProvider {
export class FileUserDataProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability {
private _onDidChangeFile: Emitter<string[]> = this._register(new Emitter<string[]>());
readonly onDidChangeFile: Event<string[]> = 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.fileService.watch(this.userDataHome);
this._register(this.fileSystemProvider.watch(this.userDataHome, { recursive: false, excludes: [] }));
this._register(this.fileSystemProvider.onDidChangeFile(e => this.handleFileChanges(e)));
}
this._register(this.fileService.onFileChanges(e => this.handleFileChanges(e)));
watch(resource: URI, opts: IWatchOptions): IDisposable {
return this.fileSystemProvider.watch(this.toFileSystemResource(resource), opts);
}
private handleFileChanges(event: FileChangesEvent): void {
const changedPaths: string[] = [];
for (const change of event.changes) {
if (change.resource.scheme === this.userDataHome.scheme) {
const path = this.toPath(change.resource);
if (path) {
changedPaths.push(path);
}
}
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));
}
if (changedPaths.length) {
this._onDidChangeFile.fire(changedPaths);
throw new Error('not supported');
}
readdir(resource: URI): Promise<[string, FileType][]> {
return this.fileSystemProvider.readdir(this.toFileSystemResource(resource));
}
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
if (hasReadWriteCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.writeFile(this.toFileSystemResource(resource), content, opts);
}
throw new Error('not supported');
}
async readFile(path: string): Promise<Uint8Array> {
const resource = this.toResource(path);
try {
const content = await this.fileService.readFile(resource);
return content.value.buffer;
} catch (e) {
const exists = await this.fileService.exists(resource);
if (exists) {
throw e;
}
open(resource: URI, opts: FileOpenOptions): Promise<number> {
if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.open(this.toFileSystemResource(resource), opts);
}
return VSBuffer.fromString('').buffer;
throw new Error('not supported');
}
writeFile(path: string, value: Uint8Array): Promise<void> {
return this.fileService.writeFile(this.toResource(path), VSBuffer.wrap(value)).then(() => undefined);
close(fd: number): Promise<void> {
if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) {
return this.fileSystemProvider.close(fd);
}
throw new Error('not supported');
}
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');
}
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);
}
throw new Error('not supported');
}
async listFiles(path: string): Promise<string[]> {
const result = await this.fileService.resolve(this.toResource(path));
return result.children ? result.children.map(c => this.toPath(c.resource)!) : [];
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
return this.fileSystemProvider.delete(this.toFileSystemResource(resource), opts);
}
deleteFile(path: string): Promise<void> {
return this.fileService.del(this.toResource(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);
}
}
private toResource(path: string): URI {
return resources.joinPath(this.userDataHome, path);
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 {
const resourcePath = resource.toString();
const userDataHomePath = this.userDataHome.toString();
return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined;
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 null;
}
}
\ No newline at end of file
......@@ -5,14 +5,15 @@
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
import { IUserDataProvider, FileChangeEvent } from 'vs/workbench/services/userData/common/userData';
import { VSBuffer } from 'vs/base/common/buffer';
import { FileChangeType } from 'vs/platform/files/common/files';
export class InMemoryUserDataProvider extends Disposable implements IUserDataProvider {
_serviceBrand: any;
private _onDidChangeFile: Emitter<string[]> = this._register(new Emitter<string[]>());
readonly onDidChangeFile: Event<string[]> = this._onDidChangeFile.event;
private _onDidChangeFile: Emitter<FileChangeEvent[]> = this._register(new Emitter<FileChangeEvent[]>());
readonly onDidChangeFile: Event<FileChangeEvent[]> = this._onDidChangeFile.event;
private readonly store: Map<string, string> = new Map<string, string>();
......@@ -26,29 +27,25 @@ export class InMemoryUserDataProvider extends Disposable implements IUserDataPro
}
async readFile(path: string): Promise<Uint8Array> {
return VSBuffer.fromString(this.getValue(path)).buffer;
if (this.store.has(path)) {
return VSBuffer.fromString(this.store.get(path)!).buffer;
}
throw new Error(`Not Found: ${path}`);
}
async writeFile(path: string, value: Uint8Array): Promise<void> {
const exists = this.store.has(path);
const content = VSBuffer.wrap(value).toString();
if (content !== this.getValue(path)) {
if (content) {
this.store.set(path, content);
this._onDidChangeFile.fire([path]);
} else {
this.deleteFile(path);
}
if (!exists || content !== this.store.get(path)) {
this.store.set(path, content);
this._onDidChangeFile.fire([{ path, type: exists ? FileChangeType.UPDATED : FileChangeType.ADDED }]);
}
}
async deleteFile(path: string): Promise<void> {
if (this.store.has(path)) {
this.store.delete(path);
this._onDidChangeFile.fire([path]);
this._onDidChangeFile.fire([{ path, type: FileChangeType.DELETED }]);
}
}
private getValue(key: string): string {
return this.store.get(key) || '';
}
}
\ No newline at end of file
......@@ -4,6 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { FileChangeType } from 'vs/platform/files/common/files';
/**
* The event user data providers must use to signal a file change.
*/
export interface FileChangeEvent {
/**
* The type of change.
*/
readonly type: FileChangeType;
/**
* The path of the file that has changed.
*/
readonly path: string;
}
/**
* The userDataProvider is used to handle user specific application
......@@ -27,10 +44,9 @@ import { Event } from 'vs/base/common/event';
export interface IUserDataProvider {
/**
* Emitted when one ore more files are added, changed or deleted. The event provides
* an array of paths of these files.
* An event to signal that a file has been created, changed, or deleted.
*/
readonly onDidChangeFile: Event<string[]>;
readonly onDidChangeFile: Event<FileChangeEvent[]>;
/**
* Read the file contents of the given path.
......@@ -61,4 +77,4 @@ export interface IUserDataProvider {
* Throw an error if the path does not exist or points to a file.
*/
listFiles(path: string): Promise<string[]>;
}
\ 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, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { startsWith } from 'vs/base/common/strings';
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();
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
}
return this.userDataProvider.onDidChangeFile(e => {
if (new UserDataChangesEvent(e).contains(path)) {
this.versions.set(path, (this.versions.get(path) || 1) + 1);
this._onDidChangeFile.fire(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }]).changes);
}
});
}
async stat(resource: URI): Promise<IStat> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
}
return {
type: FileType.File,
ctime: 0,
mtime: this.versions.get(path) || 0,
size: 0
};
}
mkdir(resource: URI): Promise<void> { throw new Error('not supported'); }
delete(resource: URI, opts: FileDeleteOptions): 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(`Invalud user data resource ${resource}`);
}
return this.userDataProvider.readFile(path);
}
async readdir(resource: URI): Promise<[string, FileType][]> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
}
const children = await this.userDataProvider.listFiles(path);
return children.map(c => [c, FileType.Unknown]);
}
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
}
return this.userDataProvider.writeFile(path, content);
}
private toPath(resource: URI): string | undefined {
const resourcePath = resource.toString();
const userDataHomePath = this.userDataHome.toString();
return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined;
}
}
class UserDataChangesEvent {
private _pathsTree: TernarySearchTree<string> | undefined = undefined;
constructor(readonly paths: string[]) { }
private get pathsTree(): TernarySearchTree<string> {
if (!this._pathsTree) {
this._pathsTree = TernarySearchTree.forPaths<string>();
for (const path of this.paths) {
this._pathsTree.set(path, path);
}
}
return this._pathsTree;
}
contains(pathOrSegment: string): boolean {
return this.pathsTree.findSubstr(pathOrSegment) !== 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.
先完成此消息的编辑!
想要评论请 注册