2 * Copyright (C) 2008-2010 Nokia Corporation.
3 * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
4 * Copyright (C) 2007 OpenedHand Ltd.
5 * Copyright (C) 2012 Openismus GmbH.
7 * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
8 * <zeeshan.ali@nokia.com>
9 * Jorn Baayen <jorn@openedhand.com>
10 * Jens Georg <jensg@openismus.com>
12 * This file is part of Rygel.
14 * Rygel is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * Rygel is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Lesser General Public License for more details.
24 * You should have received a copy of the GNU Lesser General Public License
25 * along with this program; if not, write to the Free Software Foundation,
26 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 public errordomain RootDeviceFactoryError {
36 * Factory for RootDevice objects. Give it a plugin and it will create a
37 * Root device for that.
39 public class Rygel.RootDeviceFactory {
40 public GUPnP.Context context;
42 private Configuration config;
44 private string desc_dir;
46 public RootDeviceFactory (GUPnP.Context context) throws GLib.Error {
47 this.config = MetaConfig.get_default ();
48 this.context = context;
50 /* We store the modified descriptions in the user's config dir */
51 var config_dir = Environment.get_user_config_dir ();
52 this.ensure_dir_exists (config_dir);
53 this.desc_dir = Path.build_filename (config_dir,
54 Environment.get_application_name ());
55 this.ensure_dir_exists (this.desc_dir);
58 public RootDevice create (Plugin plugin) throws GLib.Error {
59 var desc_path = Path.build_filename (this.desc_dir,
60 plugin.name + ".xml");
61 var template_path = plugin.desc_path;
63 /* Create the description xml */
64 var doc = this.create_desc (plugin, desc_path, template_path);
66 var device = new RootDevice (this.context,
70 BuildConfig.DATA_DIR);
71 plugin.apply_hacks (device, desc_path);
76 private XMLDoc create_desc (Plugin plugin,
78 string template_path) throws GLib.Error {
79 var doc = this.get_latest_doc (desc_path, template_path);
81 /* Modify description to include Plugin-specific stuff */
82 this.prepare_desc_for_plugin (doc, plugin);
84 var file = new DescriptionFile.from_xml_document (doc);
85 file.save (desc_path);
90 private void prepare_desc_for_plugin (XMLDoc doc, Plugin plugin) {
91 Xml.Node *device_element;
93 device_element = XMLUtils.get_element ((Xml.Node *) doc.doc,
97 if (device_element == null) {
98 warning (_("XML node '%s' not found."), "/root/device");
103 /* First, set the Friendly name and UDN */
104 this.set_friendly_name_and_udn (device_element,
107 this.set_dlnacap (device_element);
109 if (plugin.description != null) {
110 this.set_description (device_element, plugin.description);
113 /* Then list each icon */
114 this.add_icons_to_desc (device_element, plugin);
116 /* Then list each service */
117 this.add_services_to_desc (device_element, plugin);
121 * Fills the description doc @doc with a friendly name, and UDN from gconf.
122 * If these keys are not present in gconf, they are set with default values.
124 private void set_friendly_name_and_udn (Xml.Node *device_element,
126 string plugin_title) {
128 Xml.Node *element = XMLUtils.get_element (device_element,
131 if (element == null) {
132 warning (_("XML node '%s' not found."),
133 "/root/device/friendlyName");
140 title = this.config.get_title (plugin_name);
141 } catch (GLib.Error err) {
142 title = plugin_title;
145 title = title.replace ("@REALNAME@", Environment.get_real_name ());
146 title = title.replace ("@USERNAME@", Environment.get_user_name ());
147 title = title.replace ("@HOSTNAME@", Environment.get_host_name ());
149 element->set_content (title);
152 element = XMLUtils.get_element (device_element, "UDN");
153 if (element == null) {
154 warning (_("XML node '%s' not found."), "/root/device/UDN");
159 var udn = element->get_content ();
160 if (udn == null || udn == "") {
161 udn = this.generate_random_udn ();
163 element->set_content (udn);
167 private void set_dlnacap (Xml.Node *device_element) {
168 var element = XMLUtils.get_element (device_element,
173 var allow_upload = true;
174 var allow_delete = false;
177 allow_upload = config.get_allow_upload ();
178 allow_delete = config.get_allow_deletion ();
179 } catch (Error error) { }
182 content += "av-upload,image-upload,audio-upload";
186 content += ",create-item-with-OCM-destroy-item";
188 element->set_content (content);
191 private void set_description (Xml.Node *device_element,
192 string description) {
193 Xml.Node *element = XMLUtils.get_element (device_element,
196 if (element == null) {
197 device_element->new_child (null, "modelDescription", description);
200 element->set_content (description);
203 private void add_services_to_desc (Xml.Node *device_element,
205 Xml.Node *service_list_node = XMLUtils.get_element (device_element,
208 if (service_list_node == null) {
209 warning (_("XML node '%s' not found."), "/root/device/serviceList");
214 // Clear the existing service list first
215 service_list_node->set_content ("");
217 foreach (ResourceInfo resource_info in plugin.resource_infos) {
218 // FIXME: We only support plugable services for now
219 if (resource_info.type.is_a (typeof (Service))) {
220 this.add_service_to_desc (service_list_node,
227 private void add_service_to_desc (Xml.Node *service_list_node,
229 ResourceInfo resource_info) {
230 // Now create the service node
231 Xml.Node *service_node = service_list_node->new_child (null, "service");
233 service_node->new_child (null, "serviceType", resource_info.upnp_type);
234 service_node->new_child (null, "serviceId", resource_info.upnp_id);
236 /* Now the relative (to base URL) URLs*/
237 string url = "/" + resource_info.description_path;
238 service_node->new_child (null, "SCPDURL", url);
240 url = "/Event/" + plugin_name + "/" + resource_info.type.name ();
241 service_node->new_child (null, "eventSubURL", url);
243 url = "/Control/" + plugin_name + "/" + resource_info.type.name ();
244 service_node->new_child (null, "controlURL", url);
247 private void add_icons_to_desc (Xml.Node *device_element,
249 var icons = plugin.icon_infos;
251 if (icons == null || icons.size == 0) {
252 debug ("No icon provided by plugin '%s'. Using Rygel logo.",
255 icons = plugin.default_icons;
258 Xml.Node *icon_list_node = XMLUtils.get_element (device_element,
261 if (icon_list_node == null) {
262 icon_list_node = device_element->new_child (null, "iconList", null);
264 // Clear the existing icon list first
265 icon_list_node->set_content ("");
268 foreach (var icon in icons) {
269 add_icon_to_desc (icon_list_node, icon, plugin);
273 private void add_icon_to_desc (Xml.Node *icon_list_node,
276 // Create the service node
277 Xml.Node *icon_node = icon_list_node->new_child (null, "icon");
279 string width = icon_info.width.to_string ();
280 string height = icon_info.height.to_string ();
281 string depth = icon_info.depth.to_string ();
283 icon_node->new_child (null, "mimetype", icon_info.mime_type);
284 icon_node->new_child (null, "width", width);
285 icon_node->new_child (null, "height", height);
286 icon_node->new_child (null, "depth", depth);
288 var uri = icon_info.uri;
290 if (uri.has_prefix ("file://")) {
291 // /PLUGIN_NAME-WIDTHxHEIGHTxDEPTH.png
292 var remote_path = "/" + plugin.name + "-" +
295 depth + "." + icon_info.file_extension;
296 var local_path = uri.substring (7);
298 this.context.host_path (local_path, remote_path);
299 icon_node->new_child (null, "url", remote_path);
301 uri = uri.replace ("@ADDRESS@", this.context.host_ip);
302 icon_node->new_child (null, "url", uri);
306 private XMLDoc get_latest_doc (string path1,
307 string path2) throws GLib.Error {
308 var file = File.new_for_path (path1);
309 if (!file.query_exists (null)) {
310 return new XMLDoc.from_path (path2);
313 var info = file.query_info (FileAttribute.TIME_MODIFIED,
314 FileQueryInfoFlags.NONE);
315 var mod1 = info.get_attribute_uint64 (FileAttribute.TIME_MODIFIED);
317 file = File.new_for_path (path2);
318 info = file.query_info (FileAttribute.TIME_MODIFIED,
319 FileQueryInfoFlags.NONE);
320 var mod2 = info.get_attribute_uint64 (FileAttribute.TIME_MODIFIED);
323 // If we fail to load the derived description file, try the
326 return new XMLDoc.from_path (path1);
327 } catch (Error error) {
328 return new XMLDoc.from_path (path2);
331 return new XMLDoc.from_path (path2);
335 private void ensure_dir_exists (string dir_path) throws Error {
336 var file = File.new_for_path (dir_path);
337 if (!file.query_exists (null)) {
338 file.make_directory (null);
342 private string generate_random_udn () {
343 var udn = new uchar[50];
344 var id = new uchar[16];
346 /* Generate new UUID */
348 UUID.unparse (id, udn);
350 return "uuid:" + (string) udn;