Skip to content

Commit 84ecd86

Browse files
committed
fix: use path in user home as extension dir
* Instead of unpacking vsix files to a temporary directory, they are now unpacked to the user extension dir, which is by default <userhome>/.theia/extensions/<extension-id>. This avoids redundant unpacking after the temp directory is deleted. * If the extension is downloaded from a registry, the vsix file is downloaded to a temporary location and unpacked into the user extension dir from there. * The temporary deployment directory is no longer used. This should speed up Theia's start after the temp dir location has been cleaned (e.g., after a reboot). Signed-off-by: Olaf Lessenich <[email protected]>
1 parent de4c424 commit 84ecd86

File tree

5 files changed

+81
-80
lines changed

5 files changed

+81
-80
lines changed

packages/plugin-ext-vscode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@theia/typehierarchy": "1.44.0",
1717
"@theia/userstorage": "1.44.0",
1818
"@theia/workspace": "1.44.0",
19+
"decompress": "^4.2.1",
1920
"filenamify": "^4.1.0"
2021
},
2122
"publishConfig": {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 EclipseSource and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import * as decompress from 'decompress';
18+
import * as path from 'path';
19+
import * as fs from '@theia/core/shared/fs-extra';
20+
21+
export async function decompressExtension(sourcePath: string, destPath: string): Promise<boolean> {
22+
try {
23+
await decompress(sourcePath, destPath);
24+
if (sourcePath.endsWith('.tgz')) {
25+
const extensionPath = path.join(destPath, 'package');
26+
const vscodeNodeModulesPath = path.join(extensionPath, 'vscode_node_modules.zip');
27+
if (await fs.pathExists(vscodeNodeModulesPath)) {
28+
await decompress(vscodeNodeModulesPath, path.join(extensionPath, 'node_modules'));
29+
}
30+
}
31+
return true;
32+
} catch (error) {
33+
console.error(`Failed to decompress ${sourcePath} to ${destPath}: ${error}`);
34+
return false;
35+
}
36+
}

packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@
1515
// *****************************************************************************
1616

1717
import * as path from 'path';
18-
import * as filenamify from 'filenamify';
1918
import * as fs from '@theia/core/shared/fs-extra';
2019
import { inject, injectable } from '@theia/core/shared/inversify';
2120
import type { RecursivePartial, URI } from '@theia/core';
2221
import { Deferred, firstTrue } from '@theia/core/lib/common/promise-util';
23-
import { getTempDirPathAsync } from '@theia/plugin-ext/lib/main/node/temp-dir-util';
2422
import {
2523
PluginDeployerDirectoryHandler, PluginDeployerEntry, PluginDeployerDirectoryHandlerContext,
26-
PluginDeployerEntryType, PluginPackage, PluginType, PluginIdentifiers
24+
PluginDeployerEntryType, PluginPackage, PluginIdentifiers
2725
} from '@theia/plugin-ext';
28-
import { FileUri } from '@theia/core/lib/node';
2926
import { PluginCliContribution } from '@theia/plugin-ext/lib/main/node/plugin-cli-contribution';
3027

3128
@injectable()
@@ -35,11 +32,6 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand
3532

3633
@inject(PluginCliContribution) protected readonly pluginCli: PluginCliContribution;
3734

38-
constructor() {
39-
this.deploymentDirectory = new Deferred();
40-
getTempDirPathAsync('vscode-copied')
41-
.then(deploymentDirectoryPath => this.deploymentDirectory.resolve(FileUri.create(deploymentDirectoryPath)));
42-
}
4335

4436
async accept(plugin: PluginDeployerEntry): Promise<boolean> {
4537
console.debug(`Resolving "${plugin.id()}" as a VS Code extension...`);
@@ -62,7 +54,6 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand
6254
}
6355

6456
async handle(context: PluginDeployerDirectoryHandlerContext): Promise<void> {
65-
await this.copyDirectory(context);
6657
const types: PluginDeployerEntryType[] = [];
6758
const packageJson: PluginPackage = context.pluginEntry().getValue('package.json');
6859
if (packageJson.browser) {
@@ -74,33 +65,6 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand
7465
context.pluginEntry().accept(...types);
7566
}
7667

77-
protected async copyDirectory(context: PluginDeployerDirectoryHandlerContext): Promise<void> {
78-
if (this.pluginCli.copyUncompressedPlugins() && context.pluginEntry().type === PluginType.User) {
79-
const entry = context.pluginEntry();
80-
const id = entry.id();
81-
const pathToRestore = entry.path();
82-
const origin = entry.originalPath();
83-
const targetDir = await this.getExtensionDir(context);
84-
try {
85-
if (await fs.pathExists(targetDir) || !entry.path().startsWith(origin)) {
86-
console.log(`[${id}]: already copied.`);
87-
} else {
88-
console.log(`[${id}]: copying to "${targetDir}"`);
89-
const deploymentDirectory = await this.deploymentDirectory.promise;
90-
await fs.mkdirp(FileUri.fsPath(deploymentDirectory));
91-
await context.copy(origin, targetDir);
92-
entry.updatePath(targetDir);
93-
if (!this.deriveMetadata(entry)) {
94-
throw new Error('Unable to resolve plugin metadata after copying');
95-
}
96-
}
97-
} catch (e) {
98-
console.warn(`[${id}]: Error when copying.`, e);
99-
entry.updatePath(pathToRestore);
100-
}
101-
}
102-
}
103-
10468
protected async resolveFromSources(plugin: PluginDeployerEntry): Promise<boolean> {
10569
const pluginPath = plugin.path();
10670
const pck = await this.requirePackage(pluginPath);
@@ -152,9 +116,4 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand
152116
return undefined;
153117
}
154118
}
155-
156-
protected async getExtensionDir(context: PluginDeployerDirectoryHandlerContext): Promise<string> {
157-
const deploymentDirectory = await this.deploymentDirectory.promise;
158-
return FileUri.fsPath(deploymentDirectory.resolve(filenamify(context.pluginEntry().id(), { replacement: '_' })));
159-
}
160119
}

packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616

1717
import { PluginDeployerFileHandler, PluginDeployerEntry, PluginDeployerFileHandlerContext, PluginType } from '@theia/plugin-ext';
1818
import * as fs from '@theia/core/shared/fs-extra';
19-
import * as path from 'path';
2019
import * as filenamify from 'filenamify';
21-
import type { URI } from '@theia/core';
2220
import { inject, injectable } from '@theia/core/shared/inversify';
23-
import { Deferred } from '@theia/core/lib/common/promise-util';
24-
import { getTempDirPathAsync } from '@theia/plugin-ext/lib/main/node/temp-dir-util';
2521
import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment';
2622
import { FileUri } from '@theia/core/lib/node/file-uri';
23+
import { decompressExtension } from './plugin-vscode-decompress';
2724

2825
export const isVSCodePluginFile = (pluginPath?: string) => Boolean(pluginPath && (pluginPath.endsWith('.vsix') || pluginPath.endsWith('.tgz')));
2926

@@ -33,14 +30,6 @@ export class PluginVsCodeFileHandler implements PluginDeployerFileHandler {
3330
@inject(PluginVSCodeEnvironment)
3431
protected readonly environment: PluginVSCodeEnvironment;
3532

36-
private readonly systemExtensionsDirUri: Deferred<URI>;
37-
38-
constructor() {
39-
this.systemExtensionsDirUri = new Deferred();
40-
getTempDirPathAsync('vscode-unpacked')
41-
.then(systemExtensionsDirPath => this.systemExtensionsDirUri.resolve(FileUri.create(systemExtensionsDirPath)));
42-
}
43-
4433
async accept(resolvedPlugin: PluginDeployerEntry): Promise<boolean> {
4534
return resolvedPlugin.isFile().then(file => {
4635
if (!file) {
@@ -52,31 +41,38 @@ export class PluginVsCodeFileHandler implements PluginDeployerFileHandler {
5241

5342
async handle(context: PluginDeployerFileHandlerContext): Promise<void> {
5443
const id = context.pluginEntry().id();
55-
const extensionDir = await this.getExtensionDir(context);
56-
console.log(`[${id}]: trying to decompress into "${extensionDir}"...`);
57-
if (context.pluginEntry().type === PluginType.User && await fs.pathExists(extensionDir)) {
58-
console.log(`[${id}]: already found`);
44+
try {
45+
const extensionDir = await this.getExtensionDir(context);
46+
47+
console.log(`[${id}]: trying to decompress into "${extensionDir}"...`);
48+
if (context.pluginEntry().type === PluginType.User && await fs.pathExists(extensionDir)) {
49+
console.log(`[${id}]: extension is already unpacked`);
50+
context.pluginEntry().updatePath(extensionDir);
51+
return;
52+
}
53+
54+
if (!await decompressExtension(context.pluginEntry().path(), extensionDir)) {
55+
console.error(`[${id}]: decompression failed`);
56+
return;
57+
}
58+
59+
console.log(`[${id}]: decompressed`);
5960
context.pluginEntry().updatePath(extensionDir);
61+
} catch (error) {
62+
console.error(`[${id}]: error while decompressing`, error);
6063
return;
6164
}
62-
await this.decompress(extensionDir, context);
63-
console.log(`[${id}]: decompressed`);
64-
context.pluginEntry().updatePath(extensionDir);
6565
}
6666

6767
protected async getExtensionDir(context: PluginDeployerFileHandlerContext): Promise<string> {
68-
const systemExtensionsDirUri = await this.systemExtensionsDirUri.promise;
69-
return FileUri.fsPath(systemExtensionsDirUri.resolve(filenamify(context.pluginEntry().id(), { replacement: '_' })));
70-
}
71-
72-
protected async decompress(extensionDir: string, context: PluginDeployerFileHandlerContext): Promise<void> {
73-
await context.unzip(context.pluginEntry().path(), extensionDir);
74-
if (context.pluginEntry().path().endsWith('.tgz')) {
75-
const extensionPath = path.join(extensionDir, 'package');
76-
const vscodeNodeModulesPath = path.join(extensionPath, 'vscode_node_modules.zip');
77-
if (await fs.pathExists(vscodeNodeModulesPath)) {
78-
await context.unzip(vscodeNodeModulesPath, path.join(extensionPath, 'node_modules'));
79-
}
68+
try {
69+
const userExtensionsDirUri = await this.environment.getExtensionsDirUri();
70+
const normalizedExtensionId = filenamify(context.pluginEntry().id(), { replacement: '_' });
71+
const extensionDirPath = FileUri.fsPath(userExtensionsDirUri.resolve(normalizedExtensionId));
72+
return extensionDirPath;
73+
} catch (error) {
74+
console.error('Error resolving extension directory:', error);
75+
throw error;
8076
}
8177
}
8278

packages/vsx-registry/src/node/vsx-extension-resolver.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
1515
// *****************************************************************************
1616

17+
import * as os from 'os';
1718
import * as path from 'path';
1819
import * as semver from 'semver';
1920
import * as fs from '@theia/core/shared/fs-extra';
2021
import { injectable, inject } from '@theia/core/shared/inversify';
2122
import URI from '@theia/core/lib/common/uri';
2223
import { PluginDeployerHandler, PluginDeployerResolver, PluginDeployerResolverContext, PluginDeployOptions, PluginIdentifiers } from '@theia/plugin-ext/lib/common/plugin-protocol';
24+
import { getTempDirPathAsync } from '@theia/plugin-ext/lib/main/node/temp-dir-util';
2325
import { VSCodeExtensionUri } from '@theia/plugin-ext-vscode/lib/common/plugin-vscode-uri';
2426
import { OVSXClientProvider } from '../common/ovsx-client-provider';
2527
import { OVSXApiFilter, VSXExtensionRaw } from '@theia/ovsx-client';
@@ -74,16 +76,23 @@ export class VSXExtensionResolver implements PluginDeployerResolver {
7476
return;
7577
}
7678
}
77-
const downloadPath = (await this.environment.getExtensionsDirUri()).path.fsPath();
78-
await fs.ensureDir(downloadPath);
79-
const extensionPath = path.resolve(downloadPath, path.basename(downloadUrl));
80-
console.log(`[${resolvedId}]: trying to download from "${downloadUrl}"...`, 'to path', downloadPath);
81-
if (!await this.download(downloadUrl, extensionPath)) {
79+
const downloadDir = await this.getTempDir(resolvedId, 'download');
80+
await fs.ensureDir(downloadDir);
81+
const downloadedExtensionPath = path.resolve(downloadDir, path.basename(downloadUrl));
82+
console.log(`[${resolvedId}]: trying to download from "${downloadUrl}"...`, 'to path', downloadDir);
83+
if (!await this.download(downloadUrl, downloadedExtensionPath)) {
8284
console.log(`[${resolvedId}]: not found`);
8385
return;
8486
}
85-
console.log(`[${resolvedId}]: downloaded to ${extensionPath}"`);
86-
context.addPlugin(resolvedId, extensionPath);
87+
console.log(`[${resolvedId}]: downloaded to ${downloadedExtensionPath}"`);
88+
context.addPlugin(resolvedId, downloadedExtensionPath);
89+
}
90+
91+
protected async getTempDir(id: string, type: string): Promise<string> {
92+
// returns a user-specific temp dir to avoid conflicts and permission issues
93+
const userId = os.userInfo().username;
94+
const dirName = path.join(os.tmpdir(), 'vscode-download-' + userId);
95+
return getTempDirPathAsync(dirName);
8796
}
8897

8998
protected hasSameOrNewerVersion(id: string, extension: VSXExtensionRaw): string | undefined {

0 commit comments

Comments
 (0)