2 * Copyright (C) 2012 Nokia Corporation.
3 * Copyright (C) 2012 Intel Corporation.
5 * Author: Jens Georg <jensg@openismus.com>
7 * This file is part of Rygel.
9 * Rygel is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * Rygel is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 [CCode (cname = "uuid_generate", cheader_filename = "uuid/uuid.h")]
26 internal extern static void uuid_generate ([CCode (array_length = false)]
28 [CCode (cname = "uuid_unparse", cheader_filename = "uuid/uuid.h")]
29 internal extern static void uuid_unparse ([CCode (array_length = false)]
31 [CCode (array_length = false)]
35 public const string DIDL_ITEM = """<?xml version="1.0" encoding="UTF-8"?>
37 xmlns:dc="http://purl.org/dc/elements/1.1/"
38 xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
39 xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
40 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
41 xsi:schemaLocation="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/
42 http://www.upnp.org/schemas/av/didl-lite-v2-20060531.xsd
43 urn:schemas-upnp-org:metadata-1-0/upnp/
44 http://www.upnp.org/schemas/av/upnp-v2-20060531.xsd">
45 <item id="" parentID="0" restricted="0">
46 <dc:title>New Song</dc:title>
47 <upnp:class>object.item.audioItem</upnp:class>
48 <res protocolInfo="*:*:*:*" />
52 public class Rygel.ServiceAction : GLib.Object {
53 public int error_code;
54 public string error_message;
56 public string elements;
58 public ServiceAction (string? container_id,
60 this.id = container_id;
61 this.elements = elements;
64 public void @return() {}
65 public void return_error (int code, string message) {
66 this.error_code = code;
67 this.error_message = message;
70 public new void @get (string arg1_name,
75 out string arg2_val) {
76 assert (arg1_name == "ContainerID");
77 assert (arg1_type == typeof (string));
80 assert (arg2_name == "Elements");
81 assert (arg2_type == typeof (string));
85 public new void @set (string arg1_name,
91 assert (arg1_name == "ObjectID");
92 assert (arg1_type == typeof (string));
94 assert (arg2_name == "Result");
95 assert (arg2_type == typeof (string));
99 public class Rygel.HTTPServer : GLib.Object {
102 public class Rygel.ObjectRemovalQueue : GLib.Object {
103 public static ObjectRemovalQueue get_default () {
104 return new ObjectRemovalQueue ();
107 public void queue (MediaObject object, Cancellable? cancellable) {
111 public class Rygel.MediaServerPlugin : GLib.Object {
112 public GLib.List<DLNAProfile> upload_profiles = new GLib.List<DLNAProfile>
116 public class Rygel.MediaObject : GLib.Object {
117 public string id {get; set; }
118 public string ref_id;
119 public unowned MediaContainer parent { get; set; }
120 public string upnp_class;
121 public string title { get; set; }
122 public GUPnP.OCMFlags ocm_flags;
123 public Gee.ArrayList<string> uris;
124 public uint object_update_id;
126 public void add_uri (string uri) {
130 internal void serialize (Rygel.Serializer serializer, HTTPServer server) {
133 public virtual async MediaObjects? get_children
136 string sort_criteria,
137 Cancellable? cancellable)
142 public virtual async MediaObject? find_object (string id,
143 Cancellable? cancellable)
149 public interface Rygel.TrackableContainer : Rygel.MediaContainer {
152 public interface Rygel.TrackableItem : Rygel.MediaItem {
155 public class Rygel.MediaItem : Rygel.MediaObject {
156 public string dlna_profile;
157 public string mime_type;
159 public bool place_holder;
162 public MediaItem (string id, MediaContainer parent, string title) {
164 this.parent = parent;
170 public class Rygel.MusicItem : Rygel.AudioItem {
171 public new const string UPNP_CLASS = "object.item.audioItem.musicTrack";
173 public MusicItem (string id, MediaContainer parent, string title) {
174 base (id, parent, title);
178 public class Rygel.AudioItem : Rygel.MediaItem {
179 public const string UPNP_CLASS = "object.item.audioItem";
180 public string artist;
183 public AudioItem (string id, MediaContainer parent, string title) {
184 base (id, parent, title);
187 public class Rygel.ImageItem : Rygel.MediaItem {
188 public new const string UPNP_CLASS = "object.item.imageItem";
189 public ImageItem (string id, MediaContainer parent, string title) {
190 base (id, parent, title);
194 public class Rygel.VideoItem : Rygel.MediaItem {
195 public const string UPNP_CLASS = "object.item.videoItem";
196 public VideoItem (string id, MediaContainer parent, string title) {
197 base (id, parent, title);
201 public class Rygel.PhotoItem : Rygel.MediaItem {
202 public const string UPNP_CLASS = "object.item.imageItem.photo";
203 public string creator;
205 public PhotoItem (string id, MediaContainer parent, string title) {
206 base (id, parent, title);
210 public class Rygel.PlaylistItem : Rygel.MediaItem {
211 public const string UPNP_CLASS = "object.item.playlistItem";
213 public PlaylistItem (string id, MediaContainer parent, string title) {
214 base (id, parent, title);
218 public class Rygel.RootDevice : GLib.Object {
219 public MediaServerPlugin resource_factory;
221 public RootDevice () {
222 this.resource_factory = new MediaServerPlugin ();
226 public class Rygel.ContentDirectory : GLib.Object {
227 public Cancellable cancellable;
228 public MediaContainer root_container;
229 public HTTPServer http_server;
230 public RootDevice root_device;
232 public ContentDirectory () {
233 this.root_device = new RootDevice ();
237 public class Rygel.MediaContainer : Rygel.MediaObject {
238 public Gee.ArrayList<string> create_classes = new Gee.ArrayList<string> ();
239 public int child_count { get; set; }
240 public string sort_criteria = "+dc:title";
241 public static const string ANY = "DLNA.ORG_AnyContainer";
242 public static const string UPNP_CLASS = "object.container";
243 public static const string STORAGE_FOLDER =
244 "object.container.storageFolder";
245 public static const string PLAYLIST =
246 "object.container.playlistContainer";
247 public uint update_id;
250 public MediaObject found_object = null;
252 public override async MediaObject? find_object (string id,
253 Cancellable? cancellable = null)
255 Idle.add (() => { find_object.callback (); return false; });
261 public signal void container_updated (MediaContainer container);
264 public class Rygel.MediaObjects : Gee.ArrayList<MediaObject> {
267 public class Rygel.WritableContainer : Rygel.MediaContainer {
268 public bool can_create (string upnp_class) {
269 return this.create_classes.contains (upnp_class);
272 public async File? get_writable (Cancellable? cancellable = null) {
273 return File.new_for_commandline_arg ("/tmp");
276 public async void add_item (MediaItem item,
277 Cancellable? cancellable = null) {
280 public async void add_container (MediaContainer container, Cancellable?
281 cancellable = null) { }
284 public class Rygel.SearchableContainer : Rygel.MediaContainer {
285 public MediaObjects result = new MediaObjects ();
287 public async MediaObjects search (SearchExpression expression,
290 out int total_matches,
291 string soer_criteria,
292 Cancellable? cancellable = null) {
294 Idle.add (() => { search.callback (); return false; });
301 public errordomain Rygel.ContentDirectoryError {
310 public class Rygel.Transcoder {
313 public static void log_func (string? domain,
317 // Ignore critical of gee 0.6 and recent glib
318 if (message.has_prefix ("Read-only property 'read-only-view' on class")) {
319 Log.default_handler (domain, flags, message);
324 if (LogLevelFlags.LEVEL_CRITICAL in flags ||
325 LogLevelFlags.LEVEL_ERROR in flags ||
326 LogLevelFlags.FLAG_FATAL in flags) {
327 print ("======> FAILED: %s: %s\n", domain ?? "", message);
328 assert_not_reached ();
332 public class Rygel.HTTPObjectCreatorTest : GLib.Object {
334 public static int main (string[] args) {
335 Log.set_default_handler (log_func);
336 var test = new HTTPObjectCreatorTest ();
337 test.test_parse_args ();
338 test.test_didl_parsing ();
339 test.test_fetch_container ();
341 /* This is just here to avoid warnings about unused methods: */
342 var serializer = new Serializer (SerializerType.GENERIC_DIDL);
343 serializer.add_item ();
344 serializer.add_container ();
345 serializer.filter ("something");
351 Error no_such_object;
352 Error no_such_container;
353 Error restricted_parent;
357 public HTTPObjectCreatorTest () {
358 this.no_such_object = new ContentDirectoryError.NO_SUCH_OBJECT("");
359 this.no_such_container = new ContentDirectoryError.NO_SUCH_CONTAINER("");
360 this.restricted_parent = new ContentDirectoryError.RESTRICTED_PARENT("");
361 this.bad_metadata = new ContentDirectoryError.BAD_METADATA("");
362 this.invalid_args = new ContentDirectoryError.INVALID_ARGS("");
365 private void test_parse_args () {
366 // check null container id
367 var content_directory = new ContentDirectory ();
369 var action = new ServiceAction (null, "");
370 var creator = new ObjectCreator (content_directory, action);
371 creator.run.begin ();
372 assert (action.error_code == invalid_args.code);
374 // check elements containing a comment
375 action = new ServiceAction ("0", "<!-- This is an XML comment -->");
376 creator = new ObjectCreator (content_directory, action);
377 creator.run.begin ();
378 assert (action.error_code == bad_metadata.code);
380 // check null elements
381 action = new ServiceAction ("0", null);
382 creator = new ObjectCreator (content_directory, action);
383 creator.run.begin ();
384 assert (action.error_code == bad_metadata.code);
387 private void test_didl_parsing_step (Xml.Doc *doc, int expected_code) {
390 doc->dump_memory_enc (out xml);
391 var action = new ServiceAction ("0", xml);
392 var content_directory = new ContentDirectory ();
393 var creator = new ObjectCreator (content_directory, action);
394 creator.run.begin ();
395 assert (action.error_code == expected_code);
398 private void test_didl_parsing () {
399 var xml = Xml.Parser.read_memory (DIDL_ITEM,
403 Xml.ParserOption.RECOVER |
404 Xml.ParserOption.NOBLANKS);
405 var didl_node = xml->children;
406 var item_node = didl_node->children;
407 var content_directory = new ContentDirectory ();
410 var action = new ServiceAction ("0", "");
411 var creator = new ObjectCreator (content_directory, action);
412 creator.run.begin ();
413 assert (action.error_code == bad_metadata.code);
414 assert (action.error_message == "Bad metadata");
417 item_node->unlink ();
418 didl_node->set_content (" ");
419 this.test_didl_parsing_step (xml, bad_metadata.code);
421 // test item node with missing restricted attribute
422 var tmp = item_node->copy (1);
423 tmp->unset_prop ("restricted");
424 didl_node->add_child (tmp);
425 this.test_didl_parsing_step (xml, bad_metadata.code);
427 // test item node with restricted=1
428 tmp->set_prop ("restricted", "1");
429 this.test_didl_parsing_step (xml, bad_metadata.code);
431 // test item node with invalid id
433 tmp = item_node->copy (1);
434 tmp->set_prop ("id", "InvalidItemId");
435 didl_node->add_child (tmp);
436 this.test_didl_parsing_step (xml, bad_metadata.code);
438 // test item node with missing id
439 tmp->unset_prop ("id");
440 this.test_didl_parsing_step (xml, bad_metadata.code);
442 // test item node with missing title
444 tmp = item_node->copy (1);
445 var title_node = tmp->children;
446 title_node->unlink ();
447 didl_node->add_child (tmp);
448 this.test_didl_parsing_step (xml, bad_metadata.code);
450 // test missing or empty upnp class
452 tmp = item_node->copy (1);
453 var class_node = tmp->children->next;
455 class_node->set_content ("");
456 this.test_didl_parsing_step (xml, bad_metadata.code);
458 class_node->unlink ();
459 this.test_didl_parsing_step (xml, bad_metadata.code);
462 private void test_fetch_container_run (ObjectCreator creator) {
463 var main_loop = new MainLoop (null, false);
464 creator.run.begin ( () => { main_loop.quit (); });
468 private void test_fetch_container () {
469 // check case when object is not found
470 var content_directory = new ContentDirectory ();
471 var root_container = new SearchableContainer ();
472 content_directory.root_container = root_container;
473 var action = new ServiceAction ("0", DIDL_ITEM);
474 var creator = new ObjectCreator (content_directory, action);
475 this.test_fetch_container_run (creator);
476 assert (action.error_code == no_such_container.code);
478 // check case when found object is not a container → Error 710
479 // cf. ContentDirectory:2 spec, Table 2-22
480 root_container.found_object = new MediaObject ();
481 this.test_fetch_container_run (creator);
482 assert (action.error_code == no_such_container.code);
484 // check case when found container does not have OCMUpload set
485 root_container.found_object = new MediaContainer ();
486 this.test_fetch_container_run (creator);
487 assert (action.error_code == restricted_parent.code);
489 // check case when found container is not a writable container
490 root_container.found_object.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
491 this.test_fetch_container_run (creator);
492 assert (action.error_code == restricted_parent.code);
494 // check when found container does not have the correct create class
495 var container = new WritableContainer ();
496 container.create_classes.add ("object.item.imageItem.musicTrack");
497 container.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
498 root_container.found_object = container;
499 this.test_fetch_container_run (creator);
500 assert (action.error_code == bad_metadata.code);
502 // check DLNA.ORG_AnyContainer when root container is not searchable
503 content_directory.root_container = new MediaContainer ();
504 action.id = "DLNA.ORG_AnyContainer";
505 this.test_fetch_container_run (creator);
506 assert (action.error_code == no_such_container.code);
508 // check DLNA.ORG_AnyContainer when no writable container is found
509 content_directory.root_container = new SearchableContainer ();
510 this.test_fetch_container_run (creator);
511 // We cannot distinguish this case from the "create-class doesn't match"
513 assert (action.error_code == bad_metadata.code);