diff options
author | Philip Withnall <philip@tecnocode.co.uk> | 2015-04-15 00:39:36 +0100 |
---|---|---|
committer | Philip Withnall <philip@tecnocode.co.uk> | 2015-04-15 00:39:36 +0100 |
commit | 7a3b725db8e6350a20e3dc2c653ead04c5622cce (patch) | |
tree | a8e235bb00cf11380c150ed350a54bae14554a08 /gdata | |
parent | 4e811cac69a9bfa9b3dac3cc52135a12a45720ee (diff) | |
download | libgdata-7a3b725db8e6350a20e3dc2c653ead04c5622cce.tar.gz |
youtube: WIP work to port to API v3youtube-v3
Diffstat (limited to 'gdata')
-rw-r--r-- | gdata/gdata-entry.c | 34 | ||||
-rw-r--r-- | gdata/gdata-private.h | 2 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-query.c | 3 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-service.c | 451 | ||||
-rw-r--r-- | gdata/services/youtube/gdata-youtube-video.c | 347 |
5 files changed, 540 insertions, 297 deletions
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c index be242e59..4b156e45 100644 --- a/gdata/gdata-entry.c +++ b/gdata/gdata-entry.c @@ -849,6 +849,40 @@ _gdata_entry_set_updated (GDataEntry *self, gint64 updated) self->priv->updated = updated; } +/* + * _gdata_entry_set_published: + * @self: a #GDataEntry + * @updated: the new published value + * + * Sets the value of the #GDataEntry:published property to @published. + * + * Since: UNRELEASED + */ +void +_gdata_entry_set_published (GDataEntry *self, gint64 published) +{ + g_return_if_fail (GDATA_IS_ENTRY (self)); + self->priv->published = published; +} + +/* + * _gdata_entry_set_id: + * @self: a #GDataEntry + * @id: the new updated value + * + * Sets the value of the #GDataEntry:updated property to @updated. TODO + * + * Since: UNRELEASED + */ +void +_gdata_entry_set_id (GDataEntry *self, const gchar *id) +{ + g_return_if_fail (GDATA_IS_ENTRY (self)); + + g_free (self->priv->id); + self->priv->id = g_strdup (id); +} + /** * gdata_entry_get_published: * @self: a #GDataEntry diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h index 8ad24876..17a64c27 100644 --- a/gdata/gdata-private.h +++ b/gdata/gdata-private.h @@ -110,6 +110,8 @@ G_GNUC_INTERNAL void _gdata_feed_call_progress_callback (GDataFeed *self, gpoint #include "gdata-entry.h" #include "gdata-batch-operation.h" G_GNUC_INTERNAL void _gdata_entry_set_updated (GDataEntry *self, gint64 updated); +G_GNUC_INTERNAL void _gdata_entry_set_published (GDataEntry *self, gint64 published); +G_GNUC_INTERNAL void _gdata_entry_set_id (GDataEntry *self, const gchar *id); G_GNUC_INTERNAL void _gdata_entry_set_batch_data (GDataEntry *self, guint id, GDataBatchOperationType type); #include "gdata-parser.h" diff --git a/gdata/services/youtube/gdata-youtube-query.c b/gdata/services/youtube/gdata-youtube-query.c index bf95730a..24e99b91 100644 --- a/gdata/services/youtube/gdata-youtube-query.c +++ b/gdata/services/youtube/gdata-youtube-query.c @@ -455,6 +455,8 @@ get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboo /* Chain up to the parent class */ GDATA_QUERY_CLASS (gdata_youtube_query_parent_class)->get_query_uri (self, feed_uri, query_uri, params_started); +#if 0 +TODO APPEND_SEP switch (priv->age) { case GDATA_YOUTUBE_AGE_TODAY: @@ -538,6 +540,7 @@ get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboo g_string_append (query_uri, "&license="); g_string_append_uri_escaped (query_uri, priv->license, NULL, FALSE); } +#endif } /** diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index e1210ae4..c1b5d957 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 @@ -235,6 +235,8 @@ * g_object_unref (feed); * </programlisting> * </example> + * + * TODO: Update the above **/ #include <config.h> @@ -252,7 +254,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: http://code.google.com/apis/youtube/2.0/reference.html TODO */ GQuark gdata_youtube_service_error_quark (void) @@ -277,7 +279,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 @@ -300,7 +304,7 @@ 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>. **/ g_object_class_install_property (gobject_class, PROP_DEVELOPER_KEY, g_param_spec_string ("developer-key", @@ -359,161 +363,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." + * } + * } + */ +/* TODO: 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); @@ -532,7 +595,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() * @@ -550,6 +613,22 @@ gdata_youtube_service_new (const gchar *developer_key, GDataAuthorizer *authoriz NULL); } +/* Standard list of ‘part’ parameter values used for most queries. + * Reference: https://developers.google.com/youtube/v3/docs/videos/list#part */ +#define STANDARD_VIDEO_PART \ + "contentDetails," \ + "fileDetails," \ + "id," \ + "liveStreamingDetails," \ + "player," \ + "processingDetails," \ + "recordingDetails," \ + "snippet," \ + "statistics," \ + "status," \ + "suggestions," \ + "topicDetails" + /** * gdata_youtube_service_get_primary_authorization_domain: * @@ -573,26 +652,22 @@ static const gchar * standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type) { switch (feed_type) { + case GDATA_YOUTUBE_MOST_POPULAR_FEED: + return "https://www.googleapis.com/youtube/v3/videos?part=" STANDARD_VIDEO_PART "&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"; + /* TODO: Maybe see 'My most-viewed videos' on https://developers.google.com/youtube/v3/sample_requests */ + /* TODO: test */ + g_warning ("The %u standard feed type is deprecated. Returning " + "a generic feed instead.", feed_type); + return "https://www.googleapis.com/youtube/v3/videos?part=" STANDARD_VIDEO_PART; default: g_assert_not_reached (); } @@ -612,6 +687,8 @@ standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type) * * Parameters and errors are as for gdata_service_query(). * + * TODO: Document deprecation of most feed types + * * Return value: (transfer full): a #GDataFeed of query results, or %NULL; unref with g_object_unref() **/ GDataFeed * @@ -694,8 +771,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); } /** @@ -731,9 +814,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); } /** @@ -748,8 +837,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() **/ @@ -758,7 +846,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; @@ -768,19 +855,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; @@ -815,7 +900,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)); @@ -824,23 +908,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); } @@ -876,6 +954,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); @@ -888,18 +968,24 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo _("The entry has already been inserted.")); return NULL; } - +/* TODO: 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, _("You must be authenticated to upload a video.")); 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)); +/* TODO: check against https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol */ +/* TODO: deprecate this method and create a upload_video_resumable() version */ + stream = gdata_upload_stream_new_resumable (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + SOUP_METHOD_POST, + "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=" STANDARD_VIDEO_PART, + GDATA_ENTRY (video), slug, + content_type, + -1, /* content_length, */ + cancellable); + return GDATA_UPLOAD_STREAM (stream); } /** @@ -934,7 +1020,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)); } /** @@ -970,6 +1059,7 @@ gdata_youtube_service_get_developer_key (GDataYouTubeService *self) GDataAPPCategories * gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *cancellable, GError **error) { + gchar *uri; SoupMessage *message; GDataAPPCategories *categories; @@ -978,15 +1068,24 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c 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); + uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/videoCategories" + "?part=" STANDARD_VIDEO_PART + "®ionCode=%s", + gdata_service_get_locale (GDATA_SERVICE (self))); + 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-video.c b/gdata/services/youtube/gdata-youtube-video.c index e93751da..239dff2b 100644 --- a/gdata/services/youtube/gdata-youtube-video.c +++ b/gdata/services/youtube/gdata-youtube-video.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 @@ -64,12 +64,14 @@ * g_object_unref (video); * </programlisting> * </example> + * + * TODO: update **/ #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" @@ -93,10 +95,9 @@ 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 gboolean post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error); +static void get_json (GDataParsable *parsable, JsonBuilder *builder); 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; @@ -118,8 +119,19 @@ struct _GDataYouTubeVideoPrivate { gdouble average; } rating; - /* media:group */ - GDataMediaGroup *media_group; /* is actually a GDataYouTubeGroup */ + gchar **keywords; + gchar *player_uri; + GHashTable *restricted_countries; + gchar *simple_rating; + gchar *mpaa_rating; + gchar *v_chip_rating; + GList *thumbnails; /* GDataMediaThumbnail */ + GDataMediaCategory *category; + GList *contents; /* GDataMediaContent */ + GDataMediaCredit *credit; + guint duration; + gboolean is_private; + gchar *aspect_ratio; /* georss:where */ GDataGeoRSSWhere *georss_where; @@ -175,18 +187,17 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass) 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->post_parse_json = post_parse_json; + parsable_class->get_json = get_json; 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"; /* TODO: also: youtube#searchResult */ /** * GDataYouTubeVideo:view-count: * - * The number of times the video has been viewed. + * The number of times the video has been viewed. TODO: update links * * For more information, see the <ulink type="http" * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:statistics">online documentation</ulink>. @@ -515,25 +526,12 @@ gdata_youtube_video_commentable_init (GDataCommentableInterface *iface) } static void -notify_title_cb (GDataYouTubeVideo *self, GParamSpec *pspec, gpointer user_data) -{ - /* Update our media:group title */ - if (self->priv->media_group != NULL) - gdata_media_group_set_title (self->priv->media_group, gdata_entry_get_title (GDATA_ENTRY (self))); -} - -static void 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); - - /* The video's title is duplicated between atom:title and media:group/media:title, so listen for change notifications on atom:title - * and propagate them to media:group/media:title accordingly. Since the media group isn't publically accessible, we don't need to - * listen for notifications from it. */ - g_signal_connect (GDATA_ENTRY (self), "notify::title", G_CALLBACK (notify_title_cb), NULL); } static GObject * @@ -545,9 +543,8 @@ gdata_youtube_video_constructor (GType type, guint n_construct_params, GObjectCo 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) { + if (_gdata_parsable_is_constructed_from_xml (GDATA_PARSABLE (object)) == FALSE) { /* TODO */ 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); } @@ -559,10 +556,6 @@ 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; @@ -586,6 +579,16 @@ 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_hash_table_unref (priv->restricted_countries); + g_free (priv->simple_rating); + g_free (priv->mpaa_rating); + g_free (priv->v_chip_rating); + g_list_free_full (priv->thumbnails, (GDestroyNotify) g_object_unref); + g_clear_object (&priv->category); + g_list_free_full (priv->credit, (GDestroyNotify) g_object_unref); + g_free (priv->aspect_ratio); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_youtube_video_parent_class)->finalize (object); @@ -619,31 +622,31 @@ 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, priv->player_uri); 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, priv->credit); 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)); @@ -655,7 +658,7 @@ gdata_youtube_video_get_property (GObject *object, guint property_id, GValue *va 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, priv->aspect_ratio); break; case PROP_LATITUDE: g_value_set_double (value, gdata_georss_where_get_latitude (priv->georss_where)); @@ -716,11 +719,134 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal } 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); +/* TODO: + gchar *player_uri; + GDataMediaCategory *category; + GList *contents; /* GDataMediaContent * / + GDataMediaCredit *credit; + gchar *aspect_ratio; +*/ + + if (g_strcmp0 (json_reader_get_member_name (reader), "id") == 0) { + const gchar *id; + + /* 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)) { + if (!json_reader_read_member (reader, "videoId")) { + id = json_reader_get_string_value (reader); + json_reader_end_member (reader); + return gdata_parser_error_required_json_content_missing (reader, error); + } + + 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; + + json_reader_read_element (reader, i); + + /* TODO */ + if (gdata_parser_int64_time_from_json_member (reader, "publishedAt", P_DEFAULT, &published_at, &success, error)) { + _gdata_entry_set_published (GDATA_ENTRY (parsable), + published_at); + } else if (gdata_parser_string_from_json_member (reader, "title", P_DEFAULT, &title, &success, error)) { + 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)) { + gdata_entry_set_summary (GDATA_ENTRY (parsable), + description); + g_free (description); + } else if (string_array_from_json_member (reader, "tags", P_DEFAULT, &priv->keywords, &success, error)) { + /* TODO */ + } else if (thumbnails_from_json_member (reader, "thumbnails", P_DEFAULT, &priv->thumbnails, &success, error)) { + /* TODO */ + } else { + /* TODO */ + } + + json_reader_end_element (reader); + } + } 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); + + /* TODO: Check formats for all of these match up with MediaGroup documentation */ + if (gdata_parser_string_from_json_member (reader, "duration", P_DEFAULT, &priv->duration, &success, error) || + restricted_countries_from_json_member (reader, "regionRestriction", P_DEFAULT, &priv->restricted_countries, &success, error) || + content_rating_from_json_member (reader, "contentRating", P_DEFAULT, &priv->simple_rating, &priv->mpaa_rating, &priv->v_chip_rating, &success, error)) { + /* TODO */ + } + + json_reader_end_element (reader); + } + } else if (g_strcmp0 (json_reader_get_member_name (reader), + "status") == 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 (privacy_status_from_json_member (reader, "privacyStatus", P_DEFAULT, &priv->is_private, &success, error) || + + ) { + /* TODO */ + } + + json_reader_end_element (reader); + } + } else { + return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_json (parsable, reader, user_data, error); + } + +#if 0 +TODO 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) { @@ -857,17 +983,23 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da } 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) +post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error) { GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv; + GDataParsableClass *parsable_class; /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->post_parse_xml (parsable, user_data, error); + parsable_class = GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class); + + if (parsable_class->post_parse_json != NULL) { + parsable_class->post_parse_json (parsable, user_data, error); + } /* This must always exist, so is_draft can be set on it */ if (priv->youtube_control == NULL) @@ -901,13 +1033,15 @@ access_control_cb (const gchar *action, gpointer value, GString *xml_string) } static void -get_xml (GDataParsable *parsable, GString *xml_string) +get_json (GDataParsable *parsable, JsonBuilder *builder) { GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv; /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_xml (parsable, xml_string); + GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_json (parsable, builder); +#if 0 +TODO /* media:group */ _gdata_parsable_get_xml (GDATA_PARSABLE (priv->media_group), xml_string, FALSE); @@ -931,48 +1065,26 @@ get_xml (GDataParsable *parsable, GString *xml_string) 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) -{ - 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); -} +/* Standard list of ‘part’ parameter values used for most queries. + * Reference: https://developers.google.com/youtube/v3/docs/videos/list#part */ +/* TODO: update */ +#define STANDARD_VIDEO_PART \ + "contentDetails," \ + "id," \ + "recordingDetails," \ + "snippet," \ + "status" 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); - - for (i = 0; parts[i] != NULL && parts[i + 1] != NULL; i += 2) { - if (strcmp (parts[i], "video") == 0) { - video_id = parts[i + 1]; - break; - } - } - - 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; +{/* TODO: test query_single_entry() */ + /* Build the query URI for a single video. */ + return g_strdup_printf ("https://www.googleapis.com/youtube/v3/videos" + "?part=" STANDARD_VIDEO_PART + "&id=%s", id); } static GDataAuthorizationDomain * @@ -984,35 +1096,24 @@ get_authorization_domain (GDataCommentable *self) static gchar * get_query_comments_uri (GDataCommentable *self) { - GDataGDFeedLink *feed_link; - - feed_link = GDATA_YOUTUBE_VIDEO (self)->priv->comments_feed_link; - - if (feed_link == NULL) { - return NULL; - } - - return _gdata_service_fix_uri_scheme (gdata_gd_feed_link_get_uri (feed_link)); + /* FIXME: Currently unsupported: + * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated */ + return NULL; } static gchar * get_insert_comment_uri (GDataCommentable *self, GDataComment *comment_) { - GDataGDFeedLink *feed_link; - - feed_link = GDATA_YOUTUBE_VIDEO (self)->priv->comments_feed_link; - - if (feed_link == NULL) { - return NULL; - } - - return _gdata_service_fix_uri_scheme (gdata_gd_feed_link_get_uri (feed_link)); + /* FIXME: Currently unsupported: + * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated */ + return NULL; } static gboolean is_comment_deletable (GDataCommentable *self, GDataComment *comment_) { - /* Deletion of comments is unsupported. */ + /* FIXME: Currently unsupported: + * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated */ return FALSE; } @@ -1177,7 +1278,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 priv->keywords; } /** @@ -1196,7 +1297,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 (priv->keywords); + priv->keywords = g_strdupv (keywords); g_object_notify (G_OBJECT (self), "keywords"); } @@ -1212,7 +1314,7 @@ const gchar * gdata_youtube_video_get_player_uri (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_media_group_get_player_uri (self->priv->media_group); + return self->priv->player_uri; } /** @@ -1275,7 +1377,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; } /** @@ -1294,7 +1396,9 @@ 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"); } @@ -1310,7 +1414,7 @@ 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 GDATA_YOUTUBE_CREDIT (self->priv->credit); } /** @@ -1325,7 +1429,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)); } /** @@ -1341,8 +1445,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"); } @@ -1377,7 +1480,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; } /** @@ -1392,7 +1495,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; } /** @@ -1407,7 +1510,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; } /** @@ -1421,7 +1524,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"); } @@ -1437,7 +1540,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)); } /** @@ -1452,7 +1555,7 @@ const gchar * gdata_youtube_video_get_video_id (GDataYouTubeVideo *self) { g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL); - return gdata_youtube_group_get_video_id (GDATA_YOUTUBE_GROUP (self->priv->media_group)); + return gdata_entry_get_id (GDATA_ENTRY (self)); } /** @@ -1622,7 +1725,7 @@ 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)); + return self->priv->aspect_ratio; } /** @@ -1639,7 +1742,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_free (self->priv->aspect_ratio); + self->priv->aspect_ratio = g_strdup (aspect_ratio); g_object_notify (G_OBJECT (self), "aspect-ratio"); } |