From 3a9ca3d4c04c18a3013ebc76ade31b99f416a81c Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 17 Dec 2019 11:42:12 +0200 Subject: [PATCH 1/2] Make theia data folder configurable Signed-off-by: Mykola Morhun --- .../preferences/preference-configurations.ts | 13 ++++- .../test/mock-preference-provider.ts | 5 ++ .../browser/test/mock-env-variables-server.ts | 46 +++++++++++++++ .../env-variables/env-variables-protocol.ts | 5 ++ .../env-variables/env-variables-server.ts | 32 +++++++++- packages/core/src/node/file-uri.ts | 3 + .../browser/debug-configuration-manager.ts | 2 +- packages/java/src/node/java-contribution.ts | 7 ++- .../plugin-ext/src/common/plugin-api-rpc.ts | 9 ++- .../src/hosted/browser/hosted-plugin.ts | 35 +++++++++-- .../plugin-ext/src/main/browser/env-main.ts | 27 ++++++++- .../src/main/common/plugin-paths-protocol.ts | 2 - .../plugin-ext/src/main/node/paths/const.ts | 4 -- .../main/node/paths/plugin-paths-service.ts | 52 ++++++----------- .../main/node/plugins-key-value-storage.ts | 11 +++- .../src/plugin/env-variables-server-ext.ts | 58 +++++++++++++++++++ .../plugin-ext/src/plugin/plugin-manager.ts | 9 +-- .../browser/folders-preferences-provider.ts | 8 +-- .../src/browser/preferences-tree-widget.ts | 8 ++- .../src/browser/task-configuration-manager.ts | 2 +- .../task/src/browser/task-configurations.ts | 11 +++- .../user-storage-service-filesystem.spec.ts | 3 + .../user-storage-service-filesystem.ts | 26 ++++----- .../src/browser/quick-open-workspace.ts | 12 ++-- .../src/browser/workspace-service.spec.ts | 3 + .../src/browser/workspace-service.ts | 7 ++- packages/workspace/src/common/utils.ts | 11 +++- .../src/node/default-workspace-server.ts | 13 +++-- 28 files changed, 317 insertions(+), 107 deletions(-) create mode 100644 packages/core/src/browser/test/mock-env-variables-server.ts create mode 100644 packages/plugin-ext/src/plugin/env-variables-server-ext.ts diff --git a/packages/core/src/browser/preferences/preference-configurations.ts b/packages/core/src/browser/preferences/preference-configurations.ts index ada87a389c0c3..8615e0ab56383 100644 --- a/packages/core/src/browser/preferences/preference-configurations.ts +++ b/packages/core/src/browser/preferences/preference-configurations.ts @@ -17,6 +17,7 @@ import { injectable, inject, named, interfaces } from 'inversify'; import URI from '../../common/uri'; import { ContributionProvider, bindContributionProvider } from '../../common/contribution-provider'; +import { EnvVariablesServer } from '../../common/env-variables'; export const PreferenceConfiguration = Symbol('PreferenceConfiguration'); export interface PreferenceConfiguration { @@ -34,9 +35,12 @@ export class PreferenceConfigurations { @inject(ContributionProvider) @named(PreferenceConfiguration) protected readonly provider: ContributionProvider; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + /* prefer Theia over VS Code by default */ - getPaths(): string[] { - return ['.theia', '.vscode']; + async getPaths(): Promise { + return [await this.envServer.getDataFolderName(), '.vscode']; } getConfigName(): string { @@ -71,7 +75,10 @@ export class PreferenceConfigurations { return configUri.parent.path.base; } - createUri(folder: URI, configPath: string = this.getPaths()[0], configName: string = this.getConfigName()): URI { + async createUri(folder: URI, configPath: string, configName: string = this.getConfigName()): Promise { + if (!configPath) { + configPath = (await this.getPaths())[0]; + } return folder.resolve(configPath).resolve(configName + '.json'); } diff --git a/packages/core/src/browser/preferences/test/mock-preference-provider.ts b/packages/core/src/browser/preferences/test/mock-preference-provider.ts index c0c892167bfaa..ef65b265e6878 100644 --- a/packages/core/src/browser/preferences/test/mock-preference-provider.ts +++ b/packages/core/src/browser/preferences/test/mock-preference-provider.ts @@ -20,6 +20,8 @@ import { interfaces } from 'inversify'; import { PreferenceProvider } from '../'; import { PreferenceScope } from '../preference-scope'; import { PreferenceProviderDataChanges, PreferenceProviderDataChange } from '../preference-provider'; +import { EnvVariablesServer } from '../../../common/env-variables/env-variables-protocol'; +import { MockEnvVariablesServerImpl } from '../../test/mock-env-variables-server'; export class MockPreferenceProvider extends PreferenceProvider { readonly prefs: { [p: string]: any } = {}; @@ -50,6 +52,9 @@ export class MockPreferenceProvider extends PreferenceProvider { export function bindMockPreferenceProviders(bind: interfaces.Bind, unbind: interfaces.Unbind): void { unbind(PreferenceProvider); + // Needed for PreferenceConfigurations in PreferenceSchemaProvider + bind(EnvVariablesServer).to(MockEnvVariablesServerImpl).inSingletonScope(); + bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.User)).inSingletonScope().whenTargetNamed(PreferenceScope.User); bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.Workspace)).inSingletonScope().whenTargetNamed(PreferenceScope.Workspace); bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.Folder)).inSingletonScope().whenTargetNamed(PreferenceScope.Folder); diff --git a/packages/core/src/browser/test/mock-env-variables-server.ts b/packages/core/src/browser/test/mock-env-variables-server.ts new file mode 100644 index 0000000000000..7cf770a5ec0d4 --- /dev/null +++ b/packages/core/src/browser/test/mock-env-variables-server.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (C) 2020 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { EnvVariablesServer, EnvVariable } from '../../common/env-variables'; + +@injectable() +export class MockEnvVariablesServerImpl implements EnvVariablesServer { + async getDataFolderName(): Promise { + return '.theia'; + } + + async getUserHomeFolder(): Promise { + return 'file:///home/test'; + } + + async getUserDataFolder(): Promise { + return 'file:///home/test/.theia'; + } + + getExecPath(): Promise { + throw new Error('Method not implemented.'); + } + getVariables(): Promise { + throw new Error('Method not implemented.'); + } + getValue(key: string): Promise { + throw new Error('Method not implemented.'); + } + getAppDataFolder(): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/core/src/common/env-variables/env-variables-protocol.ts b/packages/core/src/common/env-variables/env-variables-protocol.ts index fca91897abdda..f60629f2dc56a 100644 --- a/packages/core/src/common/env-variables/env-variables-protocol.ts +++ b/packages/core/src/common/env-variables/env-variables-protocol.ts @@ -21,6 +21,11 @@ export interface EnvVariablesServer { getExecPath(): Promise getVariables(): Promise getValue(key: string): Promise + getUserHomeFolder(): Promise + getDataFolderName(): Promise + getUserDataFolder(): Promise + /** Windows specific. Returns system data folder of Theia. On other than Windows systems is the same as getUserDataFolder */ + getAppDataFolder(): Promise } export interface EnvVariable { diff --git a/packages/core/src/node/env-variables/env-variables-server.ts b/packages/core/src/node/env-variables/env-variables-server.ts index 166de48dedfed..128e5792b9257 100644 --- a/packages/core/src/node/env-variables/env-variables-server.ts +++ b/packages/core/src/node/env-variables/env-variables-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2018 Red Hat, Inc. and others. + * Copyright (C) 2018-2020 Red Hat, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import * as os from 'os'; import { injectable } from 'inversify'; import { EnvVariable, EnvVariablesServer } from '../../common/env-variables'; import { isWindows } from '../../common/os'; +import { FileUri } from '../file-uri'; + +const THEIA_DATA_FOLDER = '.theia'; + +const WINDOWS_APP_DATA_DIR = 'AppData'; +const WINDOWS_ROAMING_DIR = 'Roaming'; @injectable() export class EnvVariablesServerImpl implements EnvVariablesServer { @@ -44,4 +51,27 @@ export class EnvVariablesServerImpl implements EnvVariablesServer { } return this.envs[key]; } + + async getUserHomeFolder(): Promise { + return FileUri.create(os.homedir()).toString(); + } + + async getDataFolderName(): Promise { + return THEIA_DATA_FOLDER; + } + + async getUserDataFolder(): Promise { + return FileUri.create(await this.getUserHomeFolder()).resolve(await this.getDataFolderName()).toString(); + } + + async getAppDataFolder(): Promise { + const dataFolderUriBuilder = FileUri.create(await this.getUserHomeFolder()); + if (isWindows) { + dataFolderUriBuilder.resolve(WINDOWS_APP_DATA_DIR); + dataFolderUriBuilder.resolve(WINDOWS_ROAMING_DIR); + } + dataFolderUriBuilder.resolve(await this.getDataFolderName()); + return dataFolderUriBuilder.toString(); + } + } diff --git a/packages/core/src/node/file-uri.ts b/packages/core/src/node/file-uri.ts index 8878c6ae27539..3915620d8ce47 100644 --- a/packages/core/src/node/file-uri.ts +++ b/packages/core/src/node/file-uri.ts @@ -54,6 +54,9 @@ export namespace FileUri { return fsPathFromVsCodeUri + '\\'; } } + if (fsPathFromVsCodeUri.startsWith('/file:')) { + return fsPathFromVsCodeUri.substring('/file:'.length); + } return fsPathFromVsCodeUri; } } diff --git a/packages/debug/src/browser/debug-configuration-manager.ts b/packages/debug/src/browser/debug-configuration-manager.ts index ce25e327a44ac..3aec103b5310e 100644 --- a/packages/debug/src/browser/debug-configuration-manager.ts +++ b/packages/debug/src/browser/debug-configuration-manager.ts @@ -268,7 +268,7 @@ export class DebugConfigurationManager { if (configUri && configUri.path.base === 'launch.json') { uri = configUri; } else { // fallback - uri = new URI(model.workspaceFolderUri).resolve(`${this.preferenceConfigurations.getPaths()[0]}/launch.json`); + uri = new URI(model.workspaceFolderUri).resolve((await this.preferenceConfigurations.getPaths())[0] + '/launch.json'); } const debugType = await this.selectDebugType(); const configurations = debugType ? await this.provideDebugConfigurations(debugType, model.workspaceFolderUri) : []; diff --git a/packages/java/src/node/java-contribution.ts b/packages/java/src/node/java-contribution.ts index 4d36ce38991ac..727856fa2b552 100644 --- a/packages/java/src/node/java-contribution.ts +++ b/packages/java/src/node/java-contribution.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as os from 'os'; import * as path from 'path'; import * as glob from 'glob'; import { Socket } from 'net'; @@ -22,7 +21,8 @@ import { injectable, inject, named } from 'inversify'; import { Message, isRequestMessage } from 'vscode-ws-jsonrpc'; import { InitializeParams, InitializeRequest } from 'vscode-languageserver-protocol'; import { createSocketConnection } from 'vscode-ws-jsonrpc/lib/server'; -import { DEBUG_MODE } from '@theia/core/lib/node'; +import { DEBUG_MODE, FileUri } from '@theia/core/lib/node'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { IConnection, BaseLanguageServerContribution, LanguageServerStartOptions } from '@theia/languages/lib/node'; import { JAVA_LANGUAGE_ID, JAVA_LANGUAGE_NAME, JavaStartParams } from '../common'; import { JavaCliContribution } from './java-cli-contribution'; @@ -52,6 +52,7 @@ export class JavaContribution extends BaseLanguageServerContribution { protected readonly ready: Promise; constructor( + @inject(EnvVariablesServer) protected readonly envServer: EnvVariablesServer, @inject(JavaCliContribution) protected readonly cli: JavaCliContribution, @inject(ContributionProvider) @named(JavaExtensionContribution) protected readonly contributions: ContributionProvider @@ -103,7 +104,7 @@ export class JavaContribution extends BaseLanguageServerContribution { this.activeDataFolders.add(dataFolderSuffix); clientConnection.onClose(() => this.activeDataFolders.delete(dataFolderSuffix)); - const workspacePath = path.resolve(os.homedir(), '.theia', 'jdt.ls', '_ws_' + dataFolderSuffix); + const workspacePath = path.resolve(FileUri.fsPath(await this.envServer.getUserDataFolder()), 'jdt.ls', '_ws_' + dataFolderSuffix); const configuration = configurations.get(process.platform); if (!configuration) { throw new Error('Cannot find Java server configuration for ' + process.platform); diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 46f0eee498ebe..f83c55037757e 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -79,6 +79,7 @@ import { ArgumentProcessor } from '../plugin/command-registry'; import { MaybePromise } from '@theia/core/lib/common/types'; import { QuickOpenItem, QuickOpenItemOptions } from '@theia/core/lib/common/quick-open-model'; import { QuickTitleButton } from '@theia/core/lib/common/quick-open-model'; +import { EnvVariable } from '@theia/core/lib/common/env-variables'; export interface PreferenceData { [scope: number]: any; @@ -95,7 +96,7 @@ export interface Plugin { export interface ConfigStorage { hostLogPath: string; hostStoragePath?: string; - hostGlobalStoragePath?: string; + hostGlobalStoragePath: string; } export interface EnvInit { @@ -992,7 +993,13 @@ export interface DocumentsMain { export interface EnvMain { $getEnvVariable(envVarName: string): Promise; + $getAllEnvVariables(): Promise $getClientOperatingSystem(): Promise; + $getExecPath(): Promise + $getUserHomeFolderPath(): Promise + $getDataFolderName(): Promise + $getUserDataFolderPath(): Promise + $getAppDataPath(): Promise } export interface PreferenceRegistryMain { diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 2eae278986c0f..12a919fd8ed70 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -27,13 +27,13 @@ import { injectable, inject, interfaces, named, postConstruct } from 'inversify' import { PluginWorker } from '../../main/browser/plugin-worker'; import { PluginMetadata, getPluginId, HostedPluginServer, DeployedPlugin } from '../../common/plugin-protocol'; import { HostedPluginWatcher } from './hosted-plugin-watcher'; -import { MAIN_RPC_CONTEXT, PluginManagerExt } from '../../common/plugin-api-rpc'; +import { MAIN_RPC_CONTEXT, PluginManagerExt, ConfigStorage } from '../../common/plugin-api-rpc'; import { setUpPluginApi } from '../../main/browser/main-context'; import { RPCProtocol, RPCProtocolImpl } from '../../common/rpc-protocol'; import { Disposable, DisposableCollection, ILogger, ContributionProvider, CommandRegistry, WillExecuteCommandEvent, - CancellationTokenSource, JsonRpcProxy, ProgressService + CancellationTokenSource, JsonRpcProxy, ProgressService, Path } from '@theia/core'; import { PreferenceServiceImpl, PreferenceProviderProvider } from '@theia/core/lib/browser/preferences'; import { WorkspaceService } from '@theia/workspace/lib/browser'; @@ -47,6 +47,7 @@ import { Deferred } from '@theia/core/lib/common/promise-util'; import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { WaitUntilEvent } from '@theia/core/lib/common/event'; +import { FileSystem } from '@theia/filesystem/lib/common'; import { FileSearchService } from '@theia/file-search/lib/common/file-search-service'; import { Emitter, isCancelled } from '@theia/core'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @@ -56,6 +57,7 @@ import { WebviewEnvironment } from '../../main/browser/webview/webview-environme import { WebviewWidget } from '../../main/browser/webview/webview'; import { WidgetManager } from '@theia/core/lib/browser/widget-manager'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; export type PluginHost = 'frontend' | string; export type DebugActivationEvent = 'onDebugResolve' | 'onDebugInitialConfigurations' | 'onDebugAdapterProtocolTracker'; @@ -109,6 +111,9 @@ export class HostedPluginSupport { @inject(DebugConfigurationManager) protected readonly debugConfigurationManager: DebugConfigurationManager; + @inject(FileSystem) + protected readonly fileSystem: FileSystem; + @inject(FileSearchService) protected readonly fileSearchService: FileSearchService; @@ -136,6 +141,9 @@ export class HostedPluginSupport { @inject(TerminalService) protected readonly terminalService: TerminalService; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + private theiaReadyPromise: Promise; protected readonly managers = new Map(); @@ -330,15 +338,20 @@ export class HostedPluginSupport { let started = 0; const startPluginsMeasurement = this.createMeasurement('startPlugins'); - const [hostLogPath, hostStoragePath] = await Promise.all([ + const [hostLogPath, hostStoragePath, hostGlobalStoragePath] = await Promise.all([ this.pluginPathsService.getHostLogPath(), - this.getStoragePath() + this.getStoragePath(), + this.getHostGlobalStoragePath() ]); if (toDisconnect.disposed) { return; } const thenable: Promise[] = []; - const configStorage = { hostLogPath, hostStoragePath }; + const configStorage: ConfigStorage = { + hostLogPath: hostLogPath!, + hostStoragePath: hostStoragePath, + hostGlobalStoragePath: hostGlobalStoragePath! + }; for (const [host, hostContributions] of contributionsByHost) { const manager = await this.obtainManager(host, hostContributions, toDisconnect); if (!manager) { @@ -456,6 +469,18 @@ export class HostedPluginSupport { return this.pluginPathsService.getHostStoragePath(this.workspaceService.workspace, roots); } + protected async getHostGlobalStoragePath(): Promise { + const userDataFolderPath: string = (await this.fileSystem.getFsPath(await this.envServer.getUserDataFolder()))!; + const globalStorageFolderPath = new Path(userDataFolderPath).join('globalStorage').toString(); + + // Make sure that folder by the path exists + if (! await this.fileSystem.exists(globalStorageFolderPath)) { + await this.fileSystem.createFolder(globalStorageFolderPath); + } + + return globalStorageFolderPath; + } + async activateByEvent(activationEvent: string): Promise { if (this.activationEvents.has(activationEvent)) { return; diff --git a/packages/plugin-ext/src/main/browser/env-main.ts b/packages/plugin-ext/src/main/browser/env-main.ts index 55fd26eafcba2..5bd05e7870581 100644 --- a/packages/plugin-ext/src/main/browser/env-main.ts +++ b/packages/plugin-ext/src/main/browser/env-main.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { interfaces } from 'inversify'; -import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { EnvVariablesServer, EnvVariable } from '@theia/core/lib/common/env-variables'; import { RPCProtocol } from '../../common/rpc-protocol'; import { EnvMain } from '../../common/plugin-api-rpc'; import { QueryParameters } from '../../common/env'; @@ -23,6 +23,7 @@ import { isWindows, isOSX } from '@theia/core'; import { OperatingSystem } from '../../plugin/types-impl'; export class EnvMainImpl implements EnvMain { + private envVariableServer: EnvVariablesServer; constructor(rpc: RPCProtocol, container: interfaces.Container) { @@ -33,6 +34,10 @@ export class EnvMainImpl implements EnvMain { return this.envVariableServer.getValue(envVarName).then(result => result ? result.value : undefined); } + $getAllEnvVariables(): Promise { + return this.envVariableServer.getVariables(); + } + async $getClientOperatingSystem(): Promise { if (isWindows) { return OperatingSystem.Windows; @@ -42,6 +47,26 @@ export class EnvMainImpl implements EnvMain { } return OperatingSystem.Linux; } + + $getExecPath(): Promise { + return this.envVariableServer.getExecPath(); + } + + $getUserHomeFolderPath(): Promise { + return this.envVariableServer.getUserHomeFolder(); + } + + $getDataFolderName(): Promise { + return this.envVariableServer.getDataFolderName(); + } + + $getUserDataFolderPath(): Promise { + return this.envVariableServer.getUserDataFolder(); + } + + $getAppDataPath(): Promise { + return this.envVariableServer.getAppDataFolder(); + } } /** diff --git a/packages/plugin-ext/src/main/common/plugin-paths-protocol.ts b/packages/plugin-ext/src/main/common/plugin-paths-protocol.ts index 10f736bd6b934..dfee0cdb4d24a 100644 --- a/packages/plugin-ext/src/main/common/plugin-paths-protocol.ts +++ b/packages/plugin-ext/src/main/common/plugin-paths-protocol.ts @@ -25,6 +25,4 @@ export interface PluginPathsService { getHostLogPath(): Promise; /** Returns storage path for given workspace */ getHostStoragePath(workspace: FileStat | undefined, roots: FileStat[]): Promise; - /** Returns Theia data directory (one for all Theia workspaces, so doesn't change) */ - getTheiaDirPath(): Promise; } diff --git a/packages/plugin-ext/src/main/node/paths/const.ts b/packages/plugin-ext/src/main/node/paths/const.ts index 79290a5b961b9..00c134417484f 100644 --- a/packages/plugin-ext/src/main/node/paths/const.ts +++ b/packages/plugin-ext/src/main/node/paths/const.ts @@ -15,10 +15,6 @@ ********************************************************************************/ export namespace PluginPaths { - export const WINDOWS_APP_DATA_DIR = 'AppData'; - export const WINDOWS_ROAMING_DIR = 'Roaming'; - - export const THEIA_DIR = '.theia'; export const PLUGINS_LOGS_DIR = 'logs'; export const PLUGINS_GLOBAL_STORAGE_DIR = 'plugin-storage'; export const PLUGINS_WORKSPACE_STORAGE_DIR = 'workspace-storage'; diff --git a/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts b/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts index 7cc35b6b75882..8c8a15c45cd56 100644 --- a/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts +++ b/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts @@ -20,10 +20,12 @@ import * as path from 'path'; import { readdir, remove } from 'fs-extra'; import * as crypto from 'crypto'; import URI from '@theia/core/lib/common/uri'; -import { ILogger, isWindows } from '@theia/core'; +import { FileUri } from '@theia/core/lib/node'; +import { ILogger } from '@theia/core'; import { PluginPaths } from './const'; import { PluginPathsService } from '../../common/plugin-paths-protocol'; import { THEIA_EXT, VSCODE_EXT, getTemporaryWorkspaceFileUri } from '@theia/workspace/lib/common'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { PluginCliContribution } from '../plugin-cli-contribution'; const SESSION_TIMESTAMP_PATTERN = /^\d{8}T\d{6}$/; @@ -32,14 +34,15 @@ const SESSION_TIMESTAMP_PATTERN = /^\d{8}T\d{6}$/; @injectable() export class PluginPathsServiceImpl implements PluginPathsService { - private readonly windowsDataFolders = [PluginPaths.WINDOWS_APP_DATA_DIR, PluginPaths.WINDOWS_ROAMING_DIR]; - @inject(ILogger) protected readonly logger: ILogger; @inject(FileSystem) protected readonly fileSystem: FileSystem; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + @inject(PluginCliContribution) protected readonly cliContribution: PluginCliContribution; @@ -82,8 +85,7 @@ export class PluginPathsServiceImpl implements PluginPathsService { } protected async buildWorkspaceId(workspace: FileStat, roots: FileStat[]): Promise { - const homeDir = await this.getUserHomeDir(); - const untitledWorkspace = getTemporaryWorkspaceFileUri(new URI(homeDir)); + const untitledWorkspace = getTemporaryWorkspaceFileUri(await this.envServer.getUserDataFolder()); if (untitledWorkspace.toString() === workspace.uri) { // if workspace is temporary @@ -102,6 +104,16 @@ export class PluginPathsServiceImpl implements PluginPathsService { } } + private async getLogsDirPath(): Promise { + const theiaDirPath = FileUri.fsPath(await this.envServer.getUserDataFolder()); + return path.join(theiaDirPath, PluginPaths.PLUGINS_LOGS_DIR); + } + + private async getWorkspaceStorageDirPath(): Promise { + const theiaDirPath = FileUri.fsPath(await this.envServer.getUserDataFolder()); + return path.join(theiaDirPath, PluginPaths.PLUGINS_WORKSPACE_STORAGE_DIR); + } + /** * Generate time folder name in format: YYYYMMDDTHHMMSS, for example: 20181205T093828 */ @@ -115,34 +127,6 @@ export class PluginPathsServiceImpl implements PluginPathsService { return timeStamp; } - private async getLogsDirPath(): Promise { - const theiaDir = await this.getTheiaDirPath(); - return path.join(theiaDir, PluginPaths.PLUGINS_LOGS_DIR); - } - - private async getWorkspaceStorageDirPath(): Promise { - const theiaDir = await this.getTheiaDirPath(); - return path.join(theiaDir, PluginPaths.PLUGINS_WORKSPACE_STORAGE_DIR); - } - - async getTheiaDirPath(): Promise { - const homeDir = await this.getUserHomeDir(); - return path.join( - homeDir, - ...(isWindows ? this.windowsDataFolders : ['']), - PluginPaths.THEIA_DIR - ); - } - - private async getUserHomeDir(): Promise { - const homeDirStat = await this.fileSystem.getCurrentUserHome(); - if (!homeDirStat) { - throw new Error('Unable to get user home directory'); - } - const homeDirPath = await this.fileSystem.getFsPath(homeDirStat.uri); - return homeDirPath!; - } - private async cleanupOldLogs(parentLogsDir: string): Promise { // @ts-ignore - fs-extra types (Even latest version) is not updated with the `withFileTypes` option. const dirEntries = await readdir(parentLogsDir, { withFileTypes: true }); @@ -150,7 +134,7 @@ export class PluginPathsServiceImpl implements PluginPathsService { // However, upgrading the @types/node in theia to 10.11 (as defined in engine field) // Causes other packages to break in compilation, so we are using the infamous `any` type... // eslint-disable-next-line @typescript-eslint/no-explicit-any - const subDirEntries = dirEntries.filter((dirent: any) => dirent.isDirectory() ); + const subDirEntries = dirEntries.filter((dirent: any) => dirent.isDirectory()); // eslint-disable-next-line @typescript-eslint/no-explicit-any const subDirNames = subDirEntries.map((dirent: any) => dirent.name); // We never clean a folder that is not a Theia logs session folder. diff --git a/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts b/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts index 86459038602be..b2acde3cb5996 100644 --- a/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts +++ b/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts @@ -17,8 +17,10 @@ import { injectable, inject, postConstruct } from 'inversify'; import * as fs from 'fs-extra'; import * as path from 'path'; +import { FileUri } from '@theia/core/lib/node/file-uri'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { FileSystem } from '@theia/filesystem/lib/common'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { PluginPaths } from './paths/const'; import { PluginPathsService } from '../common/plugin-paths-protocol'; import { KeysToAnyValues, KeysToKeysToAnyValue } from '../../common/types'; @@ -32,15 +34,18 @@ export class PluginsKeyValueStorage { @inject(PluginPathsService) private readonly pluginPathsService: PluginPathsService; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + @inject(FileSystem) protected readonly fileSystem: FileSystem; @postConstruct() protected async init(): Promise { try { - const theiaDirPath = await this.pluginPathsService.getTheiaDirPath(); - await this.fileSystem.createFolder(theiaDirPath); - const globalDataPath = path.join(theiaDirPath, PluginPaths.PLUGINS_GLOBAL_STORAGE_DIR, 'global-state.json'); + const theiaDataFolderPath = FileUri.fsPath(await this.envServer.getUserDataFolder()); + await this.fileSystem.createFolder(theiaDataFolderPath); + const globalDataPath = path.join(theiaDataFolderPath, PluginPaths.PLUGINS_GLOBAL_STORAGE_DIR, 'global-state.json'); await this.fileSystem.createFolder(path.dirname(globalDataPath)); this.deferredGlobalDataPath.resolve(globalDataPath); } catch (e) { diff --git a/packages/plugin-ext/src/plugin/env-variables-server-ext.ts b/packages/plugin-ext/src/plugin/env-variables-server-ext.ts new file mode 100644 index 0000000000000..19872df120c32 --- /dev/null +++ b/packages/plugin-ext/src/plugin/env-variables-server-ext.ts @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { RPCProtocol } from '../common/rpc-protocol'; +import { PLUGIN_RPC_CONTEXT, EnvMain } from '../common'; +import { EnvVariablesServer, EnvVariable } from '@theia/core/lib/common/env-variables'; + +export class EnvVariablesServerExt implements EnvVariablesServer { + + protected readonly proxy: EnvMain; + + constructor(rpc: RPCProtocol) { + this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.ENV_MAIN); + } + + getExecPath(): Promise { + return this.proxy.$getExecPath(); + } + + getVariables(): Promise { + return this.proxy.$getAllEnvVariables(); + } + + async getValue(name: string): Promise { + const value = await this.proxy.$getEnvVariable(name); + return { name, value }; + } + + getUserHomeFolder(): Promise { + return this.proxy.$getUserHomeFolderPath(); + } + + getDataFolderName(): Promise { + return this.proxy.$getDataFolderName(); + } + + getUserDataFolder(): Promise { + return this.proxy.$getUserDataFolderPath(); + } + + getAppDataFolder(): Promise { + return this.proxy.$getAppDataPath(); + } + +} diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index 3e56b44f0514d..2916b0d9d6dcc 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -37,8 +37,6 @@ import { Memento, KeyValueStorageProxy } from './plugin-storage'; import { ExtPluginApi } from '../common/plugin-ext-api-contribution'; import { RPCProtocol } from '../common/rpc-protocol'; import { Emitter } from '@theia/core/lib/common/event'; -import * as os from 'os'; -import * as fs from 'fs-extra'; import { WebviewsExtImpl } from './webviews'; export interface PluginHost { @@ -317,12 +315,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { const asAbsolutePath = (relativePath: string): string => join(plugin.pluginFolder, relativePath); const logPath = join(configStorage.hostLogPath, plugin.model.id); // todo check format const storagePath = join(configStorage.hostStoragePath || '', plugin.model.id); - async function defaultGlobalStorage(): Promise { - const globalStorage = join(os.homedir(), '.theia', 'globalStorage'); - await fs.ensureDir(globalStorage); - return globalStorage; - } - const globalStoragePath = join(configStorage.hostGlobalStoragePath || (await defaultGlobalStorage()), plugin.model.id); + const globalStoragePath = join(configStorage.hostGlobalStoragePath, plugin.model.id); const pluginContext: theia.PluginContext = { extensionPath: plugin.pluginFolder, globalState: new Memento(plugin.model.id, true, this.storageProxy), diff --git a/packages/preferences/src/browser/folders-preferences-provider.ts b/packages/preferences/src/browser/folders-preferences-provider.ts index 48a3014decbd2..d3187249fe86e 100644 --- a/packages/preferences/src/browser/folders-preferences-provider.ts +++ b/packages/preferences/src/browser/folders-preferences-provider.ts @@ -41,7 +41,7 @@ export class FoldersPreferencesProvider extends PreferenceProvider { protected async init(): Promise { await this.workspaceService.roots; - this.updateProviders(); + await this.updateProviders(); this.workspaceService.onWorkspaceChanged(() => this.updateProviders()); const readyPromises: Promise[] = []; @@ -51,13 +51,13 @@ export class FoldersPreferencesProvider extends PreferenceProvider { Promise.all(readyPromises).then(() => this._ready.resolve()); } - protected updateProviders(): void { + protected async updateProviders(): Promise { const roots = this.workspaceService.tryGetRoots(); const toDelete = new Set(this.providers.keys()); for (const folder of roots) { - for (const configPath of this.configurations.getPaths()) { + for (const configPath of await this.configurations.getPaths()) { for (const configName of [...this.configurations.getSectionNames(), this.configurations.getConfigName()]) { - const configUri = this.configurations.createUri(new URI(folder.uri), configPath, configName); + const configUri = await this.configurations.createUri(new URI(folder.uri), configPath, configName); const key = configUri.toString(); toDelete.delete(key); if (!this.providers.has(key)) { diff --git a/packages/preferences/src/browser/preferences-tree-widget.ts b/packages/preferences/src/browser/preferences-tree-widget.ts index d74c113bc24bf..2135de3c9e33e 100644 --- a/packages/preferences/src/browser/preferences-tree-widget.ts +++ b/packages/preferences/src/browser/preferences-tree-widget.ts @@ -45,11 +45,12 @@ import { EditorWidget, EditorManager } from '@theia/editor/lib/browser'; import { DisposableCollection, Emitter, Event, MessageService } from '@theia/core'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { FileSystem, FileSystemUtils } from '@theia/filesystem/lib/common'; -import { UserStorageUri, THEIA_USER_STORAGE_FOLDER } from '@theia/userstorage/lib/browser'; +import { UserStorageUri } from '@theia/userstorage/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import URI from '@theia/core/lib/common/uri'; import { FoldersPreferencesProvider } from './folders-preferences-provider'; import { PreferenceConfigurations } from '@theia/core/lib/browser/preferences/preference-configurations'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; @injectable() export class PreferencesContainer extends SplitPanel implements ApplicationShell.TrackableWidgetProvider, Saveable { @@ -264,6 +265,9 @@ export class PreferencesEditorsContainer extends DockPanel { @inject(PreferenceProvider) @named(PreferenceScope.Workspace) protected readonly workspacePreferenceProvider: WorkspacePreferenceProvider; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + private userPreferenceEditorWidget: PreferencesEditorWidget; private workspacePreferenceEditorWidget: PreferencesEditorWidget | undefined; private foldersPreferenceEditorWidget: PreferencesEditorWidget | undefined; @@ -450,7 +454,7 @@ export class PreferencesEditorsContainer extends DockPanel { let uri = preferenceUri; if (preferenceUri.scheme === UserStorageUri.SCHEME && homeUri) { - uri = homeUri.resolve(THEIA_USER_STORAGE_FOLDER).resolve(preferenceUri.path); + uri = homeUri.resolve(await this.envServer.getDataFolderName()).resolve(preferenceUri.path); } return homeUri ? FileSystemUtils.tildifyPath(uri.path.toString(), homeUri.path.toString()) diff --git a/packages/task/src/browser/task-configuration-manager.ts b/packages/task/src/browser/task-configuration-manager.ts index b4fed15ea5a43..d0a41a8fae06d 100644 --- a/packages/task/src/browser/task-configuration-manager.ts +++ b/packages/task/src/browser/task-configuration-manager.ts @@ -171,7 +171,7 @@ export class TaskConfigurationManager { if (configUri && configUri.path.base === 'tasks.json') { uri = configUri; } else { // fallback - uri = new URI(model.workspaceFolderUri).resolve(`${this.preferenceConfigurations.getPaths()[0]}/tasks.json`); + uri = new URI(model.workspaceFolderUri).resolve((await this.preferenceConfigurations.getPaths())[0] + '/tasks.json'); } const fileStat = await this.filesystem.getFileStat(uri.toString()); diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index a063506c83a06..d926c01522adf 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -33,6 +33,7 @@ import URI from '@theia/core/lib/common/uri'; import { FileChange, FileChangeType } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { OpenerService } from '@theia/core/lib/browser'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; export interface TaskConfigurationClient { /** @@ -60,7 +61,7 @@ export class TaskConfigurations implements Disposable { protected taskCustomizationMap = new Map(); /** last directory element under which we look for task config */ - protected readonly TASKFILEPATH = '.theia'; + protected taskFilePath: string; /** task configuration file name */ protected readonly TASKFILE = 'tasks.json'; @@ -96,6 +97,9 @@ export class TaskConfigurations implements Disposable { @inject(TaskSourceResolver) protected readonly taskSourceResolver: TaskSourceResolver; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + constructor() { this.toDispose.push(Disposable.create(() => { this.tasksMap.clear(); @@ -106,7 +110,7 @@ export class TaskConfigurations implements Disposable { } @postConstruct() - protected init(): void { + protected async init(): Promise { this.toDispose.push( this.taskConfigurationManager.onDidChangeTaskConfig(async change => { try { @@ -119,6 +123,7 @@ export class TaskConfigurations implements Disposable { } }) ); + this.taskFilePath = await this.envServer.getDataFolderName(); this.reorganizeTasks(); this.toDispose.push(this.taskSchemaUpdater.onDidChangeTaskSchema(() => this.reorganizeTasks())); } @@ -241,7 +246,7 @@ export class TaskConfigurations implements Disposable { /** returns the string uri of where the config file would be, if it existed under a given root directory */ protected getConfigFileUri(rootDir: string): string { - return new URI(rootDir).resolve(this.TASKFILEPATH).resolve(this.TASKFILE).toString(); + return new URI(rootDir).resolve(this.taskFilePath).resolve(this.TASKFILE).toString(); } /** diff --git a/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts b/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts index b03d8880fcfc3..f7e9a91aa5151 100644 --- a/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts +++ b/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts @@ -29,6 +29,8 @@ import { PreferenceService } from '@theia/core/lib/browser/preferences'; import { MockPreferenceService } from '@theia/core/lib/browser/preferences/test/mock-preference-service'; import { FileSystemWatcherServer } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; import { MockFilesystem, MockFilesystemWatcherServer } from '@theia/filesystem/lib/common/test'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { MockEnvVariablesServerImpl } from '@theia/core/lib/browser/test/mock-env-variables-server'; import { UserStorageUri } from './user-storage-uri'; import URI from '@theia/core/lib/common/uri'; @@ -105,6 +107,7 @@ before(async () => { return fs; }).inSingletonScope(); + testContainer.bind(EnvVariablesServer).to(MockEnvVariablesServerImpl).inSingletonScope(); testContainer.bind(UserStorageService).to(UserStorageServiceFilesystemImpl); }); diff --git a/packages/userstorage/src/browser/user-storage-service-filesystem.ts b/packages/userstorage/src/browser/user-storage-service-filesystem.ts index f73cc77a4fdb0..f95993c03f417 100644 --- a/packages/userstorage/src/browser/user-storage-service-filesystem.ts +++ b/packages/userstorage/src/browser/user-storage-service-filesystem.ts @@ -18,38 +18,34 @@ import { DisposableCollection, ILogger, Emitter, Event } from '@theia/core/lib/c import { UserStorageChangeEvent, UserStorageService } from './user-storage-service'; import { injectable, inject } from 'inversify'; import { FileSystemWatcher, FileChangeEvent } from '@theia/filesystem/lib/browser/filesystem-watcher'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { FileSystem } from '@theia/filesystem/lib/common'; import URI from '@theia/core/lib/common/uri'; import { UserStorageUri } from './user-storage-uri'; -export const THEIA_USER_STORAGE_FOLDER = '.theia'; - @injectable() export class UserStorageServiceFilesystemImpl implements UserStorageService { protected readonly toDispose = new DisposableCollection(); protected readonly onUserStorageChangedEmitter = new Emitter(); - protected readonly userStorageFolder: Promise; + protected userStorageFolder: Promise; constructor( @inject(FileSystem) protected readonly fileSystem: FileSystem, @inject(FileSystemWatcher) protected readonly watcher: FileSystemWatcher, - @inject(ILogger) protected readonly logger: ILogger - + @inject(ILogger) protected readonly logger: ILogger, + @inject(EnvVariablesServer) protected readonly envServer: EnvVariablesServer ) { - this.userStorageFolder = this.fileSystem.getCurrentUserHome().then(home => { - if (home) { - const userStorageFolderUri = new URI(home.uri).resolve(THEIA_USER_STORAGE_FOLDER); - watcher.watchFileChanges(userStorageFolderUri).then(disposable => - this.toDispose.push(disposable) - ); - this.toDispose.push(this.watcher.onFilesChanged(changes => this.onDidFilesChanged(changes))); - return new URI(home.uri).resolve(THEIA_USER_STORAGE_FOLDER); - } + this.userStorageFolder = this.envServer.getUserDataFolder().then(userDataFolder => { + const userDataFolderUri = new URI(userDataFolder); + watcher.watchFileChanges(userDataFolderUri).then(disposable => + this.toDispose.push(disposable) + ); + this.toDispose.push(this.watcher.onFilesChanged(changes => this.onDidFilesChanged(changes))); + return userDataFolderUri; }); this.toDispose.push(this.onUserStorageChangedEmitter); - } dispose(): void { diff --git a/packages/workspace/src/browser/quick-open-workspace.ts b/packages/workspace/src/browser/quick-open-workspace.ts index 1264fe12387e6..bc09983f6c822 100644 --- a/packages/workspace/src/browser/quick-open-workspace.ts +++ b/packages/workspace/src/browser/quick-open-workspace.ts @@ -16,6 +16,7 @@ import { injectable, inject } from 'inversify'; import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenGroupItem, QuickOpenMode, LabelProvider } from '@theia/core/lib/browser'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { WorkspaceService } from './workspace-service'; import { getTemporaryWorkspaceFileUri } from '../common'; import { WorkspacePreferences } from './workspace-preferences'; @@ -34,15 +35,12 @@ export class QuickOpenWorkspace implements QuickOpenModel { @inject(FileSystem) protected readonly fileSystem: FileSystem; @inject(LabelProvider) protected readonly labelProvider: LabelProvider; @inject(WorkspacePreferences) protected preferences: WorkspacePreferences; + @inject(EnvVariablesServer) protected readonly envServer: EnvVariablesServer; async open(workspaces: string[]): Promise { this.items = []; - const homeStat = await this.fileSystem.getCurrentUserHome(); - const home = (homeStat) ? new URI(homeStat.uri).path.toString() : undefined; - let tempWorkspaceFile: URI | undefined; - if (home) { - tempWorkspaceFile = getTemporaryWorkspaceFileUri(new URI(home)); - } + const homeDirPath: string = (await this.fileSystem.getFsPath(await this.envServer.getUserHomeFolder()))!; + const tempWorkspaceFile = getTemporaryWorkspaceFileUri(await this.envServer.getUserDataFolder()); await this.preferences.ready; if (!workspaces.length) { this.items.push(new QuickOpenGroupItem({ @@ -64,7 +62,7 @@ export class QuickOpenWorkspace implements QuickOpenModel { const iconClass = icon === '' ? undefined : icon + ' file-icon'; this.items.push(new QuickOpenGroupItem({ label: uri.path.base, - description: (home) ? FileSystemUtils.tildifyPath(uri.path.toString(), home) : uri.path.toString(), + description: (homeDirPath) ? FileSystemUtils.tildifyPath(uri.path.toString(), homeDirPath) : uri.path.toString(), groupLabel: `last modified ${moment(stat.lastModification).fromNow()}`, iconClass, run: (mode: QuickOpenMode): boolean => { diff --git a/packages/workspace/src/browser/workspace-service.spec.ts b/packages/workspace/src/browser/workspace-service.spec.ts index 0ab1e744f9f59..6b2f25e9d9a3d 100644 --- a/packages/workspace/src/browser/workspace-service.spec.ts +++ b/packages/workspace/src/browser/workspace-service.spec.ts @@ -25,6 +25,8 @@ import { FileSystemNode } from '@theia/filesystem/lib/node/node-filesystem'; import { FileSystemWatcher, FileChangeEvent, FileChangeType } from '@theia/filesystem/lib/browser/filesystem-watcher'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { DefaultWindowService } from '@theia/core/lib/browser/window/default-window-service'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { MockEnvVariablesServerImpl } from '@theia/core/lib/browser/test/mock-env-variables-server'; import { WorkspaceServer } from '../common'; import { DefaultWorkspaceServer } from '../node/default-workspace-server'; import { Emitter, Disposable, DisposableCollection, ILogger, Logger } from '@theia/core'; @@ -107,6 +109,7 @@ describe('WorkspaceService', () => { testContainer.bind(WindowService).toConstantValue(mockWindowService); testContainer.bind(ILogger).toConstantValue(mockILogger); testContainer.bind(WorkspacePreferences).toConstantValue(mockPref); + testContainer.bind(EnvVariablesServer).to(MockEnvVariablesServerImpl); testContainer.bind(PreferenceServiceImpl).toConstantValue(mockPreferenceServiceImpl); testContainer.bind(PreferenceSchemaProvider).toConstantValue(mockPreferenceSchemaProvider); diff --git a/packages/workspace/src/browser/workspace-service.ts b/packages/workspace/src/browser/workspace-service.ts index eaf63830219f2..43c8d62aeaae1 100644 --- a/packages/workspace/src/browser/workspace-service.ts +++ b/packages/workspace/src/browser/workspace-service.ts @@ -24,6 +24,7 @@ import { FrontendApplicationContribution, PreferenceServiceImpl, PreferenceScope, PreferenceSchemaProvider } from '@theia/core/lib/browser'; import { Deferred } from '@theia/core/lib/common/promise-util'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { ILogger, Disposable, DisposableCollection, Emitter, Event, MaybePromise } from '@theia/core'; import { WorkspacePreferences } from './workspace-preferences'; import * as jsoncparser from 'jsonc-parser'; @@ -65,6 +66,9 @@ export class WorkspaceService implements FrontendApplicationContribution { @inject(PreferenceSchemaProvider) protected readonly schemaProvider: PreferenceSchemaProvider; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + protected applicationName: string; @postConstruct() @@ -376,8 +380,7 @@ export class WorkspaceService implements FrontendApplicationContribution { } protected async getUntitledWorkspace(): Promise { - const home = await this.fileSystem.getCurrentUserHome(); - return home && getTemporaryWorkspaceFileUri(new URI(home.uri)); + return getTemporaryWorkspaceFileUri(await this.envServer.getUserDataFolder()); } private async writeWorkspaceFile(workspaceFile: FileStat | undefined, workspaceData: WorkspaceData): Promise { diff --git a/packages/workspace/src/common/utils.ts b/packages/workspace/src/common/utils.ts index 7039c183ee987..375574021a569 100644 --- a/packages/workspace/src/common/utils.ts +++ b/packages/workspace/src/common/utils.ts @@ -19,6 +19,13 @@ import URI from '@theia/core/lib/common/uri'; export const THEIA_EXT = 'theia-workspace'; export const VSCODE_EXT = 'code-workspace'; -export function getTemporaryWorkspaceFileUri(home: URI): URI { - return home.resolve('.theia').resolve(`Untitled.${THEIA_EXT}`).withScheme('file'); +export function getTemporaryWorkspaceFileUri(userDataFolder: string | URI): URI { + let userDataFolderUri: URI; + if (typeof userDataFolder === 'string') { + userDataFolderUri = new URI(userDataFolder); + } else { + userDataFolderUri = userDataFolder; + } + + return userDataFolderUri.resolve(`Untitled.${THEIA_EXT}`).withScheme('file'); } diff --git a/packages/workspace/src/node/default-workspace-server.ts b/packages/workspace/src/node/default-workspace-server.ts index f6e35ca98b350..e6821b52e9d07 100644 --- a/packages/workspace/src/node/default-workspace-server.ts +++ b/packages/workspace/src/node/default-workspace-server.ts @@ -17,7 +17,6 @@ import * as path from 'path'; import * as yargs from 'yargs'; import * as fs from 'fs-extra'; -import * as os from 'os'; import * as jsoncparser from 'jsonc-parser'; import { injectable, inject, postConstruct } from 'inversify'; @@ -25,6 +24,7 @@ import { FileUri } from '@theia/core/lib/node'; import { CliContribution } from '@theia/core/lib/node/cli'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { WorkspaceServer } from '../common'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; @injectable() export class WorkspaceCliContribution implements CliContribution { @@ -66,6 +66,9 @@ export class DefaultWorkspaceServer implements WorkspaceServer { @inject(WorkspaceCliContribution) protected readonly cliParams: WorkspaceCliContribution; + @inject(EnvVariablesServer) + protected readonly envServer: EnvVariablesServer; + @postConstruct() protected async init(): Promise { const root = await this.getRoot(); @@ -134,7 +137,7 @@ export class DefaultWorkspaceServer implements WorkspaceServer { * @param uri most recently used uri */ protected async writeToUserHome(data: RecentWorkspacePathsData): Promise { - const file = this.getUserStoragePath(); + const file = await this.getUserStoragePath(); await this.writeToFile(file, data); } @@ -149,7 +152,7 @@ export class DefaultWorkspaceServer implements WorkspaceServer { * Reads the most recently used workspace root from the user's home directory. */ protected async readRecentWorkspacePathsFromUserHome(): Promise { - const filePath = this.getUserStoragePath(); + const filePath = await this.getUserStoragePath(); const data = await this.readJsonFromFile(filePath); return RecentWorkspacePathsData.is(data) ? data : undefined; } @@ -162,8 +165,8 @@ export class DefaultWorkspaceServer implements WorkspaceServer { } } - protected getUserStoragePath(): string { - return path.resolve(os.homedir(), '.theia', 'recentworkspace.json'); + protected async getUserStoragePath(): Promise { + return path.resolve(FileUri.fsPath(await this.envServer.getUserDataFolder()), 'recentworkspace.json'); } } From 6c87fa8696d4f47c83ce0d12feaeb5b6e6a3f429 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Tue, 25 Feb 2020 13:42:40 +0100 Subject: [PATCH 2/2] Use the config dir for all settings and extensions Closes #4488. Signed-off-by: Akos Kitta --- CHANGELOG.md | 27 +++++++++ packages/core/src/browser/label-provider.ts | 2 +- .../preferences/preference-configurations.ts | 13 +---- .../test/mock-preference-provider.ts | 5 -- .../browser/test/mock-env-variables-server.ts | 20 +++---- .../env-variables/env-variables-protocol.ts | 6 +- .../env-variables/env-variables-server.ts | 31 ++-------- packages/core/src/node/file-uri.ts | 3 - .../browser/debug-configuration-manager.ts | 2 +- packages/java/src/node/java-contribution.ts | 4 +- .../plugin-ext/src/common/plugin-api-rpc.ts | 7 --- .../src/hosted/browser/hosted-plugin.ts | 28 +++++---- .../plugin-ext/src/main/browser/env-main.ts | 27 +-------- .../main/node/paths/plugin-paths-service.ts | 24 ++++---- .../main/node/plugins-key-value-storage.ts | 19 +++--- .../src/plugin/env-variables-server-ext.ts | 58 ------------------- .../browser/folders-preferences-provider.ts | 8 +-- .../src/browser/preferences-tree-widget.ts | 7 +-- .../src/browser/task-configuration-manager.ts | 2 +- .../task/src/browser/task-configurations.ts | 19 +----- .../user-storage-service-filesystem.spec.ts | 39 ++++++------- .../user-storage-service-filesystem.ts | 8 ++- .../src/browser/quick-open-workspace.ts | 11 ++-- .../src/browser/workspace-service.spec.ts | 6 +- .../src/browser/workspace-service.ts | 4 +- packages/workspace/src/common/utils.ts | 13 ++--- .../src/node/default-workspace-server.ts | 25 ++++---- 27 files changed, 150 insertions(+), 268 deletions(-) delete mode 100644 packages/plugin-ext/src/plugin/env-variables-server-ext.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e06f4d6ea8297..5a82bf1a4660b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,33 @@ - `test:references`: fails if typescript references are out of sync. - `prepare:references`: updates typescript references, if required. - [repo] the `prepare` script now updates typescript references. +- [core] From now on, downstream projects can refine where the configuration files (such as `settings.json`, `keymaps.json`, `recentworkspace.json`, etc.) will be stored by Theia. [#4488](https://github.com/eclipse-theia/theia/pull/4488) +The default location remains the same: `~/.theia`, however it can be customized by overriding the `#getConfigDirUri` method of the `EnvVariablesServer` API. The easiest way is to subclass the `EnvVariablesServerImpl` and rebind it in your backend module: + ```ts + // your-env-variables-server.ts: + + import { injectable } from 'inversify'; + import { EnvVariablesServerImpl } from '@theia/core/lib/node/env-variables'; + + @injectable() + export class YourEnvVariableServer extends EnvVariablesServerImpl { + + async getConfigDirUri(): Promise { + return 'file:///path/to/your/desired/config/dir'; + } + + } + + // your-backend-application-module.ts: + + import { ContainerModule } from 'inversify'; + import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; + import { YourEnvVariableServer } from './your-env-variables-server'; + + export default new ContainerModule((bind, unbind, isBound, rebind) => { + rebind(EnvVariablesServer).to(YourEnvVariableServer).inSingletonScope(); + }); + ``` Breaking changes: diff --git a/packages/core/src/browser/label-provider.ts b/packages/core/src/browser/label-provider.ts index ec548bce8f89c..055dd4b843b31 100644 --- a/packages/core/src/browser/label-provider.ts +++ b/packages/core/src/browser/label-provider.ts @@ -78,7 +78,7 @@ export interface LabelProviderContribution { /** * Check whether the given element is affected by the given change event. * Contributions delegating to the label provider can use this hook - * to perfrom a recursive check. + * to perform a recursive check. */ affects?(element: object, event: DidChangeLabelEvent): boolean; diff --git a/packages/core/src/browser/preferences/preference-configurations.ts b/packages/core/src/browser/preferences/preference-configurations.ts index 8615e0ab56383..ada87a389c0c3 100644 --- a/packages/core/src/browser/preferences/preference-configurations.ts +++ b/packages/core/src/browser/preferences/preference-configurations.ts @@ -17,7 +17,6 @@ import { injectable, inject, named, interfaces } from 'inversify'; import URI from '../../common/uri'; import { ContributionProvider, bindContributionProvider } from '../../common/contribution-provider'; -import { EnvVariablesServer } from '../../common/env-variables'; export const PreferenceConfiguration = Symbol('PreferenceConfiguration'); export interface PreferenceConfiguration { @@ -35,12 +34,9 @@ export class PreferenceConfigurations { @inject(ContributionProvider) @named(PreferenceConfiguration) protected readonly provider: ContributionProvider; - @inject(EnvVariablesServer) - protected readonly envServer: EnvVariablesServer; - /* prefer Theia over VS Code by default */ - async getPaths(): Promise { - return [await this.envServer.getDataFolderName(), '.vscode']; + getPaths(): string[] { + return ['.theia', '.vscode']; } getConfigName(): string { @@ -75,10 +71,7 @@ export class PreferenceConfigurations { return configUri.parent.path.base; } - async createUri(folder: URI, configPath: string, configName: string = this.getConfigName()): Promise { - if (!configPath) { - configPath = (await this.getPaths())[0]; - } + createUri(folder: URI, configPath: string = this.getPaths()[0], configName: string = this.getConfigName()): URI { return folder.resolve(configPath).resolve(configName + '.json'); } diff --git a/packages/core/src/browser/preferences/test/mock-preference-provider.ts b/packages/core/src/browser/preferences/test/mock-preference-provider.ts index ef65b265e6878..c0c892167bfaa 100644 --- a/packages/core/src/browser/preferences/test/mock-preference-provider.ts +++ b/packages/core/src/browser/preferences/test/mock-preference-provider.ts @@ -20,8 +20,6 @@ import { interfaces } from 'inversify'; import { PreferenceProvider } from '../'; import { PreferenceScope } from '../preference-scope'; import { PreferenceProviderDataChanges, PreferenceProviderDataChange } from '../preference-provider'; -import { EnvVariablesServer } from '../../../common/env-variables/env-variables-protocol'; -import { MockEnvVariablesServerImpl } from '../../test/mock-env-variables-server'; export class MockPreferenceProvider extends PreferenceProvider { readonly prefs: { [p: string]: any } = {}; @@ -52,9 +50,6 @@ export class MockPreferenceProvider extends PreferenceProvider { export function bindMockPreferenceProviders(bind: interfaces.Bind, unbind: interfaces.Unbind): void { unbind(PreferenceProvider); - // Needed for PreferenceConfigurations in PreferenceSchemaProvider - bind(EnvVariablesServer).to(MockEnvVariablesServerImpl).inSingletonScope(); - bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.User)).inSingletonScope().whenTargetNamed(PreferenceScope.User); bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.Workspace)).inSingletonScope().whenTargetNamed(PreferenceScope.Workspace); bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.Folder)).inSingletonScope().whenTargetNamed(PreferenceScope.Folder); diff --git a/packages/core/src/browser/test/mock-env-variables-server.ts b/packages/core/src/browser/test/mock-env-variables-server.ts index 7cf770a5ec0d4..350bb243e8806 100644 --- a/packages/core/src/browser/test/mock-env-variables-server.ts +++ b/packages/core/src/browser/test/mock-env-variables-server.ts @@ -14,33 +14,27 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; +import URI from '../../common/uri'; import { EnvVariablesServer, EnvVariable } from '../../common/env-variables'; -@injectable() export class MockEnvVariablesServerImpl implements EnvVariablesServer { - async getDataFolderName(): Promise { - return '.theia'; - } - async getUserHomeFolder(): Promise { - return 'file:///home/test'; - } + constructor(protected readonly configDirUri: URI) { } - async getUserDataFolder(): Promise { - return 'file:///home/test/.theia'; + async getConfigDirUri(): Promise { + return this.configDirUri.toString(); } getExecPath(): Promise { throw new Error('Method not implemented.'); } + getVariables(): Promise { throw new Error('Method not implemented.'); } + getValue(key: string): Promise { throw new Error('Method not implemented.'); } - getAppDataFolder(): Promise { - throw new Error('Method not implemented.'); - } + } diff --git a/packages/core/src/common/env-variables/env-variables-protocol.ts b/packages/core/src/common/env-variables/env-variables-protocol.ts index f60629f2dc56a..167f0b2a2e1e9 100644 --- a/packages/core/src/common/env-variables/env-variables-protocol.ts +++ b/packages/core/src/common/env-variables/env-variables-protocol.ts @@ -21,11 +21,7 @@ export interface EnvVariablesServer { getExecPath(): Promise getVariables(): Promise getValue(key: string): Promise - getUserHomeFolder(): Promise - getDataFolderName(): Promise - getUserDataFolder(): Promise - /** Windows specific. Returns system data folder of Theia. On other than Windows systems is the same as getUserDataFolder */ - getAppDataFolder(): Promise + getConfigDirUri(): Promise; } export interface EnvVariable { diff --git a/packages/core/src/node/env-variables/env-variables-server.ts b/packages/core/src/node/env-variables/env-variables-server.ts index 128e5792b9257..165fc05c3d2d9 100644 --- a/packages/core/src/node/env-variables/env-variables-server.ts +++ b/packages/core/src/node/env-variables/env-variables-server.ts @@ -14,21 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as os from 'os'; +import { join } from 'path'; +import { homedir } from 'os'; import { injectable } from 'inversify'; import { EnvVariable, EnvVariablesServer } from '../../common/env-variables'; import { isWindows } from '../../common/os'; import { FileUri } from '../file-uri'; -const THEIA_DATA_FOLDER = '.theia'; - -const WINDOWS_APP_DATA_DIR = 'AppData'; -const WINDOWS_ROAMING_DIR = 'Roaming'; - @injectable() export class EnvVariablesServerImpl implements EnvVariablesServer { protected readonly envs: { [key: string]: EnvVariable } = {}; + protected readonly configDirUri = FileUri.create(join(homedir(), '.theia')).toString(); constructor() { const prEnv = process.env; @@ -52,26 +49,8 @@ export class EnvVariablesServerImpl implements EnvVariablesServer { return this.envs[key]; } - async getUserHomeFolder(): Promise { - return FileUri.create(os.homedir()).toString(); - } - - async getDataFolderName(): Promise { - return THEIA_DATA_FOLDER; - } - - async getUserDataFolder(): Promise { - return FileUri.create(await this.getUserHomeFolder()).resolve(await this.getDataFolderName()).toString(); - } - - async getAppDataFolder(): Promise { - const dataFolderUriBuilder = FileUri.create(await this.getUserHomeFolder()); - if (isWindows) { - dataFolderUriBuilder.resolve(WINDOWS_APP_DATA_DIR); - dataFolderUriBuilder.resolve(WINDOWS_ROAMING_DIR); - } - dataFolderUriBuilder.resolve(await this.getDataFolderName()); - return dataFolderUriBuilder.toString(); + async getConfigDirUri(): Promise { + return this.configDirUri; } } diff --git a/packages/core/src/node/file-uri.ts b/packages/core/src/node/file-uri.ts index 3915620d8ce47..8878c6ae27539 100644 --- a/packages/core/src/node/file-uri.ts +++ b/packages/core/src/node/file-uri.ts @@ -54,9 +54,6 @@ export namespace FileUri { return fsPathFromVsCodeUri + '\\'; } } - if (fsPathFromVsCodeUri.startsWith('/file:')) { - return fsPathFromVsCodeUri.substring('/file:'.length); - } return fsPathFromVsCodeUri; } } diff --git a/packages/debug/src/browser/debug-configuration-manager.ts b/packages/debug/src/browser/debug-configuration-manager.ts index 3aec103b5310e..ce25e327a44ac 100644 --- a/packages/debug/src/browser/debug-configuration-manager.ts +++ b/packages/debug/src/browser/debug-configuration-manager.ts @@ -268,7 +268,7 @@ export class DebugConfigurationManager { if (configUri && configUri.path.base === 'launch.json') { uri = configUri; } else { // fallback - uri = new URI(model.workspaceFolderUri).resolve((await this.preferenceConfigurations.getPaths())[0] + '/launch.json'); + uri = new URI(model.workspaceFolderUri).resolve(`${this.preferenceConfigurations.getPaths()[0]}/launch.json`); } const debugType = await this.selectDebugType(); const configurations = debugType ? await this.provideDebugConfigurations(debugType, model.workspaceFolderUri) : []; diff --git a/packages/java/src/node/java-contribution.ts b/packages/java/src/node/java-contribution.ts index 727856fa2b552..217a72f56b370 100644 --- a/packages/java/src/node/java-contribution.ts +++ b/packages/java/src/node/java-contribution.ts @@ -104,7 +104,9 @@ export class JavaContribution extends BaseLanguageServerContribution { this.activeDataFolders.add(dataFolderSuffix); clientConnection.onClose(() => this.activeDataFolders.delete(dataFolderSuffix)); - const workspacePath = path.resolve(FileUri.fsPath(await this.envServer.getUserDataFolder()), 'jdt.ls', '_ws_' + dataFolderSuffix); + const configDirUri = await this.envServer.getConfigDirUri(); + const configDirFsPath = FileUri.fsPath(configDirUri); + const workspacePath = path.resolve(configDirFsPath, 'jdt.ls', '_ws_' + dataFolderSuffix); const configuration = configurations.get(process.platform); if (!configuration) { throw new Error('Cannot find Java server configuration for ' + process.platform); diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index f83c55037757e..7d5c500590acf 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -79,7 +79,6 @@ import { ArgumentProcessor } from '../plugin/command-registry'; import { MaybePromise } from '@theia/core/lib/common/types'; import { QuickOpenItem, QuickOpenItemOptions } from '@theia/core/lib/common/quick-open-model'; import { QuickTitleButton } from '@theia/core/lib/common/quick-open-model'; -import { EnvVariable } from '@theia/core/lib/common/env-variables'; export interface PreferenceData { [scope: number]: any; @@ -993,13 +992,7 @@ export interface DocumentsMain { export interface EnvMain { $getEnvVariable(envVarName: string): Promise; - $getAllEnvVariables(): Promise $getClientOperatingSystem(): Promise; - $getExecPath(): Promise - $getUserHomeFolderPath(): Promise - $getDataFolderName(): Promise - $getUserDataFolderPath(): Promise - $getAppDataPath(): Promise } export interface PreferenceRegistryMain { diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 12a919fd8ed70..645340e7fecbf 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -33,7 +33,7 @@ import { RPCProtocol, RPCProtocolImpl } from '../../common/rpc-protocol'; import { Disposable, DisposableCollection, ILogger, ContributionProvider, CommandRegistry, WillExecuteCommandEvent, - CancellationTokenSource, JsonRpcProxy, ProgressService, Path + CancellationTokenSource, JsonRpcProxy, ProgressService } from '@theia/core'; import { PreferenceServiceImpl, PreferenceProviderProvider } from '@theia/core/lib/browser/preferences'; import { WorkspaceService } from '@theia/workspace/lib/browser'; @@ -58,6 +58,7 @@ import { WebviewWidget } from '../../main/browser/webview/webview'; import { WidgetManager } from '@theia/core/lib/browser/widget-manager'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import URI from '@theia/core/lib/common/uri'; export type PluginHost = 'frontend' | string; export type DebugActivationEvent = 'onDebugResolve' | 'onDebugInitialConfigurations' | 'onDebugAdapterProtocolTracker'; @@ -348,9 +349,9 @@ export class HostedPluginSupport { } const thenable: Promise[] = []; const configStorage: ConfigStorage = { - hostLogPath: hostLogPath!, - hostStoragePath: hostStoragePath, - hostGlobalStoragePath: hostGlobalStoragePath! + hostLogPath, + hostStoragePath, + hostGlobalStoragePath }; for (const [host, hostContributions] of contributionsByHost) { const manager = await this.obtainManager(host, hostContributions, toDisconnect); @@ -470,15 +471,18 @@ export class HostedPluginSupport { } protected async getHostGlobalStoragePath(): Promise { - const userDataFolderPath: string = (await this.fileSystem.getFsPath(await this.envServer.getUserDataFolder()))!; - const globalStorageFolderPath = new Path(userDataFolderPath).join('globalStorage').toString(); + const configDirUri = await this.envServer.getConfigDirUri(); + const globalStorageFolderUri = new URI(configDirUri).resolve('globalStorage').toString(); // Make sure that folder by the path exists - if (! await this.fileSystem.exists(globalStorageFolderPath)) { - await this.fileSystem.createFolder(globalStorageFolderPath); + if (!await this.fileSystem.exists(globalStorageFolderUri)) { + await this.fileSystem.createFolder(globalStorageFolderUri); } - - return globalStorageFolderPath; + const globalStorageFolderFsPath = await this.fileSystem.getFsPath(globalStorageFolderUri); + if (!globalStorageFolderFsPath) { + throw new Error(`Could not resolve the FS path for URI: ${globalStorageFolderUri}`); + } + return globalStorageFolderFsPath; } async activateByEvent(activationEvent: string): Promise { @@ -624,8 +628,8 @@ export class HostedPluginSupport { protected logMeasurement(prefix: string, count: number, measurement: () => number): void { const duration = measurement(); if (duration === Number.NaN) { - // Measurement was prevented by native API, do not log NaN duration - return; + // Measurement was prevented by native API, do not log NaN duration + return; } const pluginCount = `${count} plugin${count === 1 ? '' : 's'}`; diff --git a/packages/plugin-ext/src/main/browser/env-main.ts b/packages/plugin-ext/src/main/browser/env-main.ts index 5bd05e7870581..55fd26eafcba2 100644 --- a/packages/plugin-ext/src/main/browser/env-main.ts +++ b/packages/plugin-ext/src/main/browser/env-main.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { interfaces } from 'inversify'; -import { EnvVariablesServer, EnvVariable } from '@theia/core/lib/common/env-variables'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { RPCProtocol } from '../../common/rpc-protocol'; import { EnvMain } from '../../common/plugin-api-rpc'; import { QueryParameters } from '../../common/env'; @@ -23,7 +23,6 @@ import { isWindows, isOSX } from '@theia/core'; import { OperatingSystem } from '../../plugin/types-impl'; export class EnvMainImpl implements EnvMain { - private envVariableServer: EnvVariablesServer; constructor(rpc: RPCProtocol, container: interfaces.Container) { @@ -34,10 +33,6 @@ export class EnvMainImpl implements EnvMain { return this.envVariableServer.getValue(envVarName).then(result => result ? result.value : undefined); } - $getAllEnvVariables(): Promise { - return this.envVariableServer.getVariables(); - } - async $getClientOperatingSystem(): Promise { if (isWindows) { return OperatingSystem.Windows; @@ -47,26 +42,6 @@ export class EnvMainImpl implements EnvMain { } return OperatingSystem.Linux; } - - $getExecPath(): Promise { - return this.envVariableServer.getExecPath(); - } - - $getUserHomeFolderPath(): Promise { - return this.envVariableServer.getUserHomeFolder(); - } - - $getDataFolderName(): Promise { - return this.envVariableServer.getDataFolderName(); - } - - $getUserDataFolderPath(): Promise { - return this.envVariableServer.getUserDataFolder(); - } - - $getAppDataPath(): Promise { - return this.envVariableServer.getAppDataFolder(); - } } /** diff --git a/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts b/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts index 8c8a15c45cd56..12532d521f7a1 100644 --- a/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts +++ b/packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts @@ -20,8 +20,8 @@ import * as path from 'path'; import { readdir, remove } from 'fs-extra'; import * as crypto from 'crypto'; import URI from '@theia/core/lib/common/uri'; -import { FileUri } from '@theia/core/lib/node'; import { ILogger } from '@theia/core'; +import { FileUri } from '@theia/core/lib/node'; import { PluginPaths } from './const'; import { PluginPathsService } from '../../common/plugin-paths-protocol'; import { THEIA_EXT, VSCODE_EXT, getTemporaryWorkspaceFileUri } from '@theia/workspace/lib/common'; @@ -85,7 +85,7 @@ export class PluginPathsServiceImpl implements PluginPathsService { } protected async buildWorkspaceId(workspace: FileStat, roots: FileStat[]): Promise { - const untitledWorkspace = getTemporaryWorkspaceFileUri(await this.envServer.getUserDataFolder()); + const untitledWorkspace = await getTemporaryWorkspaceFileUri(this.envServer); if (untitledWorkspace.toString() === workspace.uri) { // if workspace is temporary @@ -104,16 +104,6 @@ export class PluginPathsServiceImpl implements PluginPathsService { } } - private async getLogsDirPath(): Promise { - const theiaDirPath = FileUri.fsPath(await this.envServer.getUserDataFolder()); - return path.join(theiaDirPath, PluginPaths.PLUGINS_LOGS_DIR); - } - - private async getWorkspaceStorageDirPath(): Promise { - const theiaDirPath = FileUri.fsPath(await this.envServer.getUserDataFolder()); - return path.join(theiaDirPath, PluginPaths.PLUGINS_WORKSPACE_STORAGE_DIR); - } - /** * Generate time folder name in format: YYYYMMDDTHHMMSS, for example: 20181205T093828 */ @@ -127,6 +117,16 @@ export class PluginPathsServiceImpl implements PluginPathsService { return timeStamp; } + private async getLogsDirPath(): Promise { + const configDirUri = await this.envServer.getConfigDirUri(); + return path.join(FileUri.fsPath(configDirUri), PluginPaths.PLUGINS_LOGS_DIR); + } + + private async getWorkspaceStorageDirPath(): Promise { + const configDirUri = await this.envServer.getConfigDirUri(); + return path.join(FileUri.fsPath(configDirUri), PluginPaths.PLUGINS_WORKSPACE_STORAGE_DIR); + } + private async cleanupOldLogs(parentLogsDir: string): Promise { // @ts-ignore - fs-extra types (Even latest version) is not updated with the `withFileTypes` option. const dirEntries = await readdir(parentLogsDir, { withFileTypes: true }); diff --git a/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts b/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts index b2acde3cb5996..e52bde12afb93 100644 --- a/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts +++ b/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts @@ -19,7 +19,6 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { Deferred } from '@theia/core/lib/common/promise-util'; -import { FileSystem } from '@theia/filesystem/lib/common'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { PluginPaths } from './paths/const'; import { PluginPathsService } from '../common/plugin-paths-protocol'; @@ -37,19 +36,19 @@ export class PluginsKeyValueStorage { @inject(EnvVariablesServer) protected readonly envServer: EnvVariablesServer; - @inject(FileSystem) - protected readonly fileSystem: FileSystem; - @postConstruct() protected async init(): Promise { try { - const theiaDataFolderPath = FileUri.fsPath(await this.envServer.getUserDataFolder()); - await this.fileSystem.createFolder(theiaDataFolderPath); - const globalDataPath = path.join(theiaDataFolderPath, PluginPaths.PLUGINS_GLOBAL_STORAGE_DIR, 'global-state.json'); - await this.fileSystem.createFolder(path.dirname(globalDataPath)); - this.deferredGlobalDataPath.resolve(globalDataPath); + const configDirUri = await this.envServer.getConfigDirUri(); + const globalStorageFsPath = path.join(FileUri.fsPath(configDirUri), PluginPaths.PLUGINS_GLOBAL_STORAGE_DIR); + const exists = await fs.pathExists(globalStorageFsPath); + if (!exists) { + await fs.mkdirs(globalStorageFsPath); + } + const globalDataFsPath = path.join(globalStorageFsPath, 'global-state.json'); + this.deferredGlobalDataPath.resolve(globalDataFsPath); } catch (e) { - console.error('Faild to initialize global state path: ', e); + console.error('Failed to initialize global state path: ', e); this.deferredGlobalDataPath.resolve(undefined); } } diff --git a/packages/plugin-ext/src/plugin/env-variables-server-ext.ts b/packages/plugin-ext/src/plugin/env-variables-server-ext.ts deleted file mode 100644 index 19872df120c32..0000000000000 --- a/packages/plugin-ext/src/plugin/env-variables-server-ext.ts +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************** - * Copyright (C) 2019 Red Hat, Inc. and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { RPCProtocol } from '../common/rpc-protocol'; -import { PLUGIN_RPC_CONTEXT, EnvMain } from '../common'; -import { EnvVariablesServer, EnvVariable } from '@theia/core/lib/common/env-variables'; - -export class EnvVariablesServerExt implements EnvVariablesServer { - - protected readonly proxy: EnvMain; - - constructor(rpc: RPCProtocol) { - this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.ENV_MAIN); - } - - getExecPath(): Promise { - return this.proxy.$getExecPath(); - } - - getVariables(): Promise { - return this.proxy.$getAllEnvVariables(); - } - - async getValue(name: string): Promise { - const value = await this.proxy.$getEnvVariable(name); - return { name, value }; - } - - getUserHomeFolder(): Promise { - return this.proxy.$getUserHomeFolderPath(); - } - - getDataFolderName(): Promise { - return this.proxy.$getDataFolderName(); - } - - getUserDataFolder(): Promise { - return this.proxy.$getUserDataFolderPath(); - } - - getAppDataFolder(): Promise { - return this.proxy.$getAppDataPath(); - } - -} diff --git a/packages/preferences/src/browser/folders-preferences-provider.ts b/packages/preferences/src/browser/folders-preferences-provider.ts index d3187249fe86e..48a3014decbd2 100644 --- a/packages/preferences/src/browser/folders-preferences-provider.ts +++ b/packages/preferences/src/browser/folders-preferences-provider.ts @@ -41,7 +41,7 @@ export class FoldersPreferencesProvider extends PreferenceProvider { protected async init(): Promise { await this.workspaceService.roots; - await this.updateProviders(); + this.updateProviders(); this.workspaceService.onWorkspaceChanged(() => this.updateProviders()); const readyPromises: Promise[] = []; @@ -51,13 +51,13 @@ export class FoldersPreferencesProvider extends PreferenceProvider { Promise.all(readyPromises).then(() => this._ready.resolve()); } - protected async updateProviders(): Promise { + protected updateProviders(): void { const roots = this.workspaceService.tryGetRoots(); const toDelete = new Set(this.providers.keys()); for (const folder of roots) { - for (const configPath of await this.configurations.getPaths()) { + for (const configPath of this.configurations.getPaths()) { for (const configName of [...this.configurations.getSectionNames(), this.configurations.getConfigName()]) { - const configUri = await this.configurations.createUri(new URI(folder.uri), configPath, configName); + const configUri = this.configurations.createUri(new URI(folder.uri), configPath, configName); const key = configUri.toString(); toDelete.delete(key); if (!this.providers.has(key)) { diff --git a/packages/preferences/src/browser/preferences-tree-widget.ts b/packages/preferences/src/browser/preferences-tree-widget.ts index 2135de3c9e33e..ba525717713d4 100644 --- a/packages/preferences/src/browser/preferences-tree-widget.ts +++ b/packages/preferences/src/browser/preferences-tree-widget.ts @@ -454,7 +454,8 @@ export class PreferencesEditorsContainer extends DockPanel { let uri = preferenceUri; if (preferenceUri.scheme === UserStorageUri.SCHEME && homeUri) { - uri = homeUri.resolve(await this.envServer.getDataFolderName()).resolve(preferenceUri.path); + const configDirUri = await this.envServer.getConfigDirUri(); + uri = new URI(configDirUri).resolve(preferenceUri.path); } return homeUri ? FileSystemUtils.tildifyPath(uri.path.toString(), homeUri.path.toString()) @@ -473,8 +474,6 @@ export class PreferencesTreeWidget extends TreeWidget { private readonly onPreferenceSelectedEmitter: Emitter<{ [key: string]: string }>; readonly onPreferenceSelected: Event<{ [key: string]: string }>; - protected readonly toDispose: DisposableCollection; - @inject(PreferencesMenuFactory) protected readonly preferencesMenuFactory: PreferencesMenuFactory; @inject(PreferenceService) protected readonly preferenceService: PreferenceService; @inject(PreferencesDecorator) protected readonly decorator: PreferencesDecorator; @@ -490,14 +489,12 @@ export class PreferencesTreeWidget extends TreeWidget { this.onPreferenceSelectedEmitter = new Emitter<{ [key: string]: string }>(); this.onPreferenceSelected = this.onPreferenceSelectedEmitter.event; - this.toDispose = new DisposableCollection(); this.toDispose.push(this.onPreferenceSelectedEmitter); this.id = PreferencesTreeWidget.ID; } dispose(): void { - this.toDispose.dispose(); super.dispose(); } diff --git a/packages/task/src/browser/task-configuration-manager.ts b/packages/task/src/browser/task-configuration-manager.ts index d0a41a8fae06d..b4fed15ea5a43 100644 --- a/packages/task/src/browser/task-configuration-manager.ts +++ b/packages/task/src/browser/task-configuration-manager.ts @@ -171,7 +171,7 @@ export class TaskConfigurationManager { if (configUri && configUri.path.base === 'tasks.json') { uri = configUri; } else { // fallback - uri = new URI(model.workspaceFolderUri).resolve((await this.preferenceConfigurations.getPaths())[0] + '/tasks.json'); + uri = new URI(model.workspaceFolderUri).resolve(`${this.preferenceConfigurations.getPaths()[0]}/tasks.json`); } const fileStat = await this.filesystem.getFileStat(uri.toString()); diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index d926c01522adf..e33d5ffb2724f 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -33,7 +33,6 @@ import URI from '@theia/core/lib/common/uri'; import { FileChange, FileChangeType } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { OpenerService } from '@theia/core/lib/browser'; -import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; export interface TaskConfigurationClient { /** @@ -60,11 +59,6 @@ export class TaskConfigurations implements Disposable { */ protected taskCustomizationMap = new Map(); - /** last directory element under which we look for task config */ - protected taskFilePath: string; - /** task configuration file name */ - protected readonly TASKFILE = 'tasks.json'; - protected client: TaskConfigurationClient | undefined = undefined; /** @@ -97,9 +91,6 @@ export class TaskConfigurations implements Disposable { @inject(TaskSourceResolver) protected readonly taskSourceResolver: TaskSourceResolver; - @inject(EnvVariablesServer) - protected readonly envServer: EnvVariablesServer; - constructor() { this.toDispose.push(Disposable.create(() => { this.tasksMap.clear(); @@ -110,7 +101,7 @@ export class TaskConfigurations implements Disposable { } @postConstruct() - protected async init(): Promise { + protected init(): void { this.toDispose.push( this.taskConfigurationManager.onDidChangeTaskConfig(async change => { try { @@ -123,7 +114,6 @@ export class TaskConfigurations implements Disposable { } }) ); - this.taskFilePath = await this.envServer.getDataFolderName(); this.reorganizeTasks(); this.toDispose.push(this.taskSchemaUpdater.onDidChangeTaskSchema(() => this.reorganizeTasks())); } @@ -244,11 +234,6 @@ export class TaskConfigurations implements Disposable { return undefined; } - /** returns the string uri of where the config file would be, if it existed under a given root directory */ - protected getConfigFileUri(rootDir: string): string { - return new URI(rootDir).resolve(this.taskFilePath).resolve(this.TASKFILE).toString(); - } - /** * Called when a change, to a config file we watch, is detected. */ @@ -306,7 +291,7 @@ export class TaskConfigurations implements Disposable { try { await this.taskConfigurationManager.openConfiguration(sourceFolderUri); } catch (e) { - console.error(`Error occurred while opening: ${this.TASKFILE}.`, e); + console.error(`Error occurred while opening 'tasks.json' in ${sourceFolderUri}.`, e); } } diff --git a/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts b/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts index f7e9a91aa5151..9b9605a5bcd9c 100644 --- a/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts +++ b/packages/userstorage/src/browser/user-storage-service-filesystem.spec.ts @@ -16,6 +16,7 @@ import { Container } from 'inversify'; import * as chai from 'chai'; +import * as temp from 'temp'; import { UserStorageServiceFilesystemImpl } from './user-storage-service-filesystem'; import { UserStorageService } from './user-storage-service'; import { UserStorageResource } from './user-storage-resource'; @@ -33,6 +34,7 @@ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { MockEnvVariablesServerImpl } from '@theia/core/lib/browser/test/mock-env-variables-server'; import { UserStorageUri } from './user-storage-uri'; import URI from '@theia/core/lib/common/uri'; +import { FileUri } from '@theia/core/lib/node'; import * as sinon from 'sinon'; @@ -41,9 +43,10 @@ let testContainer: Container; let userStorageService: UserStorageServiceFilesystemImpl; -const homeDir = '/home/test'; -const THEIA_USER_STORAGE_FOLDER = '.theia'; -const userStorageFolder = new URI('file://' + homeDir).resolve(THEIA_USER_STORAGE_FOLDER); +const track = temp.track(); +const userStorageFolder = FileUri.create(track.mkdirSync()); +const envVariableServer = new MockEnvVariablesServerImpl(userStorageFolder); + const mockOnFileChangedEmitter = new Emitter(); let files: { [key: string]: string; } = {}; @@ -82,13 +85,6 @@ before(async () => { testContainer.bind(FileSystem).toDynamicValue(ctx => { const fs = new MockFilesystem(); - sinon.stub(fs, 'getCurrentUserHome').callsFake(() => Promise.resolve( - { - uri: 'file://' + homeDir, - lastModification: 0, - isDirectory: true - })); - sinon.stub(fs, 'resolveContent').callsFake((uri): Promise<{ stat: FileStat, content: string }> => { const content = files[uri]; return Promise.resolve( @@ -107,10 +103,14 @@ before(async () => { return fs; }).inSingletonScope(); - testContainer.bind(EnvVariablesServer).to(MockEnvVariablesServerImpl).inSingletonScope(); + testContainer.bind(EnvVariablesServer).toConstantValue(envVariableServer); testContainer.bind(UserStorageService).to(UserStorageServiceFilesystemImpl); }); +after(() => { + track.cleanupSync(); +}); + describe('User Storage Service (Filesystem implementation)', () => { let testFile: string; before(() => { @@ -128,21 +128,20 @@ describe('User Storage Service (Filesystem implementation)', () => { it('Should return a user storage uri from a filesystem uri', () => { - const test = UserStorageServiceFilesystemImpl.toUserStorageUri(userStorageFolder, new URI('file://' + homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile)); + const test = UserStorageServiceFilesystemImpl.toUserStorageUri(userStorageFolder, userStorageFolder.resolve(testFile)); expect(test.scheme).eq(UserStorageUri.SCHEME); expect(test.toString()).eq(UserStorageUri.SCHEME + ':' + testFile); const testFragment = UserStorageServiceFilesystemImpl. - toUserStorageUri(userStorageFolder, new URI('file://' + homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile + '#test')); + toUserStorageUri(userStorageFolder, userStorageFolder.resolve(testFile).withFragment('test')); expect(testFragment.fragment).eq('test'); const testQuery = UserStorageServiceFilesystemImpl. - toUserStorageUri(userStorageFolder, new URI('file://' + homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile + '?test=1')); + toUserStorageUri(userStorageFolder, userStorageFolder.resolve(testFile).withQuery('test=1')); expect(testQuery.query).eq('test=1'); const testQueryAndFragment = UserStorageServiceFilesystemImpl. - toUserStorageUri(userStorageFolder, new URI('file://' + homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile - + '?test=1' + '#test')); + toUserStorageUri(userStorageFolder, userStorageFolder.resolve(testFile).withQuery('test=1').withFragment('test')); expect(testQueryAndFragment.fragment).eq('test'); expect(testQueryAndFragment.query).eq('test=1'); }); @@ -151,10 +150,10 @@ describe('User Storage Service (Filesystem implementation)', () => { const test = UserStorageServiceFilesystemImpl.toFilesystemURI(userStorageFolder, new URI(UserStorageUri.SCHEME + ':' + testFile)); expect(test.scheme).eq('file'); - expect(test.path.toString()).eq(homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile); + expect(test.path.toString()).eq(userStorageFolder.resolve(testFile).path.toString()); }); - it('Should register a client and notifies it of the fs changesby converting them to user storage changes', done => { + it('Should register a client and notifies it of the fs changes by converting them to user storage changes', done => { userStorageService.onUserStorageChanged(event => { const userStorageUri = event.uris[0]; expect(userStorageUri.scheme).eq(UserStorageUri.SCHEME); @@ -174,7 +173,7 @@ describe('User Storage Service (Filesystem implementation)', () => { it('Should save the contents correctly using a user storage uri to a filesystem uri', async () => { const userStorageUri = UserStorageServiceFilesystemImpl. - toUserStorageUri(userStorageFolder, new URI('file://' + homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile)); + toUserStorageUri(userStorageFolder, userStorageFolder.resolve(testFile)); await userStorageService.saveContents(userStorageUri, 'test content'); @@ -197,7 +196,7 @@ describe('User Storage Resource (Filesystem implementation)', () => { testFile = 'test.json'; userStorageService = testContainer.get(UserStorageService); const userStorageUriTest = UserStorageServiceFilesystemImpl. - toUserStorageUri(userStorageFolder, new URI('file://' + homeDir + '/' + THEIA_USER_STORAGE_FOLDER + '/' + testFile)); + toUserStorageUri(userStorageFolder, userStorageFolder.resolve(testFile)); userStorageResource = new UserStorageResource(userStorageUriTest, userStorageService); }); diff --git a/packages/userstorage/src/browser/user-storage-service-filesystem.ts b/packages/userstorage/src/browser/user-storage-service-filesystem.ts index f95993c03f417..ec0917e9370a7 100644 --- a/packages/userstorage/src/browser/user-storage-service-filesystem.ts +++ b/packages/userstorage/src/browser/user-storage-service-filesystem.ts @@ -28,16 +28,17 @@ export class UserStorageServiceFilesystemImpl implements UserStorageService { protected readonly toDispose = new DisposableCollection(); protected readonly onUserStorageChangedEmitter = new Emitter(); - protected userStorageFolder: Promise; + protected readonly userStorageFolder: Promise; constructor( @inject(FileSystem) protected readonly fileSystem: FileSystem, @inject(FileSystemWatcher) protected readonly watcher: FileSystemWatcher, @inject(ILogger) protected readonly logger: ILogger, @inject(EnvVariablesServer) protected readonly envServer: EnvVariablesServer + ) { - this.userStorageFolder = this.envServer.getUserDataFolder().then(userDataFolder => { - const userDataFolderUri = new URI(userDataFolder); + this.userStorageFolder = this.envServer.getConfigDirUri().then(configDirUri => { + const userDataFolderUri = new URI(configDirUri); watcher.watchFileChanges(userDataFolderUri).then(disposable => this.toDispose.push(disposable) ); @@ -46,6 +47,7 @@ export class UserStorageServiceFilesystemImpl implements UserStorageService { }); this.toDispose.push(this.onUserStorageChangedEmitter); + } dispose(): void { diff --git a/packages/workspace/src/browser/quick-open-workspace.ts b/packages/workspace/src/browser/quick-open-workspace.ts index bc09983f6c822..485b6bcd4a054 100644 --- a/packages/workspace/src/browser/quick-open-workspace.ts +++ b/packages/workspace/src/browser/quick-open-workspace.ts @@ -39,8 +39,11 @@ export class QuickOpenWorkspace implements QuickOpenModel { async open(workspaces: string[]): Promise { this.items = []; - const homeDirPath: string = (await this.fileSystem.getFsPath(await this.envServer.getUserHomeFolder()))!; - const tempWorkspaceFile = getTemporaryWorkspaceFileUri(await this.envServer.getUserDataFolder()); + const [homeDirUri, tempWorkspaceFile] = await Promise.all([ + this.fileSystem.getCurrentUserHome(), + getTemporaryWorkspaceFileUri(this.envServer) + ]); + const home = homeDirUri ? await this.fileSystem.getFsPath(homeDirUri.uri) : undefined; await this.preferences.ready; if (!workspaces.length) { this.items.push(new QuickOpenGroupItem({ @@ -55,14 +58,14 @@ export class QuickOpenWorkspace implements QuickOpenModel { !this.preferences['workspace.supportMultiRootWorkspace'] && !stat.isDirectory) { continue; // skip the workspace files if multi root is not supported } - if (tempWorkspaceFile && uri.toString() === tempWorkspaceFile.toString()) { + if (uri.toString() === tempWorkspaceFile.toString()) { continue; // skip the temporary workspace files } const icon = this.labelProvider.getIcon(stat); const iconClass = icon === '' ? undefined : icon + ' file-icon'; this.items.push(new QuickOpenGroupItem({ label: uri.path.base, - description: (homeDirPath) ? FileSystemUtils.tildifyPath(uri.path.toString(), homeDirPath) : uri.path.toString(), + description: (home) ? FileSystemUtils.tildifyPath(uri.path.toString(), home) : uri.path.toString(), groupLabel: `last modified ${moment(stat.lastModification).fromNow()}`, iconClass, run: (mode: QuickOpenMode): boolean => { diff --git a/packages/workspace/src/browser/workspace-service.spec.ts b/packages/workspace/src/browser/workspace-service.spec.ts index 6b2f25e9d9a3d..7a887f6ed278d 100644 --- a/packages/workspace/src/browser/workspace-service.spec.ts +++ b/packages/workspace/src/browser/workspace-service.spec.ts @@ -37,10 +37,13 @@ import * as jsoncparser from 'jsonc-parser'; import * as sinon from 'sinon'; import * as chai from 'chai'; import * as assert from 'assert'; +import * as temp from 'temp'; +import { FileUri } from '@theia/core/lib/node'; import URI from '@theia/core/lib/common/uri'; const expect = chai.expect; disableJSDOM(); +const track = temp.track(); const folderA = Object.freeze({ uri: 'file:///home/folderA', @@ -88,6 +91,7 @@ describe('WorkspaceService', () => { after(() => { disableJSDOM(); + track.cleanupSync(); }); beforeEach(() => { @@ -109,7 +113,7 @@ describe('WorkspaceService', () => { testContainer.bind(WindowService).toConstantValue(mockWindowService); testContainer.bind(ILogger).toConstantValue(mockILogger); testContainer.bind(WorkspacePreferences).toConstantValue(mockPref); - testContainer.bind(EnvVariablesServer).to(MockEnvVariablesServerImpl); + testContainer.bind(EnvVariablesServer).toConstantValue(new MockEnvVariablesServerImpl(FileUri.create(track.mkdirSync()))); testContainer.bind(PreferenceServiceImpl).toConstantValue(mockPreferenceServiceImpl); testContainer.bind(PreferenceSchemaProvider).toConstantValue(mockPreferenceSchemaProvider); diff --git a/packages/workspace/src/browser/workspace-service.ts b/packages/workspace/src/browser/workspace-service.ts index 43c8d62aeaae1..64a2b86da72da 100644 --- a/packages/workspace/src/browser/workspace-service.ts +++ b/packages/workspace/src/browser/workspace-service.ts @@ -67,7 +67,7 @@ export class WorkspaceService implements FrontendApplicationContribution { protected readonly schemaProvider: PreferenceSchemaProvider; @inject(EnvVariablesServer) - protected readonly envServer: EnvVariablesServer; + protected readonly envVariableServer: EnvVariablesServer; protected applicationName: string; @@ -380,7 +380,7 @@ export class WorkspaceService implements FrontendApplicationContribution { } protected async getUntitledWorkspace(): Promise { - return getTemporaryWorkspaceFileUri(await this.envServer.getUserDataFolder()); + return getTemporaryWorkspaceFileUri(this.envVariableServer); } private async writeWorkspaceFile(workspaceFile: FileStat | undefined, workspaceData: WorkspaceData): Promise { diff --git a/packages/workspace/src/common/utils.ts b/packages/workspace/src/common/utils.ts index 375574021a569..6288e0e80542c 100644 --- a/packages/workspace/src/common/utils.ts +++ b/packages/workspace/src/common/utils.ts @@ -15,17 +15,12 @@ ********************************************************************************/ import URI from '@theia/core/lib/common/uri'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; export const THEIA_EXT = 'theia-workspace'; export const VSCODE_EXT = 'code-workspace'; -export function getTemporaryWorkspaceFileUri(userDataFolder: string | URI): URI { - let userDataFolderUri: URI; - if (typeof userDataFolder === 'string') { - userDataFolderUri = new URI(userDataFolder); - } else { - userDataFolderUri = userDataFolder; - } - - return userDataFolderUri.resolve(`Untitled.${THEIA_EXT}`).withScheme('file'); +export async function getTemporaryWorkspaceFileUri(envVariableServer: EnvVariablesServer): Promise { + const configDirUri = await envVariableServer.getConfigDirUri(); + return new URI(configDirUri).resolve(`Untitled.${THEIA_EXT}`); } diff --git a/packages/workspace/src/node/default-workspace-server.ts b/packages/workspace/src/node/default-workspace-server.ts index e6821b52e9d07..dcf2cd425753a 100644 --- a/packages/workspace/src/node/default-workspace-server.ts +++ b/packages/workspace/src/node/default-workspace-server.ts @@ -123,8 +123,8 @@ export class DefaultWorkspaceServer implements WorkspaceServer { return listUri; } - protected workspaceStillExist(wspath: string): boolean { - return fs.pathExistsSync(FileUri.fsPath(wspath)); + protected workspaceStillExist(workspaceRootUri: string): boolean { + return fs.pathExistsSync(FileUri.fsPath(workspaceRootUri)); } protected async getWorkspaceURIFromCli(): Promise { @@ -141,32 +141,33 @@ export class DefaultWorkspaceServer implements WorkspaceServer { await this.writeToFile(file, data); } - protected async writeToFile(filePath: string, data: object): Promise { - if (!await fs.pathExists(filePath)) { - await fs.mkdirs(path.resolve(filePath, '..')); + protected async writeToFile(fsPath: string, data: object): Promise { + if (!await fs.pathExists(fsPath)) { + await fs.mkdirs(path.resolve(fsPath, '..')); } - await fs.writeJson(filePath, data); + await fs.writeJson(fsPath, data); } /** * Reads the most recently used workspace root from the user's home directory. */ protected async readRecentWorkspacePathsFromUserHome(): Promise { - const filePath = await this.getUserStoragePath(); - const data = await this.readJsonFromFile(filePath); + const fsPath = await this.getUserStoragePath(); + const data = await this.readJsonFromFile(fsPath); return RecentWorkspacePathsData.is(data) ? data : undefined; } - protected async readJsonFromFile(filePath: string): Promise { - if (await fs.pathExists(filePath)) { - const rawContent = await fs.readFile(filePath, 'utf-8'); + protected async readJsonFromFile(fsPath: string): Promise { + if (await fs.pathExists(fsPath)) { + const rawContent = await fs.readFile(fsPath, 'utf-8'); const strippedContent = jsoncparser.stripComments(rawContent); return jsoncparser.parse(strippedContent); } } protected async getUserStoragePath(): Promise { - return path.resolve(FileUri.fsPath(await this.envServer.getUserDataFolder()), 'recentworkspace.json'); + const configDirUri = await this.envServer.getConfigDirUri(); + return path.resolve(FileUri.fsPath(configDirUri), 'recentworkspace.json'); } }