[libgdata] Bug 615721 — Support listing video categories



commit 649ab36963e57d839a845bdf0dd823a1f4f14485
Author: Philip Withnall <philip tecnocode co uk>
Date:   Mon Jun 14 16:36:15 2010 +0100

    Bug 615721 â?? Support listing video categories
    
    Add support for listing the categories currently available on YouTube, along
    with whether they're assignable, browsable or deprecated. Coupled with locale
    support, this provides for localised category listings, too.
    Closes: bgo#615721

 Makefile.am                                     |   21 ++-
 docs/reference/gdata-docs.xml                   |    6 +
 docs/reference/gdata-sections.txt               |   42 ++++
 gdata/app/gdata-app-categories.c                |  230 ++++++++++++++++++++++
 gdata/app/gdata-app-categories.h                |   71 +++++++
 gdata/gdata.h                                   |    4 +
 gdata/gdata.symbols                             |   10 +
 gdata/services/youtube/gdata-youtube-category.c |  239 +++++++++++++++++++++++
 gdata/services/youtube/gdata-youtube-category.h |   72 +++++++
 gdata/services/youtube/gdata-youtube-service.c  |  150 ++++++++++++++
 gdata/services/youtube/gdata-youtube-service.h  |    8 +
 gdata/tests/general.c                           |   50 +++++
 gdata/tests/youtube.c                           |  134 +++++++++++++
 13 files changed, 1031 insertions(+), 6 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index dcb46da..97479da 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -175,6 +175,10 @@ gdataatominclude_HEADERS = \
 	gdata/atom/gdata-generator.h	\
 	gdata/atom/gdata-link.h
 
+gdataappincludedir = $(gdataincludedir)/app
+gdataappinclude_HEADERS = \
+	gdata/app/gdata-app-categories.h
+
 gdatagdincludedir = $(gdataincludedir)/gd
 gdatagdinclude_HEADERS = \
 	gdata/gd/gdata-gd-email-address.h	\
@@ -256,7 +260,8 @@ gdata_youtube_headers = \
 	gdata/services/youtube/gdata-youtube-content.h	\
 	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-state.h	\
+	gdata/services/youtube/gdata-youtube-category.h
 private_headers += \
 	gdata/services/youtube/gdata-youtube-group.h	\
 	gdata/services/youtube/gdata-youtube-control.h
@@ -286,6 +291,8 @@ gdata_libgdata_la_SOURCES = \
 	gdata/atom/gdata-generator.c	\
 	gdata/atom/gdata-link.c		\
 	\
+	gdata/app/gdata-app-categories.c	\
+	\
 	gdata/exif/gdata-exif-tags.c	\
 	\
 	gdata/gd/gdata-gd-email-address.c	\
@@ -348,7 +355,8 @@ gdata_libgdata_la_SOURCES = \
 	gdata/services/youtube/gdata-youtube-query.c		\
 	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-control.c		\
+	gdata/services/youtube/gdata-youtube-category.c
 
 # All but the first two -I flags here are to cope with compiling the enum files in a srcdir!=builddir environment
 gdata_libgdata_la_CPPFLAGS = \
@@ -397,10 +405,11 @@ gdata/GData-$(GDATA_API_VERSION).gir: Makefile gdata/libgdata.la
 	--library=gdata/libgdata.la $(_gir_libtool) --output $@				\
 	-I$(top_srcdir) -I$(top_srcdir)/gdata						\
 	$(addprefix $(srcdir)/,$(gdatainclude_HEADERS) $(gdataatominclude_HEADERS)	\
-	$(gdatagdinclude_HEADERS) $(gdatagcontactinclude_HEADERS)			\
-	$(gdatamediainclude_HEADERS) $(gdatacalendarinclude_HEADERS)			\
-	$(gdatacontactsinclude_HEADERS) $(gdatadocumentsinclude_HEADERS)		\
-	$(gdatapicasawebinclude_HEADERS) $(gdatayoutubeinclude_HEADERS)))
+	$(gdataappinclude_HEADERS) $(gdatagdinclude_HEADERS)				\
+	$(gdatagcontactinclude_HEADERS) $(gdatamediainclude_HEADERS)			\
+	$(gdatacalendarinclude_HEADERS) $(gdatacontactsinclude_HEADERS)			\
+	$(gdatadocumentsinclude_HEADERS) $(gdatapicasawebinclude_HEADERS)		\
+	$(gdatayoutubeinclude_HEADERS)))
 BUILT_GIRSOURCES = gdata/GData-$(GDATA_API_VERSION).gir
 
 girdir = $(datadir)/gir-1.0
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 2c1cdee..2a62fc2 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -53,6 +53,11 @@
 		</chapter>
 
 		<chapter>
+			<title>Atom Publishing Protocol API</title>
+			<xi:include href="xml/gdata-app-categories.xml"/>
+		</chapter>
+
+		<chapter>
 			<title>GData API</title>
 			<xi:include href="xml/gdata-gd-email-address.xml"/>
 			<xi:include href="xml/gdata-gd-im-address.xml"/>
@@ -87,6 +92,7 @@
 
 		<chapter>
 			<title>YouTube API</title>
+			<xi:include href="xml/gdata-youtube-category.xml"/>
 			<xi:include href="xml/gdata-youtube-content.xml"/>
 			<xi:include href="xml/gdata-youtube-credit.xml"/>
 			<xi:include href="xml/gdata-youtube-state.xml"/>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 4d6fda1..941c91a 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -186,6 +186,9 @@ gdata_youtube_service_query_related_async
 gdata_youtube_service_query_standard_feed
 gdata_youtube_service_query_standard_feed_async
 gdata_youtube_service_upload_video
+gdata_youtube_service_get_categories
+gdata_youtube_service_get_categories_async
+gdata_youtube_service_get_categories_finish
 gdata_youtube_service_get_developer_key
 gdata_youtube_service_get_youtube_user
 <SUBSECTION Standard>
@@ -2001,3 +2004,42 @@ GDATA_TYPE_GCONTACT_LANGUAGE
 <SUBSECTION Private>
 GDataGContactLanguagePrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-youtube-category</FILE>
+<TITLE>GDataYouTubeCategory</TITLE>
+GDataYouTubeCategory
+GDataYouTubeCategoryClass
+gdata_youtube_category_is_assignable
+gdata_youtube_category_is_browsable
+gdata_youtube_category_is_deprecated
+<SUBSECTION Standard>
+gdata_youtube_category_get_type
+GDATA_YOUTUBE_CATEGORY
+GDATA_YOUTUBE_CATEGORY_CLASS
+GDATA_YOUTUBE_CATEGORY_GET_CLASS
+GDATA_IS_YOUTUBE_CATEGORY
+GDATA_IS_YOUTUBE_CATEGORY_CLASS
+GDATA_TYPE_YOUTUBE_CATEGORY
+<SUBSECTION Private>
+GDataYouTubeCategoryPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-app-categories</FILE>
+<TITLE>GDataAPPCategories</TITLE>
+GDataAPPCategories
+GDataAPPCategoriesClass
+gdata_app_categories_get_categories
+gdata_app_categories_is_fixed
+<SUBSECTION Standard>
+gdata_app_categories_get_type
+GDATA_APP_CATEGORIES
+GDATA_APP_CATEGORIES_CLASS
+GDATA_APP_CATEGORIES_GET_CLASS
+GDATA_IS_APP_CATEGORIES
+GDATA_IS_APP_CATEGORIES_CLASS
+GDATA_TYPE_APP_CATEGORIES
+<SUBSECTION Private>
+GDataAPPCategoriesPrivate
+</SECTION>
diff --git a/gdata/app/gdata-app-categories.c b/gdata/app/gdata-app-categories.c
new file mode 100644
index 0000000..2c70485
--- /dev/null
+++ b/gdata/app/gdata-app-categories.c
@@ -0,0 +1,230 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2010 <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-app-categories
+ * @short_description: GData APP categories object
+ * @stability: Unstable
+ * @include: gdata/app/gdata-app-categories.h
+ *
+ * #GDataAPPCategories is a list of categories (#GDataCategory) returned as the result of querying an
+ * <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#category";>Atom Publishing Protocol Category Document</ulink>.
+ *
+ * Since: 0.7.0
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "gdata-app-categories.h"
+#include "atom/gdata-category.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+
+static void gdata_app_categories_dispose (GObject *object);
+static void gdata_app_categories_finalize (GObject *object);
+static void gdata_app_categories_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+static gboolean post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error);
+
+struct _GDataAPPCategoriesPrivate {
+	GList *categories;
+	gchar *scheme;
+	gboolean fixed;
+};
+
+enum {
+	PROP_IS_FIXED = 1
+};
+
+G_DEFINE_TYPE (GDataAPPCategories, gdata_app_categories, GDATA_TYPE_PARSABLE)
+
+static void
+gdata_app_categories_class_init (GDataAPPCategoriesClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataAPPCategoriesPrivate));
+
+	gobject_class->get_property = gdata_app_categories_get_property;
+	gobject_class->dispose = gdata_app_categories_dispose;
+	gobject_class->finalize = gdata_app_categories_finalize;
+
+	parsable_class->pre_parse_xml = pre_parse_xml;
+	parsable_class->parse_xml = parse_xml;
+	parsable_class->post_parse_xml = post_parse_xml;
+	parsable_class->element_name = "categories";
+	parsable_class->element_namespace = "app";
+
+	/**
+	 * GDataAPPCategories:is-fixed:
+	 *
+	 * Whether entries may use categories not in this category list.
+	 *
+	 * API reference: <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appCategories2";>app:categories</ulink>
+	 *
+	 * Since: 0.7.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_IS_FIXED,
+	                                 g_param_spec_boolean ("is-fixed",
+	                                                       "Fixed?", "Whether entries may use categories not in this category list.",
+	                                                       FALSE,
+	                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_app_categories_init (GDataAPPCategories *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_APP_CATEGORIES, GDataAPPCategoriesPrivate);
+}
+
+static void
+gdata_app_categories_dispose (GObject *object)
+{
+	GDataAPPCategoriesPrivate *priv = GDATA_APP_CATEGORIES (object)->priv;
+
+	if (priv->categories != NULL) {
+		g_list_foreach (priv->categories, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->categories);
+	}
+	priv->categories = NULL;
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_app_categories_parent_class)->dispose (object);
+}
+
+static void
+gdata_app_categories_finalize (GObject *object)
+{
+	GDataAPPCategoriesPrivate *priv = GDATA_APP_CATEGORIES (object)->priv;
+
+	g_free (priv->scheme);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_app_categories_parent_class)->finalize (object);
+}
+
+static void
+gdata_app_categories_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataAPPCategoriesPrivate *priv = GDATA_APP_CATEGORIES (object)->priv;
+
+	switch (property_id) {
+		case PROP_IS_FIXED:
+			g_value_set_boolean (value, priv->fixed);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static gboolean
+pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
+{
+	GDataAPPCategoriesPrivate *priv = GDATA_APP_CATEGORIES (parsable)->priv;
+	xmlChar *fixed;
+
+	/* Extract fixed and scheme */
+	priv->scheme = (gchar*) xmlGetProp (root_node, (xmlChar*) "scheme");
+
+	fixed = xmlGetProp (root_node, (xmlChar*) "fixed");
+	if (xmlStrcmp (fixed, (xmlChar*) "yes") == 0)
+		priv->fixed = TRUE;
+	xmlFree (fixed);
+
+	return TRUE;
+}
+
+static void
+_gdata_app_categories_add_category (GDataAPPCategories *self, GDataCategory *category)
+{
+	g_return_if_fail (GDATA_IS_APP_CATEGORIES (self));
+	g_return_if_fail (GDATA_IS_CATEGORY (category));
+
+	/* If the category doesn't have a scheme, make it inherit ours */
+	if (gdata_category_get_scheme (category) == NULL)
+		gdata_category_set_scheme (category, self->priv->scheme);
+
+	self->priv->categories = g_list_prepend (self->priv->categories, g_object_ref (category));
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	gboolean success;
+
+	if (gdata_parser_is_namespace (node, "http://www.w3.org/2005/Atom";) == TRUE &&
+	    gdata_parser_object_from_element_setter (node, "category", P_REQUIRED,
+	                                             (user_data == NULL) ? GDATA_TYPE_CATEGORY : GPOINTER_TO_SIZE (user_data),
+		                                     _gdata_app_categories_add_category, GDATA_APP_CATEGORIES (parsable), &success, error) == TRUE) {
+		return success;
+	}
+
+	return GDATA_PARSABLE_CLASS (gdata_app_categories_parent_class)->parse_xml (parsable, doc, node, user_data, error);
+}
+
+static gboolean
+post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error)
+{
+	GDataAPPCategoriesPrivate *priv = GDATA_APP_CATEGORIES (parsable)->priv;
+
+	/* Reverse our lists of stuff */
+	priv->categories = g_list_reverse (priv->categories);
+
+	return TRUE;
+}
+
+/**
+ * gdata_app_categories_get_categories:
+ * @self: a #GDataAPPCategories
+ *
+ * Returns a list of the categories in this category list.
+ *
+ * Return value: a #GList of #GDataCategory<!-- -->s
+ *
+ * Since: 0.7.0
+ **/
+GList *
+gdata_app_categories_get_categories (GDataAPPCategories *self)
+{
+	g_return_val_if_fail (GDATA_IS_APP_CATEGORIES (self), NULL);
+	return self->priv->categories;
+}
+
+/**
+ * gdata_app_categories_is_fixed:
+ * @self: a #GDataAPPCategories
+ *
+ * Returns #GDataAPPCategories:is-fixed.
+ *
+ * Return value: whether entries may use categories not in this category list
+ *
+ * Since: 0.7.0
+ **/
+gboolean
+gdata_app_categories_is_fixed (GDataAPPCategories *self)
+{
+	g_return_val_if_fail (GDATA_IS_APP_CATEGORIES (self), FALSE);
+	return self->priv->fixed;
+}
diff --git a/gdata/app/gdata-app-categories.h b/gdata/app/gdata-app-categories.h
new file mode 100644
index 0000000..9cdd127
--- /dev/null
+++ b/gdata/app/gdata-app-categories.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2010 <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_APP_CATEGORIES_H
+#define GDATA_APP_CATEGORIES_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-parsable.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_APP_CATEGORIES		(gdata_app_categories_get_type ())
+#define GDATA_APP_CATEGORIES(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_APP_CATEGORIES, GDataAPPCategories))
+#define GDATA_APP_CATEGORIES_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_APP_CATEGORIES, GDataAPPCategoriesClass))
+#define GDATA_IS_APP_CATEGORIES(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_APP_CATEGORIES))
+#define GDATA_IS_APP_CATEGORIES_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_APP_CATEGORIES))
+#define GDATA_APP_CATEGORIES_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_APP_CATEGORIES, GDataAPPCategoriesClass))
+
+typedef struct _GDataAPPCategoriesPrivate	GDataAPPCategoriesPrivate;
+
+/**
+ * GDataAPPCategories:
+ *
+ * All the fields in the #GDataAPPCategories structure are private and should never be accessed directly.
+ *
+ * Since: 0.7.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataParsable parent;
+	GDataAPPCategoriesPrivate *priv;
+} GDataAPPCategories;
+
+/**
+ * GDataAPPCategoriesClass:
+ *
+ * All the fields in the #GDataAPPCategoriesClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.7.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataParsableClass parent;
+} GDataAPPCategoriesClass;
+
+GType gdata_app_categories_get_type (void) G_GNUC_CONST;
+
+GList *gdata_app_categories_get_categories (GDataAPPCategories *self) G_GNUC_PURE;
+gboolean gdata_app_categories_is_fixed (GDataAPPCategories *self) G_GNUC_PURE;
+
+G_END_DECLS
+
+#endif /* !GDATA_APP_CATEGORIES_H */
diff --git a/gdata/gdata.h b/gdata/gdata.h
index eea2021..7f0c5ef 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -41,6 +41,9 @@
 #include <gdata/atom/gdata-generator.h>
 #include <gdata/atom/gdata-link.h>
 
+/* APP */
+#include <gdata/app/gdata-app-categories.h>
+
 /* GData */
 #include <gdata/gd/gdata-gd-email-address.h>
 #include <gdata/gd/gdata-gd-im-address.h>
@@ -79,6 +82,7 @@
 #include <gdata/services/youtube/gdata-youtube-credit.h>
 #include <gdata/services/youtube/gdata-youtube-state.h>
 #include <gdata/services/youtube/gdata-youtube-enums.h>
+#include <gdata/services/youtube/gdata-youtube-category.h>
 
 /* Google Calendar */
 #include <gdata/services/calendar/gdata-calendar-service.h>
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 16695a2..daf63fa 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -835,3 +835,13 @@ gdata_service_get_timeout
 gdata_service_set_timeout
 gdata_service_get_locale
 gdata_service_set_locale
+gdata_youtube_service_get_categories
+gdata_youtube_service_get_categories_async
+gdata_youtube_service_get_categories_finish
+gdata_youtube_category_get_type
+gdata_youtube_category_is_assignable
+gdata_youtube_category_is_browsable
+gdata_youtube_category_is_deprecated
+gdata_app_categories_get_type
+gdata_app_categories_get_categories
+gdata_app_categories_is_fixed
diff --git a/gdata/services/youtube/gdata-youtube-category.c b/gdata/services/youtube/gdata-youtube-category.c
new file mode 100644
index 0000000..6aba857
--- /dev/null
+++ b/gdata/services/youtube/gdata-youtube-category.c
@@ -0,0 +1,239 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2010 <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-category
+ * @short_description: YouTube category element
+ * @stability: Unstable
+ * @include: gdata/services/youtube/gdata-youtube-category.h
+ *
+ * #GDataYouTubeCategory represents the YouTube-specific customizations to #GDataCategory. For more information,
+ * see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html#YouTube_Category_List";>
+ * online documentation</ulink>.
+ *
+ * Since: 0.7.0
+ **/
+
+#include <glib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-youtube-category.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+
+static void gdata_youtube_category_finalize (GObject *object);
+static void gdata_youtube_category_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+
+struct _GDataYouTubeCategoryPrivate {
+	gboolean assignable;
+	gchar **browsable_regions;
+	/*gboolean deprecated; --- "Categories that are neither assignable or browsable are deprecated and are identified as such using the
+	 * <yt:deprecated> tag." */
+};
+
+enum {
+	PROP_IS_ASSIGNABLE = 1,
+	PROP_IS_DEPRECATED
+};
+
+G_DEFINE_TYPE (GDataYouTubeCategory, gdata_youtube_category, GDATA_TYPE_CATEGORY)
+
+static void
+gdata_youtube_category_class_init (GDataYouTubeCategoryClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataYouTubeCategoryPrivate));
+
+	gobject_class->get_property = gdata_youtube_category_get_property;
+	gobject_class->finalize = gdata_youtube_category_finalize;
+
+	parsable_class->parse_xml = parse_xml;
+	parsable_class->get_namespaces = get_namespaces;
+
+	/**
+	 * GDataYouTubeCategory:is-assignable:
+	 *
+	 * Whether new videos can be added to the category.
+	 *
+	 * Since: 0.7.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_IS_ASSIGNABLE,
+	                                 g_param_spec_boolean ("is-assignable",
+	                                                       "Assignable?", "Whether new videos can be added to the category.",
+	                                                       FALSE,
+	                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataYouTubeCategory:is-deprecated:
+	 *
+	 * Whether the category is deprecated.
+	 *
+	 * Since: 0.7.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_IS_DEPRECATED,
+	                                 g_param_spec_boolean ("is-deprecated",
+	                                                       "Deprecated?", "Whether the category is deprecated.",
+	                                                       TRUE,
+	                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_youtube_category_init (GDataYouTubeCategory *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_CATEGORY, GDataYouTubeCategoryPrivate);
+}
+
+static void
+gdata_youtube_category_finalize (GObject *object)
+{
+	GDataYouTubeCategoryPrivate *priv = GDATA_YOUTUBE_CATEGORY (object)->priv;
+
+	g_strfreev (priv->browsable_regions);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_youtube_category_parent_class)->finalize (object);
+}
+
+static void
+gdata_youtube_category_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataYouTubeCategoryPrivate *priv = GDATA_YOUTUBE_CATEGORY (object)->priv;
+
+	switch (property_id) {
+		case PROP_IS_ASSIGNABLE:
+			g_value_set_boolean (value, priv->assignable);
+			break;
+		case PROP_IS_DEPRECATED:
+			g_value_set_boolean (value, (priv->assignable == FALSE && priv->browsable_regions == NULL) ? TRUE : FALSE);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	GDataYouTubeCategory *self = GDATA_YOUTUBE_CATEGORY (parsable);
+
+	if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007";) == TRUE) {
+		if (xmlStrcmp (node->name, (xmlChar*) "assignable") == 0) {
+			/* yt:assignable */
+			self->priv->assignable = TRUE;
+		} else if (xmlStrcmp (node->name, (xmlChar*) "deprecated") == 0) {
+			/* yt:deprecated */
+			self->priv->assignable = FALSE;
+			g_strfreev (self->priv->browsable_regions);
+			self->priv->browsable_regions = NULL;
+		} else if (xmlStrcmp (node->name, (xmlChar*) "browsable") == 0) {
+			/* yt:browsable */
+			xmlChar *regions;
+
+			regions = xmlGetProp (node, (xmlChar*) "regions");
+			if (regions == NULL)
+				return gdata_parser_error_required_property_missing (node, "regions", error);
+
+			self->priv->browsable_regions = g_strsplit ((gchar*) regions, " ", -1);
+		} else {
+			return GDATA_PARSABLE_CLASS (gdata_youtube_category_parent_class)->parse_xml (parsable, doc, node, user_data, error);
+		}
+	} else {
+		return GDATA_PARSABLE_CLASS (gdata_youtube_category_parent_class)->parse_xml (parsable, doc, node, user_data, error);
+	}
+
+	return TRUE;
+}
+
+static void
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
+{
+	/* Chain up to the parent class */
+	GDATA_PARSABLE_CLASS (gdata_youtube_category_parent_class)->get_namespaces (parsable, namespaces);
+
+	g_hash_table_insert (namespaces, (gchar*) "youtube", (gchar*) "http://gdata.youtube.com/schemas/2007";);
+}
+
+/**
+ * gdata_youtube_category_is_assignable:
+ * @self: a #GDataYouTubeCategory
+ *
+ * Gets the #GDataYouTubeCategory:is-assignable property.
+ *
+ * Return value: whether new videos can be assigned to the category
+ *
+ * Since: 0.7.0
+ **/
+gboolean
+gdata_youtube_category_is_assignable (GDataYouTubeCategory *self)
+{
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_CATEGORY (self), FALSE);
+	return self->priv->assignable;
+}
+
+/**
+ * gdata_youtube_category_is_browsable:
+ * @self: a #GDataYouTubeCategory
+ * @region: a two-letter region ID
+ *
+ * Returns whether the category is browsable in the given @region. The list of supported region IDs is
+ * <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html#Region_specific_feeds";>available online</ulink>.
+ *
+ * Return value: whether the category is browsable in @region
+ *
+ * Since: 0.7.0
+ **/
+gboolean
+gdata_youtube_category_is_browsable (GDataYouTubeCategory *self, const gchar *region)
+{
+	const gchar **i;
+
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_CATEGORY (self), FALSE);
+	g_return_val_if_fail (region != NULL && *region != '\0', FALSE);
+
+	for (i = self->priv->browsable_regions; i != NULL && *i != NULL; i++) {
+		if (strcmp (*i, region) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**
+ * gdata_youtube_category_is_deprecated:
+ * @self: a #GDataYouTubeCategory
+ *
+ * Gets the #GDataYouTubeCategory:is-deprecated property.
+ *
+ * Return value: whether the category is deprecated
+ *
+ * Since: 0.7.0
+ **/
+gboolean
+gdata_youtube_category_is_deprecated (GDataYouTubeCategory *self)
+{
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_CATEGORY (self), TRUE);
+	return (self->priv->assignable == FALSE && self->priv->browsable_regions == NULL) ? TRUE : FALSE;
+}
diff --git a/gdata/services/youtube/gdata-youtube-category.h b/gdata/services/youtube/gdata-youtube-category.h
new file mode 100644
index 0000000..6b503fc
--- /dev/null
+++ b/gdata/services/youtube/gdata-youtube-category.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2010 <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_CATEGORY_H
+#define GDATA_YOUTUBE_CATEGORY_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-parsable.h>
+#include <gdata/atom/gdata-category.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_YOUTUBE_CATEGORY		(gdata_youtube_category_get_type ())
+#define GDATA_YOUTUBE_CATEGORY(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_YOUTUBE_CATEGORY, GDataYouTubeCategory))
+#define GDATA_YOUTUBE_CATEGORY_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_YOUTUBE_CATEGORY, GDataYouTubeCategoryClass))
+#define GDATA_IS_YOUTUBE_CATEGORY(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_YOUTUBE_CATEGORY))
+#define GDATA_IS_YOUTUBE_CATEGORY_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_CATEGORY))
+#define GDATA_YOUTUBE_CATEGORY_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_CATEGORY, GDataYouTubeCategoryClass))
+
+typedef struct _GDataYouTubeCategoryPrivate	GDataYouTubeCategoryPrivate;
+
+/**
+ * GDataYouTubeCategory:
+ *
+ * All the fields in the #GDataYouTubeCategory structure are private and should never be accessed directly.
+ *
+ * Since: 0.7.0
+ **/
+typedef struct {
+	GDataCategory parent;
+	GDataYouTubeCategoryPrivate *priv;
+} GDataYouTubeCategory;
+
+/**
+ * GDataYouTubeCategoryClass:
+ *
+ * All the fields in the #GDataYouTubeCategoryClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.7.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataCategoryClass parent;
+} GDataYouTubeCategoryClass;
+
+GType gdata_youtube_category_get_type (void) G_GNUC_CONST;
+
+gboolean gdata_youtube_category_is_assignable (GDataYouTubeCategory *self) G_GNUC_PURE;
+gboolean gdata_youtube_category_is_browsable (GDataYouTubeCategory *self, const gchar *region) G_GNUC_PURE;
+gboolean gdata_youtube_category_is_deprecated (GDataYouTubeCategory *self) G_GNUC_PURE;
+
+G_END_DECLS
+
+#endif /* !GDATA_YOUTUBE_CATEGORY_H */
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index ad519d6..31e2524 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -28,6 +28,36 @@
  *
  * 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>.
+ *
+ * <example>
+ * 	<title>Getting a Localized List of YouTube Categories</title>
+ * 	<programlisting>
+ *	GDataYouTubeService *service;
+ *	GDataAPPCategories *app_categories;
+ *	GList *categories, *i;
+ *
+ *	/<!-- -->* Create a service and set its locale to Italian, which localizes the categories to Italian *<!-- -->/
+ *	service = create_youtube_service ();
+ *	gdata_service_set_locale (GDATA_SERVICE (service), "it");
+ *
+ *	/<!-- -->* Query the server for the current list of YouTube categories (in Italian) *<!-- -->/
+ *	app_categories = gdata_youtube_service_get_categories (service, NULL, NULL);
+ *	categories = gdata_app_categories_get_categories (app_categories);
+ *
+ *	/<!-- -->* Iterate through the categories *<!-- -->/
+ *	for (i = categories; i != NULL; i = i->next) {
+ *		GDataYouTubeCategory *category = GDATA_YOUTUBE_CATEGORY (i->data);
+ *
+ *		if (gdata_youtube_category_is_deprecated (category) == FALSE && gdata_youtube_category_is_browsable (category, "IT") == TRUE) {
+ *			/<!-- -->* Do something with the category here, as it's not deprecated, and is browsable in the given region *<!-- -->/
+ *			add_to_ui (gdata_category_get_term (GDATA_CATEGORY (category)), gdata_category_get_label (GDATA_CATEGORY (category)));
+ *		}
+ *	}
+ *
+ *	g_object_unref (app_categories);
+ *	g_object_unref (service);
+ * 	</programlisting>
+ * </example>
  **/
 
 #include <config.h>
@@ -42,6 +72,7 @@
 #include "gdata-parser.h"
 #include "atom/gdata-link.h"
 #include "gdata-upload-stream.h"
+#include "gdata-youtube-category.h"
 
 /* Standards reference here: http://code.google.com/apis/youtube/2.0/reference.html */
 
@@ -746,3 +777,122 @@ gdata_youtube_service_get_youtube_user (GDataYouTubeService *self)
 	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
 	return self->priv->youtube_user;
 }
+
+/**
+ * gdata_youtube_service_get_categories:
+ * @self: a #GDataYouTubeService
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Gets a list of the categories currently in use on YouTube. The returned #GDataAPPCategories contains a list of #GDataYouTubeCategory<!-- -->s which
+ * enumerate the current YouTube categories.
+ *
+ * The category labels (#GDataCategory:label) are localised based on the value of #GDataService:locale.
+ *
+ * Return value: a #GDataAPPCategories, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.7.0
+ **/
+GDataAPPCategories *
+gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *cancellable, GError **error)
+{
+	SoupMessage *message;
+	GDataAPPCategories *categories;
+
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* Download the category list. Note that this is (service) locale-dependent. */
+	message = _gdata_service_query (GDATA_SERVICE (self), "http://gdata.youtube.com/schemas/2007/categories.cat";, NULL, cancellable, error);
+	if (message == NULL)
+		return NULL;
+
+	g_assert (message->response_body->data != NULL);
+	categories = GDATA_APP_CATEGORIES (_gdata_parsable_new_from_xml (GDATA_TYPE_APP_CATEGORIES, message->response_body->data,
+	                                                                 message->response_body->length,
+	                                                                 GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY), error));
+	g_object_unref (message);
+
+	return categories;
+}
+
+static void
+get_categories_thread (GSimpleAsyncResult *result, GDataYouTubeService *service, GCancellable *cancellable)
+{
+	GDataAPPCategories *categories;
+	GError *error = NULL;
+
+	/* Get the categories and return */
+	categories = gdata_youtube_service_get_categories (service, cancellable, &error);
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+	}
+
+	g_simple_async_result_set_op_res_gpointer (result, categories, (GDestroyNotify) g_object_unref);
+}
+
+/**
+ * gdata_youtube_service_get_categories_async:
+ * @self: a #GDataYouTubeService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is finished
+ * @user_data: data to pass to the @callback function
+ *
+ * Gets a list of the categories currently in use on YouTube. @self is reffed when this function is called, so can safely be unreffed after this
+ * function returns.
+ *
+ * For more details, see gdata_youtube_service_get_categories(), which is the synchronous version of this function.
+ *
+ * When the operation is finished, @callback will be called. You can then call gdata_youtube_service_get_categories_finish() to get the results of the
+ * operation.
+ *
+ * Since: 0.7.0
+ **/
+void
+gdata_youtube_service_get_categories_async (GDataYouTubeService *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+
+	g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+	g_return_if_fail (callback != NULL);
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_youtube_service_get_categories_async);
+	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) get_categories_thread, G_PRIORITY_DEFAULT, cancellable);
+	g_object_unref (result);
+}
+
+/**
+ * gdata_youtube_service_get_categories_finish:
+ * @self: a #GDataYouTubeService
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous request for a list of categories on YouTube, as started with gdata_youtube_service_get_categories_async().
+ *
+ * Return value: a #GDataAPPCategories, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.7.0
+ **/
+GDataAPPCategories *
+gdata_youtube_service_get_categories_finish (GDataYouTubeService *self, GAsyncResult *async_result, GError **error)
+{
+	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
+	GDataAPPCategories *categories;
+
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_youtube_service_get_categories_async);
+
+	if (g_simple_async_result_propagate_error (result, error) == TRUE)
+		return NULL;
+
+	categories = g_simple_async_result_get_op_res_gpointer (result);
+	if (categories != NULL)
+		return g_object_ref (categories);
+	return NULL;
+}
diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h
index aea7693..19566c9 100644
--- a/gdata/services/youtube/gdata-youtube-service.h
+++ b/gdata/services/youtube/gdata-youtube-service.h
@@ -26,6 +26,7 @@
 
 #include <gdata/gdata-service.h>
 #include <gdata/services/youtube/gdata-youtube-video.h>
+#include <gdata/app/gdata-app-categories.h>
 
 G_BEGIN_DECLS
 
@@ -136,6 +137,13 @@ GDataYouTubeVideo *gdata_youtube_service_upload_video (GDataYouTubeService *self
 const gchar *gdata_youtube_service_get_developer_key (GDataYouTubeService *self) G_GNUC_PURE;
 const gchar *gdata_youtube_service_get_youtube_user (GDataYouTubeService *self) G_GNUC_PURE;
 
+GDataAPPCategories *gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *cancellable,
+                                                          GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+void gdata_youtube_service_get_categories_async (GDataYouTubeService *self, GCancellable *cancellable, GAsyncReadyCallback callback,
+                                                 gpointer user_data);
+GDataAPPCategories *gdata_youtube_service_get_categories_finish (GDataYouTubeService *self, GAsyncResult *async_result,
+                                                                 GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
 G_END_DECLS
 
 #endif /* !GDATA_YOUTUBE_SERVICE_H */
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index d18d623..8fcb4a4 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -1333,6 +1333,54 @@ test_atom_link_error_handling (void)
 }
 
 static void
+test_app_categories (void)
+{
+	GDataAPPCategories *categories;
+	GList *_categories;
+	gboolean fixed;
+	GError *error = NULL;
+
+	categories = GDATA_APP_CATEGORIES (gdata_parsable_new_from_xml (GDATA_TYPE_APP_CATEGORIES,
+		"<app:categories xmlns:app='http://www.w3.org/2007/app' fixed='yes' scheme='http://scheme'>"
+			"<category term='foo'/>"
+			"<category scheme='http://other.scheme' term='bar'/>"
+		"</app:categories>", -1, &error));
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_APP_CATEGORIES (categories));
+	g_clear_error (&error);
+
+	/* Check the properties */
+	g_assert (gdata_app_categories_is_fixed (categories) == TRUE);
+
+	/* Check them a different way too */
+	g_object_get (G_OBJECT (categories), "is-fixed", &fixed, NULL);
+	g_assert (fixed == TRUE);
+
+	/* Check the categories and scheme inheritance */
+	_categories = gdata_app_categories_get_categories (categories);
+	g_assert_cmpint (g_list_length (_categories), ==, 2);
+
+	g_assert (GDATA_IS_CATEGORY (_categories->data));
+	g_assert_cmpstr (gdata_category_get_scheme (GDATA_CATEGORY (_categories->data)), ==, "http://scheme";);
+
+	g_assert (GDATA_IS_CATEGORY (_categories->next->data));
+	g_assert_cmpstr (gdata_category_get_scheme (GDATA_CATEGORY (_categories->next->data)), ==, "http://other.scheme";);
+
+	g_object_unref (categories);
+
+	/* Now parse one with less information available */
+	categories = GDATA_APP_CATEGORIES (gdata_parsable_new_from_xml (GDATA_TYPE_APP_CATEGORIES,
+		"<app:categories xmlns:app='http://www.w3.org/2007/app'/>", -1, &error));
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_APP_CATEGORIES (categories));
+	g_clear_error (&error);
+
+	/* Check the properties */
+	g_assert (gdata_app_categories_is_fixed (categories) == FALSE);
+	g_object_unref (categories);
+}
+
+static void
 test_gd_email_address (void)
 {
 	GDataGDEmailAddress *email, *email2;
@@ -3078,6 +3126,8 @@ main (int argc, char *argv[])
 	g_test_add_func ("/atom/link", test_atom_link);
 	g_test_add_func ("/atom/link/error_handling", test_atom_link_error_handling);
 
+	g_test_add_func ("/app/categories", test_app_categories);
+
 	g_test_add_func ("/gd/email_address", test_gd_email_address);
 	g_test_add_func ("/gd/im_address", test_gd_im_address);
 	g_test_add_func ("/gd/name", test_gd_name);
diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c
index c5f6f89..385b6f2 100644
--- a/gdata/tests/youtube.c
+++ b/gdata/tests/youtube.c
@@ -546,6 +546,60 @@ test_parsing_yt_access_control (gconstpointer service)
 	g_object_unref (video);
 }
 
+static void
+test_parsing_yt_category (gconstpointer service)
+{
+	GDataYouTubeCategory *category;
+	gboolean assignable, deprecated;
+	GError *error = NULL;
+
+	/* Test a non-deprecated category */
+	category = GDATA_YOUTUBE_CATEGORY (gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_CATEGORY,
+		"<category xmlns='http://www.w3.org/2005/Atom' xmlns:yt='http://gdata.youtube.com/schemas/2007' "
+			"scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'>"
+			"<yt:assignable/>"
+			"<yt:browsable regions='CZ AU HK'/>"
+		"</category>", -1, &error));
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_YOUTUBE_CATEGORY (category));
+	g_clear_error (&error);
+
+	/* Test the category's properties */
+	g_assert (gdata_youtube_category_is_assignable (category) == TRUE);
+	g_assert (gdata_youtube_category_is_browsable (category, "CZ") == TRUE);
+	g_assert (gdata_youtube_category_is_browsable (category, "AU") == TRUE);
+	g_assert (gdata_youtube_category_is_browsable (category, "HK") == TRUE);
+	g_assert (gdata_youtube_category_is_browsable (category, "GB") == FALSE);
+	g_assert (gdata_youtube_category_is_deprecated (category) == FALSE);
+
+	/* Test the properties the other way */
+	g_object_get (category, "is-assignable", &assignable, "is-deprecated", &deprecated, NULL);
+	g_assert (assignable == TRUE);
+	g_assert (deprecated == FALSE);
+
+	g_object_unref (category);
+
+	/* Test a deprecated category */
+	category = GDATA_YOUTUBE_CATEGORY (gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_CATEGORY,
+		"<category xmlns='http://www.w3.org/2005/Atom' xmlns:yt='http://gdata.youtube.com/schemas/2007' "
+			"scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'>"
+			"<yt:deprecated/>"
+		"</category>", -1, &error));
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_YOUTUBE_CATEGORY (category));
+	g_clear_error (&error);
+
+	/* Test the category's properties */
+	g_assert (gdata_youtube_category_is_assignable (category) == FALSE);
+	g_assert (gdata_youtube_category_is_browsable (category, "CZ") == FALSE);
+	g_assert (gdata_youtube_category_is_browsable (category, "AU") == FALSE);
+	g_assert (gdata_youtube_category_is_browsable (category, "HK") == FALSE);
+	g_assert (gdata_youtube_category_is_browsable (category, "GB") == FALSE);
+	g_assert (gdata_youtube_category_is_deprecated (category) == TRUE);
+
+	g_object_unref (category);
+}
+
 /*static void
 test_parsing_comments_feed_link (void)
 {
@@ -786,6 +840,83 @@ test_parsing_video_id_from_uri (void)
 	g_assert (video_id == NULL);
 }
 
+static void
+test_categories (gconstpointer service)
+{
+	GDataAPPCategories *app_categories;
+	GList *categories;
+	GError *error = NULL;
+	gchar *category_label, *old_locale;
+
+	app_categories = gdata_youtube_service_get_categories (GDATA_YOUTUBE_SERVICE (service), NULL, &error);
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_APP_CATEGORIES (app_categories));
+	g_clear_error (&error);
+
+	categories = gdata_app_categories_get_categories (app_categories);
+	g_assert_cmpint (g_list_length (categories), >, 0);
+	g_assert (GDATA_IS_YOUTUBE_CATEGORY (categories->data));
+
+	/* Save a label for comparison against a different locale */
+	category_label = g_strdup (gdata_category_get_label (GDATA_CATEGORY (categories->data)));
+
+	g_object_unref (app_categories);
+
+	/* Test with a different locale */
+	old_locale = g_strdup (gdata_service_get_locale (GDATA_SERVICE (service)));
+	gdata_service_set_locale (GDATA_SERVICE (service), "it");
+
+	app_categories = gdata_youtube_service_get_categories (GDATA_YOUTUBE_SERVICE (service), NULL, &error);
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_APP_CATEGORIES (app_categories));
+	g_clear_error (&error);
+
+	categories = gdata_app_categories_get_categories (app_categories);
+	g_assert_cmpint (g_list_length (categories), >, 0);
+	g_assert (GDATA_IS_YOUTUBE_CATEGORY (categories->data));
+
+	/* Compare the labels */
+	g_assert_cmpstr (category_label, !=, gdata_category_get_label (GDATA_CATEGORY (categories->data)));
+
+	g_object_unref (app_categories);
+	g_free (category_label);
+
+	/* Reset the locale */
+	gdata_service_set_locale (GDATA_SERVICE (service), old_locale);
+	g_free (old_locale);
+}
+
+static void
+test_categories_async_cb (GDataService *service, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	GDataAPPCategories *app_categories;
+	GList *categories;
+	GError *error = NULL;
+
+	app_categories = gdata_youtube_service_get_categories_finish (GDATA_YOUTUBE_SERVICE (service), async_result, &error);
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_APP_CATEGORIES (app_categories));
+	g_clear_error (&error);
+
+	categories = gdata_app_categories_get_categories (app_categories);
+	g_assert_cmpint (g_list_length (categories), >, 0);
+	g_assert (GDATA_IS_YOUTUBE_CATEGORY (categories->data));
+
+	g_main_loop_quit (main_loop);
+	g_object_unref (app_categories);
+}
+
+static void
+test_categories_async (gconstpointer service)
+{
+	GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
+
+	gdata_youtube_service_get_categories_async (GDATA_YOUTUBE_SERVICE (service), NULL, (GAsyncReadyCallback) test_categories_async_cb, main_loop);
+
+	g_main_loop_run (main_loop);
+	g_main_loop_unref (main_loop);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -812,12 +943,15 @@ main (int argc, char *argv[])
 	/*g_test_add_func ("/youtube/parsing/comments/feedLink", test_parsing_comments_feed_link);*/
 	g_test_add_data_func ("/youtube/parsing/yt:recorded", service, test_parsing_yt_recorded);
 	g_test_add_data_func ("/youtube/parsing/yt:accessControl", service, test_parsing_yt_access_control);
+	g_test_add_data_func ("/youtube/parsing/yt:category", service, test_parsing_yt_category);
 	g_test_add_func ("/youtube/query/uri", test_query_uri);
 	g_test_add_func ("/youtube/query/etag", test_query_etag);
 	g_test_add_data_func ("/youtube/query/single", service, test_query_single);
 	if (g_test_slow () == TRUE)
 		g_test_add_data_func ("/youtube/query/single_async", service, test_query_single_async);
 	g_test_add_func ("/youtube/parsing/video_id_from_uri", test_parsing_video_id_from_uri);
+	g_test_add_data_func ("/youtube/categories", service, test_categories);
+	g_test_add_data_func ("/youtube/categories/async", service, test_categories_async);
 
 	retval = g_test_run ();
 	g_object_unref (service);



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