addon-sdk/source/python-lib/cuddlefish/rdf.py
author Fernando Jiménez <ferjmoreno@gmail.com>
Fri, 10 May 2013 16:16:56 +0200
changeset 138297 81e072111e989546362b2313e31a2495cf7f1e25
parent 133075 f5bfd7b0b6bc558c5d6ae298a30bef979199151b
child 162747 f4fdb40f96b1321df9417302ac970ba66e2ab3da
permissions -rw-r--r--
Bug 861894 - Avoid apps to schedule new offline cache downloads while device free space is low. r=honzab

# 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 xml.dom.minidom
import StringIO

RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
EM_NS = "http://www.mozilla.org/2004/em-rdf#"

class RDF(object):
    def __str__(self):
        # real files have an .encoding attribute and use it when you
        # write() unicode into them: they read()/write() unicode and
        # put encoded bytes in the backend file. StringIO objects
        # read()/write() unicode and put unicode in the backing store,
        # so we must encode the output of getvalue() to get a
        # bytestring. (cStringIO objects are weirder: they effectively
        # have .encoding hardwired to "ascii" and put only bytes in
        # the backing store, so we can't use them here).
        #
        # The encoding= argument to dom.writexml() merely sets the XML header's
        # encoding= attribute. It still writes unencoded unicode to the output file,
        # so we have to encode it for real afterwards.
        #
        # Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660

        buf = StringIO.StringIO()
        self.dom.writexml(buf, encoding="utf-8")
        return buf.getvalue().encode('utf-8')

class RDFUpdate(RDF):
    def __init__(self):
        impl = xml.dom.minidom.getDOMImplementation()
        self.dom = impl.createDocument(RDF_NS, "RDF", None)
        self.dom.documentElement.setAttribute("xmlns", RDF_NS)
        self.dom.documentElement.setAttribute("xmlns:em", EM_NS)

    def _make_node(self, name, value, parent):
        elem = self.dom.createElement(name)
        elem.appendChild(self.dom.createTextNode(value))
        parent.appendChild(elem)
        return elem

    def add(self, manifest, update_link):
        desc = self.dom.createElement("Description")
        desc.setAttribute(
            "about",
            "urn:mozilla:extension:%s" % manifest.get("em:id")
            )
        self.dom.documentElement.appendChild(desc)

        updates = self.dom.createElement("em:updates")
        desc.appendChild(updates)

        seq = self.dom.createElement("Seq")
        updates.appendChild(seq)

        li = self.dom.createElement("li")
        seq.appendChild(li)

        li_desc = self.dom.createElement("Description")
        li.appendChild(li_desc)

        self._make_node("em:version", manifest.get("em:version"),
                        li_desc)

        apps = manifest.dom.documentElement.getElementsByTagName(
            "em:targetApplication"
            )

        for app in apps:
            target_app = self.dom.createElement("em:targetApplication")
            li_desc.appendChild(target_app)

            ta_desc = self.dom.createElement("Description")
            target_app.appendChild(ta_desc)

            for name in ["em:id", "em:minVersion", "em:maxVersion"]:
                elem = app.getElementsByTagName(name)[0]
                self._make_node(name, elem.firstChild.nodeValue, ta_desc)

            self._make_node("em:updateLink", update_link, ta_desc)

class RDFManifest(RDF):
    def __init__(self, path):
        self.dom = xml.dom.minidom.parse(path)

    def set(self, property, value):
        elements = self.dom.documentElement.getElementsByTagName(property)
        if not elements:
            raise ValueError("Element with value not found: %s" % property)
        if not elements[0].firstChild:
            elements[0].appendChild(self.dom.createTextNode(value))
        else:
            elements[0].firstChild.nodeValue = value

    def get(self, property, default=None):
        elements = self.dom.documentElement.getElementsByTagName(property)
        if not elements:
            return default
        return elements[0].firstChild.nodeValue

    def remove(self, property):
        elements = self.dom.documentElement.getElementsByTagName(property)
        if not elements:
            return True
        else:
            for i in elements:
                i.parentNode.removeChild(i);

        return True;

def gen_manifest(template_root_dir, target_cfg, jid,
                 update_url=None, bootstrap=True, enable_mobile=False):
    install_rdf = os.path.join(template_root_dir, "install.rdf")
    manifest = RDFManifest(install_rdf)
    dom = manifest.dom

    manifest.set("em:id", jid)
    manifest.set("em:version",
                 target_cfg.get('version', '1.0'))
    manifest.set("em:name",
                 target_cfg.get('fullName', target_cfg['name']))
    manifest.set("em:description",
                 target_cfg.get("description", ""))
    manifest.set("em:creator",
                 target_cfg.get("author", ""))
    manifest.set("em:bootstrap", str(bootstrap).lower())
    # XPIs remain packed by default, but package.json can override that. The
    # RDF format accepts "true" as True, anything else as False. We expect
    # booleans in the .json file, not strings.
    manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false")

    for translator in target_cfg.get("translators", [ ]):
        elem = dom.createElement("em:translator");
        elem.appendChild(dom.createTextNode(translator))
        dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)

    for contributor in target_cfg.get("contributors", [ ]):
        elem = dom.createElement("em:contributor");
        elem.appendChild(dom.createTextNode(contributor))
        dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)

    if update_url:
        manifest.set("em:updateURL", update_url)
    else:
        manifest.remove("em:updateURL")

    if target_cfg.get("preferences"):
        manifest.set("em:optionsType", "2")
    else:
        manifest.remove("em:optionsType")

    if enable_mobile:
        target_app = dom.createElement("em:targetApplication")
        dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app)

        ta_desc = dom.createElement("Description")
        target_app.appendChild(ta_desc)

        elem = dom.createElement("em:id")
        elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}"))
        ta_desc.appendChild(elem)

        elem = dom.createElement("em:minVersion")
        elem.appendChild(dom.createTextNode("19.0"))
        ta_desc.appendChild(elem)

        elem = dom.createElement("em:maxVersion")
        elem.appendChild(dom.createTextNode("22.0a1"))
        ta_desc.appendChild(elem)

    if target_cfg.get("homepage"):
        manifest.set("em:homepageURL", target_cfg.get("homepage"))
    else:
        manifest.remove("em:homepageURL")

    return manifest

if __name__ == "__main__":
    print "Running smoke test."
    root = os.path.join(os.path.dirname(__file__), '../../app-extension')
    manifest = gen_manifest(root, {'name': 'test extension'},
                            'fakeid', 'http://foo.com/update.rdf')
    update = RDFUpdate()
    update.add(manifest, "https://foo.com/foo.xpi")
    exercise_str = str(manifest) + str(update)
    for tagname in ["em:targetApplication", "em:version", "em:id"]:
        if not len(update.dom.getElementsByTagName(tagname)):
            raise Exception("tag does not exist: %s" % tagname)
        if not update.dom.getElementsByTagName(tagname)[0].firstChild:
            raise Exception("tag has no children: %s" % tagname)
    print "Success!"