[rygel] core: Refactor description document manipulation



commit 040781492cbf80f065781a613a20254729d8c3aa
Author: Jens Georg <mail jensge org>
Date:   Sun Nov 27 12:57:37 2011 +0100

    core: Refactor description document manipulation
    
    Move basic document manipulation inside a new class and use it in V1
    and XBox hacks to eliminate the code duplication introduced when
    splitting these two classes.

 src/rygel/Makefile.am                    |    3 +-
 src/rygel/rygel-description-file.vala    |  182 ++++++++++++++++++++++++++++++
 src/rygel/rygel-root-device-factory.vala |   10 +-
 src/rygel/rygel-v1-hacks.vala            |   67 ++----------
 src/rygel/rygel-xbox-hacks.vala          |   96 ++--------------
 5 files changed, 211 insertions(+), 147 deletions(-)
---
diff --git a/src/rygel/Makefile.am b/src/rygel/Makefile.am
index 7c30625..121bc25 100644
--- a/src/rygel/Makefile.am
+++ b/src/rygel/Makefile.am
@@ -112,7 +112,8 @@ VAPI_SOURCE_FILES = \
 	rygel-changelog.vala \
 	rygel-volume.vala \
 	rygel-free-desktop-interfaces.vala \
-	rygel-dbus-interface.vala
+	rygel-dbus-interface.vala \
+	rygel-description-file.vala
 
 rygel_VALAFLAGS = \
 	-H rygel.h -C --library=rygel-1.0 \
diff --git a/src/rygel/rygel-description-file.vala b/src/rygel/rygel-description-file.vala
new file mode 100644
index 0000000..433b099
--- /dev/null
+++ b/src/rygel/rygel-description-file.vala
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011 Jens Georg
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GUPnP;
+using Xml;
+
+/**
+ * Represents a device description document and offers methods for easy
+ * manipulation of those.
+ */
+internal class DescriptionFile : Object {
+    /// XML doc wrapper representing the description document
+    private XMLDoc doc;
+
+    // FIXME: Why does //serviceType not work? Seems a bug in libxml2.
+    /**
+     * XPath template for searching the service type node in
+     * modify_service_type()
+     */
+    private const string SERVICE_TYPE_TEMPLATE = "//*[.='%s']";
+
+    /**
+     * Constructor to load a description file from disk
+     *
+     * @param template the path to the description file.
+     * @throws GUPnP.XMLError.PARSE if there was an error reading or parsing
+     * the file.
+     */
+    public DescriptionFile (string template) throws GLib.Error {
+        this.doc = new XMLDoc.from_path (template);
+    }
+
+    /**
+     * Change the type of a service.
+     *
+     * Usually used to modify the device version, e.g. default device type is
+     * "MediaServer:2" and device_type = "MediaServer:1".
+     *
+     * @param device_type is the current content of serviceType.
+     */
+    public void set_device_type (string device_type) {
+        this.set_device_element ("deviceType", device_type);
+    }
+
+    /**
+     * Modify the model name.
+     *
+     * Usually the name of the software implementing this device.
+     *
+     * @param device_type is the new model name.
+     */
+    public void set_model_name (string model_name) {
+        this.set_device_element ("modelName", model_name);
+    }
+
+    /**
+     * Modify the model number.
+     *
+     * Usually the version of the software implementing this device.
+     *
+     * @param model_number is the new model number.
+     */
+    public void set_model_number (string model_number) {
+        this.set_device_element ("modelNumber", model_number);
+    }
+
+    /**
+     * Set the friendly name of the device.
+     *
+     * The friendly name is the one usually presented to the user in control
+     * points or DMPs
+     *
+     * @param friendly_name is the new friendly name of the device.
+     */
+    public void set_friendly_name (string friendly_name) {
+        this.set_device_element ("friendlyName", friendly_name);
+    }
+
+    /**
+     * Get the current friendly name of the device.
+     *
+     * @return The currenly set friendly name.
+     */
+    public string get_friendly_name () {
+        var element = Rygel.XMLUtils.get_element ((Xml.Node *) this.doc.doc,
+                                                  "root",
+                                                  "device",
+                                                  "friendlyName");
+        assert (element != null);
+
+        return element->get_content ();
+    }
+
+    /**
+     * Change the type of a service.
+     *
+     * Usually used to modify the service version, e.g. old_type =
+     * "ContentDirectory:2" and new_type = "ContentDirectory:1".
+     *
+     * @param old_type is the current content of serviceType.
+     * @param new_type is the content serviceType will be set to.
+     */
+    public void modify_service_type (string old_type,
+                                     string new_type) {
+        var context = new XPath.Context (this.doc.doc);
+
+        var xpath = SERVICE_TYPE_TEMPLATE.printf (old_type);
+        var xpath_object = context.eval_expression (xpath);
+        assert (xpath_object != null);
+        assert (xpath_object->type == XPath.ObjectType.NODESET);
+        assert (!xpath_object->nodesetval->is_empty ());
+
+        xpath_object->nodesetval->item (0)->set_content (new_type);
+    }
+
+    /**
+     * Writes the current document to a file.
+     *
+     * It makes sure that the resulting file has the correct UTF-8 encoding
+     * and does not have any kind of newlines. This is necessary as some
+     * devices with broken XML parsers can't cope with UNIX newlines.
+     * If a file with the same name exists it will be overwritten.
+     *
+     * @param path is a path to a file.
+     * @throws IOError.FAILED if anything fails while creating the XML dump.
+     */
+    public void save (string path) throws GLib.Error {
+        var file = FileStream.open (path, "w+");
+        var message = _("Failed to write modified description to %s");
+
+        if (unlikely (file == null)) {
+            throw new IOError.FAILED (message, path);
+        }
+
+        string mem = null;
+        int len = -1;
+        doc.doc.dump_memory_enc (out mem, out len, "UTF-8");
+
+        if (unlikely (len <= 0)) {
+            throw new IOError.FAILED (message, path);
+        }
+
+        // Make sure we don't have any newlines
+        file.puts (mem.replace ("\n", ""));
+    }
+
+    /**
+     * Internal helper function to set an element to a new value.
+     *
+     * @param element below /root/device to be set.
+     * @param new_vale is the new content of that element.
+     */
+    private void set_device_element (string element, string new_value) {
+        var xml_element = Rygel.XMLUtils.get_element
+                                        ((Xml.Node *) this.doc.doc,
+                                         "root",
+                                         "device",
+                                         element);
+        assert (xml_element != null);
+
+        xml_element->set_content (new_value);
+    }
+}
diff --git a/src/rygel/rygel-root-device-factory.vala b/src/rygel/rygel-root-device-factory.vala
index e95a604..fa0155d 100644
--- a/src/rygel/rygel-root-device-factory.vala
+++ b/src/rygel/rygel-root-device-factory.vala
@@ -74,14 +74,14 @@ internal class Rygel.RootDeviceFactory {
                                      doc,
                                      desc_path,
                                      BuildConfig.DATA_DIR);
-
-        /* Now apply the Xbox hacks */
-        var xbox_hacks = new XBoxHacks ();
-        xbox_hacks.apply_on_device (device, desc_path);
-
+        // Apply V1 downgrades
         var v1_hacks = new V1Hacks ();
         v1_hacks.apply_on_device (device, desc_path);
 
+        // Apply XBox hacks on top of that
+        var xbox_hacks = new XBoxHacks ();
+        xbox_hacks.apply_on_device (device, v1_hacks.description_path);
+
         return device;
     }
 
diff --git a/src/rygel/rygel-v1-hacks.vala b/src/rygel/rygel-v1-hacks.vala
index 173dfae..8fe1733 100644
--- a/src/rygel/rygel-v1-hacks.vala
+++ b/src/rygel/rygel-v1-hacks.vala
@@ -37,6 +37,8 @@ internal class Rygel.V1Hacks : ClientHacks {
 
     private static string agent_pattern;
 
+    public string description_path;
+
     /**
      * Read the user-agent snippets from the config file and generate the
      * regular expression string for matching.
@@ -81,68 +83,17 @@ internal class Rygel.V1Hacks : ClientHacks {
             return;
         }
 
-        var doc = new XMLDoc.from_path (template_path);
-        this.modify_dms_desc (doc.doc);
+        var description_file = new DescriptionFile (template_path);
+        description_file.set_device_type (DMS_V1);
+        description_file.modify_service_type (ContentDirectory.UPNP_TYPE,
+                                              ContentDirectory.UPNP_TYPE_V1);
 
-        var desc_path = template_path.replace (".xml", "-v1.xml");
-        this.save_modified_desc (doc, desc_path);
+        this.description_path = template_path.replace (".xml", "-v1.xml");
+        description_file.save (this.description_path);
 
         var server_path = "/" + device.get_relative_location ();
-        device.context.host_path_for_agent (desc_path,
+        device.context.host_path_for_agent (this.description_path,
                                             server_path,
                                             this.agent_regex);
     }
-
-    private void modify_dms_desc (Xml.Doc doc) {
-        Xml.Node *element = XMLUtils.get_element ((Xml.Node *) doc,
-                                                  "root",
-                                                  "device",
-                                                  "deviceType");
-        assert (element != null);
-        element->set_content (DMS_V1);
-
-        this.modify_service_list (doc);
-    }
-
-    private void modify_service_list (Xml.Node *doc_node) {
-        Xml.Node *element = XMLUtils.get_element (doc_node,
-                                                  "root",
-                                                  "device",
-                                                  "serviceList");
-        assert (element != null && element->children != null);
-
-        for (var service_node = element->children;
-             service_node != null;
-             service_node = service_node->next) {
-            for (var type_node = service_node->children;
-                 type_node != null;
-                 type_node = type_node->next) {
-                if (type_node->name == "serviceType") {
-                    switch (type_node->get_content ()) {
-                        case ContentDirectory.UPNP_TYPE:
-                            type_node->set_content
-                                        (ContentDirectory.UPNP_TYPE_V1);
-                            break;
-                        default:
-                            break;
-                    }
-                }
-            }
-        }
-    }
-
-    private void save_modified_desc (XMLDoc doc,
-                                     string desc_path) throws GLib.Error {
-        FileStream f = FileStream.open (desc_path, "w+");
-        int res = -1;
-
-        if (f != null)
-            res = doc.doc.dump (f);
-
-        if (f == null || res == -1) {
-            var message = _("Failed to write modified description to %s.");
-
-            throw new IOError.FAILED (message, desc_path);
-        }
-    }
 }
diff --git a/src/rygel/rygel-xbox-hacks.vala b/src/rygel/rygel-xbox-hacks.vala
index 07f4cfc..e346155 100644
--- a/src/rygel/rygel-xbox-hacks.vala
+++ b/src/rygel/rygel-xbox-hacks.vala
@@ -64,11 +64,20 @@ internal class Rygel.XBoxHacks : ClientHacks {
             return;
         }
 
-        var doc = new XMLDoc.from_path (template_path);
-        this.modify_dms_desc (doc.doc);
+        var description_file = new DescriptionFile (template_path);
+        description_file.set_model_name (MODEL_NAME);
+        description_file.set_model_number (MODEL_VERSION);
 
-        var desc_path = template_path.replace (".xml", "-xbox.xml");
-        this.save_modified_desc (doc, desc_path);
+        var friendly_name = description_file.get_friendly_name ();
+        description_file.set_friendly_name (friendly_name +
+                                            FRIENDLY_NAME_POSTFIX);
+
+        description_file.modify_service_type
+                                        (MediaReceiverRegistrar.UPNP_TYPE,
+                                         MediaReceiverRegistrar.COMPAT_TYPE);
+
+        var desc_path = template_path.replace ("v1.xml", "xbox.xml");
+        description_file.save (desc_path);
 
         var server_path = "/" + device.get_relative_location ();
         device.context.host_path_for_agent (desc_path,
@@ -135,83 +144,4 @@ internal class Rygel.XBoxHacks : ClientHacks {
 
         return results;
     }
-
-    private void modify_dms_desc (Xml.Doc doc) {
-        Xml.Node *element = XMLUtils.get_element ((Xml.Node *) doc,
-                                                  "root",
-                                                  "device",
-                                                  "deviceType");
-        assert (element != null);
-        element->set_content (DMS_V1);
-
-        element = XMLUtils.get_element ((Xml.Node *) doc,
-                                        "root",
-                                        "device",
-                                        "modelName");
-        assert (element != null);
-        element->set_content (MODEL_NAME);
-
-        element = XMLUtils.get_element ((Xml.Node *) doc,
-                                        "root",
-                                        "device",
-                                        "modelNumber");
-
-        assert (element != null);
-        element->set_content (MODEL_VERSION);
-
-        element = XMLUtils.get_element ((Xml.Node *) doc,
-                                        "root",
-                                        "device",
-                                        "friendlyName");
-        assert (element != null);
-        element->add_content (FRIENDLY_NAME_POSTFIX);
-
-        this.modify_service_list (doc);
-    }
-
-    private void modify_service_list (Xml.Node *doc_node) {
-        Xml.Node *element = XMLUtils.get_element (doc_node,
-                                                  "root",
-                                                  "device",
-                                                  "serviceList");
-        assert (element != null && element->children != null);
-
-        for (var service_node = element->children;
-             service_node != null;
-             service_node = service_node->next) {
-            for (var type_node = service_node->children;
-                 type_node != null;
-                 type_node = type_node->next) {
-                if (type_node->name == "serviceType") {
-                    switch (type_node->get_content ()) {
-                        case ContentDirectory.UPNP_TYPE:
-                            type_node->set_content
-                                        (ContentDirectory.UPNP_TYPE_V1);
-                            break;
-                        case MediaReceiverRegistrar.UPNP_TYPE:
-                            type_node->set_content
-                                        (MediaReceiverRegistrar.COMPAT_TYPE);
-                            break;
-                        default:
-                            break;
-                    }
-                }
-            }
-        }
-    }
-
-    private void save_modified_desc (XMLDoc doc,
-                                     string desc_path) throws GLib.Error {
-        FileStream f = FileStream.open (desc_path, "w+");
-        int res = -1;
-
-        if (f != null)
-            res = doc.doc.dump (f);
-
-        if (f == null || res == -1) {
-            var message = _("Failed to write modified description to %s.");
-
-            throw new IOError.FAILED (message, desc_path);
-        }
-    }
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]