diff options
author | Philip Withnall <philip@tecnocode.co.uk> | 2015-04-20 00:06:33 +0100 |
---|---|---|
committer | Philip Withnall <philip@tecnocode.co.uk> | 2015-04-20 00:18:42 +0100 |
commit | 30a1aa76dce13e854dae0825cf981665254ebf40 (patch) | |
tree | f4bbcf904ec3606f000934f5dbdd76dc2041ca4e | |
parent | a581ba449e82593cc418f4cc415e2aadaa13593a (diff) | |
download | libgdata-wip/pwithnall/youtube-v3.tar.gz |
youtube: WIP v3 portwip/pwithnall/youtube-v3
Pending documentation:
standard feeds are deprecated
might have to re-register developer key
(https://developers.google.com/youtube/registering_an_application)
some properties are now only retrieved for single queries
which properties are supported for uploads
-rw-r--r-- | Makefile.am | 5 | ||||
-rw-r--r-- | docs/reference/Makefile.am | 2 | ||||
-rw-r--r-- | gdata/app/gdata-app-categories.c | 1 | ||||
-rw-r--r-- | gdata/media/gdata-media-thumbnail.c | 1 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-control.c | 183 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-control.h | 75 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-credit.c | 12 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-credit.h | 28 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-group.c | 265 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-group.h | 80 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-service.c | 513 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-service.h | 59 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-state.c | 50 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-video.c | 1343 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-video.h | 8 | ||||
-rw-r--r-- | gdata/tests/youtube.c | 2 |
16 files changed, 1456 insertions, 1171 deletions
diff --git a/Makefile.am b/Makefile.am index 1e1e0096..34c9539c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -323,9 +323,6 @@ gdata_youtube_headers = \ gdata/services/youtube/gdata-youtube-category.h \ gdata/services/youtube/gdata-youtube-comment.h \ gdata/services/youtube/gdata-youtube-feed.h -private_headers += \ - gdata/services/youtube/gdata-youtube-group.h \ - gdata/services/youtube/gdata-youtube-control.h gdatayoutubeinclude_HEADERS = \ $(gdata_youtube_headers) \ gdata/services/youtube/gdata-youtube-enums.h @@ -449,9 +446,7 @@ gdata_sources = \ gdata/services/youtube/gdata-youtube-content.c \ gdata/services/youtube/gdata-youtube-credit.c \ 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-category.c \ gdata/services/youtube/gdata-youtube-comment.c \ gdata/services/youtube/gdata-youtube-feed.c \ diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am index d5b020bb..0eff9646 100644 --- a/docs/reference/Makefile.am +++ b/docs/reference/Makefile.am @@ -56,8 +56,6 @@ IGNORE_HFILES = \ gdata-enums.h \ gdata-media-enums.h \ gdata-media-group.h \ - gdata-youtube-group.h \ - gdata-youtube-control.h \ gdata-youtube-enums.h \ gdata-picasaweb-enums.h \ gdata-documents-enums.h \ diff --git a/gdata/app/gdata-app-categories.c b/gdata/app/gdata-app-categories.c index aaf7bfa1..3c04e1d8 100644 --- a/gdata/app/gdata-app-categories.c +++ b/gdata/app/gdata-app-categories.c @@ -74,6 +74,7 @@ gdata_app_categories_class_init (GDataAPPCategoriesClass *klass) gobject_class->dispose = gdata_app_categories_dispose; gobject_class->finalize = gdata_app_categories_finalize; + /* TODO: eliminate */ parsable_class->pre_parse_xml = pre_parse_xml; parsable_class->parse_xml = parse_xml; parsable_class->post_parse_xml = post_parse_xml; diff --git a/gdata/media/gdata-media-thumbnail.c b/gdata/media/gdata-media-thumbnail.c index be55c127..75b5ae38 100644 --- a/gdata/media/gdata-media-thumbnail.c +++ b/gdata/media/gdata-media-thumbnail.c @@ -41,6 +41,7 @@ static void gdata_media_thumbnail_finalize (GObject *object); static void gdata_media_thumbnail_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); +/* TODO: eliminate XML parsing */ static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error); static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); static gboolean diff --git a/gdata/services/youtube/gdata-youtube-control.c b/gdata/services/youtube/gdata-youtube-control.c deleted file mode 100644 index 3436e1b2..00000000 --- a/gdata/services/youtube/gdata-youtube-control.c +++ /dev/null @@ -1,183 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ -/* - * GData Client - * Copyright (C) Philip Withnall 2009 <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-control - * @short_description: Atom Publishing Protocol control element with YouTube-specific modifications - * @stability: Stable - * @include: gdata/services/youtube/gdata-youtube-control.h - * - * #GDataYouTubeControl represents a "control" element from the - * <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appControl">Atom Publishing Protocol specification</ulink>, - * with support for the YouTube-specific - * <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:state">"state" element</ulink>. - * - * It is private API, since it would be unnecessary and confusing to expose #GDataYouTubeControl itself. There is the option - * of breaking this up into #GDataAPPControl and extending #GDataYouTubeControl classes in the future, if more entry types implement - * APP support. - * - * For these reasons, properties have not been implemented on #GDataYouTubeControl (yet). - */ - -#include <glib.h> -#include <libxml/parser.h> - -#include "gdata-youtube-control.h" -#include "gdata-parsable.h" -#include "gdata-parser.h" -#include "gdata-private.h" -#include "gdata-youtube-state.h" - -static void gdata_youtube_control_dispose (GObject *object); -static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error); -static void get_xml (GDataParsable *parsable, GString *xml_string); -static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); - -struct _GDataYouTubeControlPrivate { - gboolean is_draft; - GDataYouTubeState *state; -}; - -G_DEFINE_TYPE (GDataYouTubeControl, gdata_youtube_control, GDATA_TYPE_PARSABLE) - -static void -gdata_youtube_control_class_init (GDataYouTubeControlClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass); - - g_type_class_add_private (klass, sizeof (GDataYouTubeControlPrivate)); - - gobject_class->dispose = gdata_youtube_control_dispose; - - parsable_class->parse_xml = parse_xml; - parsable_class->get_xml = get_xml; - parsable_class->get_namespaces = get_namespaces; - parsable_class->element_name = "control"; - parsable_class->element_namespace = "app"; -} - -static void -gdata_youtube_control_init (GDataYouTubeControl *self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControlPrivate); -} - -static void -gdata_youtube_control_dispose (GObject *object) -{ - GDataYouTubeControlPrivate *priv = GDATA_YOUTUBE_CONTROL (object)->priv; - - if (priv->state != NULL) - g_object_unref (priv->state); - priv->state = NULL; - - /* Chain up to the parent class */ - G_OBJECT_CLASS (gdata_youtube_control_parent_class)->dispose (object); -} - -static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) -{ - gboolean success; - GDataYouTubeControl *self = GDATA_YOUTUBE_CONTROL (parsable); - - if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE && - xmlStrcmp (node->name, (xmlChar*) "draft") == 0) { - /* app:draft */ - xmlChar *draft = xmlNodeListGetString (doc, node, TRUE); - if (xmlStrcmp (draft, (xmlChar*) "no") == 0) - self->priv->is_draft = FALSE; - else - self->priv->is_draft = TRUE; - xmlFree (draft); - } else if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007") == TRUE && - gdata_parser_object_from_element (node, "state", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_STATE, - &(self->priv->state), &success, error) == TRUE) { - return success; - } else { - return GDATA_PARSABLE_CLASS (gdata_youtube_control_parent_class)->parse_xml (parsable, doc, node, user_data, error); - } - - return TRUE; -} - -static void -get_xml (GDataParsable *parsable, GString *xml_string) -{ - GDataYouTubeControlPrivate *priv = GDATA_YOUTUBE_CONTROL (parsable)->priv; - - if (priv->is_draft == TRUE) - g_string_append (xml_string, "<app:draft>yes</app:draft>"); - else - g_string_append (xml_string, "<app:draft>no</app:draft>"); -} - -static void -get_namespaces (GDataParsable *parsable, GHashTable *namespaces) -{ - g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app"); -} - -/** - * gdata_youtube_control_is_draft: - * @self: a #GDataYouTubeControl - * - * Gets the #GDataYouTubeControl:is-draft property. - * - * Return value: %TRUE if the object is a draft, %FALSE otherwise - **/ -gboolean -gdata_youtube_control_is_draft (GDataYouTubeControl *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_CONTROL (self), FALSE); - return self->priv->is_draft; -} - -/** - * gdata_youtube_control_set_is_draft: - * @self: a #GDataYouTubeControl - * @is_draft: whether the object is a draft - * - * Sets the #GDataYouTubeControl:is-draft property to decide whether the object is a draft. - **/ -void -gdata_youtube_control_set_is_draft (GDataYouTubeControl *self, gboolean is_draft) -{ - g_return_if_fail (GDATA_IS_YOUTUBE_CONTROL (self)); - self->priv->is_draft = is_draft; -} - -/** - * gdata_youtube_control_get_state: - * @self: a #GDataYouTubeControl - * - * Gets the #GDataYouTubeControl:state property. - * - * For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:state">online documentation</ulink>. - * - * Return value: (transfer none): a #GDataYouTubeState showing the state of the video, or %NULL - **/ -GDataYouTubeState * -gdata_youtube_control_get_state (GDataYouTubeControl *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_CONTROL (self), NULL); - return self->priv->state; -} diff --git a/gdata/services/youtube/gdata-youtube-control.h b/gdata/services/youtube/gdata-youtube-control.h deleted file mode 100644 index bfc3fbb6..00000000 --- a/gdata/services/youtube/gdata-youtube-control.h +++ /dev/null @@ -1,75 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ -/* - * GData Client - * Copyright (C) Philip Withnall 2009 <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_CONTROL_H -#define GDATA_YOUTUBE_CONTROL_H - -#include <glib.h> -#include <glib-object.h> - -#include <gdata/gdata-parsable.h> -#include <gdata/services/youtube/gdata-youtube-state.h> - -G_BEGIN_DECLS - -#define GDATA_TYPE_YOUTUBE_CONTROL (gdata_youtube_control_get_type ()) -#define GDATA_YOUTUBE_CONTROL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControl)) -#define GDATA_YOUTUBE_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControlClass)) -#define GDATA_IS_YOUTUBE_CONTROL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_YOUTUBE_CONTROL)) -#define GDATA_IS_YOUTUBE_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_CONTROL)) -#define GDATA_YOUTUBE_CONTROL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControlClass)) - -typedef struct _GDataYouTubeControlPrivate GDataYouTubeControlPrivate; - -/** - * GDataYouTubeControl: - * - * All the fields in the #GDataYouTubeControl structure are private and should never be accessed directly. - **/ -typedef struct { - GDataParsable parent; - GDataYouTubeControlPrivate *priv; -} GDataYouTubeControl; - -/** - * GDataYouTubeControlClass: - * - * All the fields in the #GDataYouTubeControlClass structure are private and should never be accessed directly. - * - * Since: 0.4.0 - **/ -typedef struct { - /*< private >*/ - GDataParsableClass parent; - - /*< private >*/ - /* Padding for future expansion */ - void (*_g_reserved0) (void); - void (*_g_reserved1) (void); -} GDataYouTubeControlClass; - -GType gdata_youtube_control_get_type (void) G_GNUC_CONST; - -gboolean gdata_youtube_control_is_draft (GDataYouTubeControl *self) G_GNUC_PURE; -void gdata_youtube_control_set_is_draft (GDataYouTubeControl *self, gboolean is_draft); -GDataYouTubeState *gdata_youtube_control_get_state (GDataYouTubeControl *self) G_GNUC_PURE; - -G_END_DECLS - -#endif /* !GDATA_YOUTUBE_CONTROL_H */ diff --git a/gdata/services/youtube/gdata-youtube-credit.c b/gdata/services/youtube/gdata-youtube-credit.c index bfaf7edc..9ce5cb45 100644 --- a/gdata/services/youtube/gdata-youtube-credit.c +++ b/gdata/services/youtube/gdata-youtube-credit.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009–2010, 2015 <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 @@ -28,6 +28,8 @@ * online documentation</ulink>. * * Since: 0.4.0 + * Deprecated: UNRELEASED: This is no longer supported by Google. There is no + * replacement. **/ #include <glib.h> @@ -37,6 +39,8 @@ #include "gdata-parsable.h" #include "gdata-parser.h" +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + static void gdata_youtube_credit_finalize (GObject *object); static void gdata_youtube_credit_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); @@ -78,6 +82,8 @@ gdata_youtube_credit_class_init (GDataYouTubeCreditClass *klass) * YouTube documentation</ulink>. * * Since: 0.4.0 + * Deprecated: UNRELEASED: This is no longer supported by Google. There + * is no replacement. **/ g_object_class_install_property (gobject_class, PROP_ENTITY_TYPE, g_param_spec_string ("entity-type", @@ -160,6 +166,8 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces) * Return value: the type of the credited user (e.g. %GDATA_YOUTUBE_CREDIT_ENTITY_PARTNER), or %NULL * * Since: 0.4.0 + * Deprecated: UNRELEASED: This is no longer supported by Google. There is no + * replacement. **/ const gchar * gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self) @@ -167,3 +175,5 @@ gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self) g_return_val_if_fail (GDATA_IS_YOUTUBE_CREDIT (self), NULL); return self->priv->entity_type; } + +G_GNUC_END_IGNORE_DEPRECATIONS diff --git a/gdata/services/youtube/gdata-youtube-credit.h b/gdata/services/youtube/gdata-youtube-credit.h index b140ff11..4462d957 100644 --- a/gdata/services/youtube/gdata-youtube-credit.h +++ b/gdata/services/youtube/gdata-youtube-credit.h @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009–2010, 2015 <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 @@ -28,12 +28,16 @@ G_BEGIN_DECLS +#ifndef LIBGDATA_DISABLE_DEPRECATED + /** * GDATA_YOUTUBE_CREDIT_ENTITY_PARTNER: * * The credited entity is a YouTube partner. * * Since: 0.7.0 + * Deprecated: UNRELEASED: This is no longer supported by Google. There is no + * replacement. **/ #define GDATA_YOUTUBE_CREDIT_ENTITY_PARTNER "partner" @@ -44,7 +48,7 @@ G_BEGIN_DECLS #define GDATA_IS_YOUTUBE_CREDIT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_CREDIT)) #define GDATA_YOUTUBE_CREDIT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_CREDIT, GDataYouTubeCreditClass)) -typedef struct _GDataYouTubeCreditPrivate GDataYouTubeCreditPrivate; +typedef struct _GDataYouTubeCreditPrivate GDataYouTubeCreditPrivate G_GNUC_DEPRECATED; /** * GDataYouTubeCredit: @@ -52,11 +56,15 @@ typedef struct _GDataYouTubeCreditPrivate GDataYouTubeCreditPrivate; * All the fields in the #GDataYouTubeCredit structure are private and should never be accessed directly. * * Since: 0.4.0 + * Deprecated: UNRELEASED: This is no longer supported by Google. There is no + * replacement. **/ typedef struct { + G_GNUC_BEGIN_IGNORE_DEPRECATIONS GDataMediaCredit parent; GDataYouTubeCreditPrivate *priv; -} GDataYouTubeCredit; + G_GNUC_END_IGNORE_DEPRECATIONS +} GDataYouTubeCredit G_GNUC_DEPRECATED; /** * GDataYouTubeCreditClass: @@ -64,19 +72,27 @@ typedef struct { * All the fields in the #GDataYouTubeCreditClass structure are private and should never be accessed directly. * * Since: 0.4.0 + * Deprecated: UNRELEASED: This is no longer supported by Google. There is no + * replacement. **/ typedef struct { + G_GNUC_BEGIN_IGNORE_DEPRECATIONS /*< private >*/ GDataMediaCreditClass parent; + G_GNUC_END_IGNORE_DEPRECATIONS /*< private >*/ /* Padding for future expansion */ void (*_g_reserved0) (void); void (*_g_reserved1) (void); -} GDataYouTubeCreditClass; +} GDataYouTubeCreditClass G_GNUC_DEPRECATED; + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +GType gdata_youtube_credit_get_type (void) G_GNUC_CONST G_GNUC_DEPRECATED; +const gchar *gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self) G_GNUC_PURE G_GNUC_DEPRECATED; +G_GNUC_END_IGNORE_DEPRECATIONS -GType gdata_youtube_credit_get_type (void) G_GNUC_CONST; -const gchar *gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self) G_GNUC_PURE; +#endif /* !LIBGDATA_DISABLE_DEPRECATED */ G_END_DECLS diff --git a/gdata/services/youtube/gdata-youtube-group.c b/gdata/services/youtube/gdata-youtube-group.c deleted file mode 100644 index 921fbdd0..00000000 --- a/gdata/services/youtube/gdata-youtube-group.c +++ /dev/null @@ -1,265 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ -/* - * GData Client - * Copyright (C) Philip Withnall 2009–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-group - * @short_description: YouTube group element - * @stability: Stable - * @include: gdata/services/youtube/gdata-youtube-group.h - * - * #GDataYouTubeGroup represents the YouTube-specific customizations to #GDataMediaGroup. For more information, - * see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:group"> - * online documentation</ulink>. - */ - -#include <glib.h> -#include <libxml/parser.h> - -#include "gdata-youtube-group.h" -#include "gdata-parsable.h" -#include "gdata-parser.h" -#include "gdata-private.h" -#include "gdata-youtube-enums.h" -#include "gdata-youtube-content.h" -#include "gdata-youtube-credit.h" - -static void gdata_youtube_group_finalize (GObject *object); -static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error); -static void get_xml (GDataParsable *parsable, GString *xml_string); -static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); - -struct _GDataYouTubeGroupPrivate { - guint duration; - gboolean is_private; - gint64 uploaded; - gchar *video_id; - gchar *aspect_ratio; -}; - -G_DEFINE_TYPE (GDataYouTubeGroup, gdata_youtube_group, GDATA_TYPE_MEDIA_GROUP) - -static void -gdata_youtube_group_class_init (GDataYouTubeGroupClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass); - - g_type_class_add_private (klass, sizeof (GDataYouTubeGroupPrivate)); - - gobject_class->finalize = gdata_youtube_group_finalize; - - parsable_class->parse_xml = parse_xml; - parsable_class->get_xml = get_xml; - parsable_class->get_namespaces = get_namespaces; -} - -static void -gdata_youtube_group_init (GDataYouTubeGroup *self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroupPrivate); - self->priv->uploaded = -1; -} - -static void -gdata_youtube_group_finalize (GObject *object) -{ - GDataYouTubeGroupPrivate *priv = GDATA_YOUTUBE_GROUP (object)->priv; - - g_free (priv->video_id); - - /* Chain up to the parent class */ - G_OBJECT_CLASS (gdata_youtube_group_parent_class)->finalize (object); -} - -static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) -{ - gboolean success; - GDataYouTubeGroup *self = GDATA_YOUTUBE_GROUP (parsable); - - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - - if (gdata_parser_is_namespace (node, "http://search.yahoo.com/mrss/") == TRUE && - (gdata_parser_object_from_element_setter (node, "content", P_REQUIRED, GDATA_TYPE_YOUTUBE_CONTENT, - _gdata_media_group_add_content, self, &success, error) == TRUE || - gdata_parser_object_from_element_setter (node, "credit", P_REQUIRED, GDATA_TYPE_YOUTUBE_CREDIT, - _gdata_media_group_set_credit, self, &success, error) == TRUE)) { - return success; - } else if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007") == TRUE) { - if (gdata_parser_string_from_element (node, "videoid", P_NO_DUPES, &(self->priv->video_id), &success, error) == TRUE || - gdata_parser_string_from_element (node, "aspectRatio", P_REQUIRED | P_NO_DUPES, - &(self->priv->aspect_ratio), &success, error) == TRUE || - gdata_parser_int64_time_from_element (node, "uploaded", P_REQUIRED | P_NO_DUPES, - &(self->priv->uploaded), &success, error) == TRUE) { - return success; - } else if (xmlStrcmp (node->name, (xmlChar*) "duration") == 0) { - /* yt:duration */ - xmlChar *duration = xmlGetProp (node, (xmlChar*) "seconds"); - if (duration == NULL) - return gdata_parser_error_required_property_missing (node, "seconds", error); - - self->priv->duration = g_ascii_strtoull ((gchar*) duration, NULL, 10); - xmlFree (duration); - } else if (xmlStrcmp (node->name, (xmlChar*) "private") == 0) { - /* yt:private */ - gdata_youtube_group_set_is_private (self, TRUE); - } else { - return GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->parse_xml (parsable, doc, node, user_data, error); - } - } else { - return GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->parse_xml (parsable, doc, node, user_data, error); - } - - G_GNUC_END_IGNORE_DEPRECATIONS - - return TRUE; -} - -static void -get_xml (GDataParsable *parsable, GString *xml_string) -{ - GDataYouTubeGroupPrivate *priv = GDATA_YOUTUBE_GROUP (parsable)->priv; - - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->get_xml (parsable, xml_string); - - if (priv->is_private == TRUE) - g_string_append (xml_string, "<yt:private/>"); - if (priv->aspect_ratio != NULL) - gdata_parser_string_append_escaped (xml_string, "<yt:aspectratio>", priv->aspect_ratio, "</yt:aspectratio>"); -} - -static void -get_namespaces (GDataParsable *parsable, GHashTable *namespaces) -{ - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->get_namespaces (parsable, namespaces); - - g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007"); -} - -/** - * gdata_youtube_group_get_duration: - * @self: a #GDataYouTubeGroup - * - * Gets the #GDataYouTubeGroup:duration property. - * - * Return value: the video duration in seconds, or <code class="literal">0</code> if unknown - **/ -guint -gdata_youtube_group_get_duration (GDataYouTubeGroup *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), 0); - return self->priv->duration; -} - -/** - * gdata_youtube_group_is_private: - * @self: a #GDataYouTubeGroup - * - * Gets the #GDataYouTubeGroup:is-private property. - * - * Return value: %TRUE if the video is private, %FALSE otherwise - **/ -gboolean -gdata_youtube_group_is_private (GDataYouTubeGroup *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), FALSE); - return self->priv->is_private; -} - -/** - * gdata_youtube_group_set_is_private: - * @self: a #GDataYouTubeGroup - * @is_private: whether the video is private - * - * Sets the #GDataYouTubeGroup:is-private property to decide whether the video is publicly viewable. - **/ -void -gdata_youtube_group_set_is_private (GDataYouTubeGroup *self, gboolean is_private) -{ - g_return_if_fail (GDATA_IS_YOUTUBE_GROUP (self)); - self->priv->is_private = is_private; -} - -/** - * gdata_youtube_group_get_uploaded: - * @self: a #GDataYouTubeGroup - * - * Gets the #GDataYouTubeGroup:uploaded property. If the property is unset, <code class="literal">-1</code> will be returned. - * - * Return value: the UNIX timestamp for the time the group was uploaded, or <code class="literal">-1</code> - **/ -gint64 -gdata_youtube_group_get_uploaded (GDataYouTubeGroup *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), -1); - return self->priv->uploaded; -} - -/** - * gdata_youtube_group_get_video_id: - * @self: a #GDataYouTubeGroup - * - * Gets the #GDataYouTubeGroup:video-id property. - * - * Return value: the video's unique and permanent ID - **/ -const gchar * -gdata_youtube_group_get_video_id (GDataYouTubeGroup *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), NULL); - return self->priv->video_id; -} - -/** - * gdata_youtube_group_get_aspect_ratio: - * @self: a #GDataYouTubeGroup - * - * Gets the #GDataYouTubeGroup:aspect-ratio property. - * - * Return value: the aspect ratio property, or %NULL - * - * Since: 0.4.0 - **/ -const gchar * -gdata_youtube_group_get_aspect_ratio (GDataYouTubeGroup *self) -{ - g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), NULL); - return self->priv->aspect_ratio; -} - -/** - * gdata_youtube_group_set_aspect_ratio: - * @self: a #GDataYouTubeGroup - * @aspect_ratio: (allow-none): the aspect ratio property, or %NULL - * - * Sets the #GDataYouTubeGroup:aspect-ratio property to decide the video aspect ratio. - * If @aspect_ratio is %NULL, the property will be unset. - * - * Since: 0.4.0 - **/ -void -gdata_youtube_group_set_aspect_ratio (GDataYouTubeGroup *self, const gchar *aspect_ratio) -{ - g_return_if_fail (GDATA_IS_YOUTUBE_GROUP (self)); - - g_free (self->priv->aspect_ratio); - self->priv->aspect_ratio = g_strdup (aspect_ratio); -} diff --git a/gdata/services/youtube/gdata-youtube-group.h b/gdata/services/youtube/gdata-youtube-group.h deleted file mode 100644 index c48f33c9..00000000 --- a/gdata/services/youtube/gdata-youtube-group.h +++ /dev/null @@ -1,80 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ -/* - * GData Client - * Copyright (C) Philip Withnall 2009 <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_GROUP_H -#define GDATA_YOUTUBE_GROUP_H - -#include <glib.h> -#include <glib-object.h> - -#include <gdata/gdata-parsable.h> -#include <gdata/media/gdata-media-group.h> -#include <gdata/services/youtube/gdata-youtube-video.h> - -G_BEGIN_DECLS - -#define GDATA_TYPE_YOUTUBE_GROUP (gdata_youtube_group_get_type ()) -#define GDATA_YOUTUBE_GROUP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroup)) -#define GDATA_YOUTUBE_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroupClass)) -#define GDATA_IS_YOUTUBE_GROUP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_YOUTUBE_GROUP)) -#define GDATA_IS_YOUTUBE_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_GROUP)) -#define GDATA_YOUTUBE_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroupClass)) - -typedef struct _GDataYouTubeGroupPrivate GDataYouTubeGroupPrivate; - -/** - * GDataYouTubeGroup: - * - * All the fields in the #GDataYouTubeGroup structure are private and should never be accessed directly. - **/ -typedef struct { - GDataMediaGroup parent; - GDataYouTubeGroupPrivate *priv; -} GDataYouTubeGroup; - -/** - * GDataYouTubeGroupClass: - * - * All the fields in the #GDataYouTubeGroupClass structure are private and should never be accessed directly. - * - * Since: 0.4.0 - **/ -typedef struct { - /*< private >*/ - GDataMediaGroupClass parent; - - /*< private >*/ - /* Padding for future expansion */ - void (*_g_reserved0) (void); - void (*_g_reserved1) (void); -} GDataYouTubeGroupClass; - -GType gdata_youtube_group_get_type (void) G_GNUC_CONST; - -guint gdata_youtube_group_get_duration (GDataYouTubeGroup *self) G_GNUC_PURE; -gboolean gdata_youtube_group_is_private (GDataYouTubeGroup *self) G_GNUC_PURE; -void gdata_youtube_group_set_is_private (GDataYouTubeGroup *self, gboolean is_private); -gint64 gdata_youtube_group_get_uploaded (GDataYouTubeGroup *self); -const gchar *gdata_youtube_group_get_video_id (GDataYouTubeGroup *self) G_GNUC_PURE; -const gchar *gdata_youtube_group_get_aspect_ratio (GDataYouTubeGroup *self) G_GNUC_PURE; -void gdata_youtube_group_set_aspect_ratio (GDataYouTubeGroup *self, const gchar *aspect_ratio); - -G_END_DECLS - -#endif /* !GDATA_YOUTUBE_GROUP_H */ diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index 7e54fafa..06ee9b97 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2008–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2008–2010, 2015 <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 @@ -24,9 +24,16 @@ * @include: gdata/services/youtube/gdata-youtube-service.h * * #GDataYouTubeService is a subclass of #GDataService for communicating with the GData API of YouTube. It supports querying for and - * uploading videos. + * uploading videos using version 3 of the API. * - * For more details of YouTube's GData API, see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html"> + * The YouTube API supports returning different sets of properties for + * #GDataYouTubeVideos depending on the specific query. For search results, only + * ‘snippet’ properties are returned (including #GDataEntry:title, + * #GDataEntry:summary and the set of thumbnails). For querying single videos, + * a more complete set of properties are returned — so use + * gdata_service_query_single_entry_async() to get further details on a video. + * + * For more details of YouTube's GData API, see the <ulink type="http" url="https://developers.google.com/youtube/v3/docs/"> * online documentation</ulink>. * * <example> @@ -253,7 +260,7 @@ #include "gdata-youtube-category.h" #include "gdata-batchable.h" -/* Standards reference here: http://code.google.com/apis/youtube/2.0/reference.html */ +/* Standards reference here: https://developers.google.com/youtube/v3/docs/ */ GQuark gdata_youtube_service_error_quark (void) @@ -278,7 +285,9 @@ enum { PROP_DEVELOPER_KEY = 1 }; -_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube, "youtube", "http://gdata.youtube.com") +/* Reference: https://developers.google.com/youtube/v3/guides/authentication */ +_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube, "youtube", + "https://www.googleapis.com/auth/youtube") G_DEFINE_TYPE_WITH_CODE (GDataYouTubeService, gdata_youtube_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL)) static void @@ -302,7 +311,11 @@ gdata_youtube_service_class_init (GDataYouTubeServiceClass *klass) * GDataYouTubeService:developer-key: * * The developer key your application has registered with the YouTube API. For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Developer_Key">online documentation</ulink>. + * url="https://developers.google.com/youtube/registering_an_application">online documentation</ulink>. + * + * With the port from v2 to v3 of the YouTube API in libgdata + * UNRELEASED, it might be necessary to update your application’s + * developer key. **/ g_object_class_install_property (gobject_class, PROP_DEVELOPER_KEY, g_param_spec_string ("developer-key", @@ -361,161 +374,220 @@ gdata_youtube_service_set_property (GObject *object, guint property_id, const GV } static void -append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message) +append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, + SoupMessage *message) { GDataYouTubeServicePrivate *priv = GDATA_YOUTUBE_SERVICE (self)->priv; - gchar *key_header; g_assert (message != NULL); - /* Dev key and client headers */ - key_header = g_strdup_printf ("key=%s", priv->developer_key); - soup_message_headers_append (message->request_headers, "X-GData-Key", key_header); - g_free (key_header); + if (priv->developer_key != NULL && + !gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)), + get_youtube_authorization_domain ())) { + const gchar *query; + SoupURI *uri; + + uri = soup_message_get_uri (message); + query = soup_uri_get_query (uri); + + /* Set the key on every unauthorised request: + * https://developers.google.com/youtube/v3/docs/standard_parameters#key */ + if (query != NULL) { + GString *new_query; + + new_query = g_string_new (query); + + g_string_append (new_query, "&key="); + g_string_append_uri_escaped (new_query, + priv->developer_key, NULL, + FALSE); + + soup_uri_set_query (uri, new_query->str); + g_string_free (new_query, TRUE); + } + } /* Chain up to the parent class */ GDATA_SERVICE_CLASS (gdata_youtube_service_parent_class)->append_query_headers (self, domain, message); } +/* Reference: https://developers.google.com/youtube/v3/docs/errors + * + * Example response: + * { + * "error": { + * "errors": [ + * { + * "domain": "youtube.parameter", + * "reason": "missingRequiredParameter", + * "message": "No filter selected.", + * "locationType": "parameter", + * "location": "" + * } + * ], + * "code": 400, + * "message": "No filter selected." + * } + * } + */ +/* FIXME: Factor this out into a common JSON error parser helper which simply + * takes a map of expected error codes. */ static void -parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase, const gchar *response_body, - gint length, GError **error) +parse_error_response (GDataService *self, GDataOperationType operation_type, + guint status, const gchar *reason_phrase, + const gchar *response_body, gint length, GError **error) { - xmlDoc *doc; - xmlNode *node; + JsonParser *parser = NULL; /* owned */ + JsonReader *reader = NULL; /* owned */ + gint i; + GError *child_error = NULL; - if (response_body == NULL) + if (response_body == NULL) { goto parent; + } - if (length == -1) + if (length == -1) { length = strlen (response_body); + } - /* Parse the XML */ - doc = xmlReadMemory (response_body, length, "/dev/null", NULL, 0); - if (doc == NULL) + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, response_body, length, + &child_error)) { goto parent; + } + + reader = json_reader_new (json_parser_get_root (parser)); - /* Get the root element */ - node = xmlDocGetRootElement (doc); - if (node == NULL) { - /* XML document's empty; chain up to the parent class */ - xmlFreeDoc (doc); + /* Check that the outermost node is an object. */ + if (!json_reader_is_object (reader)) { goto parent; } - if (xmlStrcmp (node->name, (xmlChar*) "errors") != 0) { - /* No <errors> element (required); chain up to the parent class */ - xmlFreeDoc (doc); + /* Grab the ‘error’ member, then its ‘errors’ member. */ + if (!json_reader_read_member (reader, "error") || + !json_reader_is_object (reader) || + !json_reader_read_member (reader, "errors") || + !json_reader_is_array (reader)) { goto parent; } - /* Parse the actual errors */ - node = node->children; - while (node != NULL) { - xmlChar *domain = NULL, *code = NULL, *location = NULL; - xmlNode *child_node = node->children; + /* Parse each of the errors. Return the first one, and print out any + * others. */ + for (i = 0; i < json_reader_count_elements (reader); i++) { + const gchar *domain, *reason, *message, *extended_help; + const gchar *location_type, *location; - if (node->type == XML_TEXT_NODE) { - /* Skip text nodes; they're all whitespace */ - node = node->next; - continue; + /* Parse the error. */ + if (!json_reader_read_element (reader, i) || + !json_reader_is_object (reader)) { + goto parent; } - /* Get the error data */ - while (child_node != NULL) { - if (child_node->type == XML_TEXT_NODE) { - /* Skip text nodes; they're all whitespace */ - child_node = child_node->next; - continue; - } + json_reader_read_member (reader, "domain"); + domain = json_reader_get_string_value (reader); + json_reader_end_member (reader); - if (xmlStrcmp (child_node->name, (xmlChar*) "domain") == 0) - domain = xmlNodeListGetString (doc, child_node->children, TRUE); - else if (xmlStrcmp (child_node->name, (xmlChar*) "code") == 0) - code = xmlNodeListGetString (doc, child_node->children, TRUE); - else if (xmlStrcmp (child_node->name, (xmlChar*) "location") == 0) - location = xmlNodeListGetString (doc, child_node->children, TRUE); - else if (xmlStrcmp (child_node->name, (xmlChar*) "internalReason") != 0) { - /* Unknown element (ignore internalReason) */ - g_message ("Unhandled <error/%s> element.", child_node->name); - - xmlFree (domain); - xmlFree (code); - xmlFree (location); - xmlFreeDoc (doc); - goto check_error; - } + json_reader_read_member (reader, "reason"); + reason = json_reader_get_string_value (reader); + json_reader_end_member (reader); - child_node = child_node->next; - } + json_reader_read_member (reader, "message"); + message = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "extendedHelp"); + extended_help = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "locationType"); + location_type = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "location"); + location = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + /* End the error element. */ + json_reader_end_element (reader); /* Create an error message, but only for the first error */ if (error == NULL || *error == NULL) { - /* See http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Error_responses */ - if (xmlStrcmp (domain, (xmlChar*) "yt:service") == 0) { - if (xmlStrcmp (code, (xmlChar*) "disabled_in_maintenance_mode") == 0) { - /* Service disabled */ - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_UNAVAILABLE, - _("This service is not available at the moment.")); - } else if (xmlStrcmp (code, (xmlChar*) "youtube_signup_required") == 0) { - /* Tried to authenticate with a Google Account which hasn't yet had a YouTube channel created for it. */ - g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, GDATA_YOUTUBE_SERVICE_ERROR_CHANNEL_REQUIRED, - /* Translators: the parameter is a URI. */ - _("Your Google Account must be associated with a YouTube channel to do this. Visit %s to create one."), - "https://www.youtube.com/create_channel"); - } else { - /* Protocol error */ - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - _("Unknown error code \"%s\" in domain \"%s\" received with location \"%s\"."), - code, domain, location); - } - } else if (xmlStrcmp (domain, (xmlChar*) "yt:authentication") == 0) { + if (g_strcmp0 (domain, "usageLimits") == 0 && + g_strcmp0 (reason, + "dailyLimitExceededUnreg") == 0) { + /* Daily Limit for Unauthenticated Use + * Exceeded. */ + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_API_QUOTA_EXCEEDED, + _("You have made too many API " + "calls recently. Please wait a " + "few minutes and try again.")); + } else if (g_strcmp0 (reason, + "rateLimitExceeded") == 0) { + g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, + GDATA_YOUTUBE_SERVICE_ERROR_ENTRY_QUOTA_EXCEEDED, + _("You have exceeded your entry " + "quota. Please delete some " + "entries and try again.")); + } else if (g_strcmp0 (domain, "global") == 0 && + (g_strcmp0 (reason, "authError") == 0 || + g_strcmp0 (reason, "required") == 0)) { /* Authentication problem */ - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, - _("You must be authenticated to do this.")); - } else if (xmlStrcmp (domain, (xmlChar*) "yt:quota") == 0) { - /* Quota errors */ - if (xmlStrcmp (code, (xmlChar*) "too_many_recent_calls") == 0) { - g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, GDATA_YOUTUBE_SERVICE_ERROR_API_QUOTA_EXCEEDED, - _("You have made too many API calls recently. Please wait a few minutes and try again.")); - } else if (xmlStrcmp (code, (xmlChar*) "too_many_entries") == 0) { - g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, GDATA_YOUTUBE_SERVICE_ERROR_ENTRY_QUOTA_EXCEEDED, - _("You have exceeded your entry quota. Please delete some entries and try again.")); - } else { - /* Protocol error */ - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - /* Translators: the first parameter is an error code, which is a coded string. - * The second parameter is an error domain, which is another coded string. - * The third parameter is the location of the error, which is either a URI or an XPath. */ - _("Unknown error code \"%s\" in domain \"%s\" received with location \"%s\"."), - code, domain, location); - } + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, + _("You must be authenticated to " + "do this.")); + } else if (g_strcmp0 (reason, + "youtubeSignupRequired") == 0) { + /* Tried to authenticate with a Google Account which hasn't yet had a YouTube channel created for it. */ + g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, + GDATA_YOUTUBE_SERVICE_ERROR_CHANNEL_REQUIRED, + /* Translators: the parameter is a URI. */ + _("Your Google Account must be " + "associated with a YouTube " + "channel to do this. Visit %s " + "to create one."), + "https://www.youtube.com/create_channel"); } else { - /* Unknown or validation (protocol) error */ - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - _("Unknown error code \"%s\" in domain \"%s\" received with location \"%s\"."), - code, domain, location); + /* Unknown or validation (protocol) error. Fall + * back to working off the HTTP status code. */ + g_warning ("Unknown error code ‘%s’ in domain " + "‘%s’ received with location type " + "‘%s’, location ‘%s’, extended help " + "‘%s’ and message ‘%s’.", + reason, domain, location_type, + location, extended_help, message); + + goto parent; } } else { - /* For all errors after the first, log the error in the terminal */ - g_debug ("Error message received in response: code \"%s\", domain \"%s\", location \"%s\".", code, domain, location); + /* For all errors after the first, log the error in the + * terminal. */ + g_debug ("Error message received in response: domain " + "‘%s’, reason ‘%s’, extended help ‘%s’, " + "message ‘%s’, location type ‘%s’, location " + "‘%s’.", + domain, reason, extended_help, message, + location_type, location); } + } - xmlFree (domain); - xmlFree (code); - xmlFree (location); + /* End the ‘errors’ and ‘error’ members. */ + json_reader_end_element (reader); + json_reader_end_element (reader); - node = node->next; - } + g_clear_object (&reader); + g_clear_object (&parser); -check_error: - /* Ensure we're actually set an error message */ - if (error != NULL && *error == NULL) - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("Unknown and unparsable error received.")); + /* Ensure we’ve actually set an error message. */ + g_assert (error == NULL || *error != NULL); return; parent: + g_clear_object (&reader); + g_clear_object (&parser); + /* Chain up to the parent class */ GDATA_SERVICE_CLASS (gdata_youtube_service_parent_class)->parse_error_response (self, operation_type, status, reason_phrase, response_body, length, error); @@ -534,7 +606,7 @@ get_authorization_domains (void) * * Creates a new #GDataYouTubeService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user. * The @developer_key must be unique for your application, and as - * <ulink type="http" url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Developer_Key">registered with Google</ulink>. + * <ulink type="http" url="https://developers.google.com/youtube/registering_an_application">registered with Google</ulink>. * * Return value: a new #GDataYouTubeService, or %NULL; unref with g_object_unref() * @@ -571,30 +643,42 @@ gdata_youtube_service_get_primary_authorization_domain (void) return get_youtube_authorization_domain (); } -static const gchar * +static gchar * standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type) { switch (feed_type) { + case GDATA_YOUTUBE_MOST_POPULAR_FEED: + return g_strdup ("https://www.googleapis.com/youtube/v3/videos" + "?part=snippet" + "&chart=mostPopular"); case GDATA_YOUTUBE_TOP_RATED_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/top_rated"; case GDATA_YOUTUBE_TOP_FAVORITES_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/top_favorites"; case GDATA_YOUTUBE_MOST_VIEWED_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/most_viewed"; - case GDATA_YOUTUBE_MOST_POPULAR_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/most_popular"; case GDATA_YOUTUBE_MOST_RECENT_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/most_recent"; case GDATA_YOUTUBE_MOST_DISCUSSED_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/most_discussed"; case GDATA_YOUTUBE_MOST_LINKED_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/most_linked"; case GDATA_YOUTUBE_MOST_RESPONDED_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/most_responded"; case GDATA_YOUTUBE_RECENTLY_FEATURED_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/recently_featured"; - case GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: - return "https://gdata.youtube.com/feeds/api/standardfeeds/watch_on_mobile"; + case GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: { + gchar *date, *out; + GTimeVal tv; + + /* All feed types except MOST_POPULAR have been deprecated for + * a while, and fall back to MOST_POPULAR on the server anyway. + * See: https://developers.google.com/youtube/2.0/developers_guide_protocol_video_feeds#Standard_feeds */ + g_get_current_time (&tv); + tv.tv_sec -= 24 * 60 * 60; /* 1 day ago */ + date = g_time_val_to_iso8601 (&tv); + out = g_strconcat ("https://www.googleapis.com/youtube/v3/videos" + "?part=snippet" + "&chart=mostPopular" + "&publishedAfter=", + date, + NULL); + g_free (date); + + return out; + } default: g_assert_not_reached (); } @@ -612,6 +696,11 @@ standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type) * * Queries the service's standard @feed_type feed to build a #GDataFeed. * + * Note that with the port from v2 to v3 of the YouTube API in libgdata + * UNRELEASED, all feed types except %GDATA_YOUTUBE_MOST_POPULAR_FEED have been + * deprecated. Other feed types will now transparently return + * %GDATA_YOUTUBE_MOST_POPULAR_FEED, limited to the past 24 hours. + * * Parameters and errors are as for gdata_service_query(). * * Return value: (transfer full): a #GDataFeed of query results, or %NULL; unref with g_object_unref() @@ -621,14 +710,24 @@ gdata_youtube_service_query_standard_feed (GDataYouTubeService *self, GDataYouTu GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { + gchar *query_uri; + GDataFeed *feed; + g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL); g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* TODO: Support the "time" parameter, as well as category- and region-specific feeds */ - return gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), standard_feed_type_to_feed_uri (feed_type), query, - GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error); + query_uri = standard_feed_type_to_feed_uri (feed_type); + feed = gdata_service_query (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + query_uri, query, GDATA_TYPE_YOUTUBE_VIDEO, + cancellable, progress_callback, + progress_user_data, error); + g_free (query_uri); + + return feed; } /** @@ -660,14 +759,22 @@ gdata_youtube_service_query_standard_feed_async (GDataYouTubeService *self, GDat GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data) { + gchar *query_uri; + g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self)); g_return_if_fail (query == NULL || GDATA_IS_QUERY (query)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); - gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), standard_feed_type_to_feed_uri (feed_type), query, - GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, destroy_progress_user_data, - callback, user_data); + query_uri = standard_feed_type_to_feed_uri (feed_type); + gdata_service_query_async (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + query_uri, query, GDATA_TYPE_YOUTUBE_VIDEO, + cancellable, progress_callback, + progress_user_data, + destroy_progress_user_data, callback, + user_data); + g_free (query_uri); } /** @@ -696,8 +803,14 @@ gdata_youtube_service_query_videos (GDataYouTubeService *self, GDataQuery *query g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), "https://gdata.youtube.com/feeds/api/videos", query, - GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error); + return gdata_service_query (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + "https://www.googleapis.com/youtube/v3/search" + "?part=snippet" + "&type=video", + query, GDATA_TYPE_YOUTUBE_VIDEO, + cancellable, progress_callback, + progress_user_data, error); } /** @@ -733,9 +846,15 @@ gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQuery g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); - gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), "https://gdata.youtube.com/feeds/api/videos", query, - GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, destroy_progress_user_data, - callback, user_data); + gdata_service_query_async (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + "https://www.googleapis.com/youtube/v3/search" + "?part=snippet" + "&type=video", + query, GDATA_TYPE_YOUTUBE_VIDEO, cancellable, + progress_callback, progress_user_data, + destroy_progress_user_data, callback, + user_data); } /** @@ -750,8 +869,7 @@ gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQuery * * Queries the service for videos related to @video. The algorithm determining which videos are related is on the server side. * - * If @video does not have a link with rel value <literal>http://gdata.youtube.com/schemas/2007#video.related</literal>, a - * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error will be thrown. Parameters and other errors are as for gdata_service_query(). + * Parameters and other errors are as for gdata_service_query(). * * Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref() **/ @@ -760,7 +878,6 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { - GDataLink *related_link; GDataFeed *feed; gchar *uri; @@ -770,19 +887,17 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - /* See if the video already has a rel="http://gdata.youtube.com/schemas/2007#video.related" link */ - related_link = gdata_entry_look_up_link (GDATA_ENTRY (video), "http://gdata.youtube.com/schemas/2007#video.related"); - if (related_link == NULL) { - /* Erroring out is probably the safest thing to do */ - g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - _("The video did not have a related videos <link>.")); - return NULL; - } - /* Execute the query */ - uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (related_link)); - feed = gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), uri, query, - GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error); + uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/search" + "?part=snippet" + "&type=video" + "&relatedToVideoId=%s", + gdata_entry_get_id (GDATA_ENTRY (video))); + feed = gdata_service_query (GDATA_SERVICE (self), + get_youtube_authorization_domain (), uri, + query, GDATA_TYPE_YOUTUBE_VIDEO, + cancellable, progress_callback, + progress_user_data, error); g_free (uri); return feed; @@ -817,7 +932,6 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data) { - GDataLink *related_link; gchar *uri; g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self)); @@ -826,23 +940,17 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); - /* See if the video already has a rel="http://gdata.youtube.com/schemas/2007#video.related" link */ - related_link = gdata_entry_look_up_link (GDATA_ENTRY (video), "http://gdata.youtube.com/schemas/2007#video.related"); - if (related_link == NULL) { - /* Erroring out is probably the safest thing to do */ - GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_query_async); - g_simple_async_result_set_error (result, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, "%s", - _("The video did not have a related videos <link>.")); - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); - - return; - } - - uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (related_link)); - gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), uri, query, - GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, - destroy_progress_user_data, callback, user_data); + uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/search" + "?part=snippet" + "&type=video" + "&relatedToVideoId=%s", + gdata_entry_get_id (GDATA_ENTRY (video))); + gdata_service_query_async (GDATA_SERVICE (self), + get_youtube_authorization_domain (), uri, + query, GDATA_TYPE_YOUTUBE_VIDEO, cancellable, + progress_callback, progress_user_data, + destroy_progress_user_data, callback, + user_data); g_free (uri); } @@ -878,6 +986,8 @@ GDataUploadStream * gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, const gchar *slug, const gchar *content_type, GCancellable *cancellable, GError **error) { + GOutputStream *stream = NULL; /* owned */ + g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL); g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (video), NULL); g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); @@ -891,6 +1001,8 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo return NULL; } + /* FIXME: Could be more cunning about domains here; see scope on + * https://developers.google.com/youtube/v3/guides/authentication */ if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)), get_youtube_authorization_domain ()) == FALSE) { g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, @@ -898,10 +1010,23 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo return NULL; } - /* Streaming upload support using GDataUploadStream; automatically handles the XML and multipart stuff for us */ - return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_youtube_authorization_domain (), SOUP_METHOD_POST, - "https://uploads.gdata.youtube.com/feeds/api/users/default/uploads", - GDATA_ENTRY (video), slug, content_type, cancellable)); + /* FIXME: Add support for resumable uploads. That means a new + * gdata_youtube_service_upload_video_resumable() method a la + * Documents. */ + stream = gdata_upload_stream_new (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + SOUP_METHOD_POST, + "https://www.googleapis.com/upload/youtube/v3/videos" + "?part=snippet,status," + "recordingDetails," + "contentDetails,id," + "statistics," + "processingDetails", + GDATA_ENTRY (video), slug, + content_type, + cancellable); + + return GDATA_UPLOAD_STREAM (stream); } /** @@ -936,7 +1061,10 @@ gdata_youtube_service_finish_video_upload (GDataYouTubeService *self, GDataUploa return NULL; /* Parse the response to produce a GDataYouTubeVideo */ - return GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_VIDEO, response_body, (gint) response_length, error)); + return GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_json (GDATA_TYPE_YOUTUBE_VIDEO, + response_body, + (gint) response_length, + error)); } /** @@ -972,6 +1100,8 @@ gdata_youtube_service_get_developer_key (GDataYouTubeService *self) GDataAPPCategories * gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *cancellable, GError **error) { + const gchar *locale; + gchar *uri; SoupMessage *message; GDataAPPCategories *categories; @@ -979,16 +1109,31 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c 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), get_youtube_authorization_domain (), - "https://gdata.youtube.com/schemas/2007/categories.cat", NULL, cancellable, error); + /* Download the category list. Note that this is (service) + * locale-dependent, and a locale must always be specified. */ + locale = gdata_service_get_locale (GDATA_SERVICE (self)); + if (locale == NULL) { + locale = "US"; + } + + uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/videoCategories" + "?part=snippet" + "®ionCode=%s", + locale); + message = _gdata_service_query (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + uri, NULL, cancellable, error); + g_free (uri); + 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)); + categories = GDATA_APP_CATEGORIES (_gdata_parsable_new_from_json (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; diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h index 999fa4c7..23e6ef5e 100644 --- a/gdata/services/youtube/gdata-youtube-service.h +++ b/gdata/services/youtube/gdata-youtube-service.h @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2008-2009 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2008-2009, 2015 <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 @@ -33,20 +33,51 @@ G_BEGIN_DECLS /** * GDataYouTubeStandardFeedType: - * @GDATA_YOUTUBE_TOP_RATED_FEED: This feed contains the most highly rated YouTube videos. - * @GDATA_YOUTUBE_TOP_FAVORITES_FEED: This feed contains videos most frequently flagged as favorite videos. - * @GDATA_YOUTUBE_MOST_VIEWED_FEED: This feed contains the most frequently watched YouTube videos. - * @GDATA_YOUTUBE_MOST_POPULAR_FEED: This feed contains the most popular YouTube videos, selected using an algorithm that combines many - * different signals to determine overall popularity. - * @GDATA_YOUTUBE_MOST_RECENT_FEED: This feed contains the videos most recently submitted to YouTube. - * @GDATA_YOUTUBE_MOST_DISCUSSED_FEED: This feed contains the YouTube videos that have received the most comments. - * @GDATA_YOUTUBE_MOST_LINKED_FEED: This feed contains the YouTube videos that receive the most links from other websites. - * @GDATA_YOUTUBE_MOST_RESPONDED_FEED: This feed contains YouTube videos that receive the most video responses. - * @GDATA_YOUTUBE_RECENTLY_FEATURED_FEED: This feed contains videos recently featured on the YouTube home page or featured videos tab. - * @GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: This feed contains videos suitable for playback on mobile devices. + * @GDATA_YOUTUBE_TOP_RATED_FEED: This feed contains the most highly rated + * YouTube videos. Deprecated: UNRELEASED: Google no longer supports this feed + * type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_TOP_FAVORITES_FEED: This feed contains videos most frequently + * flagged as favorite videos. Deprecated: UNRELEASED: Google no longer + * supports this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_MOST_VIEWED_FEED: This feed contains the most frequently + * watched YouTube videos. Deprecated: UNRELEASED: Google no longer supports + * this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_MOST_POPULAR_FEED: This feed contains the most popular YouTube + * videos, selected using an algorithm that combines many different signals to + * determine overall popularity. As of version UNRELEASED, this is the only + * supported feed type. + * @GDATA_YOUTUBE_MOST_RECENT_FEED: This feed contains the videos most recently + * submitted to YouTube. Deprecated: UNRELEASED: Google no longer supports + * this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_MOST_DISCUSSED_FEED: This feed contains the YouTube videos + * that have received the most comments. Deprecated: UNRELEASED: Google no + * longer supports this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_MOST_LINKED_FEED: This feed contains the YouTube videos that + * receive the most links from other websites. Deprecated: UNRELEASED: Google + * no longer supports this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_MOST_RESPONDED_FEED: This feed contains YouTube videos that + * receive the most video responses. Deprecated: UNRELEASED: Google no longer + * supports this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_RECENTLY_FEATURED_FEED: This feed contains videos recently + * featured on the YouTube home page or featured videos tab. Deprecated: + * UNRELEASED: Google no longer supports this feed type, and it will return + * results equivalent to %GDATA_YOUTUBE_MOST_POPULAR_FEED. + * @GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: This feed contains videos suitable for + * playback on mobile devices. Deprecated: UNRELEASED: Google no longer + * supports this feed type, and it will return results equivalent to + * %GDATA_YOUTUBE_MOST_POPULAR_FEED. * - * Standard feed types for standard feed queries with gdata_youtube_service_query_standard_feed(). For more information, see - * the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Standard_feeds">online documentation</ulink>. + * Standard feed types for standard feed queries with + * gdata_youtube_service_query_standard_feed(). For more information, see the + * <ulink type="http" url="https://developers.google.com/youtube/2.0/developers_guide_protocol_video_feeds#Standard_feeds">online + * documentation</ulink>. **/ typedef enum { GDATA_YOUTUBE_TOP_RATED_FEED, diff --git a/gdata/services/youtube/gdata-youtube-state.c b/gdata/services/youtube/gdata-youtube-state.c index e2da9bb5..4b0b3542 100644 --- a/gdata/services/youtube/gdata-youtube-state.c +++ b/gdata/services/youtube/gdata-youtube-state.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009–2010, 2015 <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 @@ -30,17 +30,12 @@ **/ #include <glib.h> -#include <libxml/parser.h> #include "gdata-youtube-state.h" #include "gdata-parsable.h" -#include "gdata-parser.h" static void gdata_youtube_state_finalize (GObject *object); static void gdata_youtube_state_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 void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); struct _GDataYouTubeStatePrivate { gchar *name; @@ -62,19 +57,12 @@ static void gdata_youtube_state_class_init (GDataYouTubeStateClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass); g_type_class_add_private (klass, sizeof (GDataYouTubeStatePrivate)); gobject_class->get_property = gdata_youtube_state_get_property; gobject_class->finalize = gdata_youtube_state_finalize; - parsable_class->pre_parse_xml = pre_parse_xml; - parsable_class->parse_xml = parse_xml; - parsable_class->get_namespaces = get_namespaces; - parsable_class->element_name = "state"; - parsable_class->element_namespace = "yt"; - /** * GDataYouTubeState:name: * @@ -186,42 +174,6 @@ gdata_youtube_state_get_property (GObject *object, guint property_id, GValue *va } } -static gboolean -pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error) -{ - GDataYouTubeStatePrivate *priv = GDATA_YOUTUBE_STATE (parsable)->priv; - xmlChar *name; - - name = xmlGetProp (root_node, (xmlChar*) "name"); - if (name == NULL || *name == '\0') { - g_free (name); - return gdata_parser_error_required_property_missing (root_node, "name", error); - } - - priv->name = (gchar*) name; - priv->reason_code = (gchar*) xmlGetProp (root_node, (xmlChar*) "reasonCode"); - priv->help_uri = (gchar*) xmlGetProp (root_node, (xmlChar*) "helpUrl"); - priv->message = (gchar*) xmlNodeListGetString (doc, root_node->children, TRUE); - - return TRUE; -} - -static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) -{ - /* Textual content's handled in pre_parse_xml */ - if (node->type != XML_ELEMENT_NODE) - return TRUE; - - return GDATA_PARSABLE_CLASS (gdata_youtube_state_parent_class)->parse_xml (parsable, doc, node, user_data, error); -} - -static void -get_namespaces (GDataParsable *parsable, GHashTable *namespaces) -{ - g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007"); -} - /** * gdata_youtube_state_get_name: * @self: a #GDataYouTubeState diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c index b491a6dc..a679395b 100644 --- a/gdata/services/youtube/gdata-youtube-video.c +++ b/gdata/services/youtube/gdata-youtube-video.c @@ -25,11 +25,14 @@ * * #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"> + * #GDataYouTubeVideo implements #GDataCommentable, allowing comments on videos + * to be queried and added. However, the initial version of the v3 YouTube API + * does not currently support comments, so all #GDataCommentable calls will + * fail. It is hoped that the YouTube API will regain support for comments in + * future. + * + * For more details of YouTube’s GData API, see the + * <ulink type="http" url="https://developers.google.com/youtube/v3/docs/"> * online documentation</ulink>. * * <example> @@ -62,7 +65,7 @@ #include <config.h> #include <glib.h> #include <glib/gi18n-lib.h> -#include <libxml/parser.h> +#include <json-glib/json-glib.h> #include <string.h> #include "gdata-youtube-video.h" @@ -71,25 +74,19 @@ #include "gdata-parser.h" #include "media/gdata-media-category.h" #include "media/gdata-media-thumbnail.h" -#include "gdata-youtube-group.h" #include "gdata-types.h" -#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); static void gdata_youtube_video_finalize (GObject *object); static void gdata_youtube_video_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gdata_youtube_video_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); -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); -static void get_xml (GDataParsable *parsable, GString *xml_string); -static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); +static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error); +static void get_json (GDataParsable *parsable, JsonBuilder *builder); +static const gchar *get_content_type (void); 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; @@ -111,18 +108,31 @@ struct _GDataYouTubeVideoPrivate { gdouble average; } rating; - /* media:group */ - GDataMediaGroup *media_group; /* is actually a GDataYouTubeGroup */ + gchar **keywords; + gchar *player_uri; + gchar **region_restriction_allowed; + gchar **region_restriction_blocked; + GHashTable *content_ratings; /* owned string → owned string */ + GList *thumbnails; /* GDataMediaThumbnail */ + GDataMediaCategory *category; + guint duration; + gboolean is_private; - /* georss:where */ - GDataGeoRSSWhere *georss_where; + /* Location. */ + gdouble latitude; + gdouble longitude; /* Other properties */ - GDataYouTubeControl *youtube_control; + gchar *rejection_reason; + gchar *processing_status; + gchar *upload_status; + gchar *failure_reason; + GDataYouTubeState *upload_state; /* owned */ + gint64 recorded; - /* Comments */ - GDataGDFeedLink *comments_feed_link; + /* State for parse_json(). */ + gboolean parsing_in_video_list_response; }; enum { @@ -162,19 +172,17 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) g_type_class_add_private (klass, sizeof (GDataYouTubeVideoPrivate)); - gobject_class->constructor = gdata_youtube_video_constructor; gobject_class->get_property = gdata_youtube_video_get_property; gobject_class->set_property = gdata_youtube_video_set_property; gobject_class->dispose = gdata_youtube_video_dispose; gobject_class->finalize = gdata_youtube_video_finalize; - parsable_class->parse_xml = parse_xml; - parsable_class->post_parse_xml = post_parse_xml; - parsable_class->get_xml = get_xml; - parsable_class->get_namespaces = get_namespaces; + parsable_class->parse_json = parse_json; + parsable_class->get_json = get_json; + parsable_class->get_content_type = get_content_type; entry_class->get_entry_uri = get_entry_uri; - entry_class->kind_term = "http://gdata.youtube.com/schemas/2007#video"; + entry_class->kind_term = "youtube#video"; /* also: youtube#searchResult */ /** * GDataYouTubeVideo:view-count: @@ -280,7 +288,7 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) * A %NULL-terminated array of words associated with the video. * * For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:keywords">online documentation</ulink>. + * url="https://developers.google.com/youtube/v3/docs/videos#snippet.tags[]">online documentation</ulink>. **/ g_object_class_install_property (gobject_class, PROP_KEYWORDS, g_param_spec_boxed ("keywords", @@ -305,7 +313,7 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) * Specifies a genre or developer tag that describes the video. * * For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:category">online documentation</ulink>. + * url="https://developers.google.com/youtube/v3/docs/videos#snippet.categoryId">online documentation</ulink>. **/ g_object_class_install_property (gobject_class, PROP_CATEGORY, g_param_spec_object ("category", @@ -313,19 +321,24 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) GDATA_TYPE_MEDIA_CATEGORY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + /** * GDataYouTubeVideo:credit: * * Identifies the owner of the video. * - * For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:credit">online documentation</ulink>. + * Deprecated: UNRELEASED: This is no longer supported by Google, and + * will always be %NULL. There is no replacement. **/ g_object_class_install_property (gobject_class, PROP_CREDIT, g_param_spec_object ("credit", "Credit", "Identifies the owner of the video.", GDATA_TYPE_YOUTUBE_CREDIT, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | + G_PARAM_DEPRECATED)); + + G_GNUC_END_IGNORE_DEPRECATIONS /** * GDataYouTubeVideo:description: @@ -405,14 +418,15 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) * * Indicates whether the video is in draft, or unpublished, status. * - * For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_app:draft">online documentation</ulink>. + * Deprecated: UNRELEASED: This is now equal to + * #GDataYouTubeVideo:is-private. **/ g_object_class_install_property (gobject_class, PROP_IS_DRAFT, g_param_spec_boolean ("is-draft", "Draft?", "Indicates whether the video is in draft, or unpublished, status.", FALSE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + G_PARAM_DEPRECATED)); /** * GDataYouTubeVideo:state: @@ -451,9 +465,6 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) * The aspect ratio of the video. A %NULL value means the aspect ratio is unknown (it could still be a widescreen video). A value of * %GDATA_YOUTUBE_ASPECT_RATIO_WIDESCREEN means the video is definitely widescreen. * - * For more information see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:aspectratio">online documentation</ulink>. - * * Since: 0.4.0 **/ g_object_class_install_property (gobject_class, PROP_ASPECT_RATIO, @@ -515,25 +526,6 @@ gdata_youtube_video_init (GDataYouTubeVideo *self) self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_VIDEO, GDataYouTubeVideoPrivate); self->priv->recorded = -1; self->priv->access_controls = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL); - self->priv->georss_where = g_object_new (GDATA_TYPE_GEORSS_WHERE, NULL); -} - -static GObject * -gdata_youtube_video_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params) -{ - GObject *object; - - /* Chain up to the parent class */ - object = G_OBJECT_CLASS (gdata_youtube_video_parent_class)->constructor (type, n_construct_params, construct_params); - - /* We can't create these in init, or they would collide with the group and control created when parsing the XML */ - if (_gdata_parsable_is_constructed_from_xml (GDATA_PARSABLE (object)) == FALSE) { - GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (object)->priv; - priv->media_group = g_object_new (GDATA_TYPE_YOUTUBE_GROUP, NULL); - priv->youtube_control = g_object_new (GDATA_TYPE_YOUTUBE_CONTROL, NULL); - } - - return object; } static void @@ -541,21 +533,7 @@ gdata_youtube_video_dispose (GObject *object) { GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (object)->priv; - if (priv->media_group != NULL) - g_object_unref (priv->media_group); - priv->media_group = NULL; - - if (priv->georss_where != NULL) - g_object_unref (priv->georss_where); - priv->georss_where = NULL; - - if (priv->youtube_control != NULL) - 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; + g_clear_object (&priv->upload_state); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_youtube_video_parent_class)->dispose (object); @@ -568,6 +546,17 @@ gdata_youtube_video_finalize (GObject *object) g_free (priv->location); g_hash_table_destroy (priv->access_controls); + g_strfreev (priv->keywords); + g_free (priv->player_uri); + g_strfreev (priv->region_restriction_allowed); + g_strfreev (priv->region_restriction_blocked); + g_clear_pointer (&priv->content_ratings, (GDestroyNotify) g_hash_table_unref); + g_list_free_full (priv->thumbnails, (GDestroyNotify) g_object_unref); + g_clear_object (&priv->category); + g_free (priv->rejection_reason); + g_free (priv->processing_status); + g_free (priv->upload_status); + g_free (priv->failure_reason); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_youtube_video_parent_class)->finalize (object); @@ -601,49 +590,49 @@ gdata_youtube_video_get_property (GObject *object, guint property_id, GValue *va g_value_set_double (value, priv->rating.average); break; case PROP_KEYWORDS: - g_value_set_boxed (value, gdata_media_group_get_keywords (priv->media_group)); + g_value_set_boxed (value, priv->keywords); break; case PROP_PLAYER_URI: - g_value_set_string (value, gdata_media_group_get_player_uri (priv->media_group)); + g_value_set_string (value, gdata_youtube_video_get_player_uri (GDATA_YOUTUBE_VIDEO (object))); break; case PROP_CATEGORY: - g_value_set_object (value, gdata_media_group_get_category (priv->media_group)); + g_value_set_object (value, priv->category); break; case PROP_CREDIT: - g_value_set_object (value, gdata_media_group_get_credit (priv->media_group)); + g_value_set_object (value, NULL); break; case PROP_DESCRIPTION: - g_value_set_string (value, gdata_media_group_get_description (priv->media_group)); + g_value_set_string (value, gdata_entry_get_summary (GDATA_ENTRY (object))); break; case PROP_DURATION: - g_value_set_uint (value, gdata_youtube_group_get_duration (GDATA_YOUTUBE_GROUP (priv->media_group))); + g_value_set_uint (value, priv->duration); break; case PROP_IS_PRIVATE: - g_value_set_boolean (value, gdata_youtube_group_is_private (GDATA_YOUTUBE_GROUP (priv->media_group))); + g_value_set_boolean (value, priv->is_private); break; case PROP_UPLOADED: - g_value_set_int64 (value, gdata_youtube_group_get_uploaded (GDATA_YOUTUBE_GROUP (priv->media_group))); + g_value_set_int64 (value, gdata_entry_get_published (GDATA_ENTRY (object))); break; case PROP_VIDEO_ID: - g_value_set_string (value, gdata_youtube_group_get_video_id (GDATA_YOUTUBE_GROUP (priv->media_group))); + g_value_set_string (value, gdata_entry_get_id (GDATA_ENTRY (object))); break; case PROP_IS_DRAFT: - g_value_set_boolean (value, gdata_youtube_control_is_draft (priv->youtube_control)); + g_value_set_boolean (value, gdata_youtube_video_is_private (GDATA_YOUTUBE_VIDEO (object))); break; case PROP_STATE: - g_value_set_object (value, gdata_youtube_control_get_state (priv->youtube_control)); + g_value_set_object (value, gdata_youtube_video_get_state (GDATA_YOUTUBE_VIDEO (object))); break; case PROP_RECORDED: g_value_set_int64 (value, priv->recorded); break; case PROP_ASPECT_RATIO: - g_value_set_string (value, gdata_youtube_group_get_aspect_ratio (GDATA_YOUTUBE_GROUP (priv->media_group))); + g_value_set_string (value, gdata_youtube_video_get_aspect_ratio (GDATA_YOUTUBE_VIDEO (object))); break; case PROP_LATITUDE: - g_value_set_double (value, gdata_georss_where_get_latitude (priv->georss_where)); + g_value_set_double (value, priv->latitude); break; case PROP_LONGITUDE: - g_value_set_double (value, gdata_georss_where_get_longitude (priv->georss_where)); + g_value_set_double (value, priv->longitude); break; default: /* We don't have any other property... */ @@ -674,7 +663,7 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal gdata_youtube_video_set_is_private (self, g_value_get_boolean (value)); break; case PROP_IS_DRAFT: - gdata_youtube_video_set_is_draft (self, g_value_get_boolean (value)); + gdata_youtube_video_set_is_private (self, g_value_get_boolean (value)); break; case PROP_RECORDED: gdata_youtube_video_set_recorded (self, g_value_get_int64 (value)); @@ -683,11 +672,13 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal gdata_youtube_video_set_aspect_ratio (self, g_value_get_string (value)); break; case PROP_LATITUDE: - gdata_youtube_video_set_coordinates (self, g_value_get_double (value), - gdata_georss_where_get_longitude (self->priv->georss_where)); + gdata_youtube_video_set_coordinates (self, + g_value_get_double (value), + self->priv->longitude); break; case PROP_LONGITUDE: - gdata_youtube_video_set_coordinates (self, gdata_georss_where_get_latitude (self->priv->georss_where), + gdata_youtube_video_set_coordinates (self, + self->priv->latitude, g_value_get_double (value)); break; default: @@ -697,46 +688,630 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal } } +/* https://developers.google.com/youtube/v3/docs/videos#contentDetails.duration + * + * Note that it can also include an ‘hours’ component, as specified in ISO 8601, + * but not in the Google documentation. */ +static gboolean +duration_from_json_member (JsonReader *reader, const gchar *member_name, + GDataParserOptions options, guint *output, + gboolean *success, GError **error) +{ + gchar *duration_str = NULL, *i = NULL, *new_i = NULL; + guint64 seconds; + gboolean child_success = FALSE; + + if (!gdata_parser_string_from_json_member (reader, member_name, options, + &duration_str, + &child_success, error)) { + return FALSE; + } + + *success = child_success; + *output = 0; + + if (!child_success) { + return TRUE; + } + + /* Parse the string. Format: ‘PT(hH)?(mM)?(sS)?’, where ‘h’, ‘m’ and ‘s’ + * are integer numbers of hours, minutes and seconds. Each element may + * not be present. */ + i = duration_str; + if (strncmp (duration_str, "PT", 2) != 0) { + goto error; + } + + i += 2; /* PT */ + + seconds = 0; + + while (*i != '\0') { + guint64 element; + gchar designator; + + element = g_ascii_strtoull (i, &new_i, 10); + if (new_i == i) { + goto error; + } + + i = new_i; + + designator = i[0]; + if (designator == 'H') { + seconds += 60 * 60 * element; + } else if (designator == 'M') { + seconds += 60 * element; + } else if (designator == 'S') { + seconds += element; + } else { + goto error; + } + + i += 1; + } + + *output = seconds; + *success = child_success; + + g_free (duration_str); + + return TRUE; + +error: + gdata_parser_error_not_iso8601_format_json (reader, duration_str, + error); + g_free (duration_str); + + return TRUE; +} + +/* https://developers.google.com/youtube/v3/docs/videos#snippet.thumbnails */ +static gboolean +thumbnails_from_json_member (JsonReader *reader, const gchar *member_name, + GDataParserOptions options, GList **output, + gboolean *success, GError **error) +{ + guint i, len; + GList *thumbnails = NULL; + const GError *child_error = NULL; + + /* Check if there's such element */ + if (g_strcmp0 (json_reader_get_member_name (reader), + member_name) != 0) { + return FALSE; + } + + /* Check if the output string has already been set. The JSON parser + * guarantees this can't happen. */ + g_assert (!(options & P_NO_DUPES) || *output == NULL); + + len = json_reader_count_members (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + *success = gdata_parser_error_from_json_error (reader, + child_error, + error); + goto done; + } + + for (i = 0; i < len; i++) { + GDataParsable *thumbnail = NULL; /* GDataMediaThumbnail */ + + json_reader_read_element (reader, i); + thumbnail = _gdata_parsable_new_from_json_node (GDATA_TYPE_MEDIA_THUMBNAIL, + reader, NULL, + error); + json_reader_end_element (reader); + + if (thumbnail == NULL) { + *success = FALSE; + goto done; + } + + thumbnails = g_list_prepend (thumbnails, thumbnail); + } + + /* Success! */ + *output = thumbnails; + thumbnails = NULL; + *success = TRUE; + +done: + g_list_free_full (thumbnails, (GDestroyNotify) g_object_unref); + + return TRUE; +} + +/* https://developers.google.com/youtube/v3/docs/videos#contentDetails.regionRestriction */ +static gboolean +restricted_countries_from_json_member (JsonReader *reader, + const gchar *member_name, + GDataParserOptions options, + gchar ***output_allowed, + gchar ***output_blocked, + gboolean *success, GError **error) +{ + guint i, len; + const GError *child_error = NULL; + + /* Check if there's such element */ + if (g_strcmp0 (json_reader_get_member_name (reader), + member_name) != 0) { + return FALSE; + } + + /* Check if the output string has already been set. The JSON parser guarantees this can't happen. */ + g_assert (!(options & P_NO_DUPES) || + (*output_allowed == NULL && *output_blocked == NULL)); + + len = json_reader_count_members (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + *success = gdata_parser_error_from_json_error (reader, + child_error, + error); + return TRUE; + } + + for (i = 0; i < len; i++) { + json_reader_read_element (reader, i); + + if (gdata_parser_strv_from_json_member (reader, "allowed", + P_DEFAULT, + output_allowed, success, + error) || + gdata_parser_strv_from_json_member (reader, "blocked", + P_DEFAULT, + output_blocked, success, + error)) { + /* Nothing to do. */ + } + + json_reader_end_element (reader); + } + + /* Success! */ + *success = TRUE; + + return TRUE; +} + +/* https://developers.google.com/youtube/v3/docs/videos#contentDetails.contentRating */ +static gboolean +content_rating_from_json_member (JsonReader *reader, + const gchar *member_name, + GDataParserOptions options, + GHashTable **output, + gboolean *success, GError **error) +{ + guint i, len; + const GError *child_error = NULL; + + /* Check if there's such element */ + if (g_strcmp0 (json_reader_get_member_name (reader), + member_name) != 0) { + return FALSE; + } + + /* Check if the output string has already been set. The JSON parser + * guarantees this can't happen. */ + g_assert (!(options & P_NO_DUPES) || *output == NULL); + + len = json_reader_count_members (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + *success = gdata_parser_error_from_json_error (reader, + child_error, + error); + return TRUE; + } + + *output = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + for (i = 0; i < len; i++) { + const gchar *scheme, *rating; + + json_reader_read_element (reader, i); + + scheme = json_reader_get_member_name (reader); + rating = json_reader_get_string_value (reader); + + /* Ignore errors. */ + if (rating != NULL) { + g_hash_table_insert (*output, g_strdup (scheme), + g_strdup (rating)); + } + + json_reader_end_element (reader); + } + + /* Success! */ + *success = TRUE; + + return TRUE; +} + +static guint64 +parse_uint64_from_json_string_member (JsonReader *reader, + const gchar *member_name, + GError **error) +{ + const gchar *str_val, *end_ptr; + guint64 out; + const GError *child_error = NULL; + + /* Grab the string. */ + json_reader_read_member (reader, member_name); + + str_val = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + gdata_parser_error_from_json_error (reader, child_error, error); + out = 0; + goto done; + } + + /* Try and parse it as an integer. */ + out = g_ascii_strtoull (str_val, (gchar **) &end_ptr, 10); + + if (*end_ptr != '\0') { + gdata_parser_error_required_json_content_missing (reader, + error); + out = 0; + goto done; + } + +done: + json_reader_end_member (reader); + + return out; +} + static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) +parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error) { gboolean success; GDataYouTubeVideo *self = GDATA_YOUTUBE_VIDEO (parsable); + GDataYouTubeVideoPrivate *priv = self->priv; + + /* HACK: When called with gdata_service_query_single_entry(), the video + * list endpoint returns a 0–1 item list of results as a normal feed. + * (See: https://developers.google.com/youtube/v3/docs/videos/list) + * This differs from the v2 API, which returned just the entry. + * + * So, we need a hack to extract the single entry from the feed without + * being able to invoke the parsing machinery in GDataFeed, because + * gdata_service_query_single_entry() can’t do that. Do that by checking + * the kind, and then ignoring all subsequent members until we reach the + * items member. Recursively parse in there, then break out again. + * This all assumes that we see the kind member before items. */ + if (g_strcmp0 (json_reader_get_member_name (reader), "kind") == 0 && + g_strcmp0 (json_reader_get_string_value (reader), + "youtube#videoListResponse") == 0) { + priv->parsing_in_video_list_response = TRUE; + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "items") == 0 && + priv->parsing_in_video_list_response) { + guint i; + + /* Instead of a 404 when searching for an invalid ID, the server + * returns an empty results list. */ + if (json_reader_count_elements (reader) != 1) { + g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND, + /* Translators: the parameter is an error message returned by the server. */ + _("The requested resource was not found: %s"), + "items"); + return TRUE; + } + + /* Parse the first (and only) array element. */ + json_reader_read_element (reader, 0); + priv->parsing_in_video_list_response = FALSE; + + /* Parse all its properties. */ + for (i = 0; i < (guint) json_reader_count_members (reader); i++) { + g_return_val_if_fail (json_reader_read_element (reader, i), NULL); + + if (GDATA_PARSABLE_GET_CLASS (self)->parse_json (GDATA_PARSABLE (self), reader, user_data, error) == FALSE) { + json_reader_end_element (reader); + g_object_unref (parsable); + break; + } + + json_reader_end_element (reader); + } + + priv->parsing_in_video_list_response = TRUE; + json_reader_end_element (reader); + + return TRUE; /* handled */ + } else if (priv->parsing_in_video_list_response) { + /* Ignore the member. */ + return TRUE; + } + + /* Actual video property parsing. */ + if (g_strcmp0 (json_reader_get_member_name (reader), "id") == 0) { + const gchar *id = NULL; + + /* If this is a youtube#searchResult, the id will be an object: + * https://developers.google.com/youtube/v3/docs/search#resource + * If it is a youtube#video, the id will be a string: + * https://developers.google.com/youtube/v3/docs/videos#resource + */ + + if (json_reader_is_value (reader)) { + id = json_reader_get_string_value (reader); + } else if (json_reader_is_object (reader)) { + json_reader_read_member (reader, "videoId"); + id = json_reader_get_string_value (reader); + json_reader_end_member (reader); + } + + /* Empty ID? */ + if (id == NULL || *id == '\0') { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + _gdata_entry_set_id (GDATA_ENTRY (parsable), id); + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "snippet") == 0) { + guint i; + + /* Check this is an object. */ + if (!json_reader_is_object (reader)) { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + for (i = 0; i < (guint) json_reader_count_members (reader); i++) { + gint64 published_at; + gchar *title = NULL, *description = NULL; + gchar *category_id = NULL; + + json_reader_read_element (reader, i); + + if (gdata_parser_int64_time_from_json_member (reader, "publishedAt", P_DEFAULT, &published_at, &success, error)) { + if (success) { + _gdata_entry_set_published (GDATA_ENTRY (parsable), + published_at); + } + } else if (gdata_parser_string_from_json_member (reader, "title", P_DEFAULT, &title, &success, error)) { + if (success) { + gdata_entry_set_title (GDATA_ENTRY (parsable), + title); + } + + g_free (title); + } else if (gdata_parser_string_from_json_member (reader, "description", P_DEFAULT, &description, &success, error)) { + if (success) { + gdata_entry_set_summary (GDATA_ENTRY (parsable), + description); + } + + g_free (description); + } else if (gdata_parser_strv_from_json_member (reader, "tags", P_DEFAULT, &priv->keywords, &success, error) || + thumbnails_from_json_member (reader, "thumbnails", P_DEFAULT, &priv->thumbnails, &success, error)) { + /* Fall through. */ + } else if (gdata_parser_string_from_json_member (reader, "categoryId", P_DEFAULT, &category_id, &success, error)) { + if (success) { + priv->category = gdata_media_category_new (category_id, + NULL, + NULL); + } + + g_free (category_id); + } + + json_reader_end_element (reader); + + if (!success) { + return FALSE; + } + } + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "contentDetails") == 0) { + guint i; + + /* Check this is an object. */ + if (!json_reader_is_object (reader)) { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + for (i = 0; i < (guint) json_reader_count_members (reader); i++) { + json_reader_read_element (reader, i); + + if (duration_from_json_member (reader, "duration", P_DEFAULT, &priv->duration, &success, error) || + restricted_countries_from_json_member (reader, "regionRestriction", P_DEFAULT, &priv->region_restriction_allowed, &priv->region_restriction_blocked, &success, error) || + content_rating_from_json_member (reader, "contentRating", P_DEFAULT, &priv->content_ratings, &success, error)) { + /* Fall through. */ + } + + json_reader_end_element (reader); + + if (!success) { + return FALSE; + } + } + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "status") == 0) { + const gchar *privacy_status; + + /* Check this is an object. */ + if (!json_reader_is_object (reader)) { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + json_reader_read_member (reader, "privacyStatus"); + privacy_status = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + if (g_strcmp0 (privacy_status, "private") == 0) { + priv->is_private = TRUE; + } else if (g_strcmp0 (privacy_status, "public") == 0) { + priv->is_private = FALSE; + g_hash_table_insert (priv->access_controls, + (gpointer) "list", + GINT_TO_POINTER (GDATA_YOUTUBE_PERMISSION_ALLOWED)); + } else if (g_strcmp0 (privacy_status, "unlisted") == 0) { + /* See: ‘list’ on + * https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:accessControl */ + priv->is_private = FALSE; + g_hash_table_insert (priv->access_controls, + (gpointer) "list", + GINT_TO_POINTER (GDATA_YOUTUBE_PERMISSION_DENIED)); + } + + json_reader_read_member (reader, "embeddable"); + g_hash_table_insert (priv->access_controls, + (gpointer) GDATA_YOUTUBE_ACTION_EMBED, + GINT_TO_POINTER (json_reader_get_boolean_value (reader) ? + GDATA_YOUTUBE_PERMISSION_ALLOWED : + GDATA_YOUTUBE_PERMISSION_DENIED)); + json_reader_end_member (reader); + + json_reader_read_member (reader, "uploadStatus"); + priv->upload_status = g_strdup (json_reader_get_string_value (reader)); + json_reader_end_member (reader); + + json_reader_read_member (reader, "failureReason"); + priv->rejection_reason = g_strdup (json_reader_get_string_value (reader)); + json_reader_end_member (reader); + + json_reader_read_member (reader, "rejectionReason"); + priv->rejection_reason = g_strdup (json_reader_get_string_value (reader)); + json_reader_end_member (reader); + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "statistics") == 0) { + gint64 likes, dislikes; + GError *child_error = NULL; + + /* Check this is an object. */ + if (!json_reader_is_object (reader)) { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + /* Views and favourites. For some unknown reason, the feed + * returns them as a string, even though they’re documented as + * being unsigned longs. + * + * Reference: https://developers.google.com/youtube/v3/docs/videos#statistics */ + priv->view_count = parse_uint64_from_json_string_member (reader, "viewCount", &child_error); + if (child_error != NULL) { + g_propagate_error (error, child_error); + return FALSE; + } + + priv->favorite_count = parse_uint64_from_json_string_member (reader, "favoriteCount", &child_error); + if (child_error != NULL) { + g_propagate_error (error, child_error); + return FALSE; + } + + /* The new ratings API (total likes, total dislikes) doesn’t + * really match with the old API (collection of integer ratings + * between 1 and 5). Try and return something appropriate. */ + likes = parse_uint64_from_json_string_member (reader, "likeCount", &child_error); + if (child_error != NULL) { + g_propagate_error (error, child_error); + return FALSE; + } + + dislikes = parse_uint64_from_json_string_member (reader, "dislikeCount", &child_error); + if (child_error != NULL) { + g_propagate_error (error, child_error); + return FALSE; + } + + priv->rating.min = 0; + priv->rating.max = 1; + priv->rating.count = likes + dislikes; + if (likes + dislikes == 0) { + priv->rating.average = 0.0; /* basically undefined */ + } else { + priv->rating.average = (gdouble) likes / (gdouble) (likes + dislikes); + } + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "processingDetails") == 0) { + /* Check this is an object. */ + if (!json_reader_is_object (reader)) { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + json_reader_read_member (reader, "processingStatus"); + priv->processing_status = g_strdup (json_reader_get_string_value (reader)); + json_reader_end_member (reader); + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "recordingDetails") == 0) { + const gchar *recording_date; + const GError *child_error = NULL; + + /* Check this is an object. */ + if (!json_reader_is_object (reader)) { + return gdata_parser_error_required_json_content_missing (reader, error); + } + + json_reader_read_member (reader, "recordingDate"); + recording_date = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + if (!gdata_parser_int64_from_date (recording_date, + &priv->recorded)) { + /* Error */ + gdata_parser_error_not_iso8601_format_json (reader, + recording_date, + error); + return FALSE; + } + + json_reader_read_member (reader, "locationDescription"); + priv->location = g_strdup (json_reader_get_string_value (reader)); + json_reader_end_member (reader); - if (gdata_parser_is_namespace (node, "http://search.yahoo.com/mrss/") == TRUE && - gdata_parser_object_from_element (node, "group", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_GROUP, - &(self->priv->media_group), &success, error) == TRUE) { - return success; - } else if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE && - gdata_parser_object_from_element (node, "control", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_CONTROL, - &(self->priv->youtube_control), &success, error) == TRUE) { - return success; - } else if (gdata_parser_is_namespace (node, "http://www.georss.org/georss") == TRUE && - gdata_parser_object_from_element (node, "where", P_REQUIRED, GDATA_TYPE_GEORSS_WHERE, - &(self->priv->georss_where), &success, error) == TRUE) { - return success; + if (json_reader_read_member (reader, "location")) { + json_reader_read_member (reader, "latitude"); + priv->latitude = json_reader_get_double_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "longitude"); + priv->longitude = json_reader_get_double_value (reader); + json_reader_end_member (reader); + } + + json_reader_end_member (reader); + + child_error = json_reader_get_error (reader); + if (child_error != NULL) { + return gdata_parser_error_from_json_error (reader, + child_error, + error); + } + + return TRUE; + } else { + return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_json (parsable, reader, user_data, error); + } + +#if 0 +TODO } else if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007") == TRUE) { - if (gdata_parser_string_from_element (node, "location", P_NONE, &(self->priv->location), &success, error) == TRUE) { - return success; - } else if (xmlStrcmp (node->name, (xmlChar*) "statistics") == 0) { - /* yt:statistics */ - xmlChar *view_count, *favorite_count; - - /* View count */ - view_count = xmlGetProp (node, (xmlChar*) "viewCount"); - if (view_count == NULL) - return gdata_parser_error_required_property_missing (node, "viewCount", error); - self->priv->view_count = g_ascii_strtoull ((gchar*) view_count, NULL, 10); - xmlFree (view_count); - - /* Favourite count */ - favorite_count = xmlGetProp (node, (xmlChar*) "favoriteCount"); - self->priv->favorite_count = (favorite_count != NULL) ? g_ascii_strtoull ((gchar*) favorite_count, NULL, 10) : 0; - xmlFree (favorite_count); - } else if (xmlStrcmp (node->name, (xmlChar*) "noembed") == 0) { - /* yt:noembed */ - /* Ignore this now; it's been superceded by yt:accessControl. - * See http://apiblog.youtube.com/2010/02/extended-access-controls-available-via.html */ } else if (xmlStrcmp (node->name, (xmlChar*) "accessControl") == 0) { /* yt:accessControl */ xmlChar *action, *permission; @@ -766,195 +1341,169 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da /* Store the access control */ g_hash_table_insert (self->priv->access_controls, (gchar*) action, GINT_TO_POINTER (permission_enum)); - } else if (xmlStrcmp (node->name, (xmlChar*) "recorded") == 0) { - /* yt:recorded */ - xmlChar *recorded; - gint64 recorded_int64; - - recorded = xmlNodeListGetString (doc, node->children, TRUE); - if (gdata_parser_int64_from_date ((gchar*) recorded, &recorded_int64) == FALSE) { - /* Error */ - gdata_parser_error_not_iso8601_format (node, (gchar*) recorded, error); - xmlFree (recorded); - return FALSE; - } - xmlFree (recorded); - gdata_youtube_video_set_recorded (self, recorded_int64); - } else { - return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error); - } - } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/g/2005") == TRUE) { - if (xmlStrcmp (node->name, (xmlChar*) "rating") == 0) { - /* gd:rating */ - xmlChar *min, *max, *num_raters, *average; - guint num_raters_uint; - gdouble average_double; - - min = xmlGetProp (node, (xmlChar*) "min"); - if (min == NULL) - return gdata_parser_error_required_property_missing (node, "min", error); - - max = xmlGetProp (node, (xmlChar*) "max"); - if (max == NULL) { - gdata_parser_error_required_property_missing (node, "max", error); - xmlFree (min); - return FALSE; - } - - num_raters = xmlGetProp (node, (xmlChar*) "numRaters"); - if (num_raters == NULL) - num_raters_uint = 0; - else - num_raters_uint = g_ascii_strtoull ((gchar*) num_raters, NULL, 10); - xmlFree (num_raters); - - average = xmlGetProp (node, (xmlChar*) "average"); - if (average == NULL) - average_double = 0; - else - average_double = g_ascii_strtod ((gchar*) average, NULL); - xmlFree (average); - - self->priv->rating.min = g_ascii_strtoull ((gchar*) min, NULL, 10); - self->priv->rating.max = g_ascii_strtoull ((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); - } - } else { - return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error); - } +#endif return TRUE; } -static gboolean -post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error) +static void +get_json (GDataParsable *parsable, JsonBuilder *builder) { + GDataEntry *entry = GDATA_ENTRY (parsable); GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv; + guint i; + gpointer permission_ptr; /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->post_parse_xml (parsable, user_data, error); + GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_json (parsable, builder); + + /* Add the video-specific JSON. + * Reference: + * https://developers.google.com/youtube/v3/docs/videos/insert#request_body */ + /* snippet object. */ + json_builder_set_member_name (builder, "snippet"); + json_builder_begin_object (builder); + + if (gdata_entry_get_title (entry) != NULL) { + json_builder_set_member_name (builder, "title"); + json_builder_add_string_value (builder, + gdata_entry_get_title (entry)); + } - /* This must always exist, so is_draft can be set on it */ - if (priv->youtube_control == NULL) - priv->youtube_control = g_object_new (GDATA_TYPE_YOUTUBE_CONTROL, NULL); + if (gdata_entry_get_summary (entry) != NULL) { + json_builder_set_member_name (builder, "description"); + json_builder_add_string_value (builder, + gdata_entry_get_summary (entry)); + } - return TRUE; -} + if (priv->keywords != NULL) { + json_builder_set_member_name (builder, "tags"); + json_builder_begin_array (builder); -static void -access_control_cb (const gchar *action, gpointer value, GString *xml_string) -{ - GDataYouTubePermission permission = GPOINTER_TO_INT (value); - const gchar *permission_string; + for (i = 0; priv->keywords[i] != NULL; i++) { + json_builder_add_string_value (builder, + priv->keywords[i]); + } - switch (permission) { - case GDATA_YOUTUBE_PERMISSION_ALLOWED: - permission_string = "allowed"; - break; - case GDATA_YOUTUBE_PERMISSION_DENIED: - permission_string = "denied"; - break; - case GDATA_YOUTUBE_PERMISSION_MODERATED: - permission_string = "moderated"; - break; - default: - g_assert_not_reached (); + json_builder_end_array (builder); } - gdata_parser_string_append_escaped (xml_string, "<yt:accessControl action='", action, "'"); - g_string_append_printf (xml_string, " permission='%s'/>", permission_string); -} + if (priv->category != NULL) { + json_builder_set_member_name (builder, "categoryId"); + json_builder_add_string_value (builder, + gdata_media_category_get_category (priv->category)); + } -static void -get_xml (GDataParsable *parsable, GString *xml_string) -{ - GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv; + json_builder_end_object (builder); - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_xml (parsable, xml_string); + /* status object. */ + json_builder_set_member_name (builder, "status"); + json_builder_begin_object (builder); + + json_builder_set_member_name (builder, "privacyStatus"); + + if (!priv->is_private && + g_hash_table_lookup_extended (priv->access_controls, + "list", NULL, + &permission_ptr)) { + GDataYouTubePermission perm; + + perm = GPOINTER_TO_INT (permission_ptr); + + /* See the ‘list’ documentation on: + * https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:accessControl */ + json_builder_add_string_value (builder, + (perm == GDATA_YOUTUBE_PERMISSION_ALLOWED) ? "public" : "unlisted"); + } else { + json_builder_add_string_value (builder, + priv->is_private ? "private" : "public"); + } + + if (g_hash_table_lookup_extended (priv->access_controls, + GDATA_YOUTUBE_ACTION_EMBED, NULL, + &permission_ptr)) { + GDataYouTubePermission perm; + + perm = GPOINTER_TO_INT (permission_ptr); + + json_builder_set_member_name (builder, "embeddable"); + json_builder_add_boolean_value (builder, + perm == GDATA_YOUTUBE_PERMISSION_ALLOWED); + } + + /* FIXME: add support for: + * publicStatsViewable + * publishAt + * license + */ + + json_builder_end_object (builder); + + /* recordingDetails object. */ + json_builder_set_member_name (builder, "recordingDetails"); + json_builder_begin_object (builder); + + if (priv->location != NULL) { + json_builder_set_member_name (builder, "locationDescription"); + json_builder_add_string_value (builder, priv->location); + } + + if (priv->latitude != G_MAXDOUBLE && priv->longitude != G_MAXDOUBLE) { + json_builder_set_member_name (builder, "location"); + json_builder_begin_object (builder); - /* media:group */ - _gdata_parsable_get_xml (GDATA_PARSABLE (priv->media_group), xml_string, FALSE); + json_builder_set_member_name (builder, "latitude"); + json_builder_add_double_value (builder, priv->latitude); - if (priv->location != NULL) - gdata_parser_string_append_escaped (xml_string, "<yt:location>", priv->location, "</yt:location>"); + json_builder_set_member_name (builder, "longitude"); + json_builder_add_double_value (builder, priv->longitude); + + json_builder_end_object (builder); + } if (priv->recorded != -1) { gchar *recorded = gdata_parser_date_from_int64 (priv->recorded); - g_string_append_printf (xml_string, "<yt:recorded>%s</yt:recorded>", recorded); + json_builder_set_member_name (builder, "recordingDate"); + json_builder_add_string_value (builder, recorded); g_free (recorded); } + json_builder_end_object (builder); + +#if 0 /* yt:accessControl */ g_hash_table_foreach (priv->access_controls, (GHFunc) access_control_cb, xml_string); - - /* app:control */ - _gdata_parsable_get_xml (GDATA_PARSABLE (priv->youtube_control), xml_string, FALSE); - - /* georss:where */ - if (priv->georss_where != NULL && gdata_georss_where_get_latitude (priv->georss_where) != G_MAXDOUBLE && - gdata_georss_where_get_longitude (priv->georss_where) != G_MAXDOUBLE) { - _gdata_parsable_get_xml (GDATA_PARSABLE (priv->georss_where), xml_string, FALSE); - } +#endif } -static void -get_namespaces (GDataParsable *parsable, GHashTable *namespaces) +static const gchar * +get_content_type (void) { - GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv; - - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_namespaces (parsable, namespaces); - - g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007"); - - /* Add the media:group, app:control and georss:where namespaces */ - GDATA_PARSABLE_GET_CLASS (priv->media_group)->get_namespaces (GDATA_PARSABLE (priv->media_group), namespaces); - GDATA_PARSABLE_GET_CLASS (priv->youtube_control)->get_namespaces (GDATA_PARSABLE (priv->youtube_control), namespaces); - GDATA_PARSABLE_GET_CLASS (priv->georss_where)->get_namespaces (GDATA_PARSABLE (priv->georss_where), namespaces); + return "application/json"; } static gchar * get_entry_uri (const gchar *id) { - /* The entry ID is in the format: "tag:youtube.com,2008:video:QjA5faZF1A8"; we want the bit after "video" */ - const gchar *video_id = NULL; - gchar **parts, *uri; - guint i; - - parts = g_strsplit (id, ":", -1); + const gchar *old_prefix = "tag:youtube.com,2008:video:"; - for (i = 0; parts[i] != NULL && parts[i + 1] != NULL; i += 2) { - if (strcmp (parts[i], "video") == 0) { - video_id = parts[i + 1]; - break; - } + /* For compatibility with previous video ID formats, strip off the v2 + * ID prefix. */ + if (g_str_has_prefix (id, old_prefix)) { + id += strlen (old_prefix); } - g_assert (video_id != NULL); - - /* Build the URI using the video ID */ - uri = g_strconcat ("https://gdata.youtube.com/feeds/api/videos/", video_id, NULL); - g_strfreev (parts); - - return uri; + /* Build the query URI for a single video. This is a bit of a pain, + * because it actually returns a list containing a single video, but + * there seems no other way to do it. See parsing_in_video_list_response + * in parse_json() for the fallout. + * + * Reference: https://developers.google.com/youtube/v3/docs/videos/list#part */ + return g_strdup_printf ("https://www.googleapis.com/youtube/v3/videos" + "?part=contentDetails,id,recordingDetails," + "snippet,status,statistics," + "processingDetails" + "&id=%s", id); } static GDataAuthorizationDomain * @@ -1121,6 +1670,10 @@ gdata_youtube_video_set_access_control (GDataYouTubeVideo *self, const gchar *ac * @average: (out caller-allocates) (allow-none): return location for the average rating value, or %NULL * * Gets various properties of the ratings on the video. + * + * Note that this property may not be retrieved when querying for multiple + * videos at once, but is guaranteed to be retrieved when querying with + * gdata_service_query_single_entry_async(). **/ void gdata_youtube_video_get_rating (GDataYouTubeVideo *self, guint *min, guint *max, guint *count, gdouble *average) @@ -1148,7 +1701,7 @@ const gchar * const * gdata_youtube_video_get_keywords (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_media_group_get_keywords (self->priv->media_group); + return (const gchar * const *) self->priv->keywords; } /** @@ -1159,7 +1712,7 @@ gdata_youtube_video_get_keywords (GDataYouTubeVideo *self) * Sets the #GDataYouTubeVideo:keywords property to the new keyword list, @keywords. * * @keywords must not be %NULL. For more information, see the <ulink type="http" - * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:keywords">online documentation</ulink>. + * url="https://developers.google.com/youtube/v3/docs/videos#snippet.tags[]">online documentation</ulink>. **/ void gdata_youtube_video_set_keywords (GDataYouTubeVideo *self, const gchar * const *keywords) @@ -1167,7 +1720,8 @@ gdata_youtube_video_set_keywords (GDataYouTubeVideo *self, const gchar * const * g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); g_return_if_fail (keywords != NULL); - gdata_media_group_set_keywords (self->priv->media_group, keywords); + g_strfreev (self->priv->keywords); + self->priv->keywords = g_strdupv ((gchar **) keywords); g_object_notify (G_OBJECT (self), "keywords"); } @@ -1198,6 +1752,20 @@ gdata_youtube_video_get_player_uri (GDataYouTubeVideo *self) return priv->player_uri; } +static gboolean +strv_contains (const gchar * const *strv, const gchar *key) +{ + guint i; + + for (i = 0; strv[i] != NULL; i++) { + if (g_strcmp0 (strv[i], key) == 0) { + return TRUE; + } + } + + return FALSE; +} + /** * gdata_youtube_video_is_restricted_in_country: * @self: a #GDataYouTubeVideo @@ -1213,10 +1781,57 @@ gdata_youtube_video_get_player_uri (GDataYouTubeVideo *self) gboolean gdata_youtube_video_is_restricted_in_country (GDataYouTubeVideo *self, const gchar *country) { + GDataYouTubeVideoPrivate *priv; + g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE); g_return_val_if_fail (country != NULL && *country != '\0', FALSE); - return gdata_media_group_is_restricted_in_country (self->priv->media_group, country); + priv = self->priv; + + return (!strv_contains ((const gchar * const *) priv->region_restriction_allowed, country) && + (strv_contains ((const gchar * const *) priv->region_restriction_blocked, country) || + priv->region_restriction_allowed == NULL || + priv->region_restriction_allowed[0] == NULL)); +} + +static const gchar * +convert_mpaa_rating (const gchar *v3_rating) +{ + if (g_strcmp0 (v3_rating, "mpaaG") == 0) { + return "g"; + } else if (g_strcmp0 (v3_rating, "mpaaNc17") == 0) { + return "nc-17"; + } else if (g_strcmp0 (v3_rating, "mpaaPg") == 0) { + return "pg"; + } else if (g_strcmp0 (v3_rating, "mpaaPg13") == 0) { + return "pg-13"; + } else if (g_strcmp0 (v3_rating, "mpaaR") == 0) { + return "r"; + } else { + return NULL; + } +} + +static const gchar * +convert_tvpg_rating (const gchar *v3_rating) +{ + if (g_strcmp0 (v3_rating, "pg14") == 0) { + return "tv-14"; + } else if (g_strcmp0 (v3_rating, "tvpgG") == 0) { + return "tv-g"; + } else if (g_strcmp0 (v3_rating, "tvpgMa") == 0) { + return "tv-ma"; + } else if (g_strcmp0 (v3_rating, "tvpgPg") == 0) { + return "tv-pg"; + } else if (g_strcmp0 (v3_rating, "tvpgY") == 0) { + return "tv-y"; + } else if (g_strcmp0 (v3_rating, "tvpgY7") == 0) { + return "tv-y7"; + } else if (g_strcmp0 (v3_rating, "tvpgY7Fv") == 0) { + return "tv-y7-fv"; + } else { + return NULL; + } } /** @@ -1279,7 +1894,7 @@ GDataMediaCategory * gdata_youtube_video_get_category (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_media_group_get_category (self->priv->media_group); + return self->priv->category; } /** @@ -1298,10 +1913,14 @@ gdata_youtube_video_set_category (GDataYouTubeVideo *self, GDataMediaCategory *c g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); g_return_if_fail (GDATA_IS_MEDIA_CATEGORY (category)); - gdata_media_group_set_category (self->priv->media_group, category); + g_object_ref (category); + g_object_unref (self->priv->category); + self->priv->category = category; g_object_notify (G_OBJECT (self), "category"); } +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + /** * gdata_youtube_video_get_credit: * @self: a #GDataYouTubeVideo @@ -1309,14 +1928,18 @@ gdata_youtube_video_set_category (GDataYouTubeVideo *self, GDataMediaCategory *c * Gets the #GDataYouTubeVideo:credit property. * * Return value: (transfer none): a #GDataMediaCredit giving information on whom to credit for the video, or %NULL + * Deprecated: UNRELEASED: This is no longer supported by Google, and will + * always return %NULL. There is no replacement. **/ GDataYouTubeCredit * gdata_youtube_video_get_credit (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return GDATA_YOUTUBE_CREDIT (gdata_media_group_get_credit (self->priv->media_group)); + return NULL; } +G_GNUC_END_IGNORE_DEPRECATIONS + /** * gdata_youtube_video_get_description: * @self: a #GDataYouTubeVideo @@ -1329,7 +1952,7 @@ const gchar * gdata_youtube_video_get_description (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_media_group_get_description (self->priv->media_group); + return gdata_entry_get_summary (GDATA_ENTRY (self)); } /** @@ -1345,8 +1968,7 @@ void gdata_youtube_video_set_description (GDataYouTubeVideo *self, const gchar *description) { g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); - - gdata_media_group_set_description (self->priv->media_group, description); + gdata_entry_set_summary (GDATA_ENTRY (self), description); g_object_notify (G_OBJECT (self), "description"); } @@ -1389,7 +2011,7 @@ GList * gdata_youtube_video_get_thumbnails (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_media_group_get_thumbnails (self->priv->media_group); + return self->priv->thumbnails; } /** @@ -1404,7 +2026,7 @@ guint gdata_youtube_video_get_duration (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), 0); - return gdata_youtube_group_get_duration (GDATA_YOUTUBE_GROUP (self->priv->media_group)); + return self->priv->duration; } /** @@ -1419,7 +2041,7 @@ gboolean gdata_youtube_video_is_private (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE); - return gdata_youtube_group_is_private (GDATA_YOUTUBE_GROUP (self->priv->media_group)); + return self->priv->is_private; } /** @@ -1433,7 +2055,7 @@ void gdata_youtube_video_set_is_private (GDataYouTubeVideo *self, gboolean is_private) { g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); - gdata_youtube_group_set_is_private (GDATA_YOUTUBE_GROUP (self->priv->media_group), is_private); + self->priv->is_private = is_private; g_object_notify (G_OBJECT (self), "is-private"); } @@ -1449,7 +2071,7 @@ gint64 gdata_youtube_video_get_uploaded (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), -1); - return gdata_youtube_group_get_uploaded (GDATA_YOUTUBE_GROUP (self->priv->media_group)); + return gdata_entry_get_published (GDATA_ENTRY (self)); } /** @@ -1475,12 +2097,14 @@ gdata_youtube_video_get_video_id (GDataYouTubeVideo *self) * Gets the #GDataYouTubeVideo:is-draft property. * * Return value: %TRUE if the video is a draft, %FALSE otherwise + * Deprecated: UNRELEASED: This is now equal to + * gdata_youtube_video_is_private(). **/ gboolean gdata_youtube_video_is_draft (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE); - return gdata_youtube_control_is_draft (self->priv->youtube_control); + return gdata_youtube_video_is_private (self); } /** @@ -1489,15 +2113,97 @@ gdata_youtube_video_is_draft (GDataYouTubeVideo *self) * @is_draft: whether the video is a draft * * Sets the #GDataYouTubeVideo:is-draft property to decide whether the video is a draft. + * + * Deprecated: UNRELEASED: This is now equivalent to + * gdata_youtube_video_set_is_private(). **/ void gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft) { g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); - gdata_youtube_control_set_is_draft (self->priv->youtube_control, is_draft); + gdata_youtube_video_set_is_private (self, is_draft); g_object_notify (G_OBJECT (self), "is-draft"); } +/* Convert from v3 to v2 API video upload state. References: + * v2: https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:state + * v3: https://developers.google.com/youtube/v3/docs/videos#processingDetails.processingStatus + * https://developers.google.com/youtube/v3/docs/videos#status.uploadStatus + */ +static const gchar * +convert_state_name (const gchar *v3_processing_status, + const gchar *v3_upload_status) +{ + if (g_strcmp0 (v3_upload_status, "deleted") == 0 || + g_strcmp0 (v3_upload_status, "failed") == 0 || + g_strcmp0 (v3_upload_status, "rejected") == 0) { + return v3_upload_status; + } else if (g_strcmp0 (v3_processing_status, "processing") == 0) { + return v3_processing_status; + } + + return NULL; +} + +/* References: + * v2: https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:state + * v3: https://developers.google.com/youtube/v3/docs/videos#status.failureReason + * https://developers.google.com/youtube/v3/docs/videos#status.rejectionReason + */ +static const gchar * +convert_state_reason_code (const gchar *v2_name, + const gchar *v3_failure_reason, + const gchar *v3_rejection_reason) +{ + if (v2_name == NULL || + g_strcmp0 (v2_name, "processing") == 0 || + g_strcmp0 (v2_name, "deleted") == 0) { + /* Explicitly unset if unknown, processing or deleted. */ + return NULL; + } else if (g_strcmp0 (v2_name, "restricted") == 0) { + /* Unsupported conversion; convert_state_name() can never return + * ‘restricted’ anyway. */ + return NULL; + } else if (g_strcmp0 (v2_name, "rejected") == 0) { + if (g_strcmp0 (v3_rejection_reason, "claim") == 0 || + g_strcmp0 (v3_rejection_reason, "copyright") == 0 || + g_strcmp0 (v3_rejection_reason, "trademark") == 0) { + return "copyright"; + } else if (g_strcmp0 (v3_rejection_reason, "duplicate") == 0) { + return "duplicate"; + } else if (g_strcmp0 (v3_rejection_reason, + "inappropriate") == 0) { + return "inappropriate"; + } else if (g_strcmp0 (v3_rejection_reason, "length") == 0) { + return "tooLong"; + } else if (g_strcmp0 (v3_rejection_reason, "termsOfUse") == 0) { + return "termsOfUse"; + } else if (g_strcmp0 (v3_rejection_reason, + "uploaderAccountClosed") == 0 || + g_strcmp0 (v3_rejection_reason, + "uploaderAccountSuspended") == 0) { + return "duplicate"; + } else { + /* Generic fallback. */ + return "termsOfUse"; + } + } else if (g_strcmp0 (v2_name, "failed") == 0) { + if (g_strcmp0 (v3_failure_reason, "codec") == 0) { + return "unsupportedCodec"; + } else if (g_strcmp0 (v3_failure_reason, "conversion") == 0) { + return "invalidFormat"; + } else if (g_strcmp0 (v3_failure_reason, "emptyFile") == 0) { + return "empty"; + } else if (g_strcmp0 (v3_failure_reason, "tooSmall") == 0) { + return "tooSmall"; + } else { + return "cantProcess"; + } + } + + return NULL; +} + /** * gdata_youtube_video_get_state: * @self: a #GDataYouTubeVideo @@ -1512,8 +2218,31 @@ gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft) GDataYouTubeState * gdata_youtube_video_get_state (GDataYouTubeVideo *self) { + GDataYouTubeVideoPrivate *priv; + g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_youtube_control_get_state (self->priv->youtube_control); + + priv = self->priv; + + /* Lazily create the state object. */ + if (priv->upload_state == NULL) { + const gchar *name, *reason_code; + + name = convert_state_name (priv->processing_status, + priv->upload_status); + reason_code = convert_state_reason_code (name, + priv->failure_reason, + priv->rejection_reason); + + priv->upload_state = g_object_new (GDATA_TYPE_YOUTUBE_STATE, + "name", name, + "reason-code", reason_code, + "help-uri", NULL, + "message", NULL, + NULL); + } + + return priv->upload_state; } /** @@ -1635,7 +2364,10 @@ const gchar * gdata_youtube_video_get_aspect_ratio (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_youtube_group_get_aspect_ratio (GDATA_YOUTUBE_GROUP (self->priv->media_group)); + + /* Permanently NULL for the moment, but let’s not deprecate the property + * because it looks like it might come in useful in future. */ + return NULL; } /** @@ -1652,8 +2384,9 @@ void gdata_youtube_video_set_aspect_ratio (GDataYouTubeVideo *self, const gchar *aspect_ratio) { g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); - gdata_youtube_group_set_aspect_ratio (GDATA_YOUTUBE_GROUP (self->priv->media_group), aspect_ratio); - g_object_notify (G_OBJECT (self), "aspect-ratio"); + + /* Ignore it. See note in gdata_youtube_video_get_aspect_ratio(), + * above. */ } /** @@ -1672,10 +2405,12 @@ gdata_youtube_video_get_coordinates (GDataYouTubeVideo *self, gdouble *latitude, { g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); - if (latitude != NULL) - *latitude = gdata_georss_where_get_latitude (self->priv->georss_where); - if (longitude != NULL) - *longitude = gdata_georss_where_get_longitude (self->priv->georss_where); + if (latitude != NULL) { + *latitude = self->priv->latitude; + } + if (longitude != NULL) { + *longitude = self->priv->longitude; + } } /** @@ -1693,8 +2428,8 @@ gdata_youtube_video_set_coordinates (GDataYouTubeVideo *self, gdouble latitude, { g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self)); - gdata_georss_where_set_latitude (self->priv->georss_where, latitude); - gdata_georss_where_set_longitude (self->priv->georss_where, longitude); + self->priv->latitude = latitude; + self->priv->longitude = longitude; g_object_freeze_notify (G_OBJECT (self)); g_object_notify (G_OBJECT (self), "latitude"); diff --git a/gdata/services/youtube/gdata-youtube-video.h b/gdata/services/youtube/gdata-youtube-video.h index bb2c8299..2312cf45 100644 --- a/gdata/services/youtube/gdata-youtube-video.h +++ b/gdata/services/youtube/gdata-youtube-video.h @@ -206,7 +206,6 @@ gboolean gdata_youtube_video_is_restricted_in_country (GDataYouTubeVideo *self, const gchar *gdata_youtube_video_get_media_rating (GDataYouTubeVideo *self, const gchar *rating_type) G_GNUC_PURE; GDataMediaCategory *gdata_youtube_video_get_category (GDataYouTubeVideo *self) G_GNUC_PURE; void gdata_youtube_video_set_category (GDataYouTubeVideo *self, GDataMediaCategory *category); -GDataYouTubeCredit *gdata_youtube_video_get_credit (GDataYouTubeVideo *self) G_GNUC_PURE; const gchar *gdata_youtube_video_get_description (GDataYouTubeVideo *self) G_GNUC_PURE; void gdata_youtube_video_set_description (GDataYouTubeVideo *self, const gchar *description); GList *gdata_youtube_video_get_thumbnails (GDataYouTubeVideo *self) G_GNUC_PURE; @@ -214,8 +213,6 @@ guint gdata_youtube_video_get_duration (GDataYouTubeVideo *self) G_GNUC_PURE; gboolean gdata_youtube_video_is_private (GDataYouTubeVideo *self) G_GNUC_PURE; void gdata_youtube_video_set_is_private (GDataYouTubeVideo *self, gboolean is_private); gint64 gdata_youtube_video_get_uploaded (GDataYouTubeVideo *self); -gboolean gdata_youtube_video_is_draft (GDataYouTubeVideo *self) G_GNUC_PURE; -void gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft); GDataYouTubeState *gdata_youtube_video_get_state (GDataYouTubeVideo *self) G_GNUC_PURE; gint64 gdata_youtube_video_get_recorded (GDataYouTubeVideo *self); void gdata_youtube_video_set_recorded (GDataYouTubeVideo *self, gint64 recorded); @@ -229,6 +226,11 @@ gchar *gdata_youtube_video_get_video_id_from_uri (const gchar *video_uri) G_GNUC #ifndef LIBGDATA_DISABLE_DEPRECATED const gchar *gdata_youtube_video_get_video_id (GDataYouTubeVideo *self) G_GNUC_PURE G_GNUC_DEPRECATED_FOR(gdata_entry_get_id); GDataYouTubeContent *gdata_youtube_video_look_up_content (GDataYouTubeVideo *self, const gchar *type) G_GNUC_PURE G_GNUC_DEPRECATED_FOR(gdata_youtube_video_get_player_uri); + +gboolean gdata_youtube_video_is_draft (GDataYouTubeVideo *self) G_GNUC_PURE G_GNUC_DEPRECATED_FOR(gdata_youtube_video_is_private); +void gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft) G_GNUC_DEPRECATED_FOR(gdata_youtube_video_set_is_private); + +GDataYouTubeCredit *gdata_youtube_video_get_credit (GDataYouTubeVideo *self) G_GNUC_PURE G_GNUC_DEPRECATED; #endif /* !LIBGDATA_DISABLE_DEPRECATED */ G_END_DECLS diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c index 545ca8b9..9ced7b42 100644 --- a/gdata/tests/youtube.c +++ b/gdata/tests/youtube.c @@ -902,7 +902,9 @@ test_parsing_app_control (void) g_clear_error (&error); /* Test the app:control values */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS g_assert (gdata_youtube_video_is_draft (video) == TRUE); + G_GNUC_END_IGNORE_DEPRECATIONS state = gdata_youtube_video_get_state (video); g_assert_cmpstr (gdata_youtube_state_get_name (state), ==, "blacklisted"); |