mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568
This commit is contained in:
104
src/vs/platform/extensionManagement/node/extensionDownloader.ts
Normal file
104
src/vs/platform/extensionManagement/node/extensionDownloader.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as semver from 'semver-umd';
|
||||
|
||||
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
|
||||
|
||||
export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
private readonly extensionsDownloadDir: URI = URI.file(tmpdir());
|
||||
private readonly cache: number = 0;
|
||||
private readonly cleanUpPromise: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
if (environmentService.extensionsDownloadPath) {
|
||||
this.extensionsDownloadDir = URI.file(environmentService.extensionsDownloadPath);
|
||||
this.cache = 20; // Cache 20 downloads
|
||||
this.cleanUpPromise = this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
await this.cleanUpPromise;
|
||||
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
|
||||
await this.download(extension, location, operation);
|
||||
return location;
|
||||
}
|
||||
|
||||
async delete(location: URI): Promise<void> {
|
||||
// Delete immediately if caching is disabled
|
||||
if (!this.cache) {
|
||||
await this.fileService.del(location);
|
||||
}
|
||||
}
|
||||
|
||||
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
if (!await this.fileService.exists(location)) {
|
||||
await this.extensionGalleryService.download(extension, location, operation);
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanUp(): Promise<void> {
|
||||
try {
|
||||
if (!(await this.fileService.exists(this.extensionsDownloadDir))) {
|
||||
this.logService.trace('Extension VSIX downlads cache dir does not exist');
|
||||
return;
|
||||
}
|
||||
const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true });
|
||||
if (folderStat.children) {
|
||||
const toDelete: URI[] = [];
|
||||
const all: [ExtensionIdentifierWithVersion, IFileStatWithMetadata][] = [];
|
||||
for (const stat of folderStat.children) {
|
||||
const extension = this.parse(stat.name);
|
||||
if (extension) {
|
||||
all.push([extension, stat]);
|
||||
}
|
||||
}
|
||||
const byExtension = groupByExtension(all, ([extension]) => extension.identifier);
|
||||
const distinct: IFileStatWithMetadata[] = [];
|
||||
for (const p of byExtension) {
|
||||
p.sort((a, b) => semver.rcompare(a[0].version, b[0].version));
|
||||
toDelete.push(...p.slice(1).map(e => e[1].resource)); // Delete outdated extensions
|
||||
distinct.push(p[0][1]);
|
||||
}
|
||||
distinct.sort((a, b) => a.mtime - b.mtime); // sort by modified time
|
||||
toDelete.push(...distinct.slice(0, Math.max(0, distinct.length - this.cache)).map(s => s.resource)); // Retain minimum cacheSize and delete the rest
|
||||
await Promise.all(toDelete.map(resource => {
|
||||
this.logService.trace('Deleting vsix from cache', resource.path);
|
||||
return this.fileService.del(resource);
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private getName(extension: IGalleryExtension): string {
|
||||
return this.cache ? new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key().toLowerCase() : generateUuid();
|
||||
}
|
||||
|
||||
private parse(name: string): ExtensionIdentifierWithVersion | null {
|
||||
const matches = ExtensionIdVersionRegex.exec(name);
|
||||
return matches && matches[1] && matches[2] ? new ExtensionIdentifierWithVersion({ id: matches[1] }, matches[2]) : null;
|
||||
}
|
||||
}
|
||||
@@ -40,12 +40,13 @@ import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'
|
||||
import { tmpdir } from 'os';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { optional, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
|
||||
|
||||
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
|
||||
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
|
||||
@@ -113,6 +114,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
private readonly installingExtensions: Map<string, CancelablePromise<ILocalExtension>> = new Map<string, CancelablePromise<ILocalExtension>>();
|
||||
private readonly uninstallingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
|
||||
private readonly manifestCache: ExtensionsManifestCache;
|
||||
private readonly extensionsDownloader: ExtensionsDownloader;
|
||||
private readonly extensionLifecycle: ExtensionsLifecycle;
|
||||
|
||||
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
|
||||
@@ -133,6 +135,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@optional(IDownloadService) private downloadService: IDownloadService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
|
||||
@@ -140,6 +143,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
|
||||
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
|
||||
this.extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService));
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
@@ -347,7 +351,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
this.downloadInstallableExtension(extension, operation)
|
||||
.then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken)
|
||||
.then(local => pfs.rimraf(installableExtension.zipPath).finally(() => { }).then(() => local)))
|
||||
.then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local)))
|
||||
.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
|
||||
.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
|
||||
.then(
|
||||
@@ -425,7 +429,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
};
|
||||
|
||||
this.logService.trace('Started downloading extension:', extension.identifier.id);
|
||||
return this.galleryService.download(extension, URI.file(tmpdir()), operation)
|
||||
return this.extensionsDownloader.downloadExtension(extension, operation)
|
||||
.then(
|
||||
zip => {
|
||||
const zipPath = zip.fsPath;
|
||||
@@ -1003,6 +1007,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
|
||||
this.telemetryService.publicLogError(eventName, assign(extensionData, { success: !error, duration, errorcode }));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user