Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Divide the package manager into separate components #2188

Merged
merged 66 commits into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
6205e1d
use async await
akshita31 Apr 10, 2018
c632ab2
async await
akshita31 Apr 10, 2018
01f8cb1
remove promise
akshita31 Apr 10, 2018
56675b9
remove packagejson
akshita31 Apr 10, 2018
19ee685
resolve conflicts
akshita31 Apr 10, 2018
400ba5e
Feature tests running with refactored package manager
akshita31 Apr 10, 2018
5c4bfc0
trial to mock server
akshita31 Apr 11, 2018
3968900
package add
akshita31 Apr 11, 2018
cbf50af
package manager test not running
akshita31 Apr 11, 2018
8c09369
Mocking the server running
akshita31 Apr 11, 2018
c74aecf
Refactoring packages-1
akshita31 Apr 12, 2018
3e58f13
Test for the downloader running using the mock server
akshita31 Apr 13, 2018
5565a2e
Using network settings
akshita31 Apr 13, 2018
c8135b5
Changing the package path
akshita31 Apr 13, 2018
9afc874
Dividing packages.ts into separate components
akshita31 Apr 13, 2018
9cc0016
modification for network settings
akshita31 Apr 13, 2018
98e7e74
Clean up the downloaders
akshita31 Apr 13, 2018
e20a434
launch.json
akshita31 Apr 13, 2018
97e83ec
Clean up
akshita31 Apr 13, 2018
e7b520a
do not use filter
akshita31 Apr 13, 2018
c6bee5a
use filter async
akshita31 Apr 13, 2018
2dac16c
Use tmp file interface
akshita31 Apr 13, 2018
85d867f
Use tmpfile interface
akshita31 Apr 13, 2018
348e03a
merge master
akshita31 Apr 13, 2018
57d4bff
Check for the event stream
akshita31 Apr 14, 2018
9afa848
package json
akshita31 Apr 14, 2018
e5b0d65
Changes
akshita31 Apr 16, 2018
7e77a6e
Using FilePathResolver
akshita31 Apr 16, 2018
76e9780
Remove using
akshita31 Apr 16, 2018
47a18d1
Testing for normal and fallback case working
akshita31 Apr 16, 2018
441153a
Resolve the paths
akshita31 Apr 16, 2018
df2f874
Remove failing case
akshita31 Apr 16, 2018
4c2c15c
package installer test-1
akshita31 Apr 17, 2018
7e44a06
Add package installer test
akshita31 Apr 17, 2018
e218cb4
Create tmp asset
akshita31 Apr 17, 2018
b96fb22
Package Downloader test refactored
akshita31 Apr 17, 2018
10b6073
Rename files
akshita31 Apr 17, 2018
b9ac792
resolve compile issues
akshita31 Apr 17, 2018
10c6811
Clean up installer
akshita31 Apr 17, 2018
053efe3
Clean up the tests
akshita31 Apr 17, 2018
286fa7c
Nits
akshita31 Apr 18, 2018
12dfc3f
Rename packages
akshita31 Apr 18, 2018
24a5c03
Package installer test
akshita31 Apr 18, 2018
9e26124
PR feedback
akshita31 Apr 18, 2018
bccfa13
Package Filter test
akshita31 Apr 19, 2018
2b6ba72
Remove yauzl.d.ts
akshita31 Apr 19, 2018
7b2586f
Filter test
akshita31 Apr 19, 2018
df111f6
Added test for invalid zip file
akshita31 Apr 19, 2018
3ff44bb
method for getting the request options
akshita31 Apr 19, 2018
ebe6a2a
remove imports
akshita31 Apr 19, 2018
3a97968
please resolve the path
akshita31 Apr 20, 2018
93ea65c
remove latest in settings
akshita31 Apr 20, 2018
cc4a782
trial for travis
akshita31 Apr 20, 2018
13ad87d
Test run
akshita31 Apr 20, 2018
a6ed9a0
Package Manager test executing
akshita31 Apr 21, 2018
fdc9e0c
Use free port in package manager test
akshita31 Apr 21, 2018
16e1c88
Package Manager (using a https server running)
akshita31 Apr 23, 2018
1fe14a5
using http mock server
akshita31 Apr 23, 2018
bde8b49
Downloader test running using free port
akshita31 Apr 23, 2018
7abd6a6
Nits
akshita31 Apr 23, 2018
3e323af
clean up
akshita31 Apr 24, 2018
41b7b87
Making fallback url optional
akshita31 Apr 24, 2018
28fab5a
using else if
akshita31 Apr 24, 2018
45300ac
PR feedback
akshita31 Apr 24, 2018
573ca83
Package Error
akshita31 Apr 24, 2018
87fff8b
dispose if present
akshita31 Apr 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactoring packages-1
  • Loading branch information
akshita31 committed Apr 12, 2018
commit c74aecf639b40566734a3aa8f8c898bb0540c6e6
25 changes: 8 additions & 17 deletions src/CSharpExtDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,25 @@
*--------------------------------------------------------------------------------------------*/

import * as util from './common';
import { GetNetworkConfiguration, defaultPackageManagerFactory, IPackageManagerFactory } from './downloader.helper';
import { PackageManager, Package } from './packages';
import { PlatformInformation } from './platform';
import { PackageInstallation, LogPlatformInfo, InstallationSuccess, InstallationFailure } from './omnisharp/loggingEvents';
import { EventStream } from './EventStream';
import { vscode } from './vscodeAdapter';
import { PackageManager } from './packageManager/PackageManager';
import { Package } from './packageManager/packages';

/*
* Class used to download the runtime dependencies of the C# Extension
*/
export class CSharpExtDownloader {
private proxy: string;
private strictSSL: boolean;
private packageManager: PackageManager;

public constructor(
vscode: vscode,
private vscode: vscode,
private eventStream: EventStream,
private packageJSON: any,
private platformInfo: PlatformInformation,
packageManagerFactory: IPackageManagerFactory = defaultPackageManagerFactory) {

let networkConfiguration = GetNetworkConfiguration(vscode);
this.proxy = networkConfiguration.Proxy;
this.strictSSL = networkConfiguration.StrictSSL;
this.packageManager = packageManagerFactory(this.platformInfo);
private platformInfo: PlatformInformation) {
this.packageManager = new PackageManager();
}

public async installRuntimeDependencies(): Promise<boolean> {
Expand All @@ -45,12 +38,10 @@ export class CSharpExtDownloader {

let runTimeDependencies = this.GetRunTimeDependenciesPackages();

installationStage = 'downloadPackages';
let downloadedPackages = await this.packageManager.DownloadPackages(runTimeDependencies, this.eventStream, this.proxy, this.strictSSL);

installationStage = 'installPackages';
await this.packageManager.InstallPackages(downloadedPackages,this.eventStream);
installationStage = 'downloadAndInstallPackages';
await this.packageManager.DownloadAndInstallPackages(runTimeDependencies, this.vscode, this.platformInfo, this.eventStream);

// We probably dont need the install.Lock thing now as we are directly testing the package test path
installationStage = 'touchLockFile';
await util.touchInstallFile(util.InstallFileType.Lock);

Expand Down
19 changes: 0 additions & 19 deletions src/downloader.helper.ts

This file was deleted.

21 changes: 6 additions & 15 deletions src/omnisharp/OmnisharpDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { GetNetworkConfiguration, IPackageManagerFactory, defaultPackageManagerFactory } from '../downloader.helper';
import { GetPackagesFromVersion, GetVersionFilePackage } from './OmnisharpPackageCreator';
import { PackageManager } from '../packages';
import { PlatformInformation } from '../platform';
import { PackageInstallation, LogPlatformInfo, InstallationSuccess, InstallationFailure, InstallationProgress } from './loggingEvents';
import { EventStream } from '../EventStream';
import { vscode } from '../vscodeAdapter';
import { PackageManager } from '../packageManager/PackageManager';


export class OmnisharpDownloader {
Expand All @@ -18,16 +17,12 @@ export class OmnisharpDownloader {
private packageManager: PackageManager;

public constructor(
vscode: vscode,
private vscode: vscode,
private eventStream: EventStream,
private packageJSON: any,
private platformInfo: PlatformInformation,
packageManagerFactory: IPackageManagerFactory = defaultPackageManagerFactory) {
private platformInfo: PlatformInformation) {

let networkConfiguration = GetNetworkConfiguration(vscode);
this.proxy = networkConfiguration.Proxy;
this.strictSSL = networkConfiguration.StrictSSL;
this.packageManager = packageManagerFactory(this.platformInfo);
this.packageManager = new PackageManager();
}

public async DownloadAndInstallOmnisharp(version: string, serverUrl: string, installPath: string) {
Expand All @@ -40,12 +35,8 @@ export class OmnisharpDownloader {
installationStage = 'getPackageInfo';
let packages = GetPackagesFromVersion(version, this.packageJSON.runtimeDependencies, serverUrl, installPath);

installationStage = 'downloadPackages';

let downloadedPackages = await this.packageManager.DownloadPackages(packages, this.eventStream, this.proxy, this.strictSSL);

installationStage = 'installPackages';
await this.packageManager.InstallPackages(downloadedPackages, this.eventStream);
installationStage = 'downloadAndInstallPackages';
await this.packageManager.DownloadAndInstallPackages(packages, this.vscode, this.platformInfo, this.eventStream);

this.eventStream.post(new InstallationSuccess());
}
Expand Down
145 changes: 145 additions & 0 deletions src/packageManager/PackageDownloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as tmp from 'tmp';
import * as https from 'https';
import * as util from '../common';
import * as fs from 'fs';
import { EventStream } from "../EventStream";
import { DownloadSuccess, DownloadStart, DownloadFallBack, DownloadFailure, DownloadProgress, DownloadSizeObtained } from "../omnisharp/loggingEvents";
import { Package, PackageError } from "./packages";
import { parse as parseUrl } from 'url';
import { getProxyAgent } from './proxy';
import { vscode } from '../vscodeAdapter';

export class PackageDownloader {
constructor() {
// Ensure our temp files get cleaned up in case of error.
tmp.setGracefulCleanup();
}

public async DownloadPackage(description: string, url: string, fallbackUrl: string, vscode: vscode, eventStream: EventStream) {
const config = vscode.workspace.getConfiguration();
const proxy = config.get<string>('http.proxy');
const strictSSL = config.get('http.proxyStrictSSL', true);
return downloadPackage(pkg, eventStream, proxy, strictSSL);
}
}

// We dont need this as we are already doing this check in the package manager
/*async function maybeDownloadPackage(pkg: Package, eventStream: EventStream, proxy: string, strictSSL: boolean): Promise<Package> {
let exists = await doesPackageTestPathExist(pkg);
if (!exists) {
return downloadPackage(pkg, eventStream, proxy, strictSSL);
} else {
eventStream.post(new DownloadSuccess(`Skipping package '${pkg.description}' (already downloaded).`));
return pkg;
}
}*/

async function downloadPackage(description: string, url: string, fallbackUrl: string, eventStream: EventStream, proxy: string, strictSSL: boolean): Promise<Package> {
eventStream.post(new DownloadStart(description));
const tmpResult = await new Promise<tmp.SynchrounousResult>((resolve, reject) => {
tmp.file({ prefix: 'package-' }, (err, path, fd, cleanupCallback) => {
if (err) {
return reject(new PackageError('Error from tmp.file', description, err));
}

resolve(<tmp.SynchrounousResult>{ name: path, fd: fd, removeCallback: cleanupCallback });
});
});

try {
await downloadFile(tmpResult, description,url, eventStream, proxy, strictSSL);
eventStream.post(new DownloadSuccess(` Done!`));
}
catch (primaryUrlError) {
// If the package has a fallback Url, and downloading from the primary Url failed, try again from
// the fallback. This is used for debugger packages as some users have had issues downloading from
// the CDN link
if (fallbackUrl) {
eventStream.post(new DownloadFallBack(fallbackUrl));
try {
await downloadFile(tmpFile, description, fallbackUrl, eventStream, proxy, strictSSL);
eventStream.post(new DownloadSuccess(' Done!'));
}
catch (fallbackUrlError) {
throw primaryUrlError;
}
}
else {
throw primaryUrlError;
}
}
}

async function downloadFile(tmpFile: tmp.SynchrounousResult, description: string, urlString: string, eventStream: EventStream, proxy: string, strictSSL: boolean): Promise<void> {
const url = parseUrl(urlString);

const options: https.RequestOptions = {
host: url.hostname,
path: url.path,
agent: getProxyAgent(url, proxy, strictSSL),
port: url.port,
rejectUnauthorized: util.isBoolean(strictSSL) ? strictSSL : true
};

return new Promise<void>((resolve, reject) => {
if (!tmpFile || tmpFile.fd == 0) {
return reject(new PackageError("Temporary package file unavailable", pkg));
}

let request = https.request(options, response => {
if (response.statusCode === 301 || response.statusCode === 302) {
// Redirect - download from new location
return resolve(downloadFile(tmpFile, description, response.headers.location, eventStream, proxy, strictSSL));
}

if (response.statusCode != 200) {
// Download failed - print error message
eventStream.post(new DownloadFailure(`failed (error code '${response.statusCode}')`));
return reject(new PackageError(response.statusCode.toString(), description));
}

// Downloading - hook up events
let packageSize = parseInt(response.headers['content-length'], 10);
let downloadedBytes = 0;
let downloadPercentage = 0;
let tmpFile = fs.createWriteStream(null, { fd: tmpFile.fd });

eventStream.post(new DownloadSizeObtained(packageSize));

response.on('data', data => {
downloadedBytes += data.length;

// Update status bar item with percentage
let newPercentage = Math.ceil(100 * (downloadedBytes / packageSize));
if (newPercentage !== downloadPercentage) {
downloadPercentage = newPercentage;
eventStream.post(new DownloadProgress(downloadPercentage, description));
}
});

response.on('end', () => {
resolve();
});

response.on('error', err => {
reject(new PackageError(`Reponse error: ${err.message || 'NONE'}`, description, err));
});

// Begin piping data from the response to the package file
response.pipe(tmpFile, { end: false });
});

request.on('error', err => {
console.log(err);
reject(new PackageError(`Request error: ${err.message || 'NONE'}`, description, err));
});

// Execute the request
request.end();
});
}
109 changes: 109 additions & 0 deletions src/packageManager/PackageInstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Package, PackageError, getPackageTestPath, getBaseInstallPath } from "./packages";
import { EventStream } from "../EventStream";
import { InstallationProgress } from "../omnisharp/loggingEvents";
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import * as yauzl from 'yauzl';
import * as path from 'path';

export class PackageInstaller {
constructor() { }

public InstallPackage(pkg: Package, eventStream: EventStream): Promise<void> {
const installationStage = 'installPackages';
if (!pkg.tmpFile) {
// Download of this package was skipped, so there is nothing to install
return Promise.resolve();
}

resolvePackageBinaries(pkg);
eventStream.post(new InstallationProgress(installationStage, pkg.description));

return new Promise<void>((resolve, baseReject) => {
const reject = (err: any) => {
// If anything goes wrong with unzip, make sure we delete the test path (if there is one)
// so we will retry again later
const testPath = getPackageTestPath(pkg);
if (testPath) {
fs.unlink(testPath, unlinkErr => {
baseReject(err);
});
} else {
baseReject(err);
}
};

if (pkg.tmpFile.fd == 0) {
return reject(new PackageError('Downloaded file unavailable', pkg));
}

yauzl.fromFd(pkg.tmpFile.fd, { lazyEntries: true }, (err, zipFile) => {
if (err) {
return reject(new PackageError('Immediate zip file error', pkg, err));
}

zipFile.readEntry();

zipFile.on('entry', (entry: yauzl.Entry) => {
let absoluteEntryPath = path.resolve(getBaseInstallPath(pkg), entry.fileName);

if (entry.fileName.endsWith('/')) {
// Directory - create it
mkdirp(absoluteEntryPath, { mode: 0o775 }, err => {
if (err) {
return reject(new PackageError('Error creating directory for zip directory entry:' + err.code || '', pkg, err));
}

zipFile.readEntry();
});
}
else {
// File - extract it
zipFile.openReadStream(entry, (err, readStream) => {
if (err) {
return reject(new PackageError('Error reading zip stream', pkg, err));
}

mkdirp(path.dirname(absoluteEntryPath), { mode: 0o775 }, err => {
if (err) {
return reject(new PackageError('Error creating directory for zip file entry', pkg, err));
}

// Make sure executable files have correct permissions when extracted
let fileMode = pkg.binaries && pkg.binaries.indexOf(absoluteEntryPath) !== -1
? 0o755
: 0o664;

readStream.pipe(fs.createWriteStream(absoluteEntryPath, { mode: fileMode }));
readStream.on('end', () => zipFile.readEntry());
});
});
}
});

zipFile.on('end', () => {
resolve();
});

zipFile.on('error', err => {
reject(new PackageError('Zip File Error:' + err.code || '', pkg, err));
});
});
}).then(() => {
// Clean up temp file
pkg.tmpFile.removeCallback();
});
}
}

function resolvePackageBinaries(pkg: Package) {
// Convert relative binary paths to absolute
if (pkg.binaries) {
pkg.binaries = pkg.binaries.map(value => path.resolve(getBaseInstallPath(pkg), value));
}
}
Loading