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

Commit

Permalink
Support disabling/enabling extensions in UI.
Browse files Browse the repository at this point in the history
  • Loading branch information
busykai committed May 30, 2015
1 parent 314d7ff commit 9784ba2
Show file tree
Hide file tree
Showing 7 changed files with 588 additions and 81 deletions.
129 changes: 122 additions & 7 deletions src/extensibility/ExtensionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,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 @@ -187,8 +188,9 @@ define(function (require, exports, module) {
*/
function _reset() {
exports.extensions = extensions = {};
_idsToRemove = [];
_idsToUpdate = [];
_idsToRemove = {};
_idsToUpdate = {};
_idsToDisable = {};
}

/**
Expand Down Expand Up @@ -267,7 +269,7 @@ define(function (require, exports, module) {
metadata: metadata,
path: path,
locationType: locationType,
status: (e.type === "loadFailed" ? START_FAILED : (metadata.disabled ? DISABLED : ENABLED))
status: (e.type === "loadFailed" ? START_FAILED : (e.type === "disabled" ? DISABLED : ENABLED))
};

synchronizeEntry(id);
Expand Down Expand Up @@ -406,6 +408,60 @@ define(function (require, exports, module) {
}
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) {
var result = new $.Deferred(),
extension = extensions[id];
if (extension && extension.installInfo) {
Package.disable(extension.installInfo.path)
.done(function () {
extension.installInfo.status = DISABLED;
extension.installInfo.metadata.disabled = true;
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();
}

/**
* 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) {
var result = new $.Deferred(),
extension = extensions[id];
if (extension && extension.installInfo) {
Package.enable(extension.installInfo.path)
.done(function () {
extension.installInfo.status = ENABLED;
extension.installInfo.metadata.disabled = false;
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();
}

/**
* Updates an installed extension with the given package file.
Expand Down Expand Up @@ -460,7 +516,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 @@ -469,7 +525,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 @@ -478,6 +534,39 @@ 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;
}

/**
* 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 @@ -550,6 +639,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 @@ -784,24 +892,31 @@ 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.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
148 changes: 96 additions & 52 deletions src/extensibility/ExtensionManagerDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,20 @@ define(function (require, exports, module) {
*/
function _performChanges() {
// If an extension was removed or updated, prompt the user to quit Brackets.
var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(),
hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate();
if (!hasRemovedExtensions && !hasUpdatedExtensions) {
var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(),
hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate(),
hasDisabledExtensions = ExtensionManager.hasExtensionsToDisable();
if (!hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) {
return;
}

var buttonLabel = Strings.CHANGE_AND_RELOAD;
if (hasRemovedExtensions && !hasUpdatedExtensions) {
if (hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) {
buttonLabel = Strings.REMOVE_AND_RELOAD;
} else if (hasUpdatedExtensions && !hasRemovedExtensions) {
} else if (hasUpdatedExtensions && !hasRemovedExtensions && !hasDisabledExtensions) {
buttonLabel = Strings.UPDATE_AND_RELOAD;
} else if (hasDisabledExtensions && !hasRemovedExtensions && !hasUpdatedExtensions) {
buttonLabel = Strings.DISABLE_AND_RELOAD;
}

var dlg = Dialogs.showModalDialog(
Expand Down Expand Up @@ -107,57 +110,98 @@ define(function (require, exports, module) {
.text(Strings.PROCESSING_EXTENSIONS)
.append("<span class='spinner inline spin'/>");

ExtensionManager.removeMarkedExtensions()
var removeExtensionsPromise,
updateExtensionsPromise,
disableExtensionsPromise,
removeErrors,
updateErrors,
disableErrors;

removeExtensionsPromise = ExtensionManager.removeMarkedExtensions();
removeExtensionsPromise
.fail(function (errorArray) {
removeErrors = errorArray;
});
updateExtensionsPromise = ExtensionManager.updateExtensions();
updateExtensionsPromise
.fail(function (errorArray) {
updateErrors = errorArray;
});
disableExtensionsPromise = ExtensionManager.disableMarkedExtensions();
disableExtensionsPromise
.fail(function (errorArray) {
disableErrors = errorArray;
});

Async.waitForAll([removeExtensionsPromise, updateExtensionsPromise, disableExtensionsPromise])
.always(function () {
dlg.close();
})
.done(function () {
ExtensionManager.updateExtensions()
.done(function () {
dlg.close();
CommandManager.execute(Commands.APP_RELOAD);
})
.fail(function () {
var ids = [],
dialogs = [];

function nextDialog() {
var dialog = dialogs.shift();
if (dialog) {
Dialogs.showModalDialog(dialog.dialog, dialog.title, dialog.message)
.done(nextDialog);
} else {
// Even in case of error condition, we still have to reload
CommandManager.execute(Commands.APP_RELOAD);
})
.fail(function (errorArray) {
dlg.close();

// This error case should be very uncommon.
// Just let the user know that we couldn't update
// this extension and log the errors to the console.
var ids = [];
errorArray.forEach(function (errorObj) {
ids.push(errorObj.item);
if (errorObj.error && errorObj.error.forEach) {
console.error("Errors for", errorObj.item);
errorObj.error.forEach(function (error) {
console.error(Package.formatError(error));
});
} else {
console.error("Error for", errorObj.item, errorObj);
}
});
Dialogs.showModalDialog(
DefaultDialogs.DIALOG_ID_ERROR,
Strings.EXTENSION_MANAGER_UPDATE,
StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", "))
).done(function () {
// We still have to reload even if some of the removals failed.
CommandManager.execute(Commands.APP_RELOAD);
});
}
}

if (removeErrors) {
removeErrors.forEach(function (errorObj) {
ids.push(errorObj.item);
});
})
.fail(function (errorArray) {
dlg.close();
ExtensionManager.cleanupUpdates();
dialogs.push({
dialog: DefaultDialogs.DIALOG_ID_ERROR,
title: Strings.EXTENSION_MANAGER_REMOVE,
message: StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", "))
});
}

var ids = [];
errorArray.forEach(function (errorObj) {
ids.push(errorObj.item);
});
Dialogs.showModalDialog(
DefaultDialogs.DIALOG_ID_ERROR,
Strings.EXTENSION_MANAGER_REMOVE,
StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", "))
).done(function () {
// We still have to reload even if some of the removals failed.
CommandManager.execute(Commands.APP_RELOAD);
});
if (updateErrors) {
// This error case should be very uncommon.
// Just let the user know that we couldn't update
// this extension and log the errors to the console.
ids.length = 0;
updateErrors.forEach(function (errorObj) {
ids.push(errorObj.item);
if (errorObj.error && errorObj.error.forEach) {
console.error("Errors for", errorObj.item);
errorObj.error.forEach(function (error) {
console.error(Package.formatError(error));
});
} else {
console.error("Error for", errorObj.item, errorObj);
}
});
dialogs.push({
dialog: DefaultDialogs.DIALOG_ID_ERROR,
title: Strings.EXTENSION_MANAGER_UPDATE,
message: StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", "))
});
}

if (disableErrors) {
ids.length = 0;
disableErrors.forEach(function (errorObj) {
ids.push(errorObj.item);
});
dialogs.push({
dialog: DefaultDialogs.DIALOG_ID_ERROR,
title: Strings.EXTENSION_MANAGER_REMOVE,
message: StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", "))
});
}

nextDialog();
});
} else {
dlg.close();
Expand Down
Loading

0 comments on commit 9784ba2

Please sign in to comment.