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

Offline mode #54831

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
56a85df
First draft for offline mode
ramya-rao-a Jul 22, 2018
892c555
Fix imports
ramya-rao-a Jul 23, 2018
f1c8435
Fix tests
ramya-rao-a Jul 23, 2018
e5bdf92
Merge remote-tracking branch 'origin/master' into offline-mode
ramya-rao-a Jul 23, 2018
ee86939
Refactoring
ramya-rao-a Jul 23, 2018
d03d190
Merge remote-tracking branch 'origin/master' into offline-mode
ramya-rao-a Jul 23, 2018
ad1c8c1
dont fetch experiments in offline mode
ramya-rao-a Jul 23, 2018
f490af7
changelog opens release notes in browser in offline mode
ramya-rao-a Jul 23, 2018
7fef11f
Use MenuRegistry to register menu items
ramya-rao-a Jul 24, 2018
35d20ed
Check for updates in offline mode from context menu
ramya-rao-a Jul 24, 2018
b6dd980
Refactoring
ramya-rao-a Jul 24, 2018
ea5d929
Disclaimer
ramya-rao-a Jul 24, 2018
2a6d5f7
Register cmd pallet commands using MenuRegistry
ramya-rao-a Jul 24, 2018
1fcbb6c
Release notes in offline mode
ramya-rao-a Jul 24, 2018
10fa520
logUploader and others in offline mode
ramya-rao-a Jul 24, 2018
7cae07e
Prevent future attempt at requestservice when in offline mode
ramya-rao-a Jul 24, 2018
67b0554
Since release notes action fallsback to browser, changlog doesnt need…
ramya-rao-a Jul 24, 2018
cfdfa40
Release notes refactor
ramya-rao-a Jul 24, 2018
5004eb2
Merge remote-tracking branch 'origin/master' into offline-mode
ramya-rao-a Jul 24, 2018
36d74df
Offline mode tests
ramya-rao-a Jul 24, 2018
23f3391
Merge remote-tracking branch 'origin/master' into offline-mode
ramya-rao-a Jul 24, 2018
7d83013
Other tests
ramya-rao-a Jul 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
10 changes: 9 additions & 1 deletion src/vs/code/electron-main/logUploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import product from 'vs/platform/node/product';
import { IRequestService } from 'vs/platform/request/node/request';
import { IRequestContext } from 'vs/base/node/request';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { offlineModeSetting, unSupportedInOfflineModeMsg } from 'vs/platform/actions/common/offlineMode';

interface PostResult {
readonly blob_id: string;
Expand All @@ -36,8 +38,14 @@ class Endpoint {
export async function uploadLogs(
channel: ILaunchChannel,
requestService: IRequestService,
environmentService: IEnvironmentService
environmentService: IEnvironmentService,
configurationService: IConfigurationService
): Promise<any> {
if (configurationService.getValue(offlineModeSetting) === true) {
console.error(unSupportedInOfflineModeMsg);
return;
}

const endpoint = Endpoint.getFromProduct();
if (!endpoint) {
console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint'));
Expand Down
3 changes: 2 additions & 1 deletion src/vs/code/electron-main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const requestService = accessor.get(IRequestService);
const configurationService = accessor.get(IConfigurationService);

function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
let promise = TPromise.wrap<void>(void 0);
Expand Down Expand Up @@ -204,7 +205,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {

// Log uploader
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
return uploadLogs(channel, requestService, environmentService)
return uploadLogs(channel, requestService, environmentService, configurationService)
.then(() => TPromise.wrapError(new ExpectedError()));
}

Expand Down
8 changes: 8 additions & 0 deletions src/vs/code/electron-main/menubar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IMenubarData, IMenubarMenuItemAction, IMenubarMenuItemSeparator } from 'vs/platform/menubar/common/menubar';
import URI from 'vs/base/common/uri';
import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode';

// interface IExtensionViewlet {
// id: string;
Expand Down Expand Up @@ -581,6 +582,13 @@ export class Menubar {
}

private getUpdateMenuItems(): Electron.MenuItem[] {
if (this.configurationService.getValue(offlineModeSetting) === true) {
return [new MenuItem({
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => {
this.runActionInRenderer('workbench.action.notifyUnsupportedFeatureInOfflineMode');
}
})];
}
const state = this.updateService.state;

switch (state.type) {
Expand Down
8 changes: 8 additions & 0 deletions src/vs/code/electron-main/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/wind
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import URI from 'vs/base/common/uri';
import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode';

interface IMenuItemClickHandler {
inDevTools: (contents: Electron.WebContents) => void;
Expand Down Expand Up @@ -1124,6 +1125,13 @@ export class CodeMenu {
}

private getUpdateMenuItems(): Electron.MenuItem[] {
if (this.configurationService.getValue(offlineModeSetting) === true) {
return [new MenuItem({
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => {
this.runActionInRenderer('workbench.action.notifyUnsupportedFeatureInOfflineMode');
}
})];
}
const state = this.updateService.state;

switch (state.type) {
Expand Down
159 changes: 159 additions & 0 deletions src/vs/platform/actions/common/offlineMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

export const offlineModeSetting = 'workbench.enableOfflineMode';
export const unSupportedInOfflineModeMsg = localize('offlineModeUnsupportedFeature', "This feature is not supported in offline mode");

export class EnableOfflineMode extends Action {
static readonly ID = 'workbench.action.enableOfflineMode';
static LABEL = localize('enableOfflineMode', 'Enable Offline Mode');

private readonly disclaimerStorageKey = 'workbench.offlineMode.disclaimer.dontShowAgain';

constructor(
id: string = EnableOfflineMode.ID,
label: string = EnableOfflineMode.LABEL,
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService,
@INotificationService private notificationService: INotificationService
) {
super(id, label);
this.updateEnabled();
this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(offlineModeSetting)) {
this.updateEnabled();
}
});
}

private updateEnabled() {
this.enabled = this.configurationService.getValue(offlineModeSetting) !== true;
}

run(): TPromise<any> {
if (this.storageService.getBoolean(this.disclaimerStorageKey, StorageScope.GLOBAL, false) === false) {
this.notificationService.prompt(Severity.Info, localize('offlineModeDisclaimer', "VS Code cannot stop extensions from making network requests in offline mode. If extensions make such requests, please log an issue against them."), [
Copy link
Collaborator

@mjbvz mjbvz Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way for extensions to check if the user is in offline mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjbvz Yes, they can look at the value for the workbench.enableOfflineMode setting. I havent yet gone through built-in extensions to ensure they are able to comply with the offline mode. That will be another PR.

Copy link
Collaborator

@mjbvz mjbvz Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so that will added to vscode.d.ts? Or read the setting value directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the setting value directly

{
label: localize('DontShowAgain', "Don't Show Again"),
run: () => {
this.storageService.store(this.disclaimerStorageKey, true);
}
}
]);
}
return this.configurationService.updateValue(offlineModeSetting, true);
}
}

export class DisableOfflineMode extends Action {
static readonly ID = 'workbench.action.disableOfflineMode';
static LABEL = localize('disableOfflineMode', 'Disable Offline Mode');

constructor(
id: string = DisableOfflineMode.ID,
label: string = DisableOfflineMode.LABEL,
@IConfigurationService private configurationService: IConfigurationService
) {
super(id, label);
this.updateEnabled();
this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(offlineModeSetting)) {
this.updateEnabled();
}
});
}

private updateEnabled() {
this.enabled = this.configurationService.getValue(offlineModeSetting) === true;
}

run(): TPromise<any> {
return this.configurationService.updateValue(offlineModeSetting, false);
}
}

export class NotifyUnsupportedFeatureInOfflineMode extends Action {
static readonly ID = 'workbench.action.notifyUnsupportedFeatureInOfflineMode';

constructor(
id: string = NotifyUnsupportedFeatureInOfflineMode.ID,
label: string = '',
@IConfigurationService private configurationService: IConfigurationService,
@INotificationService private notificationService: INotificationService
) {
super(id, label);
}

run(): TPromise<any> {
this.notificationService.prompt(Severity.Info, unSupportedInOfflineModeMsg, [
{
label: DisableOfflineMode.LABEL,
run: () => {
return this.configurationService.updateValue(offlineModeSetting, false);
}
}
]);
return TPromise.as(null);
}
}

MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '5_offline',
command: {
id: EnableOfflineMode.ID,
title: localize({ key: 'miEnableOfflineMode', comment: ['&& denotes a mnemonic'] }, "Enable &&Offline Mode")
},
order: 1,
when: ContextKeyExpr.not('config.' + offlineModeSetting)
});

MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '5_offline',
command: {
id: DisableOfflineMode.ID,
title: localize({ key: 'miDisableOfflineMode', comment: ['&& denotes a mnemonic'] }, "Disable &&Offline Mode")
},
order: 1,
when: ContextKeyExpr.has('config.' + offlineModeSetting)
});

CommandsRegistry.registerCommand(EnableOfflineMode.ID, serviceAccesor => {
serviceAccesor.get(IInstantiationService).createInstance(EnableOfflineMode, EnableOfflineMode.ID, EnableOfflineMode.LABEL).run();
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: EnableOfflineMode.ID,
title: 'Preferences: Enable Offline Mode'
},
when: ContextKeyExpr.not('config.' + offlineModeSetting)
});

CommandsRegistry.registerCommand(DisableOfflineMode.ID, serviceAccesor => {
serviceAccesor.get(IInstantiationService).createInstance(DisableOfflineMode, DisableOfflineMode.ID, DisableOfflineMode.LABEL).run();
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: DisableOfflineMode.ID,
title: 'Preferences: Disable Offline Mode'
},
when: ContextKeyExpr.has('config.' + offlineModeSetting)
});

CommandsRegistry.registerCommand(NotifyUnsupportedFeatureInOfflineMode.ID, serviceAccesor => {
serviceAccesor.get(IInstantiationService).createInstance(NotifyUnsupportedFeatureInOfflineMode, NotifyUnsupportedFeatureInOfflineMode.ID, '').run();
});
3 changes: 3 additions & 0 deletions src/vs/platform/request/node/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export interface IHTTPConfiguration {
proxyStrictSSL?: boolean;
proxyAuthorization?: string;
};
workbench?: {
enableOfflineMode?: boolean;
};
}

Registry.as<IConfigurationRegistry>(Extensions.Configuration)
Expand Down
6 changes: 6 additions & 0 deletions src/vs/platform/request/node/requestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class RequestService implements IRequestService {
private proxyUrl: string;
private strictSSL: boolean;
private authorization: string;
private isOfflineMode: boolean;
private disposables: IDisposable[] = [];

constructor(
Expand All @@ -38,9 +39,14 @@ export class RequestService implements IRequestService {
this.proxyUrl = config.http && config.http.proxy;
this.strictSSL = config.http && config.http.proxyStrictSSL;
this.authorization = config.http && config.http.proxyAuthorization;
this.isOfflineMode = config.workbench && config.workbench.enableOfflineMode === true;
}

request(options: IRequestOptions, requestFn: IRequestFunction = request): TPromise<IRequestContext> {
if (this.isOfflineMode) {
return TPromise.as({ res: { headers: {}, }, stream: null });
}

this.logService.trace('RequestService#request', options.url);

const { proxyUrl, strictSSL } = this;
Expand Down
4 changes: 4 additions & 0 deletions src/vs/platform/telemetry/common/telemetryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export class TelemetryService implements ITelemetryService {
}

private _updateUserOptIn(): void {
if (this._configurationService.getValue('workbench.enableOfflineMode') === true) {
this._userOptIn = false;
return;
}
const config = this._configurationService.getValue<any>(TELEMETRY_SECTION_ID);
this._userOptIn = config ? config.enableTelemetry : this._userOptIn;
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/update/electron-main/abstractUpdateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } fr
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILogService } from 'vs/platform/log/common/log';
import { IRequestService } from 'vs/platform/request/node/request';
import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode';

export function createUpdateURL(platform: string, quality: string): string {
return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`;
Expand Down Expand Up @@ -49,7 +50,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
@IRequestService protected requestService: IRequestService,
@ILogService protected logService: ILogService,
) {
if (this.environmentService.disableUpdates) {
if (this.environmentService.disableUpdates || this.configurationService.getValue(offlineModeSetting) === true) {
this.logService.info('update#ctor - updates are disabled');
return;
}
Expand All @@ -60,7 +61,6 @@ export abstract class AbstractUpdateService implements IUpdateService {
}

const quality = this.getProductQuality();

if (!quality) {
this.logService.info('update#ctor - updates are disabled');
return;
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/electron-browser/main.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,12 @@ configurationRegistry.registerConfiguration({
'description': nls.localize('settingsTocVisible', "Controls whether the settings editor Table of Contents is visible."),
'default': true,
'scope': ConfigurationScope.WINDOW
},
'workbench.enableOfflineMode': {
'type': 'boolean',
'description': nls.localize('settingsOfflineMode', "Enables offline mode where no requests are made over the network by VS Code."),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this wording should be changed to say that no requests are made to Microsoft, as we do still make requests to SCM providers, and there's nothing we can do to stop extensions (so far as I know)

'default': false,
'scope': ConfigurationScope.WINDOW
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { assign } from 'vs/base/common/objects';
import URI from 'vs/base/common/uri';
import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService';
import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode';

const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again");
Expand Down Expand Up @@ -162,7 +163,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe

private fetchProactiveRecommendations(calledDuringStartup?: boolean): TPromise<void> {
let fetchPromise = TPromise.as(null);
if (!this.proactiveRecommendationsFetched) {
if (!this.proactiveRecommendationsFetched
&& this.configurationService.getValue(offlineModeSetting) !== true) {
this.proactiveRecommendationsFetched = true;

// Executable based recommendations carry out a lot of file stats, so run them after 10 secs
Expand Down Expand Up @@ -499,7 +501,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
let hasSuggestion = false;

const uri = model.uri;
if (!uri || !this.fileService.canHandleResource(uri)) {
if (!uri || !this.fileService.canHandleResource(uri) || this.configurationService.getValue(offlineModeSetting) === true) {
return;
}

Expand Down Expand Up @@ -717,7 +719,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
if (filteredRecs.length === 0
|| config.ignoreRecommendations
|| config.showRecommendationsOnlyOnDemand
|| this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) {
|| this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)
|| this.configurationService.getValue(offlineModeSetting) === true) {
return;
}

Expand Down
Loading