Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Disable and enable extensions #11184

Merged
merged 11 commits into from
Jul 8, 2015
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Thumbs.db

/src/extensions/disabled

# ignore .disabled file for default extensions
/src/extensions/default/*/.disabled

#OSX .DS_Store files
.DS_Store

Expand All @@ -34,3 +37,4 @@ Thumbs.db

# Files that can be automatically downloaded that we don't want to ship with our builds
/src/extensibility/node/node_modules/request/tests/

162 changes: 146 additions & 16 deletions src/extensibility/ExtensionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ define(function (require, exports, module) {
* Extension status constants.
*/
var ENABLED = "enabled",
DISABLED = "disabled",
START_FAILED = "startFailed";

/**
Expand Down Expand Up @@ -103,8 +104,9 @@ define(function (require, exports, module) {
/**
* Requested changes to the installed extensions.
*/
var _idsToRemove = [],
_idsToUpdate = [];
var _idsToRemove = {},
_idsToUpdate = {},
_idsToDisable = {};

PreferencesManager.stateManager.definePreference(FOLDER_AUTOINSTALL, "object", undefined);

Expand Down Expand Up @@ -186,8 +188,9 @@ define(function (require, exports, module) {
*/
function _reset() {
exports.extensions = extensions = {};
_idsToRemove = [];
_idsToUpdate = [];
_idsToRemove = {};
_idsToUpdate = {};
_idsToDisable = {};
}

/**
Expand Down Expand Up @@ -240,8 +243,9 @@ define(function (require, exports, module) {
* @param {string} path The local path of the loaded extension's folder.
*/
function _handleExtensionLoad(e, path) {
function setData(id, metadata) {
function setData(metadata) {
var locationType,
id = metadata.name,
userExtensionPath = ExtensionLoader.getUserExtensionPath();
if (path.indexOf(userExtensionPath) === 0) {
locationType = LOCATION_USER;
Expand All @@ -265,27 +269,33 @@ define(function (require, exports, module) {
metadata: metadata,
path: path,
locationType: locationType,
status: (e.type === "loadFailed" ? START_FAILED : ENABLED)
status: (e.type === "loadFailed" ? START_FAILED : (e.type === "disabled" ? DISABLED : ENABLED))
};

synchronizeEntry(id);
loadTheme(id);
exports.trigger("statusChange", id);
}

function deduceMetadata() {
var match = path.match(/\/([^\/]+)$/),
name = (match && match[1]) || path,
metadata = { name: name, title: name };
return metadata;
}

ExtensionUtils.loadPackageJson(path)
ExtensionUtils.loadMetadata(path)
.done(function (metadata) {
setData(metadata.name, metadata);
setData(metadata);
})
.fail(function () {
.fail(function (disabled) {
// If there's no package.json, this is a legacy extension. It was successfully loaded,
// but we don't have an official ID or metadata for it, so we just create an id and
// "title" for it (which is the last segment of its pathname)
// and record that it's enabled.
var match = path.match(/\/([^\/]+)$/),
name = (match && match[1]) || path,
metadata = { name: name, title: name };
setData(name, metadata);
var metadata = deduceMetadata();
metadata.disabled = disabled;
setData(metadata);
});
}

Expand Down Expand Up @@ -398,6 +408,58 @@ define(function (require, exports, module) {
}
return result.promise();
}

/**
* @private
*
* Disables or enables the installed extensions.
*
* @param {string} id The id of the extension to disable or enable.
* @param {boolean} enable A boolean indicating whether to enable or disable.
* @return {$.Promise} A promise that's resolved when the extension action is
* completed or rejected with an error that prevents the action from completion.
*/
function _enableOrDisable(id, enable) {
var result = new $.Deferred(),
extension = extensions[id];
if (extension && extension.installInfo) {
Package[(enable ? "enable" : "disable")](extension.installInfo.path)
.done(function () {
extension.installInfo.status = enable ? ENABLED : DISABLED;
extension.installInfo.metadata.disabled = !enable;
result.resolve();
exports.trigger("statusChange", id);
})
.fail(function (err) {
result.reject(err);
});
} else {
result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id));
}
return result.promise();
}

/**
* Disables the installed extension with the given id.
*
* @param {string} id The id of the extension to disable.
* @return {$.Promise} A promise that's resolved when the extenion is disabled or
* rejected with an error that prevented the disabling.
*/
function disable(id) {
return _enableOrDisable(id, false);
}

/**
* Enables the installed extension with the given id.
*
* @param {string} id The id of the extension to enable.
* @return {$.Promise} A promise that's resolved when the extenion is enabled or
* rejected with an error that prevented the enabling.
*/
function enable(id) {
return _enableOrDisable(id, true);
}

/**
* Updates an installed extension with the given package file.
Expand Down Expand Up @@ -452,7 +514,7 @@ define(function (require, exports, module) {
}
exports.trigger("statusChange", id);
}

/**
* Returns true if an extension is marked for removal.
* @param {string} id The id of the extension to check.
Expand All @@ -461,7 +523,7 @@ define(function (require, exports, module) {
function isMarkedForRemoval(id) {
return !!(_idsToRemove[id]);
}

/**
* Returns true if there are any extensions marked for removal.
* @return {boolean} true if there are extensions to remove
Expand All @@ -470,6 +532,46 @@ define(function (require, exports, module) {
return Object.keys(_idsToRemove).length > 0;
}

/**
* Marks an extension for disabling later, or unmarks an extension previously marked.
*
* @param {string} id The id of the extension
* @param {boolean} mark Whether to mark or unmark the extension.
*/
function markForDisabling(id, mark) {
if (mark) {
_idsToDisable[id] = true;
} else {
delete _idsToDisable[id];
}
exports.trigger("statusChange", id);
}

/**
* Returns true if an extension is mark for disabling.
*
* @param {string} id The id of the extension to check.
* @return {boolean} true if it's been mark for disabling, false otherwise.
*/
function isMarkedForDisabling(id) {
return !!(_idsToDisable[id]);
}

/**
* Returns true if there are any extensions marked for disabling.
* @return {boolean} true if there are extensions to disable
*/
function hasExtensionsToDisable() {
return Object.keys(_idsToDisable).length > 0;
}

/**
* Unmarks all the extensions that have been marked for disabling.
*/
function unmarkAllForDisabling() {
_idsToDisable = {};
}

/**
* If a downloaded package appears to be an update, mark the extension for update.
* If an extension was previously marked for removal, marking for update will
Expand Down Expand Up @@ -542,6 +644,25 @@ define(function (require, exports, module) {
}
);
}

/**
* Disables extensions marked for disabling.
*
* If the return promise is rejected, the argument will contain an array of objects. Each
* element is an object identifying the extension failed with "item" property set to the
* extension id which has failed to be disabled and "error" property set to the error.
*
* @return {$.Promise} A promise that's resolved when all extensions marked for disabling are
* disabled or rejected if one or more extensions can't be disabled.
*/
function disableMarkedExtensions() {
return Async.doInParallel_aggregateErrors(
Object.keys(_idsToDisable),
function (id) {
return disable(id);
}
);
}

/**
* Updates extensions previously marked for update.
Expand Down Expand Up @@ -764,7 +885,8 @@ define(function (require, exports, module) {
// Listen to extension load and loadFailed events
ExtensionLoader
.on("load", _handleExtensionLoad)
.on("loadFailed", _handleExtensionLoad);
.on("loadFailed", _handleExtensionLoad)
.on("disabled", _handleExtensionLoad);


EventDispatcher.makeEventDispatcher(exports);
Expand All @@ -775,24 +897,32 @@ define(function (require, exports, module) {
exports.getExtensionURL = getExtensionURL;
exports.remove = remove;
exports.update = update;
exports.disable = disable;
exports.enable = enable;
exports.extensions = extensions;
exports.cleanupUpdates = cleanupUpdates;
exports.markForRemoval = markForRemoval;
exports.isMarkedForRemoval = isMarkedForRemoval;
exports.unmarkAllForRemoval = unmarkAllForRemoval;
exports.hasExtensionsToRemove = hasExtensionsToRemove;
exports.markForDisabling = markForDisabling;
exports.isMarkedForDisabling = isMarkedForDisabling;
exports.unmarkAllForDisabling = unmarkAllForDisabling;
exports.hasExtensionsToDisable = hasExtensionsToDisable;
exports.updateFromDownload = updateFromDownload;
exports.removeUpdate = removeUpdate;
exports.isMarkedForUpdate = isMarkedForUpdate;
exports.hasExtensionsToUpdate = hasExtensionsToUpdate;
exports.removeMarkedExtensions = removeMarkedExtensions;
exports.disableMarkedExtensions = disableMarkedExtensions;
exports.updateExtensions = updateExtensions;
exports.getAvailableUpdates = getAvailableUpdates;
exports.cleanAvailableUpdates = cleanAvailableUpdates;

exports.hasDownloadedRegistry = false;

exports.ENABLED = ENABLED;
exports.DISABLED = DISABLED;
exports.START_FAILED = START_FAILED;

exports.LOCATION_DEFAULT = LOCATION_DEFAULT;
Expand Down
Loading