Skip to content

Commit 6ab381c

Browse files
authored
Change logic for downloading of language server files (microsoft#2676)
For microsoft#2580
1 parent b078e4c commit 6ab381c

18 files changed

Lines changed: 278 additions & 35 deletions

‎src/client/activation/languageServerFolderService.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { inject, injectable } from 'inversify';
77
import * as path from 'path';
88
import * as semver from 'semver';
99
import { EXTENSION_ROOT_DIR } from '../common/constants';
10+
import { log } from '../common/logger';
1011
import { NugetPackage } from '../common/nuget/types';
1112
import { IFileSystem } from '../common/platform/types';
12-
import { IConfigurationService, ILogger } from '../common/types';
13+
import { IConfigurationService } from '../common/types';
1314
import { IServiceContainer } from '../ioc/types';
1415
import { FolderVersionPair, ILanguageServerFolderService, ILanguageServerPackageService } from './types';
1516

@@ -19,6 +20,7 @@ const languageServerFolder = 'languageServer';
1920
export class LanguageServerFolderService implements ILanguageServerFolderService {
2021
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { }
2122

23+
@log('Get language server folder name')
2224
public async getLanguageServerFolderName(): Promise<string> {
2325
const currentFolder = await this.getcurrentLanguageServerDirectory();
2426
let serverVersion: NugetPackage | undefined;
@@ -29,18 +31,16 @@ export class LanguageServerFolderService implements ILanguageServerFolderService
2931
}
3032

3133
serverVersion = await this.getLatestLanguageServerVersion()
32-
.catch(ex => {
33-
const logger = this.serviceContainer.get<ILogger>(ILogger);
34-
logger.logError('Failed to get latest version of Language Server.', ex);
35-
return undefined;
36-
});
34+
.catch(ex => undefined);
3735

3836
if (currentFolder && (!serverVersion || serverVersion.version.compare(currentFolder.version) <= 0)) {
3937
return path.basename(currentFolder.path);
4038
}
4139

4240
return `${languageServerFolder}.${serverVersion!.version.raw}`;
4341
}
42+
43+
@log('Get latest version of Language Server')
4444
public getLatestLanguageServerVersion(): Promise<NugetPackage | undefined> {
4545
const lsPackageService = this.serviceContainer.get<ILanguageServerPackageService>(ILanguageServerPackageService);
4646
return lsPackageService.getLatestNugetPackageVersion();
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { AzureBlobStoreNugetRepository } from '../common/nuget/azureBlobStoreNugetRepository';
8+
import { IServiceContainer } from '../ioc/types';
9+
10+
const azureBlobStorageAccount = 'https://pvsc.blob.core.windows.net';
11+
12+
export enum LanguageServerDownloadChannel {
13+
stable = 'stable',
14+
beta = 'beta',
15+
daily = 'daily'
16+
}
17+
18+
enum LanguageServerPackageStorageContainers {
19+
stable = 'vscode-python-ls-production',
20+
beta = 'vscode-python-ls-insiders',
21+
daily = 'vscode-python-ls-internal'
22+
}
23+
24+
@injectable()
25+
export class StableLanguageServerPackageRepository extends AzureBlobStoreNugetRepository {
26+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
27+
super(serviceContainer, azureBlobStorageAccount, LanguageServerPackageStorageContainers.stable);
28+
}
29+
}
30+
31+
@injectable()
32+
export class BetaLanguageServerPackageRepository extends AzureBlobStoreNugetRepository {
33+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
34+
super(serviceContainer, azureBlobStorageAccount, LanguageServerPackageStorageContainers.beta);
35+
}
36+
}
37+
38+
@injectable()
39+
export class DailyLanguageServerPackageRepository extends AzureBlobStoreNugetRepository {
40+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
41+
super(serviceContainer, azureBlobStorageAccount, LanguageServerPackageStorageContainers.daily);
42+
}
43+
}

‎src/client/activation/languageServerPackageService.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
import { inject, injectable } from 'inversify';
77
import { Architecture, OSType } from '../../utils/platform';
8+
import { log } from '../common/logger';
89
import { INugetRepository, INugetService, NugetPackage } from '../common/nuget/types';
910
import { IPlatformService } from '../common/platform/types';
11+
import { IConfigurationService } from '../common/types';
1012
import { IServiceContainer } from '../ioc/types';
1113
import { PlatformName } from './platformData';
1214
import { ILanguageServerPackageService } from './types';
@@ -40,10 +42,13 @@ export class LanguageServerPackageService implements ILanguageServerPackageServi
4042
}
4143
}
4244

45+
@log('Get latest language server nuget package version')
4346
public async getLatestNugetPackageVersion(): Promise<NugetPackage> {
44-
const nugetRepo = this.serviceContainer.get<INugetRepository>(INugetRepository);
47+
const downloadChannel = this.getLanguageServerDownloadChannel();
48+
const nugetRepo = this.serviceContainer.get<INugetRepository>(INugetRepository, downloadChannel);
4549
const nugetService = this.serviceContainer.get<INugetService>(INugetService);
4650
const packageName = this.getNugetPackageName();
51+
log(`Listing packages for ${downloadChannel} for ${packageName}`);
4752
const packages = await nugetRepo.getPackages(packageName);
4853

4954
const validPackages = packages
@@ -52,4 +57,10 @@ export class LanguageServerPackageService implements ILanguageServerPackageServi
5257
.sort((a, b) => a.version.compare(b.version));
5358
return validPackages[validPackages.length - 1];
5459
}
60+
61+
public getLanguageServerDownloadChannel() {
62+
const configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
63+
const settings = configService.getSettings();
64+
return settings.analysis.downloadChannel || 'beta';
65+
}
5566
}

‎src/client/activation/serviceRegistry.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
'use strict';
55

6+
import { INugetRepository } from '../common/nuget/types';
67
import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, IPythonExtensionBanner } from '../common/types';
78
import { IServiceManager } from '../ioc/types';
89
import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner';
@@ -11,6 +12,7 @@ import { ExtensionActivationService } from './activationService';
1112
import { JediExtensionActivator } from './jedi';
1213
import { LanguageServerExtensionActivator } from './languageServer';
1314
import { LanguageServerFolderService } from './languageServerFolderService';
15+
import { BetaLanguageServerPackageRepository, DailyLanguageServerPackageRepository, LanguageServerDownloadChannel, StableLanguageServerPackageRepository } from './languageServerPackageRepository';
1416
import { LanguageServerPackageService } from './languageServerPackageService';
1517
import { ExtensionActivators, IExtensionActivationService, IExtensionActivator, ILanguageServerFolderService, ILanguageServerPackageService } from './types';
1618

@@ -22,4 +24,7 @@ export function registerTypes(serviceManager: IServiceManager) {
2224
serviceManager.addSingleton<IPythonExtensionBanner>(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS);
2325
serviceManager.addSingleton<ILanguageServerFolderService>(ILanguageServerFolderService, LanguageServerFolderService);
2426
serviceManager.addSingleton<ILanguageServerPackageService>(ILanguageServerPackageService, LanguageServerPackageService);
27+
serviceManager.addSingleton<INugetRepository>(INugetRepository, StableLanguageServerPackageRepository, LanguageServerDownloadChannel.stable);
28+
serviceManager.addSingleton<INugetRepository>(INugetRepository, BetaLanguageServerPackageRepository, LanguageServerDownloadChannel.beta);
29+
serviceManager.addSingleton<INugetRepository>(INugetRepository, DailyLanguageServerPackageRepository, LanguageServerDownloadChannel.daily);
2530
}

‎src/client/common/helpers.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
'use strict';
55

6+
import { isTestExecution } from './constants';
67
import { ModuleNotInstalledError } from './errors/moduleNotInstalledError';
78

89
export function isNotInstalledError(error: Error): boolean {
@@ -19,3 +20,20 @@ export function isNotInstalledError(error: Error): boolean {
1920
const isModuleNoInstalledError = error.message.indexOf('No module named') >= 0;
2021
return errorObj.code === 'ENOENT' || errorObj.code === 127 || isModuleNoInstalledError;
2122
}
23+
24+
export function skipIfTest(isAsyncFunction: boolean) {
25+
// tslint:disable-next-line:no-function-expression no-any
26+
return function (_: Object, __: string, descriptor: TypedPropertyDescriptor<any>) {
27+
const originalMethod = descriptor.value;
28+
// tslint:disable-next-line:no-function-expression no-any
29+
descriptor.value = function (...args: any[]) {
30+
if (isTestExecution()) {
31+
return isAsyncFunction ? Promise.resolve() : undefined;
32+
}
33+
// tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any
34+
return originalMethod.apply(this, args);
35+
};
36+
37+
return descriptor;
38+
};
39+
}

‎src/client/common/logger.ts‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
// tslint:disable:no-console
22

33
import { injectable } from 'inversify';
4+
import { skipIfTest } from './helpers';
45
import { ILogger } from './types';
56

67
const PREFIX = 'Python Extension: ';
78

89
@injectable()
910
export class Logger implements ILogger {
11+
@skipIfTest(false)
1012
public logError(message: string, ex?: Error) {
1113
if (ex) {
1214
console.error(`${PREFIX}${message}`, ex);
1315
} else {
1416
console.error(`${PREFIX}${message}`);
1517
}
1618
}
19+
@skipIfTest(false)
1720
public logWarning(message: string, ex?: Error) {
1821
if (ex) {
1922
console.warn(`${PREFIX}${message}`, ex);
2023
} else {
2124
console.warn(`${PREFIX}${message}`);
2225
}
2326
}
27+
@skipIfTest(false)
2428
public logInformation(message: string, ex?: Error) {
2529
if (ex) {
2630
console.info(`${PREFIX}${message}`, ex);
@@ -37,3 +41,93 @@ export function error(title: string = '', message: any) {
3741
export function warn(title: string = '', message: any) {
3842
new Logger().logWarning(`${title}, ${message}`);
3943
}
44+
45+
export enum LogOptions {
46+
Arguments, ReturnValue
47+
}
48+
49+
// tslint:disable-next-line:no-any
50+
function argsToLogString(args: any[]): string {
51+
try {
52+
return (args || []).map((item, index) => {
53+
try {
54+
return `Arg ${index + 1}: ${JSON.stringify(item)}`;
55+
} catch {
56+
return `Arg ${index + 1}: UNABLE TO DETERMINE VALUE`;
57+
}
58+
}).join(', ');
59+
} catch {
60+
return '';
61+
}
62+
}
63+
64+
// tslint:disable-next-line:no-any
65+
function returnValueToLogString(returnValue: any): string {
66+
let returnValueMessage = 'Return Value: ';
67+
if (returnValue) {
68+
try {
69+
returnValueMessage += `${JSON.stringify(returnValue)}`;
70+
} catch {
71+
returnValueMessage += 'UNABLE TO DETERMINE VALUE';
72+
}
73+
}
74+
return returnValueMessage;
75+
}
76+
77+
export function log(message: string, options: LogOptions = LogOptions.Arguments | LogOptions.ReturnValue) {
78+
// tslint:disable-next-line:no-function-expression no-any
79+
return function (_: Object, __: string, descriptor: TypedPropertyDescriptor<any>) {
80+
const originalMethod = descriptor.value;
81+
// tslint:disable-next-line:no-function-expression no-any
82+
descriptor.value = function (...args: any[]) {
83+
// tslint:disable-next-line:no-any
84+
function writeSuccess(returnValue?: any) {
85+
writeToLog(returnValue);
86+
}
87+
function writeError(ex: Error) {
88+
writeToLog(undefined, ex);
89+
}
90+
// tslint:disable-next-line:no-any
91+
function writeToLog(returnValue?: any, ex?: Error) {
92+
const messagesToLog = [message];
93+
if ((options && LogOptions.Arguments) === LogOptions.Arguments) {
94+
messagesToLog.push(argsToLogString(args));
95+
}
96+
if ((options & LogOptions.ReturnValue) === LogOptions.ReturnValue) {
97+
messagesToLog.push(returnValueToLogString(returnValue));
98+
}
99+
if (ex) {
100+
new Logger().logError(messagesToLog.join(', '), ex);
101+
} else {
102+
new Logger().logInformation(messagesToLog.join(', '));
103+
}
104+
}
105+
try {
106+
// tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any
107+
const result = originalMethod.apply(this, args);
108+
// If method being wrapped returns a promise then wait for it.
109+
// tslint:disable-next-line:no-unsafe-any
110+
if (result && typeof result.then === 'function' && typeof result.catch === 'function') {
111+
// tslint:disable-next-line:prefer-type-cast
112+
(result as Promise<void>)
113+
.then(data => {
114+
writeSuccess(data);
115+
return data;
116+
})
117+
.catch(ex => {
118+
writeError(ex);
119+
return Promise.reject(ex);
120+
});
121+
} else {
122+
writeSuccess(result);
123+
}
124+
return result;
125+
} catch (ex) {
126+
writeError(ex);
127+
throw ex;
128+
}
129+
};
130+
131+
return descriptor;
132+
};
133+
}

‎src/client/common/nuget/azureBlobStoreNugetRepository.ts‎

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,44 @@
44
'use strict';
55

66
import { common, createBlobServiceAnonymous } from 'azure-storage';
7-
import { inject, injectable } from 'inversify';
7+
import { inject, injectable, unmanaged } from 'inversify';
88
import { IServiceContainer } from '../../ioc/types';
9+
import { captureTelemetry } from '../../telemetry';
10+
import { PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES } from '../../telemetry/constants';
11+
import { log, LogOptions } from '../logger';
912
import { INugetRepository, INugetService, NugetPackage } from './types';
1013

11-
const azureBlobStorageAccount = 'https://pvsc.blob.core.windows.net';
12-
const azureBlobStorageContainer = 'python-language-server';
13-
1414
@injectable()
1515
export class AzureBlobStoreNugetRepository implements INugetRepository {
16-
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { }
16+
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
17+
@unmanaged() protected readonly azureBlobStorageAccount: string,
18+
@unmanaged() protected readonly azureBlobStorageContainer: string) { }
1719
public async getPackages(packageName: string): Promise<NugetPackage[]> {
20+
return this.listPackages(this.azureBlobStorageAccount, this.azureBlobStorageContainer, packageName);
21+
}
22+
23+
@captureTelemetry(PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES)
24+
@log('Listing Nuget Packages', LogOptions.Arguments)
25+
public listPackages(azureBlobStorageAccount: string, azureBlobStorageContainer: string, packageName: string) {
1826
const blobStore = createBlobServiceAnonymous(azureBlobStorageAccount);
1927
const nugetService = this.serviceContainer.get<INugetService>(INugetService);
2028
return new Promise<NugetPackage[]>((resolve, reject) => {
2129
// We must pass undefined according to docs, but type definition doesn't all it to be undefined or null!!!
2230
// tslint:disable-next-line:no-any
23-
blobStore.listBlobsSegmentedWithPrefix(azureBlobStorageContainer, packageName, undefined as any as common.ContinuationToken, (error, result) => {
24-
if (error) {
25-
return reject(error);
26-
}
27-
resolve(result.entries.map(item => {
28-
return {
29-
package: item.name,
30-
uri: `${azureBlobStorageAccount}/${azureBlobStorageContainer}/${item.name}`,
31-
version: nugetService.getVersionFromPackageFileName(item.name)
32-
};
33-
}));
34-
});
31+
const token = undefined as any as common.ContinuationToken;
32+
blobStore.listBlobsSegmentedWithPrefix(azureBlobStorageContainer, packageName, token,
33+
(error, result) => {
34+
if (error) {
35+
return reject(error);
36+
}
37+
resolve(result.entries.map(item => {
38+
return {
39+
package: item.name,
40+
uri: `${azureBlobStorageAccount}/${azureBlobStorageContainer}/${item.name}`,
41+
version: nugetService.getVersionFromPackageFileName(item.name)
42+
};
43+
}));
44+
});
3545
});
3646
}
3747
}

‎src/client/common/serviceRegistry.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ import { ProductInstaller } from './installer/productInstaller';
2222
import { Logger } from './logger';
2323
import { BrowserService } from './net/browser';
2424
import { HttpClient } from './net/httpClient';
25-
import { AzureBlobStoreNugetRepository } from './nuget/azureBlobStoreNugetRepository';
2625
import { NugetService } from './nuget/nugetService';
27-
import { INugetRepository, INugetService } from './nuget/types';
26+
import { INugetService } from './nuget/types';
2827
import { PersistentStateFactory } from './persistentState';
2928
import { IS_64_BIT, IS_WINDOWS } from './platform/constants';
3029
import { PathUtils } from './platform/pathUtils';
@@ -69,7 +68,6 @@ export function registerTypes(serviceManager: IServiceManager) {
6968
serviceManager.addSingleton<IBrowserService>(IBrowserService, BrowserService);
7069
serviceManager.addSingleton<IHttpClient>(IHttpClient, HttpClient);
7170
serviceManager.addSingleton<IEditorUtils>(IEditorUtils, EditorUtils);
72-
serviceManager.addSingleton<INugetRepository>(INugetRepository, AzureBlobStoreNugetRepository);
7371
serviceManager.addSingleton<INugetService>(INugetService, NugetService);
7472

7573
serviceManager.addSingleton<ITerminalHelper>(ITerminalHelper, TerminalHelper);

‎src/client/common/types.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export interface ITerminalSettings {
244244
}
245245

246246
export interface IAnalysisSettings {
247+
readonly downloadChannel?: 'stable' | 'beta' | 'daily';
247248
readonly openFilesOnly: boolean;
248249
readonly typeshedPaths: string[];
249250
readonly errors: string[];

‎src/client/telemetry/constants.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ export const PYTHON_LANGUAGE_SERVER_STARTUP = 'PYTHON_LANGUAGE_SERVER.STARTUP';
4141
export const PYTHON_LANGUAGE_SERVER_PLATFORM_NOT_SUPPORTED = 'PYTHON_LANGUAGE_SERVER.PLATFORM_NOT_SUPPORTED';
4242

4343
export const TERMINAL_CREATE = 'TERMINAL.CREATE';
44+
export const PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES = 'PYTHON_LANGUAGE_SERVER.LIST_BLOB_PACKAGES';

0 commit comments

Comments
 (0)