core: Implement 'CreateObject' action of CDS
authorZeeshan Ali (Khattak) <zeeshanak@gnome.org>
Tue, 26 Jan 2010 15:39:56 +0000 (17:39 +0200)
committerZeeshan Ali (Khattak) <zeeshanak@gnome.org>
Wed, 3 Feb 2010 13:38:48 +0000 (15:38 +0200)
Currently we can only deal with 'no URI provided' case, i-e upload but OTOH
actual uploading isn't implemented yet. :)

data/xml/ContentDirectory.xml
src/rygel/Makefile.am
src/rygel/rygel-content-directory.vala
src/rygel/rygel-item-creator.vala [new file with mode: 0644]
src/rygel/rygel-media-container.vala

index c687d0d..a129d78 100644 (file)
@@ -328,19 +328,6 @@ feature provided by your editor.
                        </argumentList>
                </action>
 
-<!-- Optional actions that are not implemented yet
-                <action>
-                       <Optional/>
-                       <name>GetSortExtensionCapabilities</name>
-                       <argumentList>
-                               <argument>
-                                       <name>SortExtensionCaps</name>
-                                       <direction>out</direction>
-                                       <relatedStateVariable>SortExtensionCapabilities</relatedStateVariable>
-                               </argument>
-                       </argumentList>
-               </action>
-
                <action>
                        <Optional/>
                        <name>CreateObject</name>
@@ -368,6 +355,19 @@ feature provided by your editor.
                        </argumentList>
                </action>
 
+<!-- Optional actions that are not implemented yet
+                <action>
+                       <Optional/>
+                       <name>GetSortExtensionCapabilities</name>
+                       <argumentList>
+                               <argument>
+                                       <name>SortExtensionCaps</name>
+                                       <direction>out</direction>
+                                       <relatedStateVariable>SortExtensionCapabilities</relatedStateVariable>
+                               </argument>
+                       </argumentList>
+               </action>
+
                <action>
                        <Optional/>
                        <name>DestroyObject</name>
index e83b227..2f72214 100644 (file)
@@ -75,6 +75,7 @@ VAPI_SOURCE_FILES = rygel-configuration.vala \
                    rygel-thumbnailer.vala \
                    rygel-browse.vala \
                    rygel-search.vala \
+                   rygel-item-creator.vala \
                    rygel-search-expression.vala \
                    rygel-relational-expression.vala \
                    rygel-logical-expression.vala \
index 19a8da3..8615d35 100644 (file)
@@ -30,6 +30,7 @@ using Gee;
  */
 public errordomain Rygel.ContentDirectoryError {
     NO_SUCH_OBJECT = 701,
+    RESTRICTED_PARENT = 713,
     CANT_PROCESS = 720,
     INVALID_ARGS = 402
 }
@@ -97,6 +98,7 @@ public class Rygel.ContentDirectory: Service {
 
         this.action_invoked["Browse"] += this.browse_cb;
         this.action_invoked["Search"] += this.search_cb;
+        this.action_invoked["CreateObject"] += this.create_object_cb;
 
         /* Connect SystemUpdateID related signals */
         this.action_invoked["GetSystemUpdateID"] +=
@@ -145,6 +147,14 @@ public class Rygel.ContentDirectory: Service {
         search.run.begin ();
     }
 
+    /* CreateObject action implementation */
+    private virtual void create_object_cb (ContentDirectory    content_dir,
+                                           owned ServiceAction action) {
+        var creator = new ItemCreator (this, action);
+
+        creator.run.begin ();
+    }
+
     /* GetSystemUpdateID action implementation */
     private void get_system_update_id_cb (ContentDirectory    content_dir,
                                           owned ServiceAction action) {
diff --git a/src/rygel/rygel-item-creator.vala b/src/rygel/rygel-item-creator.vala
new file mode 100644 (file)
index 0000000..a9e00ba
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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 GUPnP;
+
+/**
+ * CreateObject action implementation.
+ */
+internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
+    // In arguments
+    public string container_id;
+    public string elements;
+
+    public DIDLLiteItem didl_item;
+    public MediaItem item;
+
+    private ContentDirectory content_dir;
+    private ServiceAction action;
+    private Rygel.DIDLLiteWriter didl_writer;
+    private DIDLLiteParser didl_parser;
+
+    public Cancellable cancellable { get; set; }
+
+    public ItemCreator (ContentDirectory    content_dir,
+                        owned ServiceAction action) {
+        this.content_dir = content_dir;
+        this.cancellable = content_dir.cancellable;
+        this.action = (owned) action;
+        this.didl_writer = new Rygel.DIDLLiteWriter (content_dir.http_server);
+        this.didl_parser = new DIDLLiteParser ();
+    }
+
+    public async void run () {
+        try {
+            this.parse_args ();
+
+            var container = yield this.fetch_container ();
+
+            this.didl_parser.item_available.connect ((didl_item) => {
+                    this.didl_item = didl_item;
+            });
+            this.didl_parser.parse_didl (this.elements);
+
+            this.item = new MediaItem (didl_item.id,
+                                       container,
+                                       didl_item.title,
+                                       didl_item.upnp_class);
+            this.item.mime_type = this.get_generic_mime_type ();
+            this.item.place_holder = true;
+
+            yield container.add_item (this.item, this.cancellable);
+            this.didl_writer.serialize (this.item);
+
+            // Conclude the successful action
+            this.conclude ();
+        } catch (Error err) {
+            this.handle_error (err);
+        }
+    }
+
+    private async void parse_args () throws Error {
+        /* Start by parsing the 'in' arguments */
+        this.action.get ("ContainerID", typeof (string), out this.container_id,
+                         "Elements", typeof (string), out this.elements);
+
+        if (this.container_id == null || this.elements == null) {
+            // Sorry we can't do anything without ContainerID
+            throw new ContentDirectoryError.NO_SUCH_OBJECT ("No such object");
+        }
+    }
+
+    private async MediaContainer fetch_container () throws Error {
+        var media_object = yield this.content_dir.root_container.find_object (
+                                        this.container_id,
+                                        this.cancellable);
+        if (media_object == null || !(media_object is MediaContainer)) {
+            throw new ContentDirectoryError.NO_SUCH_OBJECT ("No such object");
+        }
+
+        return media_object as MediaContainer;
+    }
+
+    private void conclude () {
+        /* Retrieve generated string */
+        string didl = this.didl_writer.get_string ();
+
+        /* Set action return arguments */
+        this.action.set ("Result", typeof (string), didl,
+                         "ObjectID", typeof (string), this.item.id);
+
+        this.action.return ();
+        this.completed ();
+    }
+
+    private void handle_error (Error error) {
+        if (error is ContentDirectoryError) {
+            this.action.return_error (error.code, error.message);
+        } else {
+            this.action.return_error (701, error.message);
+        }
+
+        warning ("Failed to create '%s': %s", this.item.id, error.message);
+
+        this.completed ();
+    }
+
+    private string get_generic_mime_type () {
+        switch (this.item.upnp_class) {
+            case MediaItem.IMAGE_CLASS:
+                return "image";
+            case MediaItem.VIDEO_CLASS:
+                return "video";
+            case MediaItem.AUDIO_CLASS:
+            default:
+                return "audio";
+        }
+    }
+}
+
index 8ce6519..0dd0677 100644 (file)
@@ -159,6 +159,37 @@ public abstract class Rygel.MediaContainer : MediaObject {
     }
 
     /**
+     * Add a new item directly under this container.
+     *
+     * @param didl_item The item to add to this container.
+     *
+     * return nothing.
+     *
+     * This implementation is very basic: It only creates the file under the
+     * first writable URI it can find for this container & sets the ID of the
+     * item to that of the URI of the newly created item. If your subclass
+     * doesn't ID the items by their original URIs, you definitely want to
+     * override this method.
+     */
+    public async virtual void add_item (MediaItem    item,
+                                        Cancellable? cancellable)
+                                        throws Error {
+        var dir = yield this.get_writable (cancellable);
+        if (dir == null) {
+           throw new ContentDirectoryError.RESTRICTED_PARENT (
+                                        "Object creation in %s no allowed",
+                                        this.id);
+        }
+
+        var file = dir.get_child_for_display_name (item.title);
+        yield file.create_async (FileCreateFlags.NONE,
+                                 Priority.DEFAULT,
+                                 cancellable);
+        var uri = file.get_uri ();
+        item.id = uri;
+    }
+
+    /**
      * Method to be be called each time this container is updated (metadata
      * changes for this container, items under it gets removed/added or their
      * metadata changes etc).