From 4a079b355fd7150b7ca1980c4b4b88b42f35f307 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 22 Oct 2020 01:45:44 +0200 Subject: [PATCH] Introduce ignored extensions management service --- .../sharedProcess/sharedProcessMain.ts | 2 + .../common/extensionManagement.ts | 1 + .../common/extensionManagementIpc.ts | 6 ++ .../node/extensionManagementService.ts | 23 +++-- .../node/extensionsScanner.ts | 3 +- .../userDataSync/common/extensionsMerge.ts | 31 ------ .../userDataSync/common/extensionsSync.ts | 12 ++- .../userDataSync/common/ignoredExtensions.ts | 95 +++++++++++++++++++ .../test/common/userDataSyncClient.ts | 2 + .../browser/extensionsWorkbenchService.ts | 56 ++++++----- .../common/extensionManagementService.ts | 8 ++ .../common/webExtensionManagementService.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 2 + 13 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 src/vs/platform/userDataSync/common/ignoredExtensions.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index f6389d25d26..5945e0e145e 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -71,6 +71,7 @@ import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/plat import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; +import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -209,6 +210,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); + services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService)); services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService)); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 8148e986c9e..1df732331c5 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -217,6 +217,7 @@ export interface IExtensionManagementService { getExtensionsReport(): Promise; updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise; + updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise; } export const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 8b8b4e5f5bc..da354045436 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -69,6 +69,7 @@ export class ExtensionManagementChannel implements IServerChannel { case 'reinstallFromGallery': return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => transformOutgoingExtension(e, uriTransformer))); case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer)); + case 'updateExtensionScope': return this.service.updateExtensionScope(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer)); case 'getExtensionsReport': return this.service.getExtensionsReport(); } @@ -131,6 +132,11 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer .then(extension => transformIncomingExtension(extension, null)); } + updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise { + return Promise.resolve(this.channel.call('updateExtensionScope', [local, isMachineScoped])) + .then(extension => transformIncomingExtension(extension, null)); + } + getExtensionsReport(): Promise { return Promise.resolve(this.channel.call('getExtensionsReport')); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 051d48d4553..615a6565e1b 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -44,7 +44,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; 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'; -import { ExtensionsScanner, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner'; +import { ExtensionsScanner, ILocalExtensionManifest, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner'; import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled'; @@ -201,7 +201,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } catch (e) { /* Ignore */ } try { - const local = await this.installFromZipPath(identifierWithVersion, zipPath, { ...(metadata || {}), ...options }, operation, token); + const local = await this.installFromZipPath(identifierWithVersion, zipPath, { ...(metadata || {}), ...options }, options, operation, token); this.logService.info('Successfully installed the extension:', identifier.id); return local; } catch (e) { @@ -224,11 +224,11 @@ export class ExtensionManagementService extends Disposable implements IExtension return downloadedLocation; } - private async installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, operation: InstallOperation, token: CancellationToken): Promise { + private async installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, options: InstallOptions, operation: InstallOperation, token: CancellationToken): Promise { try { const local = await this.installExtension({ zipPath, identifierWithVersion, metadata }, token); try { - await this.installDependenciesAndPackExtensions(local, undefined); + await this.installDependenciesAndPackExtensions(local, undefined, options); } catch (error) { if (isNonEmptyArray(local.manifest.extensionDependencies)) { this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message); @@ -298,7 +298,7 @@ export class ExtensionManagementService extends Disposable implements IExtension try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ } try { - await this.installDependenciesAndPackExtensions(local, existingExtension); + await this.installDependenciesAndPackExtensions(local, existingExtension, options); } catch (error) { try { await this.uninstall(local); } catch (error) { /* Ignore */ } throw error; @@ -434,7 +434,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return local; } - private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | undefined): Promise { + private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | undefined, options: InstallOptions): Promise { if (!this.galleryService.isEnabled()) { return; } @@ -457,7 +457,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None); const extensionsToInstall = galleryResult.firstPage; try { - await Promise.all(extensionsToInstall.map(e => this.installFromGallery(e))); + await Promise.all(extensionsToInstall.map(e => this.installFromGallery(e, options))); } catch (error) { try { await this.rollback(extensionsToInstall); } catch (e) { /* ignore */ } throw error; @@ -489,7 +489,14 @@ export class ExtensionManagementService extends Disposable implements IExtension async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); - local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...metadata, isMachineScoped: local.isMachineScoped }); + local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), ...metadata }); + this.manifestCache.invalidate(); + return local; + } + + async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise { + this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id); + local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), isMachineScoped }); this.manifestCache.invalidate(); return local; } diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 878e6bd5970..3f224d1714e 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -31,7 +31,7 @@ const INSTALL_ERROR_DELETING = 'deleting'; const INSTALL_ERROR_RENAMING = 'renaming'; export type IMetadata = Partial; -type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata }; +export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata }; type IRelaxedLocalExtension = Omit & { isBuiltin: boolean }; export class ExtensionsScanner extends Disposable { @@ -365,7 +365,6 @@ export class ExtensionsScanner extends Disposable { try { const manifest = JSON.parse(raw); const metadata = manifest.__metadata || null; - delete manifest.__metadata; c({ manifest, metadata }); } catch (err) { e(new Error(localize('invalidManifest', "Extension invalid: package.json is not a JSON file."))); diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 1540037a7b9..c67ce0ff249 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -6,9 +6,6 @@ import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { deepClone } from 'vs/base/common/objects'; -import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { distinct } from 'vs/base/common/arrays'; export interface IMergeResult { added: ISyncExtension[]; @@ -202,31 +199,3 @@ function massageOutgoingExtension(extension: ISyncExtension, key: string): ISync } return massagedExtension; } - -export function getIgnoredExtensions(installed: ILocalExtension[], configurationService: IConfigurationService): string[] { - const defaultIgnoredExtensions = installed.filter(i => i.isMachineScoped).map(i => i.identifier.id.toLowerCase()); - const value = getConfiguredIgnoredExtensions(configurationService).map(id => id.toLowerCase()); - const added: string[] = [], removed: string[] = []; - if (Array.isArray(value)) { - for (const key of value) { - if (key.startsWith('-')) { - removed.push(key.substring(1)); - } else { - added.push(key); - } - } - } - return distinct([...defaultIgnoredExtensions, ...added,].filter(setting => removed.indexOf(setting) === -1)); -} - -function getConfiguredIgnoredExtensions(configurationService: IConfigurationService): string[] { - let userValue = configurationService.inspect('settingsSync.ignoredExtensions').userValue; - if (userValue !== undefined) { - return userValue; - } - userValue = configurationService.inspect('sync.ignoredExtensions').userValue; - if (userValue !== undefined) { - return userValue; - } - return configurationService.getValue('settingsSync.ignoredExtensions') || []; -} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 1b10c8ac135..c65ee377e1b 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -14,7 +14,7 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; +import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; @@ -23,6 +23,7 @@ import { applyEdits } from 'vs/base/common/jsonEdit'; import { compare } from 'vs/base/common/strings'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; interface IExtensionResourceMergeResult extends IAcceptResult { readonly added: ISyncExtension[]; @@ -94,6 +95,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @IIgnoredExtensionsManagementService private readonly extensionSyncManagementService: IIgnoredExtensionsManagementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService configurationService: IConfigurationService, @@ -117,7 +119,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); if (remoteExtensions) { this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); @@ -201,7 +203,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions); const { added, removed, updated, remote } = mergeResult; return { @@ -217,7 +219,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; if (remoteExtensions !== null) { const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions); @@ -277,7 +279,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { const installedExtensions = await this.extensionManagementService.getInstalled(); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier))); return this.format(localExtensions); } diff --git a/src/vs/platform/userDataSync/common/ignoredExtensions.ts b/src/vs/platform/userDataSync/common/ignoredExtensions.ts new file mode 100644 index 00000000000..97296af8d37 --- /dev/null +++ b/src/vs/platform/userDataSync/common/ignoredExtensions.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { distinct } from 'vs/base/common/arrays'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IIgnoredExtensionsManagementService = createDecorator('IIgnoredExtensionsManagementService'); +export interface IIgnoredExtensionsManagementService { + readonly _serviceBrand: any; + + getIgnoredExtensions(installed: ILocalExtension[]): string[]; + + hasToNeverSyncExtension(extensionId: string): boolean; + hasToAlwaysSyncExtension(extensionId: string): boolean; + updateIgnoredExtensions(ignoredExtensionId: string, ignore: boolean): Promise; + updateSynchronizedExtensions(ignoredExtensionId: string, sync: boolean): Promise; +} + +export class IgnoredExtensionsManagementService implements IIgnoredExtensionsManagementService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + } + + hasToNeverSyncExtension(extensionId: string): boolean { + const configuredIgnoredExtensions = this.getConfiguredIgnoredExtensions(); + return configuredIgnoredExtensions.includes(extensionId.toLowerCase()); + } + + hasToAlwaysSyncExtension(extensionId: string): boolean { + const configuredIgnoredExtensions = this.getConfiguredIgnoredExtensions(); + return configuredIgnoredExtensions.includes(`-${extensionId.toLowerCase()}`); + } + + updateIgnoredExtensions(ignoredExtensionId: string, ignore: boolean): Promise { + // first remove the extension completely from ignored extensions + let currentValue = [...this.configurationService.getValue('settingsSync.ignoredExtensions')].map(id => id.toLowerCase()); + currentValue = currentValue.filter(v => v !== ignoredExtensionId && v !== `-${ignoredExtensionId}`); + + // Add only if ignored + if (ignore) { + currentValue.push(ignoredExtensionId.toLowerCase()); + } + + return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); + } + + updateSynchronizedExtensions(extensionId: string, sync: boolean): Promise { + // first remove the extension completely from ignored extensions + let currentValue = [...this.configurationService.getValue('settingsSync.ignoredExtensions')].map(id => id.toLowerCase()); + currentValue = currentValue.filter(v => v !== extensionId && v !== `-${extensionId}`); + + // Add only if synced + if (sync) { + currentValue.push(`-${extensionId.toLowerCase()}`); + } + + return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); + } + + getIgnoredExtensions(installed: ILocalExtension[]): string[] { + const defaultIgnoredExtensions = installed.filter(i => i.isMachineScoped).map(i => i.identifier.id.toLowerCase()); + const value = this.getConfiguredIgnoredExtensions().map(id => id.toLowerCase()); + const added: string[] = [], removed: string[] = []; + if (Array.isArray(value)) { + for (const key of value) { + if (key.startsWith('-')) { + removed.push(key.substring(1)); + } else { + added.push(key); + } + } + } + return distinct([...defaultIgnoredExtensions, ...added,].filter(setting => removed.indexOf(setting) === -1)); + } + + private getConfiguredIgnoredExtensions(): string[] { + let userValue = this.configurationService.inspect('settingsSync.ignoredExtensions').userValue; + if (userValue !== undefined) { + return userValue; + } + userValue = this.configurationService.inspect('sync.ignoredExtensions').userValue; + if (userValue !== undefined) { + return userValue; + } + return (this.configurationService.getValue('settingsSync.ignoredExtensions') || []).map(id => id.toLowerCase()); + } +} diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index bc8a0cbbca5..6e821b1bafd 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -39,6 +39,7 @@ import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/ import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; +import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; export class UserDataSyncClient extends Disposable { @@ -106,6 +107,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageKeysSyncRegistryService, this.instantiationService.createInstance(StorageKeysSyncRegistryService)); this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService)); + this.instantiationService.stub(IIgnoredExtensionsManagementService, this.instantiationService.createInstance(IgnoredExtensionsManagementService)); this.instantiationService.stub(IExtensionManagementService, >{ async getInstalled() { return []; }, onDidInstallExtension: new Emitter().event, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 39ef03719dd..4d538d0cd0c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -19,7 +19,7 @@ import { import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensioManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -36,10 +36,10 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IProductService } from 'vs/platform/product/common/productService'; -import { getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; -import { isWeb } from 'vs/base/common/platform'; import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { FileAccess } from 'vs/base/common/network'; +import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; interface IExtensionStateProvider { (extension: Extension): T; @@ -523,6 +523,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IStorageService private readonly storageService: IStorageService, @IModeService private readonly modeService: IModeService, + @IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService, + @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, @IProductService private readonly productService: IProductService ) { super(); @@ -980,34 +982,40 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } isExtensionIgnoredToSync(extension: IExtension): boolean { - const localExtensions = (!isWeb && this.localExtensions ? this.localExtensions.local : this.local) - .filter(l => !!l.local) - .map(l => l.local!); - - const ignoredExtensions = getIgnoredExtensions(localExtensions, this.configurationService); - return ignoredExtensions.includes(extension.identifier.id.toLowerCase()); + return extension.local ? !this.isInstalledExtensionSynced(extension.local) + : this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id); } - toggleExtensionIgnoredToSync(extension: IExtension): Promise { + async toggleExtensionIgnoredToSync(extension: IExtension): Promise { const isIgnored = this.isExtensionIgnoredToSync(extension); - const isDefaultIgnored = extension.local?.isMachineScoped; - const id = extension.identifier.id.toLowerCase(); - - // first remove the extension completely from ignored extensions - let currentValue = [...this.configurationService.getValue('settingsSync.ignoredExtensions')].map(id => id.toLowerCase()); - currentValue = currentValue.filter(v => v !== id && v !== `-${id}`); - - // If ignored, then add only if it is ignored by default - if (isIgnored && isDefaultIgnored) { - currentValue.push(`-${id}`); + if (extension.local && isIgnored) { + (extension).local = await this.updateSynchronizingInstalledExtension(extension.local, true); + this._onChange.fire(extension); + } else { + this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, !isIgnored); } + await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated'], false, false); + } - // If asked not to sync, then add only if it is not ignored by default - if (!isIgnored && !isDefaultIgnored) { - currentValue.push(id); + private isInstalledExtensionSynced(extension: ILocalExtension): boolean { + if (extension.isMachineScoped) { + return false; } + if (this.extensionsSyncManagementService.hasToAlwaysSyncExtension(extension.identifier.id)) { + return true; + } + return !this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id); + } - return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); + async updateSynchronizingInstalledExtension(extension: ILocalExtension, sync: boolean): Promise { + const isMachineScoped = !sync; + if (extension.isMachineScoped !== isMachineScoped) { + extension = await this.extensionManagementService.updateExtensionScope(extension, isMachineScoped); + } + if (sync) { + this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, false); + } + return extension; } private installWithProgress(installTask: () => Promise, extensionName?: string): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 41afb8120f2..74b2fc426c7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -136,6 +136,14 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return Promise.reject(`Invalid location ${extension.location.toString()}`); } + updateExtensionScope(extension: ILocalExtension, isMachineScoped: boolean): Promise { + const server = this.getServer(extension); + if (server) { + return server.extensionManagementService.updateExtensionScope(extension, isMachineScoped); + } + return Promise.reject(`Invalid location ${extension.location.toString()}`); + } + zip(extension: ILocalExtension): Promise { const server = this.getServer(extension); if (server) { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index c9c957ce313..6a9b150be2c 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -107,5 +107,5 @@ export class WebExtensionManagementService extends Disposable implements IExtens install(vsix: URI): Promise { throw new Error('unsupported'); } reinstallFromGallery(extension: ILocalExtension): Promise { throw new Error('unsupported'); } getExtensionsReport(): Promise { throw new Error('unsupported'); } - + updateExtensionScope(): Promise { throw new Error('unsupported'); } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index fa0ea82dcf2..09ad97cd729 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -117,8 +117,10 @@ import { OpenerService } from 'vs/editor/browser/services/openerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; +import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; registerSingleton(IUserDataSyncResourceEnablementService, UserDataSyncResourceEnablementService); +registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService); registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IContextViewService, ContextViewService, true); -- GitLab