[rygel/wip/create-reference: 1/2] server, media-export: Allow container creation



commit 9c54b73c4d66471fd94d8a1f827c781a708abc32
Author: Jens Georg <jensg openismus com>
Date:   Wed Feb 20 14:27:51 2013 +0100

    server,media-export: Allow container creation
    
    https://bugzilla.gnome.org/show_bug.cgi?id=694155

 po/POTFILES.in                                     |    2 +-
 po/POTFILES.skip                                   |    4 +-
 src/librygel-server/filelist.am                    |    4 +-
 src/librygel-server/rygel-content-directory.vala   |    2 +-
 src/librygel-server/rygel-http-post.vala           |    9 +-
 src/librygel-server/rygel-import-resource.vala     |    4 +-
 src/librygel-server/rygel-item-removal-queue.vala  |   89 ------
 src/librygel-server/rygel-media-container.vala     |    1 +
 ...item-creator.vala => rygel-object-creator.vala} |  289 +++++++++++++-------
 .../rygel-object-removal-queue.vala                |   95 +++++++
 src/librygel-server/rygel-writable-container.vala  |   30 ++-
 .../rygel-media-export-writable-db-container.vala  |   31 ++
 .../rygel-tracker-category-all-container.vala      |   10 +
 tests/Makefile.am                                  |   18 +-
 ...or.vala => rygel-http-seek_object-creator.vala} |    0
 tests/rygel-item-creator.vala                      |    1 -
 ...or-test.vala => rygel-object-creator-test.vala} |   70 +++--
 tests/rygel-object-creator.vala                    |    1 +
 ...r.vala => rygel-serializer_object-creator.vala} |    0
 ...ala => rygel-state-machine_object-creator.vala} |    0
 20 files changed, 424 insertions(+), 236 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cf6e579..5e7a9bd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -100,7 +100,7 @@ src/librygel-server/rygel-http-time-seek.vala
 src/librygel-server/rygel-http-transcode-handler.vala
 src/librygel-server/rygel-image-item.vala
 src/librygel-server/rygel-import-resource.vala
-src/librygel-server/rygel-item-creator.vala
+src/librygel-server/rygel-object-creator.vala
 src/librygel-server/rygel-item-destroyer.vala
 src/librygel-server/rygel-item-updater.vala
 src/librygel-server/rygel-logical-expression.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 32c60c1..a05223c 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -68,9 +68,9 @@ src/librygel-server/rygel-http-time-seek.c
 src/librygel-server/rygel-http-transcode-handler.c
 src/librygel-server/rygel-image-item.c
 src/librygel-server/rygel-import-resource.c
-src/librygel-server/rygel-item-creator.c
+src/librygel-server/rygel-object-creator.c
 src/librygel-server/rygel-item-destroyer.c
-src/librygel-server/rygel-item-removal-queue.c
+src/librygel-server/rygel-object-removal-queue.c
 src/librygel-server/rygel-item-updater.c
 src/librygel-server/rygel-last-change.c
 src/librygel-server/rygel-last-change-entry.c
diff --git a/src/librygel-server/filelist.am b/src/librygel-server/filelist.am
index d5b1c75..71d05c6 100644
--- a/src/librygel-server/filelist.am
+++ b/src/librygel-server/filelist.am
@@ -51,10 +51,10 @@ LIBRYGEL_SERVER_NONVAPI_SOURCE_FILES = \
        rygel-http-time-seek.vala \
        rygel-http-transcode-handler.vala \
        rygel-import-resource.vala \
-       rygel-item-creator.vala \
+       rygel-object-creator.vala \
        rygel-item-destroyer.vala \
        rygel-item-updater.vala \
-       rygel-item-removal-queue.vala \
+       rygel-object-removal-queue.vala \
        rygel-last-change-entry.vala \
        rygel-last-change-obj-add.vala \
        rygel-last-change-obj-del.vala \
diff --git a/src/librygel-server/rygel-content-directory.vala 
b/src/librygel-server/rygel-content-directory.vala
index 809297e..ecef0f4 100644
--- a/src/librygel-server/rygel-content-directory.vala
+++ b/src/librygel-server/rygel-content-directory.vala
@@ -203,7 +203,7 @@ internal class Rygel.ContentDirectory: Service {
     /* CreateObject action implementation */
     private void create_object_cb (Service       content_dir,
                                    ServiceAction action) {
-        var creator = new ItemCreator (this, action);
+        var creator = new ObjectCreator (this, action);
 
         creator.run.begin ();
     }
diff --git a/src/librygel-server/rygel-http-post.vala b/src/librygel-server/rygel-http-post.vala
index 1fe63e3..7a0a34d 100644
--- a/src/librygel-server/rygel-http-post.vala
+++ b/src/librygel-server/rygel-http-post.vala
@@ -45,14 +45,13 @@ internal class Rygel.HTTPPost : HTTPRequest {
     }
 
     protected override async void handle () throws Error {
-        var queue = ItemRemovalQueue.get_default ();
-        queue.dequeue (this.object as MediaItem);
+        var queue = ObjectRemovalQueue.get_default ();
+        queue.dequeue (this.object);
 
         try {
             yield this.handle_real ();
         } catch (Error error) {
-            yield queue.remove_now (this.object as MediaItem,
-                                    this.cancellable);
+            yield queue.remove_now (this.object, this.cancellable);
 
             throw error;
         }
@@ -218,7 +217,7 @@ internal class Rygel.HTTPPost : HTTPRequest {
     }
 
     private async void remove_item () {
-        var queue = ItemRemovalQueue.get_default ();
+        var queue = ObjectRemovalQueue.get_default ();
         yield queue.remove_now (this.object as MediaItem, null);
     }
 
diff --git a/src/librygel-server/rygel-import-resource.vala b/src/librygel-server/rygel-import-resource.vala
index 1653fdb..06a01f4 100644
--- a/src/librygel-server/rygel-import-resource.vala
+++ b/src/librygel-server/rygel-import-resource.vala
@@ -139,11 +139,9 @@ internal class Rygel.ImportResource : GLib.Object, Rygel.StateMachine {
             return;
         }
 
-        var queue = ItemRemovalQueue.get_default ();
+        var queue = ObjectRemovalQueue.get_default ();
         queue.dequeue (this.item);
 
-
-
         try {
             var source_file = File.new_for_uri (this.item.uris[0]);
             this.output_stream = yield source_file.replace_async (null,
diff --git a/src/librygel-server/rygel-media-container.vala b/src/librygel-server/rygel-media-container.vala
index c1081b9..99094da 100644
--- a/src/librygel-server/rygel-media-container.vala
+++ b/src/librygel-server/rygel-media-container.vala
@@ -67,6 +67,7 @@ public abstract class Rygel.MediaContainer : MediaObject {
     public const string MUSIC_ALBUM = UPNP_CLASS + ".album.musicAlbum";
     public const string MUSIC_ARTIST = UPNP_CLASS + ".person.musicArtist";
     public const string MUSIC_GENRE = UPNP_CLASS + ".genre.musicGenre";
+    public const string PLAYLIST = UPNP_CLASS + ".playlistContainer";
 
     private const string DEFAULT_SORT_CRITERIA = "+upnp:class,+dc:title";
     public const string ALBUM_SORT_CRITERIA = "+upnp:class," +
diff --git a/src/librygel-server/rygel-item-creator.vala b/src/librygel-server/rygel-object-creator.vala
similarity index 65%
rename from src/librygel-server/rygel-item-creator.vala
rename to src/librygel-server/rygel-object-creator.vala
index e33b6e7..ece1c51 100644
--- a/src/librygel-server/rygel-item-creator.vala
+++ b/src/librygel-server/rygel-object-creator.vala
@@ -25,9 +25,69 @@
 using GUPnP;
 
 /**
+ * Dummy implementation of Rygel.MediaContainer to pass on to
+ * Rygel.WritableContianer for creation.
+ */
+private class Rygel.BaseMediaContainer : MediaContainer {
+    /**
+     * Create a media container with the specified details.
+     *
+     * @param id See the id property of the #RygelMediaObject class.
+     * @param parent The parent container, if any.
+     * @param title See the title property of the #RygelMediaObject class.
+     * @param child_count The initially-known number of child items.
+     */
+    public BaseMediaContainer (string          id,
+                               MediaContainer? parent,
+                               string          title,
+                               int             child_count) {
+        Object (id : id,
+                parent : parent,
+                title : title,
+                child_count : child_count);
+    }
+
+    /**
+     * Fetches the list of media objects directly under this container.
+     *
+     * @param offset zero-based index of the first item to return
+     * @param max_count maximum number of objects to return
+     * @param sort_criteria sorting order of objects to return
+     * @param cancellable optional cancellable for this operation
+     *
+     * @return A list of media objects.
+     */
+    public override async MediaObjects? get_children
+                                            (uint         offset,
+                                             uint         max_count,
+                                             string       sort_criteria,
+                                             Cancellable? cancellable)
+                                            throws Error {
+        return null;
+    }
+
+    /**
+     * Recursively searches this container for a media object with the given ID.
+     *
+     * @param id ID of the media object to search for
+     * @param cancellable optional cancellable for this operation
+     *
+     * @return the found media object.
+     */
+    public override async MediaObject? find_object (string       id,
+                                                    Cancellable? cancellable)
+                                                    throws Error {
+        return null;
+    }
+
+}
+
+
+
+/**
  * CreateObject action implementation.
  */
-internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
+internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
     private static PatternSpec comment_pattern = new PatternSpec ("*<!--*-->*");
 
     private const string INVALID_CHARS = "/?<>\\:*|\"";
@@ -36,8 +96,8 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
     private string container_id;
     private string elements;
 
-    private DIDLLiteItem didl_item;
-    private MediaItem item;
+    private DIDLLiteObject didl_object;
+    private MediaObject object;
 
     private ContentDirectory content_dir;
     private ServiceAction action;
@@ -47,8 +107,8 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
 
     public Cancellable cancellable { get; set; }
 
-    public ItemCreator (ContentDirectory    content_dir,
-                        owned ServiceAction action) {
+    public ObjectCreator (ContentDirectory    content_dir,
+                          owned ServiceAction action) {
         this.content_dir = content_dir;
         this.cancellable = content_dir.cancellable;
         this.action = (owned) action;
@@ -76,30 +136,38 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
              * modify the UPnP class to something we support and
              * fetch_container took care of this already.
              */
-            if (!container.can_create (this.didl_item.upnp_class) &&
+            if (!container.can_create (this.didl_object.upnp_class) &&
                 this.container_id != MediaContainer.ANY) {
                 throw new ContentDirectoryError.BAD_METADATA
                                         ("Creating of objects with class %s " +
                                          "is not supported in %s",
-                                         this.didl_item.upnp_class,
+                                         this.didl_object.upnp_class,
                                          container.id);
             }
 
-            yield this.create_item_from_didl (container);
-            yield container.add_item (this.item, this.cancellable);
+            yield this.create_object_from_didl (container);
+            if (this.object is MediaItem) {
+                yield container.add_item (this.object as MediaItem,
+                                          this.cancellable);
+            } else {
+                yield container.add_container (this.object as MediaContainer,
+                                               this.cancellable);
+            }
 
-            yield this.wait_for_item (container);
+            yield this.wait_for_object (container);
 
-            this.item.serialize (serializer, this.content_dir.http_server);
+            this.object.serialize (serializer, this.content_dir.http_server);
 
             // Conclude the successful action
             this.conclude ();
 
             if (this.container_id == MediaContainer.ANY &&
-                this.item.place_holder) {
-                var queue = ItemRemovalQueue.get_default ();
+                ((this.object is MediaContainer) ||
+                (this.object is MediaItem && (this.object as
+                                              MediaItem).place_holder))) {
+                var queue = ObjectRemovalQueue.get_default ();
 
-                queue.queue (this.item, this.cancellable);
+                queue.queue (this.object, this.cancellable);
             }
         } catch (Error err) {
             this.handle_error (err);
@@ -136,8 +204,10 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
      * according to UPnP and DLNA guidelines.
      */
     private void parse_didl () throws Error {
-        this.didl_parser.item_available.connect ((didl_item) => {
-            this.didl_item = didl_item;
+        // FIXME: This will take the last object in the DIDL-Lite, maybe we
+        // should limit it to one somehow.
+        this.didl_parser.object_available.connect ((didl_object) => {
+            this.didl_object = didl_object;
         });
 
         try {
@@ -146,27 +216,28 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
             throw new ContentDirectoryError.BAD_METADATA ("Bad metadata");
         }
 
-        if (this.didl_item == null) {
+        if (this.didl_object == null) {
+            // FIXME: Change to object after string freeze
             var message = _("No items in DIDL-Lite from client: '%s'");
 
             throw new ContentDirectoryError.BAD_METADATA
                                         (message, this.elements);
         }
 
-        if (didl_item.id == null || didl_item.id != "") {
+        if (didl_object.id == null || didl_object.id != "") {
             throw new ContentDirectoryError.BAD_METADATA
                                         ("@id must be set to \"\" in " +
                                          "CreateItem");
         }
 
-        if (didl_item.title == null) {
+        if (didl_object.title == null) {
             throw new ContentDirectoryError.BAD_METADATA
                                     ("dc:title must be set in " +
                                      "CreateItem");
         }
 
         // FIXME: Is this check really necessary? 7.3.118.4 passes without it.
-        if ((didl_item.dlna_managed &
+        if ((didl_object.dlna_managed &
             (OCMFlags.UPLOAD |
              OCMFlags.CREATE_CONTAINER |
              OCMFlags.UPLOAD_DESTROYABLE)) != 0) {
@@ -175,14 +246,14 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
                                          "were found in 'dlnaManaged'");
         }
 
-        if (didl_item.upnp_class == null ||
-            didl_item.upnp_class == "" ||
-            !didl_item.upnp_class.has_prefix ("object.item")) {
+        if (didl_object.upnp_class == null ||
+            didl_object.upnp_class == "" ||
+            !didl_object.upnp_class.has_prefix ("object")) {
             throw new ContentDirectoryError.BAD_METADATA
                                         ("Invalid upnp:class given ");
         }
 
-        if (didl_item.restricted) {
+        if (didl_object.restricted) {
             throw new ContentDirectoryError.INVALID_ARGS
                                         ("Cannot create restricted item");
         }
@@ -222,13 +293,13 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
             return null;
         }
 
-        var upnp_class = this.didl_item.upnp_class;
+        var upnp_class = this.didl_object.upnp_class;
 
         var expression = new RelationalExpression ();
         expression.op = SearchCriteriaOp.DERIVED_FROM;
         expression.operand1 = "upnp:createClass";
 
-        while (upnp_class != "object.item") {
+        while (upnp_class != "object") {
             expression.operand2 = upnp_class;
 
             uint total_matches;
@@ -239,7 +310,7 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
                                                       root_container.sort_criteria,
                                                       this.cancellable);
             if (result.size > 0) {
-                this.didl_item.upnp_class = upnp_class;
+                this.didl_object.upnp_class = upnp_class;
 
                 return result[0];
             } else {
@@ -247,10 +318,11 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
             }
         }
 
-        if (upnp_class == "object.item") {
+        if (upnp_class == "object") {
+            // FIXME: Mark translatable.
             throw new ContentDirectoryError.BAD_METADATA
                                     ("'%s' UPnP class unsupported",
-                                     this.didl_item.upnp_class);
+                                     this.didl_object.upnp_class);
         }
 
         return null;
@@ -296,7 +368,7 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
         string didl = this.serializer.get_string ();
 
         /* Set action return arguments */
-        this.action.set ("ObjectID", typeof (string), this.item.id,
+        this.action.set ("ObjectID", typeof (string), this.object.id,
                          "Result", typeof (string), didl);
 
         this.action.return ();
@@ -318,9 +390,15 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
     }
 
     private string get_generic_mime_type () {
-        if (this.item is ImageItem) {
+        if (!(this.object is MediaItem)) {
+            return "";
+        }
+
+        var item = this.object as MediaItem;
+
+        if (item is ImageItem) {
             return "image";
-        } else if (this.item is VideoItem) {
+        } else if (item is VideoItem) {
             return "video";
         } else {
             return "audio";
@@ -335,107 +413,124 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
      * profile is supported or not) or sanitize the supplied title for use as
      * part of the on-disk filename.
      *
-     * This function fills ItemCreator.item.
+     * This function fills ObjectCreator.item.
      */
-    private async void create_item_from_didl (WritableContainer container)
-                                                   throws Error {
-        this.item = this.create_item (this.didl_item.id,
-                                      container,
-                                      this.didl_item.title,
-                                      this.didl_item.upnp_class);
-
-        var resources = this.didl_item.get_resources ();
+    private async void create_object_from_didl (WritableContainer container)
+                                                throws Error {
+        this.object = this.create_object (this.didl_object.id,
+                                          container,
+                                          this.didl_object.title,
+                                          this.didl_object.upnp_class);
+
+        var resources = this.didl_object.get_resources ();
         if (resources != null && resources.length () > 0) {
             var resource = resources.nth (0).data;
             var info = resource.protocol_info;
 
-            if (info != null) {
+            if (info != null && this.didl_object is DIDLLiteItem) {
                 if (info.dlna_profile != null) {
                     if (!this.is_profile_valid (info.dlna_profile)) {
+                        // FIXME: Missing translation
                         throw new ContentDirectoryError.BAD_METADATA
                                     ("'%s' DLNA profile unsupported",
                                      info.dlna_profile);
                     }
 
-                    this.item.dlna_profile = info.dlna_profile;
+                    (this.object as MediaItem).dlna_profile = info.dlna_profile;
                 }
 
                 if (info.mime_type != null) {
-                    this.item.mime_type = info.mime_type;
+                    (this.object as MediaItem).mime_type = info.mime_type;
                 }
             }
 
-            string sanitized_uri;
-            if (this.is_valid_uri (resource.uri, out sanitized_uri)) {
-                this.item.add_uri (sanitized_uri);
+            string sanitized_uri = null;
+            if ((this.object is MediaItem) &&
+                this.is_valid_uri (resource.uri, out sanitized_uri)) {
+                (this.object as MediaItem).add_uri (sanitized_uri);
             }
 
-            if (resource.size >= 0) {
-                this.item.size = resource.size;
+            if (resource.size >= 0 && this.object is MediaItem) {
+                (this.object as MediaItem).size = resource.size;
             }
         }
 
-        if (this.item.mime_type == null) {
-            this.item.mime_type = this.get_generic_mime_type ();
-        }
+        var item = this.object as MediaItem;
+        if (this.object is MediaItem) {
+            if (item.mime_type == null) {
+                item.mime_type = this.get_generic_mime_type ();
+            }
 
-        if (this.item.size < 0) {
-            this.item.size = 0;
+            if (item.size < 0) {
+                item.size = 0;
+            }
         }
 
-        if (this.item.uris.size == 0) {
-            var uri = yield this.create_uri (container, this.item.title);
-            this.item.uris.add (uri);
-            this.item.place_holder = true;
+        if (this.object.uris.size == 0) {
+            var uri = yield this.create_uri (container, this.object.title);
+            this.object.uris.add (uri);
+            if (this.object is MediaItem) {
+                item.place_holder = true;
+            }
         } else {
-            var file = File.new_for_uri (this.item.uris[0]);
-            this.item.place_holder = !file.is_native ();
+            var file = File.new_for_uri (item.uris[0]);
+            if (this.object is MediaItem) {
+                item.place_holder = !file.is_native ();
+            }
         }
 
-        this.item.id = this.item.uris[0];
+        this.object.id = this.object.uris[0];
 
         this.parse_and_verify_didl_date ();
     }
 
     private void parse_and_verify_didl_date () throws Error {
-        if (this.didl_item.date == null) {
+        if (!(this.didl_object is DIDLLiteItem)) {
             return;
         }
 
-        var parsed_date = new Soup.Date.from_string (this.didl_item.date);
+        var didl_item = this.didl_object as DIDLLiteItem;
+        if (didl_item.date == null) {
+            return;
+        }
+
+        var parsed_date = new Soup.Date.from_string (didl_item.date);
         if (parsed_date != null) {
-            this.item.date = parsed_date.to_string (Soup.DateFormat.ISO8601);
+            (this.object as MediaItem).date = parsed_date.to_string
+                                            (Soup.DateFormat.ISO8601);
 
             return;
         }
 
         int year = 0, month = 0, day = 0;
 
-        if (this.didl_item.date.scanf ("%4d-%02d-%02d",
-                                       out year,
-                                       out month,
-                                       out day) != 3) {
+        if (didl_item.date.scanf ("%4d-%02d-%02d",
+                                  out year,
+                                  out month,
+                                  out day) != 3) {
             throw new ContentDirectoryError.BAD_METADATA
                                     ("Invalid date format: %s",
-                                     this.didl_item.date);
+                                     didl_item.date);
         }
 
         var date = GLib.Date ();
         date.set_dmy ((DateDay) day, (DateMonth) month, (DateYear) year);
 
         if (!date.valid ()) {
+            // FIXME: Add translation.
             throw new ContentDirectoryError.BAD_METADATA
                                     ("Invalid date: %s",
-                                     this.didl_item.date);
+                                     didl_item.date);
         }
 
-        this.item.date = this.didl_item.date + "T00:00:00";
+        (this.object as MediaItem).date = didl_item.date + "T00:00:00";
     }
 
-    private MediaItem create_item (string            id,
-                                   WritableContainer parent,
-                                   string            title,
-                                   string            upnp_class) throws Error {
+    private MediaObject create_object (string            id,
+                                       WritableContainer parent,
+                                       string            title,
+                                       string            upnp_class)
+                                       throws Error {
         switch (upnp_class) {
         case ImageItem.UPNP_CLASS:
             return new ImageItem (id, parent, title);
@@ -449,6 +544,12 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
             return new MusicItem (id, parent, title);
         case PlaylistItem.UPNP_CLASS:
             return new PlaylistItem (id, parent, title);
+        case MediaContainer.STORAGE_FOLDER:
+            return new BaseMediaContainer (id, parent, title, 0);
+        case MediaContainer.PLAYLIST:
+            var container = new BaseMediaContainer (id, parent, title, 0);
+            container.upnp_class = upnp_class;
+            return container;
         default:
             throw new ContentDirectoryError.BAD_METADATA
                                         ("Creation of item of class '%s' " +
@@ -501,13 +602,7 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
                                                     "_",
                                                     RegexMatchFlags.NOTEMPTY);
 
-        var udn = new uchar[50];
-        var id = new uchar[16];
-
-        UUID.generate (id);
-        UUID.unparse (id, udn);
-
-        return (string) udn + "-" + mangled;
+        return UUID.get () + "-" + mangled;
     }
 
     /**
@@ -535,37 +630,37 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
     }
 
     /**
-     * Wait for the new item
+     * Wait for the new object
      *
-     * When creating an item in the back-end via WritableContainer.add_item
-     * there might be a delay between the creation and the back-end having the
-     * newly created item available. This function waits for the item to become
-     * available by hooking into the container_updated signal. The maximum time
-     * to wait is 5 seconds.
+     * When creating an object in the back-end via WritableContainer.add_item
+     * or WritableContainer.add_container there might be a delay between the
+     * creation and the back-end having the newly created item available. This
+     * function waits for the item to become available by hooking into the
+     * container_updated signal. The maximum time to wait is 5 seconds.
      *
      * @param container to watch
      */
-    private async void wait_for_item (WritableContainer container) {
+    private async void wait_for_object (WritableContainer container) {
         debug ("Waiting for new item to appear under container '%s'..",
                container.id);
 
-        MediaItem item = null;
+        MediaObject object = null;
 
-        while (item == null) {
+        while (object == null) {
             try {
-                item = (yield container.find_object (this.item.id,
-                                                     this.cancellable))
+                object = (yield container.find_object (this.object.id,
+                                                       this.cancellable))
                        as MediaItem;
             } catch (Error error) {
                 warning ("Error from container '%s' on trying to find newly " +
                          "added child item '%s' in it",
                          container.id,
-                         this.item.id);
+                         this.object.id);
             }
 
-            if (item == null) {
+            if (object == null) {
                 var id = container.container_updated.connect ((container) => {
-                    this.wait_for_item.callback ();
+                    this.wait_for_object.callback ();
                 });
 
                 uint timeout = 0;
@@ -573,7 +668,7 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
                     debug ("Timeout on waiting for 'updated' signal on '%s'.",
                            container.id);
                     timeout = 0;
-                    this.wait_for_item.callback ();
+                    this.wait_for_object.callback ();
 
                     return false;
                 });
diff --git a/src/librygel-server/rygel-object-removal-queue.vala 
b/src/librygel-server/rygel-object-removal-queue.vala
new file mode 100644
index 0000000..b43da44
--- /dev/null
+++ b/src/librygel-server/rygel-object-removal-queue.vala
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome 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 Gee;
+
+/**
+ * Queues objects for removal after 35 seconds or immediately.
+ *
+ * The 35s timeout comes from the DLNA documentation.
+ */
+internal class Rygel.ObjectRemovalQueue: GLib.Object {
+    private const uint TIMEOUT = 35;
+
+    private static ObjectRemovalQueue removal_queue;
+
+    private HashMap<string,uint> object_timeouts;
+
+    public static ObjectRemovalQueue get_default () {
+        if (unlikely (removal_queue == null)) {
+            removal_queue = new ObjectRemovalQueue ();
+        }
+
+        return removal_queue;
+    }
+
+    public void queue (MediaObject object, Cancellable? cancellable) {
+        if (object.parent_ref == null) {
+            object.parent_ref = object.parent;
+        }
+
+        var timeout = Timeout.add_seconds (TIMEOUT, () => {
+            debug ("Timeout on temporary object '%s'.", object.id);
+            this.remove_now.begin (object, cancellable);
+
+            return false;
+        });
+
+        object_timeouts.set (object.id, timeout);
+    }
+
+    public bool dequeue (MediaObject object) {
+        uint timeout;
+
+        if (object_timeouts.unset (object.id, out timeout)) {
+            Source.remove (timeout);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public async void remove_now (MediaObject object, Cancellable? cancellable) {
+        object_timeouts.unset (object.id);
+
+        var parent = object.parent as WritableContainer;
+
+        try {
+            if (object is MediaItem) {
+                yield parent.remove_item (object.id, cancellable);
+            } else {
+                yield parent.remove_container (object.id, cancellable);
+            }
+
+            debug ("Auto-destroyed object '%s'!", object.id);
+        } catch (Error err) {
+            warning ("Failed to auto-destroy temporary object '%s': %s",
+                     object.id,
+                     err.message);
+        }
+    }
+
+    private ObjectRemovalQueue () {
+        object_timeouts = new HashMap<string,uint> ();
+    }
+}
diff --git a/src/librygel-server/rygel-writable-container.vala 
b/src/librygel-server/rygel-writable-container.vala
index 48d6056..b6a384d 100644
--- a/src/librygel-server/rygel-writable-container.vala
+++ b/src/librygel-server/rygel-writable-container.vala
@@ -26,6 +26,10 @@
 
 using Gee;
 
+public errordomain WriteableContainerError {
+    NOT_IMPLEMENTED
+}
+
 /**
  * This interface should be implemented by 'writable' containers - ones that allow
  * adding (via upload), removal and editing of items directly under them.
@@ -72,7 +76,6 @@ public interface Rygel.WritableContainer : MediaContainer {
      * is handled by the container class.
      *
      * This method corresponds to the UPnP ContentDirectory's CreateObject action.
-     * Currently there is no way to add child containers.
      *
      * @param item The item to add to this container
      * @param cancellable optional cancellable for this operation
@@ -82,6 +85,16 @@ public interface Rygel.WritableContainer : MediaContainer {
     public async abstract void add_item (MediaItem    item,
                                          Cancellable? cancellable) throws Error;
 
+
+    /**
+     * Add a new container directly under this container.
+     *
+     * @param container The container to add to this container
+     * @param cancellable optional cancellable for this operation
+     **/
+    public async abstract void add_container (MediaContainer container,
+                                              Cancellable?   cancellable)
+                                              throws Error;
     /**
      * Remove an item directly under this container that has the ID @id.
      *
@@ -97,4 +110,19 @@ public interface Rygel.WritableContainer : MediaContainer {
      */
     public async abstract void remove_item (string id, Cancellable? cancellable)
                                             throws Error;
+
+    /**
+     * Remove a container directly under this container that has the ID @id.
+     *
+     * The caller should not first remove the file(s) pointed to by the item's URI(s). That
+     * is handled by the container class.
+     *
+     * This method corresponds to the UPnP ContentDirectory's DestroyObject action.
+     *
+     * @param id The ID of the item to remove from this container
+     * @param cancellable optional cancellable for this operation
+     */
+    public async abstract void remove_container (string       id,
+                                                 Cancellable? cancellable)
+                                                 throws Error;
 }
diff --git a/src/plugins/media-export/rygel-media-export-writable-db-container.vala 
b/src/plugins/media-export/rygel-media-export-writable-db-container.vala
index d8940b9..be1a0a6 100644
--- a/src/plugins/media-export/rygel-media-export-writable-db-container.vala
+++ b/src/plugins/media-export/rygel-media-export-writable-db-container.vala
@@ -43,12 +43,17 @@ internal class Rygel.MediaExport.WritableDbContainer : TrackableDbContainer,
         base.constructed ();
 
         this.create_classes = new ArrayList<string> ();
+
+        // Items
         this.create_classes.add (Rygel.ImageItem.UPNP_CLASS);
         this.create_classes.add (Rygel.PhotoItem.UPNP_CLASS);
         this.create_classes.add (Rygel.VideoItem.UPNP_CLASS);
         this.create_classes.add (Rygel.AudioItem.UPNP_CLASS);
         this.create_classes.add (Rygel.MusicItem.UPNP_CLASS);
         this.create_classes.add (Rygel.PlaylistItem.UPNP_CLASS);
+
+        // Containers
+        this.create_classes.add (Rygel.MediaContainer.STORAGE_FOLDER);
     }
 
     public async void add_item (Rygel.MediaItem item, Cancellable? cancellable)
@@ -63,10 +68,36 @@ internal class Rygel.MediaExport.WritableDbContainer : TrackableDbContainer,
         yield this.add_child_tracked (item);
     }
 
+    public async void add_container (MediaContainer container,
+                                     Cancellable?   cancellable) 
+                                     throws Error {
+        container.parent = this;
+        switch (container.upnp_class) {
+        case MediaContainer.STORAGE_FOLDER:
+            var file = File.new_for_uri (container.uris[0]);
+            if (file.is_native ()) {
+                file.make_directory_with_parents (cancellable);
+            }
+            break;
+        default:
+            throw new WriteableContainerError.NOT_IMPLEMENTED
+                                        ("upnp:class %s not supported",
+                                         container.upnp_class);
+        }
+
+        yield this.add_child_tracked (container);
+    }
+
     public async void remove_item (string id, Cancellable? cancellable)
                                    throws Error {
         var object = this.media_db.get_object (id);
 
         yield this.remove_child_tracked (object);
     }
+
+    public async void remove_container (string id, Cancellable? cancellable)
+                                        throws Error {
+        throw new WriteableContainerError.NOT_IMPLEMENTED ("Not supported");
+    }
+
 }
diff --git a/src/plugins/tracker/rygel-tracker-category-all-container.vala 
b/src/plugins/tracker/rygel-tracker-category-all-container.vala
index dd52e92..83c104d 100644
--- a/src/plugins/tracker/rygel-tracker-category-all-container.vala
+++ b/src/plugins/tracker/rygel-tracker-category-all-container.vala
@@ -90,6 +90,11 @@ public class Rygel.Tracker.CategoryAllContainer : SearchContainer,
         item.parent = this;
     }
 
+    public async void add_container (MediaContainer container,
+                                     Cancellable? cancellable) throws Error {
+        throw new WriteableContainerError.NOT_IMPLEMENTED ("Not supported");
+    }
+
     public async void remove_item (string id, Cancellable? cancellable)
                                    throws Error {
         string parent_id;
@@ -99,6 +104,11 @@ public class Rygel.Tracker.CategoryAllContainer : SearchContainer,
         yield this.remove_entry_from_store (urn);
     }
 
+    public async void remove_container (string id, Cancellable? cancellable)
+                                        throws Error {
+        throw new WriteableContainerError.NOT_IMPLEMENTED ("Not supported");
+    }
+
     public async MediaObjects? search (SearchExpression? expression,
                                        uint              offset,
                                        uint              max_count,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6f4fc54..dd56495 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -8,7 +8,7 @@ check_PROGRAMS = rygel-http-item-uri-test \
                 rygel-album-art-spec-test \
                 rygel-http-post-test \
                 rygel-searchable-container-test \
-                rygel-item-creator-test \
+                rygel-object-creator-test \
                 rygel-user-config-test \
                 rygel-regression \
                 rygel-media-engine-test
@@ -135,22 +135,22 @@ rygel_searchable_container_test_CFLAGS =  \
 rygel_searchable_container_test_LDADD = \
        $(test_libs)
 
-rygel_item_creator_test_SOURCES = rygel-item-creator-test.vala \
-                                 rygel-item-creator.vala \
+rygel_object_creator_test_SOURCES = rygel-object-creator-test.vala \
+                                 rygel-object-creator.vala \
                                  rygel-data-source.vala \
                                  rygel-dlna-profile.vala \
-                                 rygel-http-seek_item-creator.vala \
-                                 rygel-state-machine_item-creator.vala \
+                                 rygel-http-seek_object-creator.vala \
+                                 rygel-state-machine_object-creator.vala \
                                  rygel-relational-expression.vala \
                                  rygel-search-expression.vala \
                                  rygel-media-engine.vala \
-                                 rygel-serializer_item-creator.vala
-rygel_item_creator_test_VALAFLAGS = \
+                                 rygel-serializer_object-creator.vala
+rygel_object_creator_test_VALAFLAGS = \
        $(test_valaflags) \
        --pkg uuid
-rygel_item_creator_test_CFLAGS =  \
+rygel_object_creator_test_CFLAGS =  \
        $(test_cflags)
-rygel_item_creator_test_LDADD = \
+rygel_object_creator_test_LDADD = \
        $(test_libs)
 
 rygel_user_config_test_SOURCES = rygel-configuration.vala \
diff --git a/tests/rygel-http-seek_item-creator.vala b/tests/rygel-http-seek_object-creator.vala
similarity index 100%
rename from tests/rygel-http-seek_item-creator.vala
rename to tests/rygel-http-seek_object-creator.vala
diff --git a/tests/rygel-item-creator-test.vala b/tests/rygel-object-creator-test.vala
similarity index 88%
rename from tests/rygel-item-creator-test.vala
rename to tests/rygel-object-creator-test.vala
index 019d137..f2cbb45 100644
--- a/tests/rygel-item-creator-test.vala
+++ b/tests/rygel-object-creator-test.vala
@@ -99,21 +99,21 @@ public class Rygel.ServiceAction : GLib.Object {
 public class Rygel.HTTPServer : GLib.Object {
 }
 
-public class Rygel.ItemRemovalQueue : GLib.Object {
-    public static ItemRemovalQueue get_default () {
-        return new ItemRemovalQueue ();
+public class Rygel.ObjectRemovalQueue : GLib.Object {
+    public static ObjectRemovalQueue get_default () {
+        return new ObjectRemovalQueue ();
     }
 
-    public void queue (MediaItem item, Cancellable? cancellable) {
+    public void queue (MediaObject object, Cancellable? cancellable) {
     }
 }
 
 public class Rygel.MediaObject : GLib.Object {
-    public string id;
+    public string id {get; set; }
     public string ref_id;
-    public unowned MediaContainer parent;
+    public unowned MediaContainer parent { get; set; }
     public string upnp_class;
-    public string title;
+    public string title { get; set; }
     public GUPnP.OCMFlags ocm_flags;
     public Gee.ArrayList<string> uris;
     public uint object_update_id;
@@ -121,6 +121,24 @@ public class Rygel.MediaObject : GLib.Object {
     public void add_uri (string uri) {
         this.uris.add (uri);
     }
+
+    internal void serialize (Rygel.Serializer serializer, HTTPServer server) {
+    }
+
+    public virtual async MediaObjects? get_children
+                                            (uint         offset,
+                                             uint         max_count,
+                                             string       sort_criteria,
+                                             Cancellable? cancellable)
+                                            throws Error {
+        return null;
+    }
+
+    public virtual async MediaObject? find_object (string       id,
+                                                   Cancellable? cancellable)
+                                                   throws Error {
+        return null;
+    }
 }
 
 public interface Rygel.TrackableContainer : Rygel.MediaContainer {
@@ -142,8 +160,6 @@ public class Rygel.MediaItem : Rygel.MediaObject {
         this.title = title;
     }
 
-    internal void serialize (Rygel.Serializer serializer, HTTPServer server) {
-    }
 }
 
 public class Rygel.MusicItem : Rygel.AudioItem {
@@ -202,15 +218,19 @@ public class Rygel.ContentDirectory : GLib.Object {
 
 public class Rygel.MediaContainer : Rygel.MediaObject {
     public Gee.ArrayList<string> create_classes = new Gee.ArrayList<string> ();
-    public int child_count;
+    public int child_count { get; set; }
     public string sort_criteria = "+dc:title";
     public static const string ANY = "DLNA.ORG_AnyContainer";
+    public static const string STORAGE_FOLDER =
+        "object.container.storageFolder";
+    public static const string PLAYLIST =
+        "object.container.playlistContainer";
     public uint update_id;
 
     // mockable elements
     public MediaObject found_object = null;
 
-    public async MediaObject? find_object (string       id,
+    public override async MediaObject? find_object (string       id,
                                            Cancellable? cancellable = null)
                                            throws Error {
         Idle.add (() => { find_object.callback (); return false; });
@@ -237,6 +257,9 @@ public class Rygel.WritableContainer : Rygel.MediaContainer {
     public async void add_item (MediaItem    item,
                                 Cancellable? cancellable = null) {
     }
+
+    public async void add_container (MediaContainer container, Cancellable?
+            cancellable = null) { }
 }
 
 public class Rygel.SearchableContainer : Rygel.MediaContainer {
@@ -310,11 +333,11 @@ public static void log_func (string? domain,
     }
 }
 
-public class Rygel.HTTPItemCreatorTest : GLib.Object {
+public class Rygel.HTTPObjectCreatorTest : GLib.Object {
 
     public static int main (string[] args) {
         Log.set_default_handler (log_func);
-        var test = new HTTPItemCreatorTest ();
+        var test = new HTTPObjectCreatorTest ();
         test.test_parse_args ();
         test.test_didl_parsing ();
         test.test_fetch_container ();
@@ -334,7 +357,7 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
     Error bad_metadata;
     Error invalid_args;
 
-    public HTTPItemCreatorTest () {
+    public HTTPObjectCreatorTest () {
         this.no_such_object = new ContentDirectoryError.NO_SUCH_OBJECT("");
         this.restricted_parent = new ContentDirectoryError.RESTRICTED_PARENT("");
         this.bad_metadata = new ContentDirectoryError.BAD_METADATA("");
@@ -346,19 +369,19 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
         var content_directory = new ContentDirectory ();
 
         var action = new ServiceAction (null, "");
-        var creator = new ItemCreator (content_directory, action);
+        var creator = new ObjectCreator (content_directory, action);
         creator.run.begin ();
         assert (action.error_code == no_such_object.code);
 
         // check elements containing a comment
         action = new ServiceAction ("0", "<!-- This is an XML comment -->");
-        creator = new ItemCreator (content_directory, action);
+        creator = new ObjectCreator (content_directory, action);
         creator.run.begin ();
         assert (action.error_code == bad_metadata.code);
 
         // check null elements
         action = new ServiceAction ("0", null);
-        creator = new ItemCreator (content_directory, action);
+        creator = new ObjectCreator (content_directory, action);
         creator.run.begin ();
         assert (action.error_code == bad_metadata.code);
     }
@@ -369,7 +392,7 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
         doc->dump_memory_enc (out xml);
         var action = new ServiceAction ("0", xml);
         var content_directory = new ContentDirectory ();
-        var creator = new ItemCreator (content_directory, action);
+        var creator = new ObjectCreator (content_directory, action);
         creator.run.begin ();
         assert (action.error_code == expected_code);
     }
@@ -387,7 +410,7 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
 
         // test no DIDL
         var action = new ServiceAction ("0", "");
-        var creator = new ItemCreator (content_directory, action);
+        var creator = new ObjectCreator (content_directory, action);
         creator.run.begin ();
         assert (action.error_code == bad_metadata.code);
         assert (action.error_message == "Bad metadata");
@@ -426,13 +449,10 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
         didl_node->add_child (tmp);
         this.test_didl_parsing_step (xml, bad_metadata.code);
 
-        // test missing, empty or non-item upnp class
+        // test missing or empty upnp class
         tmp->unlink ();
         tmp = item_node->copy (1);
         var class_node = tmp->children->next;
-        class_node->set_content ("object.container");
-        didl_node->add_child (tmp);
-        this.test_didl_parsing_step (xml, bad_metadata.code);
 
         class_node->set_content ("");
         this.test_didl_parsing_step (xml, bad_metadata.code);
@@ -441,7 +461,7 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
         this.test_didl_parsing_step (xml, bad_metadata.code);
     }
 
-    private void test_fetch_container_run (ItemCreator creator) {
+    private void test_fetch_container_run (ObjectCreator creator) {
         var main_loop = new MainLoop (null, false);
         creator.run.begin ( () => { main_loop.quit (); });
         main_loop.run ();
@@ -453,7 +473,7 @@ public class Rygel.HTTPItemCreatorTest : GLib.Object {
         var root_container = new SearchableContainer ();
         content_directory.root_container = root_container;
         var action = new ServiceAction ("0", DIDL_ITEM);
-        var creator = new ItemCreator (content_directory, action);
+        var creator = new ObjectCreator (content_directory, action);
         this.test_fetch_container_run (creator);
         assert (action.error_code == no_such_object.code);
 
diff --git a/tests/rygel-object-creator.vala b/tests/rygel-object-creator.vala
new file mode 120000
index 0000000..d790f61
--- /dev/null
+++ b/tests/rygel-object-creator.vala
@@ -0,0 +1 @@
+../src/librygel-server/rygel-object-creator.vala
\ No newline at end of file
diff --git a/tests/rygel-serializer_item-creator.vala b/tests/rygel-serializer_object-creator.vala
similarity index 100%
rename from tests/rygel-serializer_item-creator.vala
rename to tests/rygel-serializer_object-creator.vala
diff --git a/tests/rygel-state-machine_item-creator.vala b/tests/rygel-state-machine_object-creator.vala
similarity index 100%
rename from tests/rygel-state-machine_item-creator.vala
rename to tests/rygel-state-machine_object-creator.vala


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]