-
-
Save Pimm/2998412 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Any copyright by Panagiotis Astithas is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ | |
* | |
* Otherwise, copyright 2012 Pimm Hogeling | |
* You can redistribute and/or modify this software under the terms of the Mozilla Public License Version 1.1, the | |
* GNU Lesser General Public License version 2.1 or later or the GNU General Public License version 2 or later. | |
*/ | |
Components.utils.import("resource:///modules/devtools/gcli.jsm"); | |
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); | |
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/AddonRepository.jsm"); | |
// You might ask yourself, why does the monkey who wrote this put everything in functions and stuff? Well - before I answer | |
// your question - I'd like to point out that I am a Chinchilla; not a monkey. | |
// I put things in separate functions, because I don't want one command to reference to another command's (unrelated) helper | |
// functions. I fear this could prevent memory from being cleared under certain circumstances. If this is an irrational fear, | |
// feel more than free to clean up my code. | |
// Add the "parent" command. | |
gcli.addCommand({ | |
name: "addon", | |
description: { | |
"root": "Manipulate add-ons", | |
"nl-nl": "Beheer add-ons" | |
} | |
}); | |
(function() { | |
function representEnabledAddon(addon) { | |
return "<li><![CDATA[" + addon.name + "\u2002" + addon.version + "]]></li>"; | |
} | |
function representDisabledAddon(addon) { | |
return "<li style=\"opacity: .6; text-decoration: line-through;\"><![CDATA[" + addon.name + "\u2002" + addon.version + "]]></li>"; | |
} | |
/** | |
* Compares two addons by their name. Used in sorting. | |
*/ | |
function compareAddonNames(a, b) { | |
return String.localeCompare(a.name, b.name); | |
} | |
/** | |
* Resolves the promise which is the scope (this) of this function, filling it with an HTML representation of the passed | |
* add-ons. | |
*/ | |
function list(addons) { | |
// Separate the enabled add-ons from the disabled ones. | |
let enabledAddons = []; | |
let disabledAddons = []; | |
addons.forEach(function(addon) { | |
(addon.userDisabled ? disabledAddons : enabledAddons).push(addon); | |
}); | |
// Map and sort the add-ons, and create an HTML list. | |
// nl-nl: Deze add-ons zijn op dit moment geinstalleerd: | |
this.resolve("The following add-ons are currently installed:" + | |
"<ol>" + | |
enabledAddons.sort(compareAddonNames).map(representEnabledAddon).join("") + | |
disabledAddons.sort(compareAddonNames).map(representDisabledAddon).join("") + | |
"</ol>"); | |
} | |
// Add "addon list". | |
gcli.addCommand({ | |
name: "addon list", | |
description: { | |
"root": "List the installed add-ons", | |
"nl-nl": "Toon een lijst van geinstalleerde add-ons" | |
}, | |
exec: function(cliArguments, context) { | |
// Create the promise that will be resolved when the add-on listing has been finished. | |
let promise = context.createPromise(); | |
// Get the add-ons. TODO This command lists extensions only, though it looks like "addon install" could also install | |
// other types of add-ons. If so, these commands should be more consistent. Also, the name of the parent command - | |
// "addon" - might not be completely accurate. | |
AddonManager.getAddonsByTypes(["extension"], list.bind(promise)); | |
return promise; | |
} | |
}); | |
})(); | |
// The name parameter, used in "addon enable", "addon disable", "addon install" and "addon uninstall". | |
const nameParameter = { | |
name: "name", | |
type: "string", | |
description: { | |
"root": "The name of the add-on", | |
"nl-nl": "De naam van de add-on" | |
} | |
}; | |
/** | |
* Returns all of the latin characters in the passed string in one long string, converted to lower case. | |
*/ | |
function convertToSimplifiedLowerCase(input) { | |
const latinCharacterFinder = /[a-zA-Z]/g; | |
var match; | |
const result = []; | |
while (null != (match = latinCharacterFinder.exec(input))) { | |
result.push(match[0]); | |
} | |
return result.join("").toLowerCase(); | |
}; | |
// Define the name comparer, which will be used to compare the user input against the names of add-ons. | |
function NameComparer(targetValue) { | |
this.targetValue = convertToSimplifiedLowerCase(targetValue); | |
} | |
/** | |
* Returns whether the passed name equals the internal name, ignoring the case of the names as well as any non-Latin | |
* characters (anything but a-z, so even ignoring numbers and interpunction). | |
* Ignoring the case and non-Latin characters makes trivial "mistakes" by the user insignificant. This is probably not the | |
* most user-friendly way if said user is actually inputting non-Latin add-on names. Ideally, such input would be taken | |
* seriously. | |
* If the internal name is shorter than the passed name, only the substring of the passed name that is as long as the | |
* internal name is compared. For example, "Borat: Cultural Learnings of America for Make Benefit Glorious Nation of | |
* Kazakhstan" equals "Borat". This enables the user to provide only a "short" name of an add-on, and still have it match. | |
*/ | |
NameComparer.prototype.compare = function(value) { | |
value = convertToSimplifiedLowerCase(value); | |
return this.targetValue.substr(0, value.length) == value; | |
}; | |
/** | |
* Returns a string that represents the passed add-on. | |
*/ | |
function representAddon(addon) { | |
return addon.name + " " + addon.version; | |
} | |
(function() { | |
/** | |
* Enables the addon in the passed list which has a name that matches according to the passed name comparer, and resolves | |
* the promise which is the scope (this) of this function to display the result of this enable attempt. | |
*/ | |
function enable(nameComparer, addons) { | |
// Find the add-on. TODO consider adding something that checks for multiple matches. Currently, if the user's input | |
// matches multiple add-ons, one of them (kind-of at random) will be enabled. Probably not the best we can do. | |
let addon = null; | |
addons.some(function(candidate) { | |
if (nameComparer.compare(candidate.name)) { | |
addon = candidate; | |
return true; | |
} else { | |
return false; | |
} | |
}); | |
// Enable the add-on, if it was found. Explain the user what happened. TODO consider checking whether the addon was | |
// already enabled. Showing a "success" message when the add-on was already enabled might cause confusion. | |
if (null == addon) { | |
// nl-nl: De add-on is niet gevonden. | |
this.resolve("This add-on was not found."); | |
} else { | |
addon.userDisabled = false; | |
// nl-nl: {$1} is ingeschakeld. | |
this.resolve("<![CDATA[" + representAddon(addon) + " has been enabled.]]>"); | |
} | |
} | |
// Add "addon enable". | |
gcli.addCommand({ | |
name: "addon enable", | |
description: { | |
"root": "Enable the specified add-on", | |
"nl-nl": "Schakel de gespecificeerde add-on in" | |
}, | |
params: [nameParameter], | |
exec: function(cliArguments, context) { | |
// Create the promise that will be resolved when the enabling has been finished. | |
let promise = context.createPromise(); | |
// List the installed add-ons, enable one when done listing. | |
AddonManager.getAddonsByTypes(["extension"], enable.bind(promise, new NameComparer(cliArguments[nameParameter.name]))); | |
return promise; | |
} | |
}); | |
/** | |
* Like enable, but.. you know.. the exact opposite. | |
*/ | |
function disable(nameComparer, addons) { | |
// Find the add-on. | |
let addon = null; | |
addons.some(function(candidate) { | |
if (nameComparer.compare(candidate.name)) { | |
addon = candidate; | |
return true; | |
} else { | |
return false; | |
} | |
}); | |
// Disable the add-on, if it was found. Explain the user what happened. | |
if (null == addon) { | |
// nl-nl: De add-on is niet gevonden. | |
this.resolve("Could not find the add-on."); | |
} else { | |
addon.userDisabled = true; | |
// nl-nl: {$1} is uitgeschakeld. | |
this.resolve("<![CDATA[" + representAddon(addon) + " has been disabled.]]>"); | |
} | |
} | |
// Add "addon disable". | |
gcli.addCommand({ | |
name: "addon disable", | |
description: { | |
"root": "Disable the specified add-on", | |
"nl-nl": "Schakel de gespecificeerde add-on uit" | |
}, | |
params: [nameParameter], | |
exec: function(cliArguments, context) { | |
// Create the promise that will be resolved when the disabling has been finished. | |
let promise = context.createPromise(); | |
// List the installed add-ons, disable one when done listing. | |
AddonManager.getAddonsByTypes(["extension"], disable.bind(promise, new NameComparer(cliArguments[nameParameter.name]))); | |
return promise; | |
} | |
}); | |
})(); | |
(function() { | |
// Define the callback that will be used when the add-on repository is done searching. | |
const AddonSearchCallback = function(promise, inputtedName) { | |
this.promise = promise; | |
this.nameComparer = new NameComparer(inputtedName); | |
} | |
/** | |
* Explains to the user that the add-on was not found, and makes a suggestion for a more successful install command if | |
* possible. Resolves the promise which is the scope (this) of this function. | |
*/ | |
function suggest(nameComparer, potentialSuggestion, addons) { | |
let alreadyInstalledAddon = null; | |
addons.some(function(candidate) { | |
if (this.compare(candidate.name)) { | |
alreadyInstalledAddon = candidate; | |
return true; | |
} else { | |
return false; | |
} | |
}, nameComparer); | |
if (null != alreadyInstalledAddon) { | |
// nl-nl: {$1} is al geinstalleerd. | |
this.resolve("<![CDATA[" + representAddon(alreadyInstalledAddon) + " is already installed.]]>"); | |
} else if (null != potentialSuggestion) { | |
// nl-nl: De add-on is niet gevonden. Misschien bedoelde je {$1}. | |
this.resolve("Could not find the add-on. Perhaps you meant <code><![CDATA[addon install \"" + potentialSuggestion.name + "\"]]></code>."); | |
} else { | |
// nl-nl: De add-on is niet gevonden. | |
this.resolve("Could not find the add-on."); | |
} | |
} | |
AddonSearchCallback.prototype.searchSucceeded = function(addons, addonCount) { | |
// Find the add-on the user was looking for from the search results. | |
let addon = null; | |
if (0 != addonCount) { | |
addons.some(function(candidate) { | |
if (this.compare(candidate.name)) { | |
addon = candidate; | |
return true; | |
} else { | |
return false; | |
} | |
}, this.nameComparer); | |
} | |
if (null != addon) { | |
// Inform the user the add-on was found. | |
// nl-nl: Bezig met downloaden van {$1}... | |
// promise.setProgress("<![CDATA[Downloading " + addon.name + " " + addon.version + "…]]>"); | |
addon.install.addListener(new AddonInstallListener(this.promise)); | |
addon.install.install(); | |
} else { | |
// If not one of the add-ons returned by the repository seems to be what the user was looking for, show a suggestion. | |
let searchCallback = this; | |
AddonManager.getAddonsByTypes(["extension"], suggest.bind(this.promise, this.nameComparer, 0 == addonCount ? null : addons[0])); | |
} | |
} | |
AddonSearchCallback.prototype.searchFailed = function() { | |
// nl-nl: Zoekopdracht mislukt. Misschien kon er geen verbinding worden gemaakt met het add-on magazijn. | |
this.promise.resolve("Search failed. Perhaps no connection to the add-on repository could be made."); | |
} | |
// Define the listener that will be used when an add-on is being installed. | |
const AddonInstallListener = function(promise) { | |
this.promise = promise; | |
}; | |
const nop = function() { | |
}; | |
AddonInstallListener.prototype = { | |
/** | |
* Called when a new instance of AddonInstall is created, primarily so UI can display some kind of progress for all | |
* installs. | |
*/ | |
onNewInstall: nop, | |
/** | |
* Called when downloading begins for an add-on install. Only called for add-ons that need to be downloaded. A listener | |
* may return false to cancel the download. | |
*/ | |
onDownloadStarted: nop, | |
/** | |
* Called as data is received during a download. Check the AddonInstall.progress property for the amount of data | |
* downloaded and the AddonInstall.maxProgress property for the total data expected. A listener may return false to | |
* cancel the download. | |
*/ | |
onDownloadProgress: function(install) { | |
// Show the how much of the add-on has been downloaded. TODO Perhaps make this a percentage. | |
// nl-nl: Download op {$1}. | |
// this.promise.setProgress("Download at " + (install.progress / install.maxProgress) + "."); | |
}, | |
/** | |
* Called when downloading completes successfully for an add-on install. Only called for add-ons that need to be | |
* downloaded. | |
*/ | |
onDownloadEnded: function() { | |
// nl-nl: Klaar met downloaden. | |
//this.promise.setProgress("Download completed."); | |
}, | |
/** | |
* Called when downloading is cancelled. Only called for add-ons that need to be downloaded. | |
*/ | |
onDownloadCancelled: function() { | |
// nl-nl: Download geannuleerd. | |
//this.promise.setProgress("Download cancelled."); | |
}, | |
/** | |
* Called if there is some error downloading the add-on. Only called for add-ons that need to be downloaded. | |
*/ | |
onDownloadFailed: function() { | |
// nl-nl: Download mislukt. | |
//this.promise.setProgress("Download failed."); | |
}, | |
/** | |
* Called when installation of an add-on begins. A listener may return false to cancel the install. | |
*/ | |
onInstallStarted: function() { | |
// nl-nl: Bezig met installeren van {$1}... | |
// promise.setProgress("<![CDATA[Installing " + addon.name + " " + addon.version + "…]]>"); | |
}, | |
/** | |
* Called when installation of an add-on is complete. The newly installed add-on may require a restart in order to become | |
* active. | |
*/ | |
onInstallEnded: function(install, addon) { | |
// The add-on was successfully installed. Love! | |
// nl-nl {$1} is geinstalleerd. | |
this.promise.resolve("<![CDATA[" + representAddon(addon) + " has been installed.]]>"); | |
// TODO Determine whether the add-on needs a restart. Inform the user of this fact if so. | |
}, | |
/** | |
* Called when installation is cancelled. | |
*/ | |
onInstallCancelled: function() { | |
// nl-nl: Installatie geannuleerd. | |
//this.promise.setProgress("Installation cancelled."); | |
}, | |
/** | |
* Called when there is a failure installing the add-on. | |
*/ | |
onInstallFailed: function() { | |
// nl-nl: Installatie mislukt. | |
//this.promise.setProgress("Installation failed."); | |
}, | |
/** | |
* Called when an add-on is installed through some system other than an AddonInstall. | |
*/ | |
onExternalInstall: nop | |
}; | |
const forceParameter = { | |
name: "force", | |
type: "boolean", | |
description: { | |
"root": "Whether an add-on search currently in progress, if existent, should be cancelled.", | |
"nl-nl": "Of een zoekopdracht naar add-ons die op dit moment bezig is geannuleerd moet worden, mits deze bestaat." | |
}, | |
defaultValue: false | |
} | |
gcli.addCommand({ | |
name: "addon install", | |
description: { | |
"root": "Install the specified add-on from addons.mozilla.org", | |
"nl-nl": "Installeer de gespecificeerde add-on vanaf addons.mozilla.org" | |
}, | |
params: [nameParameter, forceParameter], | |
exec: function(cliArguments, context) { | |
// Check whether an add-on search is currently in progress. | |
if (AddonRepository.isSearching) { | |
// Cancel the currently-in-progress search, or explain how this can be done, depending on the "force" parameter. | |
if (cliArguments[forceParameter.name]) { | |
AddonRepository.cancelSearch(); | |
} else { | |
// nl-nl: Kan niet installeren. Een zoekopdracht naar add-ons is op dit moment bezig. Als je vindt dat dit te lang duurt, gebruik dan "addon install {$1} true" om deze te annuleren. | |
return "Unable to install. Another search for add-ons is already in progress. If you feel this search is taking too long, use <code><![CDATA[addon install " + cliArguments[nameParameter.name] + " true]]></code> to cancel it."; | |
} | |
} | |
// Create the promise that will be resolved when the search for add-ons has ended. | |
const promise = context.createPromise(); | |
// Inform the user a search has been initiated. | |
// nl-nl: Aan het zoeken naar {$1}... | |
// promise.setProgress("<![CDATA[Searching for " + cliArguments[nameParameter.name] + "…]]>"); | |
// Search for the add-on. | |
AddonRepository.searchAddons(cliArguments[nameParameter.name], 4, new AddonSearchCallback(promise, cliArguments[nameParameter.name])); | |
return promise; | |
} | |
}); | |
})(); | |
(function() { | |
/** | |
* Uninstalls the addon in the passed list which has a name that matches according to the passed name comparer, and | |
* resolves the promise which is the scope (this) of this function to display the result of this uninstall attempt. | |
*/ | |
function uninstall(nameComparer, addons) { | |
// Find the add-on. TODO consider adding something that checks for multiple matches. | |
let addon = null; | |
addons.some(function(candidate) { | |
if (nameComparer.compare(candidate.name)) { | |
addon = candidate; | |
return true; | |
} else { | |
return false; | |
} | |
}); | |
// Uninstall the add-on, if it was found. Explain the user what happened. | |
if (null == addon) { | |
// nl-nl: De add-on is niet gevonden. | |
this.resolve("This add-on was not found."); | |
} else { | |
addon.uninstall(); | |
// TODO Determine whether the add-on needs a restart. Inform the user of this fact if so. | |
// nl-nl: {$1} is gedeinstalleerd. | |
this.resolve("<![CDATA[" + representAddon(addon) + " has been uninstalled.]]>"); | |
} | |
} | |
// Add "addon uninstall". | |
gcli.addCommand({ | |
name: "addon uninstall", | |
description: { | |
"root": "Uninstall the specified add-on", | |
"nl-nl": "Deinstalleer de gespecificeerde add-on" | |
}, | |
params: [nameParameter], | |
exec: function(cliArguments, context) { | |
// Create the promise that will be resolved when the uninstalling has been finished. | |
let promise = context.createPromise(); | |
// List the installed add-ons, uninstall one when done listing. | |
AddonManager.getAddonsByTypes(["extension"], uninstall.bind(promise, new NameComparer(cliArguments[nameParameter.name]))); | |
return promise; | |
} | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment