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.MediaObject : GLib.Object {
112 public string id {get; set; }
113 public string ref_id;
114 public unowned MediaContainer parent { get; set; }
115 public string upnp_class;
116 public string title { get; set; }
117 public GUPnP.OCMFlags ocm_flags;
118 public Gee.ArrayList<string> uris;
119 public uint object_update_id;
121 public void add_uri (string uri) {
125 internal void serialize (Rygel.Serializer serializer, HTTPServer server) {
128 public virtual async MediaObjects? get_children
131 string sort_criteria,
132 Cancellable? cancellable)
137 public virtual async MediaObject? find_object (string id,
138 Cancellable? cancellable)
144 public interface Rygel.TrackableContainer : Rygel.MediaContainer {
147 public interface Rygel.TrackableItem : Rygel.MediaItem {
150 public class Rygel.MediaItem : Rygel.MediaObject {
151 public string dlna_profile;
152 public string mime_type;
154 public bool place_holder;
157 public MediaItem (string id, MediaContainer parent, string title) {
159 this.parent = parent;
165 public class Rygel.MusicItem : Rygel.AudioItem {
166 public new const string UPNP_CLASS = "object.item.audioItem.musicTrack";
168 public MusicItem (string id, MediaContainer parent, string title) {
169 base (id, parent, title);
173 public class Rygel.AudioItem : Rygel.MediaItem {
174 public const string UPNP_CLASS = "object.item.audioItem";
175 public string artist;
178 public AudioItem (string id, MediaContainer parent, string title) {
179 base (id, parent, title);
182 public class Rygel.ImageItem : Rygel.MediaItem {
183 public new const string UPNP_CLASS = "object.item.imageItem";
184 public ImageItem (string id, MediaContainer parent, string title) {
185 base (id, parent, title);
189 public class Rygel.VideoItem : Rygel.MediaItem {
190 public const string UPNP_CLASS = "object.item.videoItem";
191 public VideoItem (string id, MediaContainer parent, string title) {
192 base (id, parent, title);
196 public class Rygel.PhotoItem : Rygel.MediaItem {
197 public const string UPNP_CLASS = "object.item.imageItem.photo";
198 public string creator;
200 public PhotoItem (string id, MediaContainer parent, string title) {
201 base (id, parent, title);
205 public class Rygel.PlaylistItem : Rygel.MediaItem {
206 public const string UPNP_CLASS = "object.item.playlistItem";
208 public PlaylistItem (string id, MediaContainer parent, string title) {
209 base (id, parent, title);
213 public class Rygel.ContentDirectory : GLib.Object {
214 public Cancellable cancellable;
215 public MediaContainer root_container;
216 public HTTPServer http_server;
219 public class Rygel.MediaContainer : Rygel.MediaObject {
220 public Gee.ArrayList<string> create_classes = new Gee.ArrayList<string> ();
221 public int child_count { get; set; }
222 public string sort_criteria = "+dc:title";
223 public static const string ANY = "DLNA.ORG_AnyContainer";
224 public static const string STORAGE_FOLDER =
225 "object.container.storageFolder";
226 public static const string PLAYLIST =
227 "object.container.playlistContainer";
228 public uint update_id;
231 public MediaObject found_object = null;
233 public override async MediaObject? find_object (string id,
234 Cancellable? cancellable = null)
236 Idle.add (() => { find_object.callback (); return false; });
242 public signal void container_updated (MediaContainer container);
245 public class Rygel.MediaObjects : Gee.ArrayList<MediaObject> {
248 public class Rygel.WritableContainer : Rygel.MediaContainer {
249 public bool can_create (string upnp_class) {
250 return this.create_classes.contains (upnp_class);
253 public async File? get_writable (Cancellable? cancellable = null) {
254 return File.new_for_commandline_arg ("/tmp");
257 public async void add_item (MediaItem item,
258 Cancellable? cancellable = null) {
261 public async void add_container (MediaContainer container, Cancellable?
262 cancellable = null) { }
265 public class Rygel.SearchableContainer : Rygel.MediaContainer {
266 public MediaObjects result = new MediaObjects ();
268 public async MediaObjects search (SearchExpression expression,
271 out int total_matches,
272 string soer_criteria,
273 Cancellable? cancellable = null) {
275 Idle.add (() => { search.callback (); return false; });
282 public errordomain Rygel.ContentDirectoryError {
290 public class Rygel.Transcoder {
293 public class Rygel.TestMediaEngine : Rygel.MediaEngine {
294 private GLib.List<DLNAProfile> dlna_profiles = new GLib.List<DLNAProfile>();
296 public override unowned GLib.List<DLNAProfile> get_dlna_profiles () {
297 return dlna_profiles;
300 public override unowned GLib.List<Transcoder>? get_transcoders () {
304 public override DataSource? create_data_source (string uri) {
309 public class Rygel.EngineLoader {
310 public EngineLoader () { }
312 public MediaEngine load_engine () {
313 return new TestMediaEngine ();
317 public static void log_func (string? domain,
321 // Ignore critical of gee 0.6 and recent glib
322 if (message.has_prefix ("Read-only property 'read-only-view' on class")) {
323 Log.default_handler (domain, flags, message);
328 if (LogLevelFlags.LEVEL_CRITICAL in flags ||
329 LogLevelFlags.LEVEL_ERROR in flags ||
330 LogLevelFlags.FLAG_FATAL in flags) {
331 print ("======> FAILED: %s: %s\n", domain ?? "", message);
332 assert_not_reached ();
336 public class Rygel.HTTPObjectCreatorTest : GLib.Object {
338 public static int main (string[] args) {
339 Log.set_default_handler (log_func);
340 var test = new HTTPObjectCreatorTest ();
341 test.test_parse_args ();
342 test.test_didl_parsing ();
343 test.test_fetch_container ();
345 /* This is just here to avoid warnings about unused methods: */
346 var serializer = new Serializer (SerializerType.GENERIC_DIDL);
347 serializer.add_item ();
348 serializer.add_container ();
349 serializer.filter ("something");
355 Error no_such_object;
356 Error restricted_parent;
360 public HTTPObjectCreatorTest () {
361 this.no_such_object = new ContentDirectoryError.NO_SUCH_OBJECT("");
362 this.restricted_parent = new ContentDirectoryError.RESTRICTED_PARENT("");
363 this.bad_metadata = new ContentDirectoryError.BAD_METADATA("");
364 this.invalid_args = new ContentDirectoryError.INVALID_ARGS("");
367 private void test_parse_args () {
368 // check null container id
369 var content_directory = new ContentDirectory ();
371 var action = new ServiceAction (null, "");
372 var creator = new ObjectCreator (content_directory, action);
373 creator.run.begin ();
374 assert (action.error_code == no_such_object.code);
376 // check elements containing a comment
377 action = new ServiceAction ("0", "<!-- This is an XML comment -->");
378 creator = new ObjectCreator (content_directory, action);
379 creator.run.begin ();
380 assert (action.error_code == bad_metadata.code);
382 // check null elements
383 action = new ServiceAction ("0", null);
384 creator = new ObjectCreator (content_directory, action);
385 creator.run.begin ();
386 assert (action.error_code == bad_metadata.code);
389 private void test_didl_parsing_step (Xml.Doc *doc, int expected_code) {
392 doc->dump_memory_enc (out xml);
393 var action = new ServiceAction ("0", xml);
394 var content_directory = new ContentDirectory ();
395 var creator = new ObjectCreator (content_directory, action);
396 creator.run.begin ();
397 assert (action.error_code == expected_code);
400 private void test_didl_parsing () {
401 var xml = Xml.Parser.read_memory (DIDL_ITEM,
405 Xml.ParserOption.RECOVER |
406 Xml.ParserOption.NOBLANKS);
407 var didl_node = xml->children;
408 var item_node = didl_node->children;
409 var content_directory = new ContentDirectory ();
412 var action = new ServiceAction ("0", "");
413 var creator = new ObjectCreator (content_directory, action);
414 creator.run.begin ();
415 assert (action.error_code == bad_metadata.code);
416 assert (action.error_message == "Bad metadata");
419 item_node->unlink ();
420 didl_node->set_content (" ");
421 this.test_didl_parsing_step (xml, bad_metadata.code);
423 // test item node with missing restricted attribute
424 var tmp = item_node->copy (1);
425 tmp->unset_prop ("restricted");
426 didl_node->add_child (tmp);
427 this.test_didl_parsing_step (xml, bad_metadata.code);
429 // test item node with restricted=1
430 tmp->set_prop ("restricted", "1");
431 this.test_didl_parsing_step (xml, invalid_args.code);
433 // test item node with invalid id
435 tmp = item_node->copy (1);
436 tmp->set_prop ("id", "InvalidItemId");
437 didl_node->add_child (tmp);
438 this.test_didl_parsing_step (xml, bad_metadata.code);
440 // test item node with missing id
441 tmp->unset_prop ("id");
442 this.test_didl_parsing_step (xml, bad_metadata.code);
444 // test item node with missing title
446 tmp = item_node->copy (1);
447 var title_node = tmp->children;
448 title_node->unlink ();
449 didl_node->add_child (tmp);
450 this.test_didl_parsing_step (xml, bad_metadata.code);
452 // test missing or empty upnp class
454 tmp = item_node->copy (1);
455 var class_node = tmp->children->next;
457 class_node->set_content ("");
458 this.test_didl_parsing_step (xml, bad_metadata.code);
460 class_node->unlink ();
461 this.test_didl_parsing_step (xml, bad_metadata.code);
464 private void test_fetch_container_run (ObjectCreator creator) {
465 var main_loop = new MainLoop (null, false);
466 creator.run.begin ( () => { main_loop.quit (); });
470 private void test_fetch_container () {
471 // check case when object is not found
472 var content_directory = new ContentDirectory ();
473 var root_container = new SearchableContainer ();
474 content_directory.root_container = root_container;
475 var action = new ServiceAction ("0", DIDL_ITEM);
476 var creator = new ObjectCreator (content_directory, action);
477 this.test_fetch_container_run (creator);
478 assert (action.error_code == no_such_object.code);
480 // check case when found object is not a container → Error 710
481 // cf. ContentDirectory:2 spec, Table 2-22
482 root_container.found_object = new MediaObject ();
483 this.test_fetch_container_run (creator);
484 assert (action.error_code == no_such_object.code);
486 // check case when found container does not have OCMUpload set
487 root_container.found_object = new MediaContainer ();
488 this.test_fetch_container_run (creator);
489 assert (action.error_code == restricted_parent.code);
491 // check case when found container is not a writable container
492 root_container.found_object.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
493 this.test_fetch_container_run (creator);
494 assert (action.error_code == restricted_parent.code);
496 // check when found container does not have the correct create class
497 var container = new WritableContainer ();
498 container.create_classes.add ("object.item.imageItem.musicTrack");
499 container.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
500 root_container.found_object = container;
501 this.test_fetch_container_run (creator);
502 assert (action.error_code == bad_metadata.code);
504 // check DLNA.ORG_AnyContainer when root container is not searchable
505 content_directory.root_container = new MediaContainer ();
506 action.id = "DLNA.ORG_AnyContainer";
507 this.test_fetch_container_run (creator);
508 assert (action.error_code == no_such_object.code);
510 // check DLNA.ORG_AnyContainer when no writable container is found
511 content_directory.root_container = new SearchableContainer ();
512 this.test_fetch_container_run (creator);
513 // We cannot distinguish this case from the "create-class doesn't match"
515 assert (action.error_code == bad_metadata.code);