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



commit 3824a6019fca74eb4cdcc5974ecaebca763711f5
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                                   |    2 +-
 src/librygel-server/filelist.am                    |    2 +-
 src/librygel-server/rygel-content-directory.vala   |    2 +-
 src/librygel-server/rygel-item-removal-queue.vala  |    6 +-
 src/librygel-server/rygel-media-container.vala     |    1 +
 ...item-creator.vala => rygel-object-creator.vala} |  281 +++++++++++++-------
 src/librygel-server/rygel-writable-container.vala  |   30 ++-
 .../rygel-media-export-writable-db-container.vala  |   30 ++
 .../rygel-tracker-category-all-container.vala      |   10 +
 tests/rygel-item-creator-test.vala                 |   25 ++-
 11 files changed, 284 insertions(+), 107 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..fff2c6a 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -68,7 +68,7 @@ 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-item-updater.c
diff --git a/src/librygel-server/filelist.am b/src/librygel-server/filelist.am
index d5b1c75..734fb23 100644
--- a/src/librygel-server/filelist.am
+++ b/src/librygel-server/filelist.am
@@ -51,7 +51,7 @@ 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 \
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-item-removal-queue.vala 
b/src/librygel-server/rygel-item-removal-queue.vala
index 282b1a1..5ba3c4e 100644
--- a/src/librygel-server/rygel-item-removal-queue.vala
+++ b/src/librygel-server/rygel-item-removal-queue.vala
@@ -40,7 +40,7 @@ internal class Rygel.ItemRemovalQueue: GLib.Object {
         return removal_queue;
     }
 
-    public void queue (MediaItem item, Cancellable? cancellable) {
+    public void queue (MediaObject item, Cancellable? cancellable) {
         if (item.parent_ref == null) {
             item.parent_ref = item.parent;
         }
@@ -55,7 +55,7 @@ internal class Rygel.ItemRemovalQueue: GLib.Object {
         item_timeouts.set (item.id, timeout);
     }
 
-    public bool dequeue (MediaItem item) {
+    public bool dequeue (MediaObject item) {
         uint timeout;
 
         if (item_timeouts.unset (item.id, out timeout)) {
@@ -67,7 +67,7 @@ internal class Rygel.ItemRemovalQueue: GLib.Object {
         }
     }
 
-    public async void remove_now (MediaItem item, Cancellable? cancellable) {
+    public async void remove_now (MediaObject item, Cancellable? cancellable) {
         item_timeouts.unset (item.id);
 
         var parent = item.parent as WritableContainer;
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 67%
rename from src/librygel-server/rygel-item-creator.vala
rename to src/librygel-server/rygel-object-creator.vala
index e33b6e7..d230e96 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 cration.
+ */
+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) {
+                ((this.object is MediaContainer) ||
+                (this.object is MediaItem && (this.object as
+                                              MediaItem).place_holder))) {
                 var queue = ItemRemovalQueue.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_item) => {
+            this.didl_object = didl_item;
         });
 
         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,126 @@ 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 didl_item = this.didl_object as DIDLLiteItem;
+
+        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 && didl_item != null) {
                 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 +546,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 +604,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,9 +632,9 @@ 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
+     * When creating an object 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
@@ -545,27 +642,27 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
      *
      * @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 +670,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-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..82231e8 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,35 @@ 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 ContentDirectoryError.BAD_METADATA
+                                        ("upnp:class not supported");
+            break;
+
+        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/rygel-item-creator-test.vala b/tests/rygel-item-creator-test.vala
index 019d137..bbb78c7 100644
--- a/tests/rygel-item-creator-test.vala
+++ b/tests/rygel-item-creator-test.vala
@@ -104,7 +104,7 @@ public class Rygel.ItemRemovalQueue : GLib.Object {
         return new ItemRemovalQueue ();
     }
 
-    public void queue (MediaItem item, Cancellable? cancellable) {
+    public void queue (MediaObject item, Cancellable? cancellable) {
     }
 }
 
@@ -121,6 +121,9 @@ 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 interface Rygel.TrackableContainer : Rygel.MediaContainer {
@@ -142,8 +145,6 @@ public class Rygel.MediaItem : Rygel.MediaObject {
         this.title = title;
     }
 
-    internal void serialize (Rygel.Serializer serializer, HTTPServer server) {
-    }
 }
 
 public class Rygel.MusicItem : Rygel.AudioItem {
@@ -205,6 +206,8 @@ public class Rygel.MediaContainer : Rygel.MediaObject {
     public int child_count;
     public string sort_criteria = "+dc:title";
     public static const string ANY = "DLNA.ORG_AnyContainer";
+    public static const string STORAGE_FOLDER =
+        "object.container.storageFolder";
     public uint update_id;
 
     // mockable elements
@@ -222,6 +225,14 @@ public class Rygel.MediaContainer : Rygel.MediaObject {
     public signal void container_updated (MediaContainer container);
 }
 
+public class Rygel.BaseMediaContainer : Rygel.MediaContainer {
+     public BaseMediaContainer (string          id,
+                           MediaContainer? parent,
+                           string          title,
+                           int             child_count) {
+     }
+}
+
 public class Rygel.MediaObjects : Gee.ArrayList<MediaObject> {
 }
 
@@ -237,6 +248,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 {
@@ -426,13 +440,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);


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