From 3a9ca3d4c04c18a3013ebc76ade31b99f416a81c Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 17 Dec 2019 11:42:12 +0200 Subject: [PATCH] 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'); } }