From: Jens Georg Date: Sun, 27 Nov 2011 11:57:37 +0000 (+0100) Subject: core: Refactor description document manipulation X-Git-Tag: RYGEL_0_13_1~41 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=040781492cbf80f065781a613a20254729d8c3aa;p=profile%2Fivi%2Frygel.git 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. --- 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 + * + * 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); - } - } }