[libgdata] youtube: Implement GDataCommentable on GDataYouTubeVideo



commit bbb3d20ee62a740ef98a4117003c0364e7b06b26
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sat Jul 9 15:10:31 2011 +0100

    youtube: Implement GDataCommentable on GDataYouTubeVideo
    
    This adds support for comments to GDataYouTubeVideo, including a GDataComment
    subclass, GDataYouTubeComment, and test cases.
    
    This adds the following API:
     â GDataYouTubeComment
    
    Helps: bgo#598752

 Makefile.am                                    |    6 +-
 docs/reference/gdata-docs.xml                  |    1 +
 docs/reference/gdata-sections.txt              |   20 +
 gdata/gdata.h                                  |    1 +
 gdata/gdata.symbols                            |    4 +
 gdata/services/youtube/gdata-youtube-comment.c |  207 +++++++++++
 gdata/services/youtube/gdata-youtube-comment.h |   72 ++++
 gdata/services/youtube/gdata-youtube-video.c   |   88 +++++-
 gdata/tests/youtube.c                          |  445 ++++++++++++++++++++++++
 9 files changed, 841 insertions(+), 3 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index ae425c8..70f98db 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -277,7 +277,8 @@ gdata_youtube_headers = \
 	gdata/services/youtube/gdata-youtube-credit.h	\
 	gdata/services/youtube/gdata-youtube-query.h	\
 	gdata/services/youtube/gdata-youtube-state.h	\
-	gdata/services/youtube/gdata-youtube-category.h
+	gdata/services/youtube/gdata-youtube-category.h	\
+	gdata/services/youtube/gdata-youtube-comment.h
 private_headers += \
 	gdata/services/youtube/gdata-youtube-group.h	\
 	gdata/services/youtube/gdata-youtube-control.h
@@ -382,7 +383,8 @@ gdata_sources = \
 	gdata/services/youtube/gdata-youtube-group.c		\
 	gdata/services/youtube/gdata-youtube-state.c		\
 	gdata/services/youtube/gdata-youtube-control.c		\
-	gdata/services/youtube/gdata-youtube-category.c
+	gdata/services/youtube/gdata-youtube-category.c		\
+	gdata/services/youtube/gdata-youtube-comment.c
 
 main_header = gdata/gdata.h
 public_headers = \
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 2cdcf0d..2a68183 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -127,6 +127,7 @@
 			<xi:include href="xml/gdata-youtube-service.xml"/>
 			<xi:include href="xml/gdata-youtube-query.xml"/>
 			<xi:include href="xml/gdata-youtube-video.xml"/>
+			<xi:include href="xml/gdata-youtube-comment.xml"/>
 		</chapter>
 
 		<chapter>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index fda5a7b..946fdd2 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -2305,3 +2305,23 @@ gdata_comment_get_type
 <SUBSECTION Private>
 GDataCommentPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-youtube-comment</FILE>
+<TITLE>GDataYouTubeComment</TITLE>
+GDataYouTubeComment
+GDataYouTubeCommentClass
+gdata_youtube_comment_new
+gdata_youtube_comment_get_parent_comment_uri
+gdata_youtube_comment_set_parent_comment_uri
+<SUBSECTION Standard>
+GDATA_TYPE_YOUTUBE_COMMENT
+GDATA_YOUTUBE_COMMENT
+GDATA_YOUTUBE_COMMENT_CLASS
+GDATA_IS_YOUTUBE_COMMENT
+GDATA_IS_YOUTUBE_COMMENT_CLASS
+GDATA_YOUTUBE_COMMENT_GET_CLASS
+gdata_youtube_comment_get_type
+<SUBSECTION Private>
+GDataYouTubeCommentPrivate
+</SECTION>
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 4139d20..12d63bd 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -92,6 +92,7 @@
 #include <gdata/services/youtube/gdata-youtube-state.h>
 #include <gdata/services/youtube/gdata-youtube-enums.h>
 #include <gdata/services/youtube/gdata-youtube-category.h>
+#include <gdata/services/youtube/gdata-youtube-comment.h>
 
 /* Google Calendar */
 #include <gdata/services/calendar/gdata-calendar-service.h>
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index c84a339..1ee3b34 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -923,3 +923,7 @@ gdata_commentable_delete_comment
 gdata_commentable_delete_comment_async
 gdata_commentable_delete_comment_finish
 gdata_comment_get_type
+gdata_youtube_comment_get_type
+gdata_youtube_comment_new
+gdata_youtube_comment_get_parent_comment_uri
+gdata_youtube_comment_set_parent_comment_uri
diff --git a/gdata/services/youtube/gdata-youtube-comment.c b/gdata/services/youtube/gdata-youtube-comment.c
new file mode 100644
index 0000000..1cd9cee
--- /dev/null
+++ b/gdata/services/youtube/gdata-youtube-comment.c
@@ -0,0 +1,207 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client 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.1 of the License, or (at your option) any later version.
+ *
+ * GData Client 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 GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-youtube-comment
+ * @short_description: GData YouTube comment object
+ * @stability: Unstable
+ * @include: gdata/services/youtube/gdata-youtube-comment.h
+ *
+ * #GDataYouTubeComment is a subclass of #GDataComment to represent a comment on a #GDataYouTubeVideo. It is returned by the #GDataCommentable
+ * interface implementation on #GDataYouTubeVideo.
+ *
+ * It's possible to query for and add #GDataYouTubeComment<!-- -->s, but it is not possible to delete #GDataYouTubeComment<!-- -->s from any video
+ * using the GData API.
+ *
+ * Comments on YouTube videos can be arranged in a hierarchy by their #GDataYouTubeComment:parent-comment-uri<!-- -->s. If a
+ * #GDataYouTubeComment<!-- -->'s parent comment URI is non-%NULL, it should match the %GDATA_LINK_SELF #GDataLink of another #GDataYouTubeComment on
+ * the same video (as retrieved using gdata_entry_look_up_link() on the comments). Comments with #GDataYouTubeComment:parent-comment-uri set to %NULL
+ * are top-level comments.
+ *
+ * Since: 0.9.2
+ */
+
+#include <config.h>
+#include <glib.h>
+
+#include "gdata-youtube-comment.h"
+
+#define GDATA_LINK_PARENT_COMMENT_URI "http://gdata.youtube.com/schemas/2007#in-reply-to";
+
+static void gdata_youtube_comment_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void gdata_youtube_comment_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+
+enum {
+	PROP_PARENT_COMMENT_URI = 1,
+};
+
+G_DEFINE_TYPE (GDataYouTubeComment, gdata_youtube_comment, GDATA_TYPE_COMMENT)
+
+static void
+gdata_youtube_comment_class_init (GDataYouTubeCommentClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
+
+	gobject_class->get_property = gdata_youtube_comment_get_property;
+	gobject_class->set_property = gdata_youtube_comment_set_property;
+
+	entry_class->kind_term = "http://gdata.youtube.com/schemas/2007#comment";;
+
+	/**
+	 * GDataYouTubeComment:parent-comment-uri:
+	 *
+	 * The URI of the parent comment to this one, or %NULL if this comment is a top-level comment.
+	 *
+	 * See the documentation for #GDataYouTubeComment for an explanation of the semantics of parent comment URIs.
+	 *
+	 * Since: 0.9.2.
+	 */
+	g_object_class_install_property (gobject_class, PROP_PARENT_COMMENT_URI,
+	                                 g_param_spec_string ("parent-comment-uri",
+	                                                      "Parent comment URI", "The URI of the parent comment to this one.",
+	                                                      NULL,
+	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_youtube_comment_init (GDataYouTubeComment *self)
+{
+	/* Nothing to see here. */
+}
+
+static void
+gdata_youtube_comment_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataYouTubeComment *self = GDATA_YOUTUBE_COMMENT (object);
+
+	switch (property_id) {
+		case PROP_PARENT_COMMENT_URI:
+			g_value_set_string (value, gdata_youtube_comment_get_parent_comment_uri (self));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+gdata_youtube_comment_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataYouTubeComment *self = GDATA_YOUTUBE_COMMENT (object);
+
+	switch (property_id) {
+		case PROP_PARENT_COMMENT_URI:
+			gdata_youtube_comment_set_parent_comment_uri (self, g_value_get_string (value));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+/**
+ * gdata_youtube_comment_new:
+ * @id: the comment's ID, or %NULL
+ *
+ * Creates a new #GDataYouTubeComment with the given ID and default properties.
+ *
+ * Return value: a new #GDataYouTubeComment; unref with g_object_unref()
+ *
+ * Since: 0.9.2
+ */
+GDataYouTubeComment *
+gdata_youtube_comment_new (const gchar *id)
+{
+	return GDATA_YOUTUBE_COMMENT (g_object_new (GDATA_TYPE_YOUTUBE_COMMENT,
+	                                            "id", id,
+	                                            NULL));
+}
+
+/**
+ * gdata_youtube_comment_get_parent_comment_uri:
+ * @self: a #GDataYouTubeComment
+ *
+ * Gets the #GDataYouTubeComment:parent-comment-uri property.
+ *
+ * Return value: the parent comment URI, or %NULL
+ *
+ * Since: 0.9.2
+ */
+const gchar *
+gdata_youtube_comment_get_parent_comment_uri (GDataYouTubeComment *self)
+{
+	GDataLink *link_;
+
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_COMMENT (self), NULL);
+
+	link_ = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_PARENT_COMMENT_URI);
+	if (link_ == NULL) {
+		return NULL;
+	}
+
+	return gdata_link_get_uri (link_);
+}
+
+/**
+ * gdata_youtube_comment_set_parent_comment_uri:
+ * @self: a #GDataYouTubeComment
+ * @parent_comment_uri: a new parent comment URI, or %NULL
+ *
+ * Sets the #GDataYouTubeComment:parent-comment-uri property to @parent_comment_uri.
+ *
+ * Set @parent_comment_uri to %NULL to unset the #GDataYouTubeComment:parent-comment-uri property in the comment (i.e. make the comment a top-level
+ * comment).
+ *
+ * See the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol_comments.html#Retrieve_comments";>online
+ * documentation</ulink> for more information.
+ *
+ * Since: 0.9.2
+ */
+void
+gdata_youtube_comment_set_parent_comment_uri (GDataYouTubeComment *self, const gchar *parent_comment_uri)
+{
+	GDataLink *link_;
+
+	g_return_if_fail (GDATA_IS_YOUTUBE_COMMENT (self));
+	g_return_if_fail (parent_comment_uri == NULL || *parent_comment_uri != '\0');
+
+	link_ = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_PARENT_COMMENT_URI);
+
+	if ((link_ == NULL && parent_comment_uri == NULL) ||
+	    (link_ != NULL && parent_comment_uri != NULL && g_strcmp0 (gdata_link_get_uri (link_), parent_comment_uri) == 0)) {
+		/* Nothing to do. */
+		return;
+	} else if (link_ == NULL && parent_comment_uri != NULL) {
+		/* Add the new link. */
+		link_ = gdata_link_new (parent_comment_uri, GDATA_LINK_PARENT_COMMENT_URI);
+		gdata_entry_add_link (GDATA_ENTRY (self), link_);
+		g_object_unref (link_);
+	} else if (link_ != NULL && parent_comment_uri == NULL) {
+		/* Remove the old link. */
+		gdata_entry_remove_link (GDATA_ENTRY (self), link_);
+	} else if (link_ != NULL && parent_comment_uri != NULL) {
+		/* Update the existing link. */
+		gdata_link_set_uri (link_, parent_comment_uri);
+	}
+
+	g_object_notify (G_OBJECT (self), "parent-comment-uri");
+}
diff --git a/gdata/services/youtube/gdata-youtube-comment.h b/gdata/services/youtube/gdata-youtube-comment.h
new file mode 100644
index 0000000..5b64f80
--- /dev/null
+++ b/gdata/services/youtube/gdata-youtube-comment.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client 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.1 of the License, or (at your option) any later version.
+ *
+ * GData Client 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 GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_YOUTUBE_COMMENT_H
+#define GDATA_YOUTUBE_COMMENT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-comment.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_YOUTUBE_COMMENT		(gdata_youtube_comment_get_type ())
+#define GDATA_YOUTUBE_COMMENT(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_YOUTUBE_COMMENT, GDataYouTubeComment))
+#define GDATA_YOUTUBE_COMMENT_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_YOUTUBE_COMMENT, GDataYouTubeCommentClass))
+#define GDATA_IS_YOUTUBE_COMMENT(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_YOUTUBE_COMMENT))
+#define GDATA_IS_YOUTUBE_COMMENT_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_COMMENT))
+#define GDATA_YOUTUBE_COMMENT_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_COMMENT, GDataYouTubeCommentClass))
+
+typedef struct _GDataYouTubeCommentPrivate	GDataYouTubeCommentPrivate;
+
+/**
+ * GDataYouTubeComment:
+ *
+ * All the fields in the #GDataYouTubeComment structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.2
+ */
+typedef struct {
+	GDataComment parent;
+	GDataYouTubeCommentPrivate *priv;
+} GDataYouTubeComment;
+
+/**
+ * GDataYouTubeCommentClass:
+ *
+ * All the fields in the #GDataYouTubeCommentClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.2
+ */
+typedef struct {
+	/*< private >*/
+	GDataCommentClass parent;
+} GDataYouTubeCommentClass;
+
+GType gdata_youtube_comment_get_type (void) G_GNUC_CONST;
+
+GDataYouTubeComment *gdata_youtube_comment_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+const gchar *gdata_youtube_comment_get_parent_comment_uri (GDataYouTubeComment *self) G_GNUC_PURE;
+void gdata_youtube_comment_set_parent_comment_uri (GDataYouTubeComment *self, const gchar *parent_comment_uri);
+
+G_END_DECLS
+
+#endif /* !GDATA_YOUTUBE_COMMENT_H */
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index 524bc4f..c56a9ea 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -25,6 +25,10 @@
  *
  * #GDataYouTubeVideo is a subclass of #GDataEntry to represent a single video on YouTube, either when uploading or querying.
  *
+ * #GDataYouTubeVideo implements #GDataCommentable, allowing comments on videos to be queried using gdata_commentable_query_comments(), and new
+ * comments to be added to videos using gdata_commentable_insert_comment(). Note that deletion of comments on any #GDataYouTubeVideo is not permitted;
+ * gdata_commentable_delete_comment() will always fail if called on a #GDataYouTubeVideo.
+ *
  * For more details of YouTube's GData API, see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html";>
  * online documentation</ulink>.
  *
@@ -79,6 +83,10 @@
 #include "gdata-youtube-control.h"
 #include "gdata-youtube-enums.h"
 #include "georss/gdata-georss-where.h"
+#include "gd/gdata-gd-feed-link.h"
+#include "gdata-commentable.h"
+#include "gdata-youtube-comment.h"
+#include "gdata-youtube-service.h"
 
 static GObject *gdata_youtube_video_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
 static void gdata_youtube_video_dispose (GObject *object);
@@ -90,6 +98,11 @@ static gboolean post_parse_xml (GDataParsable *parsable, gpointer user_data, GEr
 static void get_xml (GDataParsable *parsable, GString *xml_string);
 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+static void gdata_youtube_video_commentable_init (GDataCommentableInterface *iface);
+static GDataAuthorizationDomain *get_authorization_domain (GDataCommentable *self) G_GNUC_CONST;
+static gchar *get_query_comments_uri (GDataCommentable *self) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+static gchar *get_insert_comment_uri (GDataCommentable *self, GDataComment *comment_) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+static gboolean is_comment_deletable (GDataCommentable *self, GDataComment *comment_);
 
 struct _GDataYouTubeVideoPrivate {
 	guint view_count;
@@ -114,6 +127,9 @@ struct _GDataYouTubeVideoPrivate {
 	/* Other properties */
 	GDataYouTubeControl *youtube_control;
 	gint64 recorded;
+
+	/* Comments */
+	GDataGDFeedLink *comments_feed_link;
 };
 
 enum {
@@ -141,7 +157,8 @@ enum {
 	PROP_LONGITUDE
 };
 
-G_DEFINE_TYPE (GDataYouTubeVideo, gdata_youtube_video, GDATA_TYPE_ENTRY)
+G_DEFINE_TYPE_WITH_CODE (GDataYouTubeVideo, gdata_youtube_video, GDATA_TYPE_ENTRY,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_COMMENTABLE, gdata_youtube_video_commentable_init))
 
 static void
 gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
@@ -488,6 +505,16 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
 }
 
 static void
+gdata_youtube_video_commentable_init (GDataCommentableInterface *iface)
+{
+	iface->comment_type = GDATA_TYPE_YOUTUBE_COMMENT;
+	iface->get_authorization_domain = get_authorization_domain;
+	iface->get_query_comments_uri = get_query_comments_uri;
+	iface->get_insert_comment_uri = get_insert_comment_uri;
+	iface->is_comment_deletable = is_comment_deletable;
+}
+
+static void
 notify_title_cb (GDataYouTubeVideo *self, GParamSpec *pspec, gpointer user_data)
 {
 	/* Update our media:group title */
@@ -544,6 +571,10 @@ gdata_youtube_video_dispose (GObject *object)
 		g_object_unref (priv->youtube_control);
 	priv->youtube_control = NULL;
 
+	if (priv->comments_feed_link != NULL)
+		g_object_unref (priv->comments_feed_link);
+	priv->comments_feed_link = NULL;
+
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_youtube_video_parent_class)->dispose (object);
 }
@@ -806,6 +837,20 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 			self->priv->rating.max = strtoul ((gchar*) max, NULL, 10);
 			self->priv->rating.count = num_raters_uint;
 			self->priv->rating.average = average_double;
+		} else if (xmlStrcmp (node->name, (xmlChar*) "comments") == 0) {
+			/* gd:comments */
+			xmlNode *child_node;
+
+			/* This is actually the child of the <comments> element */
+			child_node = node->children;
+			if (child_node == NULL) {
+				return gdata_parser_error_required_element_missing ("gd:feedLink", "gd:comments", error);
+			}
+
+			if (gdata_parser_object_from_element (child_node, "feedLink", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_GD_FEED_LINK,
+			                                      &(self->priv->comments_feed_link), &success, error) == TRUE) {
+				return success;
+			}
 		} else {
 			return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error);
 		}
@@ -930,6 +975,47 @@ get_entry_uri (const gchar *id)
 	return uri;
 }
 
+static GDataAuthorizationDomain *
+get_authorization_domain (GDataCommentable *self)
+{
+	return gdata_youtube_service_get_primary_authorization_domain ();
+}
+
+static gchar *
+get_query_comments_uri (GDataCommentable *self)
+{
+	GDataGDFeedLink *feed_link;
+
+	feed_link = GDATA_YOUTUBE_VIDEO (self)->priv->comments_feed_link;
+
+	if (feed_link == NULL) {
+		return NULL;
+	}
+
+	return g_strdup (gdata_gd_feed_link_get_uri (feed_link));
+}
+
+static gchar *
+get_insert_comment_uri (GDataCommentable *self, GDataComment *comment_)
+{
+	GDataGDFeedLink *feed_link;
+
+	feed_link = GDATA_YOUTUBE_VIDEO (self)->priv->comments_feed_link;
+
+	if (feed_link == NULL) {
+		return NULL;
+	}
+
+	return g_strdup (gdata_gd_feed_link_get_uri (feed_link));
+}
+
+static gboolean
+is_comment_deletable (GDataCommentable *self, GDataComment *comment_)
+{
+	/* Deletion of comments is unsupported. */
+	return FALSE;
+}
+
 /**
  * gdata_youtube_video_new:
  * @id: (allow-none): the video's ID, or %NULL
diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c
index 1315755..e9e0ae2 100644
--- a/gdata/tests/youtube.c
+++ b/gdata/tests/youtube.c
@@ -1057,6 +1057,81 @@ test_video_escaping (void)
 }
 
 static void
+test_comment_get_xml (void)
+{
+	GDataYouTubeComment *comment_;
+
+	comment_ = gdata_youtube_comment_new (NULL);
+	gdata_entry_set_content (GDATA_ENTRY (comment_), "This is a comment with <markup> & stÃff.");
+	gdata_youtube_comment_set_parent_comment_uri (comment_, "http://example.com/?foo=bar&baz=shizzle";);
+
+	/* Check the outputted XML is OK */
+	gdata_test_assert_xml (comment_,
+		"<?xml version='1.0' encoding='UTF-8'?>"
+		"<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>"
+			"<title type='text'></title>"
+			"<content type='text'>This is a comment with &lt;markup&gt; &amp; stÃff.</content>"
+			"<category term='http://gdata.youtube.com/schemas/2007#comment' scheme='http://schemas.google.com/g/2005#kind'/>"
+			"<link href='http://example.com/?foo=bar&amp;baz=shizzle' rel='http://gdata.youtube.com/schemas/2007#in-reply-to'/>"
+		"</entry>");
+
+	g_object_unref (comment_);
+}
+
+static void
+notify_cb (GDataYouTubeComment *comment_, GParamSpec *pspec, guint *notification_count)
+{
+	*notification_count = *notification_count + 1;
+}
+
+static void
+test_comment_properties_parent_comment_uri (void)
+{
+	GDataYouTubeComment *comment_;
+	guint notification_count = 0;
+	gchar *parent_comment_uri;
+
+	comment_ = gdata_youtube_comment_new (NULL);
+	g_signal_connect (comment_, "notify::parent-comment-uri", (GCallback) notify_cb, &notification_count);
+
+	/* Default. */
+	g_assert (gdata_youtube_comment_get_parent_comment_uri (comment_) == NULL);
+
+	/* Set the property. */
+	gdata_youtube_comment_set_parent_comment_uri (comment_, "foo");
+	g_assert_cmpuint (notification_count, ==, 1);
+
+	g_assert_cmpstr (gdata_youtube_comment_get_parent_comment_uri (comment_), ==, "foo");
+
+	/* Get the property a different way. */
+	g_object_get (G_OBJECT (comment_),
+	              "parent-comment-uri", &parent_comment_uri,
+	              NULL);
+
+	g_assert_cmpstr (parent_comment_uri, ==, "foo");
+
+	g_free (parent_comment_uri);
+
+	/* Set the property a different way. */
+	g_object_set (G_OBJECT (comment_),
+	              "parent-comment-uri", "bar",
+	              NULL);
+	g_assert_cmpuint (notification_count, ==, 2);
+
+	/* Set the property to the same value. */
+	gdata_youtube_comment_set_parent_comment_uri (comment_, "bar");
+	g_assert_cmpuint (notification_count, ==, 2);
+
+	/* Set the property back to NULL. */
+	gdata_youtube_comment_set_parent_comment_uri (comment_, NULL);
+	g_assert_cmpuint (notification_count, ==, 3);
+
+	g_assert (gdata_youtube_comment_get_parent_comment_uri (comment_) == NULL);
+
+	g_object_unref (comment_);
+}
+
+static void
 test_query_uri (void)
 {
 	gdouble latitude, longitude, radius;
@@ -1215,6 +1290,353 @@ test_query_single_async (gconstpointer service)
 	g_main_loop_unref (main_loop);
 }
 
+typedef struct {
+	GDataYouTubeVideo *video;
+} CommentData;
+
+static void
+set_up_comment (CommentData *data, gconstpointer service)
+{
+	/* Get a video known to have comments on it. */
+	data->video = GDATA_YOUTUBE_VIDEO (gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                                                     gdata_youtube_service_get_primary_authorization_domain (),
+	                                                                     "tag:youtube.com,2008:video:RzR2k8yo4NY", NULL,
+	                                                                     GDATA_TYPE_YOUTUBE_VIDEO, NULL, NULL));
+	g_assert (GDATA_IS_YOUTUBE_VIDEO (data->video));
+}
+
+static void
+tear_down_comment (CommentData *data, gconstpointer service)
+{
+	g_object_unref (data->video);
+}
+
+static void
+assert_comments_feed (GDataFeed *comments_feed)
+{
+	GList *comments;
+
+	g_assert (GDATA_IS_FEED (comments_feed));
+
+	for (comments = gdata_feed_get_entries (comments_feed); comments != NULL; comments = comments->next) {
+		GList *authors;
+		GDataYouTubeComment *comment_ = GDATA_YOUTUBE_COMMENT (comments->data);
+
+		/* We can't do much more than this, since we can't reasonably add test comments to public videos, and can't upload a new video
+		 * for each test since it has to go through moderation. */
+		g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (comment_)), !=, NULL);
+		g_assert_cmpstr (gdata_entry_get_content (GDATA_ENTRY (comment_)), !=, NULL);
+
+		g_assert_cmpuint (g_list_length (gdata_entry_get_authors (GDATA_ENTRY (comment_))), >, 0);
+
+		for (authors = gdata_entry_get_authors (GDATA_ENTRY (comment_)); authors != NULL; authors = authors->next) {
+			GDataAuthor *author = GDATA_AUTHOR (authors->data);
+
+			/* Again, we can't test these much. */
+			g_assert_cmpstr (gdata_author_get_name (author), !=, NULL);
+			g_assert_cmpstr (gdata_author_get_uri (author), !=, NULL);
+		}
+	}
+}
+
+static void
+test_comment_query (CommentData *data, gconstpointer service)
+{
+	GDataFeed *comments_feed;
+	GError *error = NULL;
+
+	/* Get the comments feed for the video */
+	comments_feed = gdata_commentable_query_comments (GDATA_COMMENTABLE (data->video), GDATA_SERVICE (service), NULL, NULL, NULL, NULL, &error);
+	g_assert_no_error (error);
+	g_clear_error (&error);
+
+	assert_comments_feed (comments_feed);
+
+	g_object_unref (comments_feed);
+}
+
+typedef struct {
+	CommentData parent;
+	GMainLoop *main_loop;
+} CommentAsyncData;
+
+static void
+set_up_comment_async (CommentAsyncData *data, gconstpointer service)
+{
+	set_up_comment ((CommentData*) data, service);
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_comment_async (CommentAsyncData *data, gconstpointer service)
+{
+	g_main_loop_unref (data->main_loop);
+	tear_down_comment ((CommentData*) data, service);
+}
+
+static void
+test_comment_query_async_cb (GDataCommentable *commentable, GAsyncResult *result, CommentAsyncData *data)
+{
+	GDataFeed *comments_feed;
+	GError *error = NULL;
+
+	comments_feed = gdata_commentable_query_comments_finish (commentable, result, &error);
+	g_assert_no_error (error);
+	g_clear_error (&error);
+
+	assert_comments_feed (comments_feed);
+
+	g_object_unref (comments_feed);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+static void
+test_comment_query_async (CommentAsyncData *data, gconstpointer service)
+{
+	gdata_commentable_query_comments_async (GDATA_COMMENTABLE (data->parent.video), GDATA_SERVICE (service), NULL, NULL, NULL, NULL, NULL,
+	                                        (GAsyncReadyCallback) test_comment_query_async_cb, data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_comment_query_async_cancellation_cb (GDataCommentable *commentable, GAsyncResult *result, CommentAsyncData *data)
+{
+	GDataFeed *comments_feed;
+	GError *error = NULL;
+
+	comments_feed = gdata_commentable_query_comments_finish (commentable, result, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_assert (comments_feed == NULL);
+	g_clear_error (&error);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+static void
+test_comment_query_async_cancellation (CommentAsyncData *data, gconstpointer service)
+{
+	GCancellable *cancellable;
+
+	cancellable = g_cancellable_new ();
+	g_cancellable_cancel (cancellable);
+
+	gdata_commentable_query_comments_async (GDATA_COMMENTABLE (data->parent.video), GDATA_SERVICE (service), NULL, cancellable, NULL, NULL, NULL,
+	                                        (GAsyncReadyCallback) test_comment_query_async_cancellation_cb, data);
+
+	g_main_loop_run (data->main_loop);
+
+	g_object_unref (cancellable);
+}
+
+/* Test that the progress callbacks from gdata_commentable_query_comments_async() are called correctly.
+ * We take a CommentAsyncData so that we can guarantee the video exists, but we don't use it much as we don't actually care about the specific
+ * video. */
+static void
+test_comment_query_async_progress_closure (CommentAsyncData *query_data, gconstpointer service)
+{
+	GDataAsyncProgressClosure *data = g_slice_new0 (GDataAsyncProgressClosure);
+
+	data->main_loop = g_main_loop_new (NULL, TRUE);
+
+	gdata_commentable_query_comments_async (GDATA_COMMENTABLE (query_data->parent.video), GDATA_SERVICE (service), NULL, NULL,
+	                                        (GDataQueryProgressCallback) gdata_test_async_progress_callback,
+	                                        data, (GDestroyNotify) gdata_test_async_progress_closure_free,
+	                                        (GAsyncReadyCallback) gdata_test_async_progress_finish_callback, data);
+
+	g_main_loop_run (data->main_loop);
+	g_main_loop_unref (data->main_loop);
+
+	/* Check that both callbacks were called exactly once */
+	g_assert_cmpuint (data->progress_destroy_notify_count, ==, 1);
+	g_assert_cmpuint (data->async_ready_notify_count, ==, 1);
+
+	g_slice_free (GDataAsyncProgressClosure, data);
+}
+
+typedef struct {
+	CommentData parent;
+	GDataYouTubeComment *comment;
+} InsertCommentData;
+
+static void
+set_up_insert_comment (InsertCommentData *data, gconstpointer service)
+{
+	set_up_comment ((CommentData*) data, service);
+
+	/* Create a test comment to be inserted. */
+	data->comment = gdata_youtube_comment_new (NULL);
+	g_assert (GDATA_IS_YOUTUBE_COMMENT (data->comment));
+
+	gdata_entry_set_content (GDATA_ENTRY (data->comment), "This is a test comment.");
+}
+
+static void
+tear_down_insert_comment (InsertCommentData *data, gconstpointer service)
+{
+	if (data->comment != NULL) {
+		g_object_unref (data->comment);
+	}
+
+	tear_down_comment ((CommentData*) data, service);
+}
+
+static void
+assert_comments_equal (GDataComment *new_comment, GDataYouTubeComment *original_comment)
+{
+	GList *authors;
+	GDataAuthor *author;
+
+	g_assert (GDATA_IS_YOUTUBE_COMMENT (new_comment));
+	g_assert (GDATA_IS_YOUTUBE_COMMENT (original_comment));
+	g_assert (GDATA_YOUTUBE_COMMENT (new_comment) != original_comment);
+
+	g_assert_cmpstr (gdata_entry_get_content (GDATA_ENTRY (new_comment)), ==, gdata_entry_get_content (GDATA_ENTRY (original_comment)));
+	g_assert_cmpstr (gdata_youtube_comment_get_parent_comment_uri (GDATA_YOUTUBE_COMMENT (new_comment)), ==,
+	                 gdata_youtube_comment_get_parent_comment_uri (original_comment));
+
+	/* Check the author of the new comment. */
+	authors = gdata_entry_get_authors (GDATA_ENTRY (new_comment));
+	g_assert_cmpuint (g_list_length (authors), ==, 1);
+
+	author = GDATA_AUTHOR (authors->data);
+
+	g_assert_cmpstr (gdata_author_get_name (author), ==, "GDataTest");
+	g_assert_cmpstr (gdata_author_get_uri (author), ==, "https://gdata.youtube.com/feeds/api/users/gdatatest";);
+}
+
+static void
+test_comment_insert (InsertCommentData *data, gconstpointer service)
+{
+	GDataComment *new_comment;
+	GError *error = NULL;
+
+	new_comment = gdata_commentable_insert_comment (GDATA_COMMENTABLE (data->parent.video), GDATA_SERVICE (service), GDATA_COMMENT (data->comment),
+	                                                NULL, &error);
+	g_assert_no_error (error);
+	g_clear_error (&error);
+
+	assert_comments_equal (new_comment, data->comment);
+
+	g_object_unref (new_comment);
+}
+
+typedef struct {
+	InsertCommentData parent;
+	GMainLoop *main_loop;
+} InsertCommentAsyncData;
+
+static void
+set_up_insert_comment_async (InsertCommentAsyncData *data, gconstpointer service)
+{
+	set_up_insert_comment ((InsertCommentData*) data, service);
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_insert_comment_async (InsertCommentAsyncData *data, gconstpointer service)
+{
+	g_main_loop_unref (data->main_loop);
+	tear_down_insert_comment ((InsertCommentData*) data, service);
+}
+
+static void
+test_comment_insert_async_cb (GDataCommentable *commentable, GAsyncResult *result, InsertCommentAsyncData *data)
+{
+	GDataComment *new_comment;
+	GError *error = NULL;
+
+	new_comment = gdata_commentable_insert_comment_finish (commentable, result, &error);
+	g_assert_no_error (error);
+	g_clear_error (&error);
+
+	assert_comments_equal (new_comment, data->parent.comment);
+
+	g_object_unref (new_comment);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+static void
+test_comment_insert_async (InsertCommentAsyncData *data, gconstpointer service)
+{
+	gdata_commentable_insert_comment_async (GDATA_COMMENTABLE (data->parent.parent.video), GDATA_SERVICE (service),
+	                                        GDATA_COMMENT (data->parent.comment), NULL, (GAsyncReadyCallback) test_comment_insert_async_cb, data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_comment_insert_async_cancellation_cb (GDataCommentable *commentable, GAsyncResult *result, InsertCommentAsyncData *data)
+{
+	GDataComment *new_comment;
+	GError *error = NULL;
+
+	new_comment = gdata_commentable_insert_comment_finish (commentable, result, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_assert (new_comment == NULL);
+	g_clear_error (&error);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+static void
+test_comment_insert_async_cancellation (InsertCommentAsyncData *data, gconstpointer service)
+{
+	GCancellable *cancellable;
+
+	cancellable = g_cancellable_new ();
+	g_cancellable_cancel (cancellable);
+
+	gdata_commentable_insert_comment_async (GDATA_COMMENTABLE (data->parent.parent.video), GDATA_SERVICE (service),
+	                                        GDATA_COMMENT (data->parent.comment), cancellable,
+	                                        (GAsyncReadyCallback) test_comment_insert_async_cancellation_cb, data);
+
+	g_main_loop_run (data->main_loop);
+
+	g_object_unref (cancellable);
+}
+
+static void
+test_comment_delete (InsertCommentData *data, gconstpointer service)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	/* We attempt to delete a comment which hasn't been inserted here, but that doesn't matter as the function should always immediately
+	 * return an error because deleting YouTube comments isn't allowed. */
+	success = gdata_commentable_delete_comment (GDATA_COMMENTABLE (data->parent.video), GDATA_SERVICE (service), GDATA_COMMENT (data->comment),
+	                                            NULL, &error);
+	g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+}
+
+static void
+test_comment_delete_async_cb (GDataCommentable *commentable, GAsyncResult *result, InsertCommentAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_commentable_delete_comment_finish (commentable, result, &error);
+	g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+static void
+test_comment_delete_async (InsertCommentAsyncData *data, gconstpointer service)
+{
+	/* See the note above in test_comment_delete(). */
+	gdata_commentable_delete_comment_async (GDATA_COMMENTABLE (data->parent.parent.video), GDATA_SERVICE (service),
+	                                        GDATA_COMMENT (data->parent.comment), NULL, (GAsyncReadyCallback) test_comment_delete_async_cb, data);
+
+	g_main_loop_run (data->main_loop);
+}
+
 static void
 test_parsing_video_id_from_uri (void)
 {
@@ -1520,6 +1942,26 @@ main (int argc, char *argv[])
 		g_test_add_data_func ("/youtube/query/single", service, test_query_single);
 		g_test_add_data_func ("/youtube/query/single_async", service, test_query_single_async);
 
+		g_test_add ("/youtube/comment/query", CommentData, service, set_up_comment, test_comment_query, tear_down_comment);
+		g_test_add ("/youtube/comment/query/async", CommentAsyncData, service, set_up_comment_async, test_comment_query_async,
+		            tear_down_comment_async);
+		g_test_add ("/youtube/comment/query/async/cancellation", CommentAsyncData, service, set_up_comment_async,
+		            test_comment_query_async_cancellation, tear_down_comment_async);
+		g_test_add ("/youtube/comment/query/async/progress_closure", CommentAsyncData, service, set_up_comment_async,
+		            test_comment_query_async_progress_closure, tear_down_comment_async);
+
+		g_test_add ("/youtube/comment/insert", InsertCommentData, service, set_up_insert_comment, test_comment_insert,
+		            tear_down_insert_comment);
+		g_test_add ("/youtube/comment/insert/async", InsertCommentAsyncData, service, set_up_insert_comment_async, test_comment_insert_async,
+		            tear_down_insert_comment_async);
+		g_test_add ("/youtube/comment/insert/async/cancellation", InsertCommentAsyncData, service, set_up_insert_comment_async,
+		            test_comment_insert_async_cancellation, tear_down_insert_comment_async);
+
+		g_test_add ("/youtube/comment/delete", InsertCommentData, service, set_up_insert_comment, test_comment_delete,
+		            tear_down_insert_comment);
+		g_test_add ("/youtube/comment/delete/async", InsertCommentAsyncData, service, set_up_insert_comment_async, test_comment_delete_async,
+		            tear_down_insert_comment_async);
+
 		g_test_add_data_func ("/youtube/categories", service, test_categories);
 		g_test_add_data_func ("/youtube/categories/async", service, test_categories_async);
 
@@ -1541,6 +1983,9 @@ main (int argc, char *argv[])
 
 	g_test_add_func ("/youtube/video/escaping", test_video_escaping);
 
+	g_test_add_func ("/youtube/comment/get_xml", test_comment_get_xml);
+	g_test_add_func ("/youtube/comment/properties/parent-comment-id", test_comment_properties_parent_comment_uri);
+
 	g_test_add_func ("/youtube/query/uri", test_query_uri);
 	g_test_add_func ("/youtube/query/etag", test_query_etag);
 



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