summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2015-04-15 00:39:36 +0100
committerPhilip Withnall <philip@tecnocode.co.uk>2015-04-15 00:39:36 +0100
commit7a3b725db8e6350a20e3dc2c653ead04c5622cce (patch)
treea8e235bb00cf11380c150ed350a54bae14554a08
parent4e811cac69a9bfa9b3dac3cc52135a12a45720ee (diff)
downloadlibgdata-youtube-v3.tar.gz
youtube: WIP work to port to API v3youtube-v3
-rw-r--r--demos/youtube/youtube-cli.c2
-rw-r--r--gdata/gdata-entry.c34
-rw-r--r--gdata/gdata-private.h2
-rw-r--r--gdata/services/youtube/gdata-youtube-query.c3
-rw-r--r--gdata/services/youtube/gdata-youtube-service.c451
-rw-r--r--gdata/services/youtube/gdata-youtube-video.c347
6 files changed, 541 insertions, 298 deletions
diff --git a/demos/youtube/youtube-cli.c b/demos/youtube/youtube-cli.c
index 7bb23b4e..b33ebb62 100644
--- a/demos/youtube/youtube-cli.c
+++ b/demos/youtube/youtube-cli.c
@@ -21,7 +21,7 @@
#include <locale.h>
#include <string.h>
-#define DEVELOPER_KEY "AI39si7Me3Q7zYs6hmkFvpRBD2nrkVjYYsUO5lh_3HdOkGRc9g6Z4nzxZatk_aAo2EsA21k7vrda0OO6oFg2rnhMedZXPyXoEw"
+#define DEVELOPER_KEY "AIzaSyCENhl8yDxDZbyhTF6p-ok-RefK07xdXUg"
static int
print_usage (char *argv[])
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
+ "&regionCode=%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");
}