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 \
--- /dev/null
+/*
+ * 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);
+ }
+}
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;
}
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.
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);
- }
- }
}
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,
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);
- }
- }
}