diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 604f4724eee0e3b76f4e7e3755304ca644a8bb3f..e0437685400a059592eb5e99d0af77b97ea7d868 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -73,7 +73,7 @@ export class CachedExtensionScanner { const devMode = !!process.env['VSCODE_DEV']; const locale = platform.language; const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations); - return ExtensionScanner.scanSingleExtension(input, log); + return ExtensionScanner.scanSingleExtension(this._fileService, input, log); } public async startScanningExtensions(log: ILog): Promise { @@ -120,7 +120,7 @@ export class CachedExtensionScanner { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); - const expected = JSON.parse(JSON.stringify(await ExtensionScanner.scanExtensions(input, new NullLogger()))); + const expected = JSON.parse(JSON.stringify(await ExtensionScanner.scanExtensions(fileService, input, new NullLogger()))); const cacheContents = await this._readExtensionCache(environmentService, fileService, cacheKey); if (!cacheContents) { @@ -185,7 +185,7 @@ export class CachedExtensionScanner { private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, fileService: IFileService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise { if (input.devMode) { // Do not cache when running out of sources... - return ExtensionScanner.scanExtensions(input, log); + return ExtensionScanner.scanExtensions(fileService, input, log); } try { @@ -214,7 +214,7 @@ export class CachedExtensionScanner { } const counterLogger = new CounterLogger(log); - const result = await ExtensionScanner.scanExtensions(input, counterLogger); + const result = await ExtensionScanner.scanExtensions(fileService, input, counterLogger); if (counterLogger.errorCnt === 0) { // Nothing bad happened => cache the result const cacheContents: IExtensionCacheData = { @@ -278,7 +278,7 @@ export class CachedExtensionScanner { const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations); const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile]) .then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control)) - .then(resolver => ExtensionScanner.scanExtensions(input, log, resolver)); + .then(resolver => ExtensionScanner.scanExtensions(fileService, input, log, resolver)); finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions); } @@ -305,6 +305,7 @@ export class CachedExtensionScanner { const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => { return ExtensionScanner.scanOneOrMultipleExtensions( + fileService, new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log ); }); @@ -319,6 +320,7 @@ export class CachedExtensionScanner { } else if (environmentService.extensionDevelopmentLocationURI) { if (environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) { developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions( + fileService, new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log ); } diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 6e2179ddd8e091d8812d092fcfa00e3b0978ef03..601c685f84a82cbc2db9a72f6abe5497f2d8d570 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -11,10 +11,10 @@ import * as arrays from 'vs/base/common/arrays'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import * as pfs from 'vs/base/node/pfs'; import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator'; import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; const MANIFEST_FILE = 'package.json'; @@ -61,6 +61,7 @@ export interface ILog { abstract class ExtensionManifestHandler { + protected readonly _fileService: IFileService; protected readonly _ourVersion: string; protected readonly _log: ILog; protected readonly _absoluteFolderPath: string; @@ -68,7 +69,8 @@ abstract class ExtensionManifestHandler { protected readonly _isUnderDevelopment: boolean; protected readonly _absoluteManifestPath: string; - constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean) { + constructor(fileService: IFileService, ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean) { + this._fileService = fileService; this._ourVersion = ourVersion; this._log = log; this._absoluteFolderPath = absoluteFolderPath; @@ -81,9 +83,9 @@ abstract class ExtensionManifestHandler { class ExtensionManifestParser extends ExtensionManifestHandler { public parse(): Promise { - return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => { + return this._fileService.resolveContent(URI.file(this._absoluteManifestPath)).then(content => { try { - const manifest = JSON.parse(manifestContents.toString()); + const manifest = JSON.parse(content.value); if (manifest.__metadata) { manifest.uuid = manifest.__metadata.id; } @@ -94,7 +96,7 @@ class ExtensionManifestParser extends ExtensionManifestHandler { } return null; }, (err) => { - if (err.code === 'ENOENT') { + if (FileOperationError.isFileOperationError(err) && err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { return null; } @@ -108,8 +110,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { private readonly _nlsConfig: NlsConfiguration; - constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration) { - super(ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); + constructor(fileService: IFileService, ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration) { + super(fileService, ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); this._nlsConfig = nlsConfig; } @@ -142,9 +144,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { let translationPath = this._nlsConfig.translations[translationId]; let localizedMessages: Promise; if (translationPath) { - localizedMessages = pfs.readFile(translationPath, 'utf8').then((content) => { + localizedMessages = this._fileService.resolveContent(URI.file(translationPath), { encoding: 'utf8' }).then(content => { let errors: json.ParseError[] = []; - let translationBundle: TranslationBundle = json.parse(content, errors); + let translationBundle: TranslationBundle = json.parse(content.value, errors); if (errors.length > 0) { reportErrors(translationPath, errors); return { values: undefined, default: `${basename}.nls.json` }; @@ -156,17 +158,17 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { return { values: undefined, default: `${basename}.nls.json` }; }); } else { - localizedMessages = pfs.fileExists(basename + '.nls' + extension).then(exists => { + localizedMessages = this._fileService.exists(URI.file(basename + '.nls' + extension)).then(exists => { if (!exists) { return undefined; } - return ExtensionManifestNLSReplacer.findMessageBundles(this._nlsConfig, basename).then((messageBundle) => { + return ExtensionManifestNLSReplacer.findMessageBundles(this._fileService, this._nlsConfig, basename).then((messageBundle) => { if (!messageBundle.localized) { return { values: undefined, default: messageBundle.original }; } - return pfs.readFile(messageBundle.localized, 'utf8').then(messageBundleContent => { + return this._fileService.resolveContent(URI.file(messageBundle.localized), { encoding: 'utf8' }).then(messageBundleContent => { let errors: json.ParseError[] = []; - let messages: MessageBag = json.parse(messageBundleContent, errors); + let messages: MessageBag = json.parse(messageBundleContent.value, errors); if (errors.length > 0) { reportErrors(messageBundle.localized, errors); return { values: undefined, default: messageBundle.original }; @@ -187,7 +189,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { } let errors: json.ParseError[] = []; // resolveOriginalMessageBundle returns null if localizedMessages.default === undefined; - return ExtensionManifestNLSReplacer.resolveOriginalMessageBundle(localizedMessages.default, errors).then((defaults) => { + return ExtensionManifestNLSReplacer.resolveOriginalMessageBundle(this._fileService, localizedMessages.default, errors).then((defaults) => { if (errors.length > 0) { reportErrors(localizedMessages.default, errors); return extensionDescription; @@ -204,11 +206,11 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { /** * Parses original message bundle, returns null if the original message bundle is null. */ - private static resolveOriginalMessageBundle(originalMessageBundle: string | null, errors: json.ParseError[]) { + private static resolveOriginalMessageBundle(fileService: IFileService, originalMessageBundle: string | null, errors: json.ParseError[]) { return new Promise<{ [key: string]: string; } | null>((c, e) => { if (originalMessageBundle) { - pfs.readFile(originalMessageBundle).then(originalBundleContent => { - c(json.parse(originalBundleContent.toString(), errors)); + fileService.resolveContent(URI.file(originalMessageBundle)).then(originalBundleContent => { + c(json.parse(originalBundleContent.value, errors)); }, (err) => { c(null); }); @@ -222,11 +224,11 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { * Finds localized message bundle and the original (unlocalized) one. * If the localized file is not present, returns null for the original and marks original as localized. */ - private static findMessageBundles(nlsConfig: NlsConfiguration, basename: string): Promise<{ localized: string; original: string | null; }> { + private static findMessageBundles(fileService: IFileService, nlsConfig: NlsConfiguration, basename: string): Promise<{ localized: string; original: string | null; }> { return new Promise<{ localized: string; original: string | null; }>((c, e) => { function loop(basename: string, locale: string): void { let toCheck = `${basename}.nls.${locale}.json`; - pfs.fileExists(toCheck).then(exists => { + fileService.exists(URI.file(toCheck)).then(exists => { if (exists) { c({ localized: toCheck, original: `${basename}.nls.json` }); } @@ -492,11 +494,14 @@ export interface IExtensionResolver { class DefaultExtensionResolver implements IExtensionResolver { - constructor(private root: string) { } + constructor(private root: string, private fileService: IFileService) { } resolveExtensions(): Promise { - return pfs.readDirsInDir(this.root) - .then(folders => folders.map(name => ({ name, path: path.join(this.root, name) }))); + return this.fileService.resolve(URI.file(this.root)).then(stat => { + return stat.isDirectory && stat.children ? stat.children.filter(child => child.isDirectory) : []; + }).then(folders => { + return folders.map(stat => ({ name: stat.name, path: path.join(this.root, stat.name) })); + }); } } @@ -505,23 +510,23 @@ export class ExtensionScanner { /** * Read the extension defined in `absoluteFolderPath` */ - private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise { + private static scanExtension(fileService: IFileService, version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise { absoluteFolderPath = path.normalize(absoluteFolderPath); - let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); + let parser = new ExtensionManifestParser(fileService, version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); return parser.parse().then((extensionDescription) => { if (extensionDescription === null) { return null; } - let nlsReplacer = new ExtensionManifestNLSReplacer(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig); + let nlsReplacer = new ExtensionManifestNLSReplacer(fileService, version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig); return nlsReplacer.replaceNLS(extensionDescription); }).then((extensionDescription) => { if (extensionDescription === null) { return null; } - let validator = new ExtensionManifestValidator(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); + let validator = new ExtensionManifestValidator(fileService, version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); return validator.validate(extensionDescription); }); } @@ -529,21 +534,21 @@ export class ExtensionScanner { /** * Scan a list of extensions defined in `absoluteFolderPath` */ - public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver | null = null): Promise { + public static async scanExtensions(fileService: IFileService, input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver | null = null): Promise { const absoluteFolderPath = input.absoluteFolderPath; const isBuiltin = input.isBuiltin; const isUnderDevelopment = input.isUnderDevelopment; if (!resolver) { - resolver = new DefaultExtensionResolver(absoluteFolderPath); + resolver = new DefaultExtensionResolver(absoluteFolderPath, fileService); } try { let obsolete: { [folderName: string]: boolean; } = {}; if (!isBuiltin) { try { - const obsoleteFileContents = await pfs.readFile(path.join(absoluteFolderPath, '.obsolete'), 'utf8'); - obsolete = JSON.parse(obsoleteFileContents); + const obsoleteFileContents = await fileService.resolveContent(URI.file(path.join(absoluteFolderPath, '.obsolete')), { encoding: 'utf8' }); + obsolete = JSON.parse(obsoleteFileContents.value); } catch (err) { // Don't care } @@ -559,7 +564,7 @@ export class ExtensionScanner { } const nlsConfig = ExtensionScannerInput.createNLSConfig(input); - let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig))); + let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(fileService, input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig))); let extensionDescriptions = arrays.coalesce(_extensionDescriptions); extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]); @@ -586,34 +591,34 @@ export class ExtensionScanner { * Combination of scanExtension and scanExtensions: If an extension manifest is found at root, we load just this extension, * otherwise we assume the folder contains multiple extensions. */ - public static scanOneOrMultipleExtensions(input: ExtensionScannerInput, log: ILog): Promise { + public static scanOneOrMultipleExtensions(fileService: IFileService, input: ExtensionScannerInput, log: ILog): Promise { const absoluteFolderPath = input.absoluteFolderPath; const isBuiltin = input.isBuiltin; const isUnderDevelopment = input.isUnderDevelopment; - return pfs.fileExists(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => { + return fileService.exists(URI.file(path.join(absoluteFolderPath, MANIFEST_FILE))).then((exists) => { if (exists) { const nlsConfig = ExtensionScannerInput.createNLSConfig(input); - return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => { + return this.scanExtension(fileService, input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => { if (extensionDescription === null) { return []; } return [extensionDescription]; }); } - return this.scanExtensions(input, log); + return this.scanExtensions(fileService, input, log); }, (err) => { log.error(absoluteFolderPath, err); return []; }); } - public static scanSingleExtension(input: ExtensionScannerInput, log: ILog): Promise { + public static scanSingleExtension(fileService: IFileService, input: ExtensionScannerInput, log: ILog): Promise { const absoluteFolderPath = input.absoluteFolderPath; const isBuiltin = input.isBuiltin; const isUnderDevelopment = input.isUnderDevelopment; const nlsConfig = ExtensionScannerInput.createNLSConfig(input); - return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig); + return this.scanExtension(fileService, input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig); } public static mergeBuiltinExtensions(builtinExtensions: Promise, extraBuiltinExtensions: Promise): Promise { @@ -642,4 +647,4 @@ export class ExtensionScanner { return resultArr; }); } -} \ No newline at end of file +}