| Index: third_party/mozprofile/mozprofile/addons.py
|
| ===================================================================
|
| --- third_party/mozprofile/mozprofile/addons.py (revision 0)
|
| +++ third_party/mozprofile/mozprofile/addons.py (revision 0)
|
| @@ -0,0 +1,229 @@
|
| +# This Source Code Form is subject to the terms of the Mozilla Public
|
| +# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
| +# You can obtain one at http://mozilla.org/MPL/2.0/.
|
| +
|
| +import os
|
| +import shutil
|
| +import tempfile
|
| +import urllib2
|
| +import zipfile
|
| +from distutils import dir_util
|
| +from manifestparser import ManifestParser
|
| +from xml.dom import minidom
|
| +
|
| +# Needed for the AMO's rest API - https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API
|
| +AMO_API_VERSION = "1.5"
|
| +
|
| +class AddonManager(object):
|
| + """
|
| + Handles all operations regarding addons including: installing and cleaning addons
|
| + """
|
| +
|
| + def __init__(self, profile):
|
| + """
|
| + profile - the path to the profile for which we install addons
|
| + """
|
| + self.profile = profile
|
| +
|
| + # information needed for profile reset:
|
| + # https://github.com/mozilla/mozbase/blob/270a857328b130860d1b1b512e23899557a3c8f7/mozprofile/mozprofile/profile.py#L93
|
| + self.installed_addons = []
|
| + self.installed_manifests = []
|
| +
|
| + # addons that we've installed; needed for cleanup
|
| + self._addon_dirs = []
|
| +
|
| + def install_addons(self, addons=None, manifests=None):
|
| + """
|
| + Installs all types of addons
|
| + addons - a list of addon paths to install
|
| + manifest - a list of addon manifests to install
|
| + """
|
| + # install addon paths
|
| + if addons:
|
| + if isinstance(addons, basestring):
|
| + addons = [addons]
|
| + self.installed_addons.extend(addons)
|
| + for addon in addons:
|
| + self.install_from_path(addon)
|
| + # install addon manifests
|
| + if manifests:
|
| + if isinstance(manifests, basestring):
|
| + manifests = [manifests]
|
| + for manifest in manifests:
|
| + self.install_from_manifest(manifest)
|
| + self.installed_manifests.extended(manifests)
|
| +
|
| + def install_from_manifest(self, filepath):
|
| + """
|
| + Installs addons from a manifest
|
| + filepath - path to the manifest of addons to install
|
| + """
|
| + manifest = ManifestParser()
|
| + manifest.read(filepath)
|
| + addons = manifest.get()
|
| +
|
| + for addon in addons:
|
| + if '://' in addon['path'] or os.path.exists(addon['path']):
|
| + self.install_from_path(addon['path'])
|
| + continue
|
| +
|
| + # No path specified, try to grab it off AMO
|
| + locale = addon.get('amo_locale', 'en_US')
|
| +
|
| + query = 'https://services.addons.mozilla.org/' + locale + '/firefox/api/' + AMO_API_VERSION + '/'
|
| + if 'amo_id' in addon:
|
| + query += 'addon/' + addon['amo_id'] # this query grabs information on the addon base on its id
|
| + else:
|
| + query += 'search/' + addon['name'] + '/default/1' # this query grabs information on the first addon returned from a search
|
| + install_path = AddonManager.get_amo_install_path(query)
|
| + self.install_from_path(install_path)
|
| +
|
| + @classmethod
|
| + def get_amo_install_path(self, query):
|
| + """
|
| + Return the addon xpi install path for the specified AMO query.
|
| + See: https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API
|
| + for query documentation.
|
| + """
|
| + response = urllib2.urlopen(query)
|
| + dom = minidom.parseString(response.read())
|
| + for node in dom.getElementsByTagName('install')[0].childNodes:
|
| + if node.nodeType == node.TEXT_NODE:
|
| + return node.data
|
| +
|
| + @classmethod
|
| + def addon_details(cls, addon_path):
|
| + """
|
| + returns a dictionary of details about the addon
|
| + - addon_path : path to the addon directory
|
| + Returns:
|
| + {'id': u'rainbow@colors.org', # id of the addon
|
| + 'version': u'1.4', # version of the addon
|
| + 'name': u'Rainbow', # name of the addon
|
| + 'unpack': False } # whether to unpack the addon
|
| + """
|
| +
|
| + # TODO: We don't use the unpack variable yet, but we should: bug 662683
|
| + details = {
|
| + 'id': None,
|
| + 'unpack': False,
|
| + 'name': None,
|
| + 'version': None
|
| + }
|
| +
|
| + def get_namespace_id(doc, url):
|
| + attributes = doc.documentElement.attributes
|
| + namespace = ""
|
| + for i in range(attributes.length):
|
| + if attributes.item(i).value == url:
|
| + if ":" in attributes.item(i).name:
|
| + # If the namespace is not the default one remove 'xlmns:'
|
| + namespace = attributes.item(i).name.split(':')[1] + ":"
|
| + break
|
| + return namespace
|
| +
|
| + def get_text(element):
|
| + """Retrieve the text value of a given node"""
|
| + rc = []
|
| + for node in element.childNodes:
|
| + if node.nodeType == node.TEXT_NODE:
|
| + rc.append(node.data)
|
| + return ''.join(rc).strip()
|
| +
|
| + doc = minidom.parse(os.path.join(addon_path, 'install.rdf'))
|
| +
|
| + # Get the namespaces abbreviations
|
| + em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#")
|
| + rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
| +
|
| + description = doc.getElementsByTagName(rdf + "Description").item(0)
|
| + for node in description.childNodes:
|
| + # Remove the namespace prefix from the tag for comparison
|
| + entry = node.nodeName.replace(em, "")
|
| + if entry in details.keys():
|
| + details.update({ entry: get_text(node) })
|
| +
|
| + # turn unpack into a true/false value
|
| + if isinstance(details['unpack'], basestring):
|
| + details['unpack'] = details['unpack'].lower() == 'true'
|
| +
|
| + return details
|
| +
|
| + def install_from_path(self, path, unpack=False):
|
| + """
|
| + Installs addon from a filepath, url
|
| + or directory of addons in the profile.
|
| + - path: url, path to .xpi, or directory of addons
|
| + - unpack: whether to unpack unless specified otherwise in the install.rdf
|
| + """
|
| +
|
| + # if the addon is a url, download it
|
| + # note that this won't work with protocols urllib2 doesn't support
|
| + if '://' in path:
|
| + response = urllib2.urlopen(path)
|
| + fd, path = tempfile.mkstemp(suffix='.xpi')
|
| + os.write(fd, response.read())
|
| + os.close(fd)
|
| + tmpfile = path
|
| + else:
|
| + tmpfile = None
|
| +
|
| + # if the addon is a directory, install all addons in it
|
| + addons = [path]
|
| + if not path.endswith('.xpi') and not os.path.exists(os.path.join(path, 'install.rdf')):
|
| + # If the path doesn't exist, then we don't really care, just return
|
| + if not os.path.isdir(path):
|
| + return
|
| + addons = [os.path.join(path, x) for x in os.listdir(path) if
|
| + os.path.isdir(os.path.join(path, x))]
|
| +
|
| + # install each addon
|
| + for addon in addons:
|
| + tmpdir = None
|
| + xpifile = None
|
| + if addon.endswith('.xpi'):
|
| + tmpdir = tempfile.mkdtemp(suffix = '.' + os.path.split(addon)[-1])
|
| + compressed_file = zipfile.ZipFile(addon, 'r')
|
| + for name in compressed_file.namelist():
|
| + if name.endswith('/'):
|
| + os.makedirs(os.path.join(tmpdir, name))
|
| + else:
|
| + if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))):
|
| + os.makedirs(os.path.dirname(os.path.join(tmpdir, name)))
|
| + data = compressed_file.read(name)
|
| + f = open(os.path.join(tmpdir, name), 'wb')
|
| + f.write(data)
|
| + f.close()
|
| + xpifile = addon
|
| + addon = tmpdir
|
| +
|
| + # determine the addon id
|
| + addon_details = AddonManager.addon_details(addon)
|
| + addon_id = addon_details.get('id')
|
| + assert addon_id, 'The addon id could not be found: %s' % addon
|
| +
|
| + # copy the addon to the profile
|
| + extensions_path = os.path.join(self.profile, 'extensions', 'staged')
|
| + addon_path = os.path.join(extensions_path, addon_id)
|
| + if not unpack and not addon_details['unpack'] and xpifile:
|
| + if not os.path.exists(extensions_path):
|
| + os.makedirs(extensions_path)
|
| + shutil.copy(xpifile, addon_path + '.xpi')
|
| + else:
|
| + dir_util.copy_tree(addon, addon_path, preserve_symlinks=1)
|
| + self._addon_dirs.append(addon_path)
|
| +
|
| + # remove the temporary directory, if any
|
| + if tmpdir:
|
| + dir_util.remove_tree(tmpdir)
|
| +
|
| + # remove temporary file, if any
|
| + if tmpfile:
|
| + os.remove(tmpfile)
|
| +
|
| + def clean_addons(self):
|
| + """Cleans up addons in the profile."""
|
| + for addon in self._addon_dirs:
|
| + if os.path.isdir(addon):
|
| + dir_util.remove_tree(addon)
|
|
|
| Property changes on: third_party/mozprofile/mozprofile/addons.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|