[libgdata] [core] Unify queries for single entries



commit a0742d438ac0cca989e3eb4756d9985596e5479a
Author: Philip Withnall <philip tecnocode co uk>
Date:   Wed Mar 31 00:22:07 2010 +0100

    [core] Unify queries for single entries
    
    Add a new gdata_service_query_single_entry() function (and async variant)
    to serve as the main function to call for retrieving a single entry from
    any service.
    
    As a consequence of this, the GDataQuery:entry-id property has been removed,
    as it's now redundant (and never worked well anyway).
    
    ABI has been broken apart from the API removals due to adding a new member
    to GDataEntryClass.
    
    Note that this includes the removal of:
     * gdata_youtube_service_query_single_video() (and async variant)
     * gdata_documents_service_query_single_document()
     * gdata_query_new_for_id()
     * gdata_query_[get|set]_entry_id()

 docs/reference/gdata-sections.txt                  |   10 +-
 gdata/gdata-entry.c                                |   11 ++
 gdata/gdata-entry.h                                |    9 +-
 gdata/gdata-query.c                                |   89 +----------
 gdata/gdata-query.h                                |    5 +-
 gdata/gdata-service.c                              |  175 +++++++++++++++++++-
 gdata/gdata-service.h                              |    6 +
 gdata/gdata.symbols                                |   10 +-
 gdata/services/contacts/gdata-contacts-contact.c   |   19 ++
 gdata/services/documents/gdata-documents-query.c   |    7 +-
 gdata/services/documents/gdata-documents-service.c |   70 +--------
 gdata/services/documents/gdata-documents-service.h |    2 -
 gdata/services/picasaweb/gdata-picasaweb-file.c    |   19 ++
 gdata/services/youtube/gdata-youtube-service.c     |  159 ------------------
 gdata/services/youtube/gdata-youtube-service.h     |    7 -
 gdata/services/youtube/gdata-youtube-video.c       |   30 ++++
 gdata/tests/contacts.c                             |    4 -
 gdata/tests/documents.c                            |   11 +-
 gdata/tests/general.c                              |    1 -
 gdata/tests/picasaweb.c                            |   22 +++
 gdata/tests/youtube.c                              |    9 +-
 21 files changed, 310 insertions(+), 365 deletions(-)
---
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 9adc0c4..b237d66 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -15,6 +15,9 @@ gdata_service_is_authenticated
 gdata_service_query
 gdata_service_query_async
 gdata_service_query_finish
+gdata_service_query_single_entry
+gdata_service_query_single_entry_async
+gdata_service_query_single_entry_finish
 gdata_service_insert_entry
 gdata_service_insert_entry_async
 gdata_service_insert_entry_finish
@@ -54,14 +57,11 @@ GDataQuery
 GDataQueryClass
 gdata_query_new
 gdata_query_new_with_limits
-gdata_query_new_for_id
 gdata_query_get_query_uri
 gdata_query_next_page
 gdata_query_previous_page
 gdata_query_get_q
 gdata_query_set_q
-gdata_query_get_entry_id
-gdata_query_set_entry_id
 gdata_query_get_etag
 gdata_query_set_etag
 gdata_query_get_author
@@ -175,9 +175,6 @@ GDataYouTubeStandardFeedType
 gdata_youtube_service_new
 gdata_youtube_service_query_videos
 gdata_youtube_service_query_videos_async
-gdata_youtube_service_query_single_video
-gdata_youtube_service_query_single_video_async
-gdata_youtube_service_query_single_video_finish
 gdata_youtube_service_query_related
 gdata_youtube_service_query_related_async
 gdata_youtube_service_query_standard_feed
@@ -1651,7 +1648,6 @@ GDataDocumentsServiceError
 gdata_documents_service_new
 gdata_documents_service_query_documents
 gdata_documents_service_query_documents_async
-gdata_documents_service_query_single_document
 gdata_documents_service_upload_document
 gdata_documents_service_update_document
 gdata_documents_service_move_document_to_folder
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index 1d1b6c7..8184df1 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -52,6 +52,7 @@ static gboolean post_parse_xml (GDataParsable *parsable, gpointer user_data, GEr
 static void pre_get_xml (GDataParsable *parsable, GString *xml_string);
 static void get_xml (GDataParsable *parsable, GString *xml_string);
 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
 
 struct _GDataEntryPrivate {
 	gchar *title;
@@ -102,6 +103,8 @@ gdata_entry_class_init (GDataEntryClass *klass)
 	parsable_class->get_namespaces = get_namespaces;
 	parsable_class->element_name = "entry";
 
+	klass->get_entry_uri = get_entry_uri;
+
 	/**
 	 * GDataEntry:title:
 	 *
@@ -478,6 +481,14 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 	g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005";);
 }
 
+static gchar *
+get_entry_uri (const gchar *id)
+{
+	/* We assume the entry ID is also its entry URI; subclasses can override this
+	 * if the service they implement has a convoluted API */
+	return g_strdup (id);
+}
+
 /**
  * gdata_entry_new:
  * @id: the entry's ID, or %NULL
diff --git a/gdata/gdata-entry.h b/gdata/gdata-entry.h
index 9a683f9..bbca7e8 100644
--- a/gdata/gdata-entry.h
+++ b/gdata/gdata-entry.h
@@ -1,7 +1,7 @@
 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
 /*
  * GData Client
- * Copyright (C) Philip Withnall 2008-2009 <philip tecnocode co uk>
+ * Copyright (C) Philip Withnall 2008â??2010 <philip tecnocode co uk>
  *
  * GData Client is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -51,12 +51,15 @@ typedef struct {
 
 /**
  * GDataEntryClass:
+ * @parent: the parent class
+ * @get_entry_uri: a function to build the entry URI for the entry, given its entry ID; free the URI with g_free()
  *
- * All the fields in the #GDataEntryClass structure are private and should never be accessed directly.
+ * The class structure for the #GDataEntry type.
  **/
 typedef struct {
-	/*< private >*/
 	GDataParsableClass parent;
+
+	gchar *(*get_entry_uri) (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
 } GDataEntryClass;
 
 GType gdata_entry_get_type (void) G_GNUC_CONST;
diff --git a/gdata/gdata-query.c b/gdata/gdata-query.c
index 58ea7ce..0e8ea0b 100644
--- a/gdata/gdata-query.c
+++ b/gdata/gdata-query.c
@@ -59,8 +59,7 @@ typedef enum {
 	GDATA_QUERY_PARAM_START_INDEX = 1 << 7,
 	GDATA_QUERY_PARAM_IS_STRICT = 1 << 8,
 	GDATA_QUERY_PARAM_MAX_RESULTS = 1 << 9,
-	GDATA_QUERY_PARAM_ENTRY_ID = 1 << 10,
-	GDATA_QUERY_PARAM_ALL = (1 << 11) - 1
+	GDATA_QUERY_PARAM_ALL = (1 << 10) - 1
 } GDataQueryParam;
 
 static void gdata_query_finalize (GObject *object);
@@ -82,7 +81,6 @@ struct _GDataQueryPrivate {
 	gint start_index;
 	gboolean is_strict;
 	gint max_results;
-	gchar *entry_id;
 
 	gchar *next_uri;
 	gchar *previous_uri;
@@ -103,7 +101,6 @@ enum {
 	PROP_START_INDEX,
 	PROP_IS_STRICT,
 	PROP_MAX_RESULTS,
-	PROP_ENTRY_ID,
 	PROP_ETAG
 };
 
@@ -271,17 +268,6 @@ gdata_query_class_init (GDataQueryClass *klass)
 					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
 	/**
-	 * GDataQuery:entry-id:
-	 *
-	 * The ID of a specific entry to be retrieved. If you specify an entry ID, you cannot specify any other parameters.
-	 **/
-	g_object_class_install_property (gobject_class, PROP_ENTRY_ID,
-				g_param_spec_string ("entry-id",
-					"Entry ID", "A specific entry ID to return.",
-					NULL,
-					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-	/**
 	 * GDataQuery:etag:
 	 *
 	 * The ETag against which to check for updates. If the server-side ETag matches this one, the requested feed hasn't changed, and is not
@@ -315,7 +301,6 @@ gdata_query_finalize (GObject *object)
 	g_free (priv->q);
 	g_free (priv->categories);
 	g_free (priv->author);
-	g_free (priv->entry_id);
 
 	g_free (priv->next_uri);
 	g_free (priv->previous_uri);
@@ -362,9 +347,6 @@ gdata_query_get_property (GObject *object, guint property_id, GValue *value, GPa
 		case PROP_MAX_RESULTS:
 			g_value_set_int (value, priv->max_results);
 			break;
-		case PROP_ENTRY_ID:
-			g_value_set_string (value, priv->entry_id);
-			break;
 		case PROP_ETAG:
 			g_value_set_string (value, priv->etag);
 			break;
@@ -411,9 +393,6 @@ gdata_query_set_property (GObject *object, guint property_id, const GValue *valu
 		case PROP_MAX_RESULTS:
 			gdata_query_set_max_results (self, g_value_get_int (value));
 			break;
-		case PROP_ENTRY_ID:
-			gdata_query_set_entry_id (self, g_value_get_string (value));
-			break;
 		case PROP_ETAG:
 			gdata_query_set_etag (self, g_value_get_string (value));
 			break;
@@ -435,20 +414,13 @@ get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboo
 	if ((priv->parameter_mask & GDATA_QUERY_PARAM_ALL) == 0)
 		return;
 
-	/* If we've been provided with an entry ID, only append that */
-	if (priv->entry_id != NULL) {
-		g_string_append_c (query_uri, '/');
-		g_string_append_uri_escaped (query_uri, priv->entry_id, NULL, FALSE);
-		return;
-	}
-
 	if (priv->categories != NULL) {
 		g_string_append (query_uri, "/-/");
 		g_string_append_uri_escaped (query_uri, priv->categories, "/", FALSE);
 	}
 
 	/* If that's it, return */
-	if ((priv->parameter_mask & (GDATA_QUERY_PARAM_ALL ^ GDATA_QUERY_PARAM_ENTRY_ID ^ GDATA_QUERY_PARAM_CATEGORIES)) == 0)
+	if ((priv->parameter_mask & (GDATA_QUERY_PARAM_ALL ^ GDATA_QUERY_PARAM_CATEGORIES)) == 0)
 		return;
 
 	/* q param */
@@ -556,20 +528,6 @@ gdata_query_new_with_limits (const gchar *q, gint start_index, gint max_results)
 }
 
 /**
- * gdata_query_new_for_id:
- * @entry_id: an entry URN ID
- *
- * Creates a new #GDataQuery to query for @entry_id.
- *
- * Return value: a new #GDataQuery
- **/
-GDataQuery *
-gdata_query_new_for_id (const gchar *entry_id)
-{
-	return g_object_new (GDATA_TYPE_QUERY, "entry-id", entry_id, NULL);
-}
-
-/**
  * gdata_query_get_query_uri:
  * @self: a #GDataQuery
  * @feed_uri: the feed URI on which to build the query URI
@@ -1051,49 +1009,6 @@ gdata_query_set_max_results (GDataQuery *self, gint max_results)
 }
 
 /**
- * gdata_query_get_entry_id:
- * @self: a #GDataQuery
- *
- * Gets the #GDataQuery:entry-id property.
- *
- * Return value: the entry ID property, or %NULL if it is unset
- **/
-const gchar *
-gdata_query_get_entry_id (GDataQuery *self)
-{
-	g_return_val_if_fail (GDATA_IS_QUERY (self), NULL);
-	return self->priv->entry_id;
-}
-
-/**
- * gdata_query_set_entry_id:
- * @self: a #GDataQuery
- * @entry_id: the new entry ID string
- *
- * Sets the #GDataQuery:entry-id property of the #GDataQuery to the new entry ID string, @entry_id.
- *
- * Set @entry_id to %NULL to unset the property in the query URI.
- **/
-void
-gdata_query_set_entry_id (GDataQuery *self, const gchar *entry_id)
-{
-	g_return_if_fail (GDATA_IS_QUERY (self));
-
-	g_free (self->priv->entry_id);
-	self->priv->entry_id = g_strdup (entry_id);
-
-	if (entry_id == NULL)
-		self->priv->parameter_mask &= ~GDATA_QUERY_PARAM_ENTRY_ID;
-	else
-		self->priv->parameter_mask |= GDATA_QUERY_PARAM_ENTRY_ID;
-
-	g_object_notify (G_OBJECT (self), "entry-id");
-
-	/* Our current ETag will no longer be relevant */
-	gdata_query_set_etag (self, NULL);
-}
-
-/**
  * gdata_query_get_etag:
  * @self: a #GDataQuery
  *
diff --git a/gdata/gdata-query.h b/gdata/gdata-query.h
index 89ebf31..8e5b383 100644
--- a/gdata/gdata-query.h
+++ b/gdata/gdata-query.h
@@ -1,7 +1,7 @@
 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
 /*
  * GData Client
- * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ * Copyright (C) Philip Withnall 2009â??2010 <philip tecnocode co uk>
  *
  * GData Client is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -60,7 +60,6 @@ GType gdata_query_get_type (void) G_GNUC_CONST;
 
 GDataQuery *gdata_query_new (const gchar *q) G_GNUC_WARN_UNUSED_RESULT;
 GDataQuery *gdata_query_new_with_limits (const gchar *q, gint start_index, gint max_results) G_GNUC_WARN_UNUSED_RESULT;
-GDataQuery *gdata_query_new_for_id (const gchar *entry_id) G_GNUC_WARN_UNUSED_RESULT;
 
 gchar *gdata_query_get_query_uri (GDataQuery *self, const gchar *feed_uri) G_GNUC_WARN_UNUSED_RESULT;
 void gdata_query_next_page (GDataQuery *self);
@@ -86,8 +85,6 @@ gboolean gdata_query_is_strict (GDataQuery *self);
 void gdata_query_set_is_strict (GDataQuery *self, gboolean is_strict);
 gint gdata_query_get_max_results (GDataQuery *self);
 void gdata_query_set_max_results (GDataQuery *self, gint max_results);
-const gchar *gdata_query_get_entry_id (GDataQuery *self);
-void gdata_query_set_entry_id (GDataQuery *self, const gchar *entry_id);
 const gchar *gdata_query_get_etag (GDataQuery *self);
 void gdata_query_set_etag (GDataQuery *self, const gchar *etag);
 
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index 84ad337..98e09a2 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -1004,7 +1004,7 @@ gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GErr
 }
 
 /* Does the bulk of the work of gdata_service_query. Split out because certain queries (such as that done by
- * gdata_youtube_service_query_single_video()) only return a single entry, and thus need special parsing code. */
+ * gdata_service_query_single_entry()) only return a single entry, and thus need special parsing code. */
 SoupMessage *
 _gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable,
 		      GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
@@ -1129,6 +1129,179 @@ gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *quer
 	return feed;
 }
 
+/**
+ * gdata_service_query_single_entry:
+ * @self: a #GDataService
+ * @entry_id: the entry ID of the desired entry
+ * @query: a #GDataQuery with the query parameters, or %NULL
+ * @entry_type: a #GType for the #GDataEntry to build from the XML
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Retrieves information about the single entry with the given @entry_id. @entry_id should be as returned by
+ * gdata_entry_get_id().
+ *
+ * Parameters and errors are as for gdata_service_query(). Most of the properties of @query aren't relevant, and
+ * will cause a server-side error if used. The most useful property to use is #GDataQuery:etag, which will cause the
+ * server to not return anything if the entry hasn't been modified since it was given the specified ETag; thus saving
+ * bandwidth. If the server does not return anything for this reason, gdata_service_query_single_entry() will return
+ * %NULL, but will not set an error in @error.
+ *
+ * Return value: a #GDataEntry, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.7.0
+ **/
+GDataEntry *
+gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
+                                  GCancellable *cancellable, GError **error)
+{
+	GDataEntryClass *klass;
+	GDataEntry *entry;
+	gchar *entry_uri;
+	SoupMessage *message;
+
+	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
+	g_return_val_if_fail (entry_id != NULL, NULL);
+	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
+	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* Query for just the specified entry */
+	klass = GDATA_ENTRY_CLASS (g_type_class_peek_static (entry_type));
+	g_assert (klass->get_entry_uri != NULL);
+
+	entry_uri = klass->get_entry_uri (entry_id);
+	message = _gdata_service_query (GDATA_SERVICE (self), entry_uri, query, cancellable, NULL, NULL, error);
+	g_free (entry_uri);
+
+	if (message == NULL)
+		return NULL;
+
+	g_assert (message->response_body->data != NULL);
+	entry = GDATA_ENTRY (gdata_parsable_new_from_xml (entry_type, message->response_body->data, message->response_body->length, error));
+	g_object_unref (message);
+
+	return entry;
+}
+
+typedef struct {
+	gchar *entry_id;
+	GDataQuery *query;
+	GType entry_type;
+} QuerySingleEntryAsyncData;
+
+static void
+query_single_entry_async_data_free (QuerySingleEntryAsyncData *data)
+{
+	g_free (data->entry_id);
+	if (data->query != NULL)
+		g_object_unref (data->query);
+	g_slice_free (QuerySingleEntryAsyncData, data);
+}
+
+static void
+query_single_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
+{
+	GDataEntry *entry;
+	GError *error = NULL;
+	QuerySingleEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
+
+	/* Check to see if it's been cancelled already */
+	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Execute the query and return */
+	entry = gdata_service_query_single_entry (service, data->entry_id, data->query, data->entry_type, cancellable, &error);
+	if (entry == NULL && error != NULL) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+	}
+
+	g_simple_async_result_set_op_res_gpointer (result, entry, (GDestroyNotify) g_object_unref);
+}
+
+/**
+ * gdata_service_query_single_entry_async:
+ * @self: a #GDataService
+ * @entry_id: the entry ID of the desired entry
+ * @query: a #GDataQuery with the query parameters, or %NULL
+ * @entry_type: a #GType for the #GDataEntry to build from the XML
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the query is finished
+ * @user_data: data to pass to the @callback function
+ *
+ * Retrieves information about the single entry with the given @entry_id. @entry_id should be as returned by
+ * gdata_entry_get_id(). @self, @query and @entry_id are reffed/copied when this
+ * function is called, so can safely be freed after this function returns.
+ *
+ * For more details, see gdata_service_query_single_entry(), which is the synchronous version of this function.
+ *
+ * When the operation is finished, @callback will be called. You can then call gdata_service_query_single_entry_finish()
+ * to get the results of the operation.
+ *
+ * Since: 0.7.0
+ **/
+void
+gdata_service_query_single_entry_async (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
+                                        GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	QuerySingleEntryAsyncData *data;
+
+	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (entry_id != NULL);
+	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
+	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE);
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	data = g_slice_new (QuerySingleEntryAsyncData);
+	data->query = (query != NULL) ? g_object_ref (query) : NULL;
+	data->entry_id = g_strdup (entry_id);
+	data->entry_type = entry_type;
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_query_single_entry_async);
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) query_single_entry_async_data_free);
+	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) query_single_entry_thread, G_PRIORITY_DEFAULT, cancellable);
+	g_object_unref (result);
+}
+
+/**
+ * gdata_service_query_single_entry_finish:
+ * @self: a #GDataService
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous query operation for a single entry, as started with gdata_service_query_single_entry_async().
+ *
+ * Return value: a #GDataEntry, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.7.0
+ **/
+GDataEntry *
+gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
+{
+	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
+	GDataEntry *entry;
+
+	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_query_single_entry_async);
+
+	if (g_simple_async_result_propagate_error (result, error) == TRUE)
+		return NULL;
+
+	entry = g_simple_async_result_get_op_res_gpointer (result);
+	if (entry != NULL)
+		return g_object_ref (entry);
+	return NULL;
+}
+
 typedef struct {
 	gchar *upload_uri;
 	GDataEntry *entry;
diff --git a/gdata/gdata-service.h b/gdata/gdata-service.h
index a2ac3c5..73af8ee 100644
--- a/gdata/gdata-service.h
+++ b/gdata/gdata-service.h
@@ -196,6 +196,12 @@ void gdata_service_query_async (GDataService *self, const gchar *feed_uri, GData
                                 GAsyncReadyCallback callback, gpointer user_data);
 GDataFeed *gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GError **error) G_GNUC_WARN_UNUSED_RESULT;
 
+GDataEntry *gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
+                                              GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+void gdata_service_query_single_entry_async (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
+                                             GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+GDataEntry *gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
 GDataEntry *gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEntry *entry,
                                         GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
 void gdata_service_insert_entry_async (GDataService *self, const gchar *upload_uri, GDataEntry *entry, GCancellable *cancellable,
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 8b501a0..3dd378b 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -49,6 +49,9 @@ gdata_service_authenticate_finish
 gdata_service_query
 gdata_service_query_async
 gdata_service_query_finish
+gdata_service_query_single_entry
+gdata_service_query_single_entry_async
+gdata_service_query_single_entry_finish
 gdata_service_insert_entry
 gdata_service_insert_entry_async
 gdata_service_insert_entry_finish
@@ -67,7 +70,6 @@ gdata_service_get_password
 gdata_query_get_type
 gdata_query_new
 gdata_query_new_with_limits
-gdata_query_new_for_id
 gdata_query_get_query_uri
 gdata_query_next_page
 gdata_query_previous_page
@@ -91,8 +93,6 @@ gdata_query_is_strict
 gdata_query_set_is_strict
 gdata_query_get_max_results
 gdata_query_set_max_results
-gdata_query_get_entry_id
-gdata_query_set_entry_id
 gdata_query_get_etag
 gdata_query_set_etag
 gdata_g_time_val_get_type
@@ -104,9 +104,6 @@ gdata_youtube_service_query_standard_feed
 gdata_youtube_service_query_standard_feed_async
 gdata_youtube_service_query_videos
 gdata_youtube_service_query_videos_async
-gdata_youtube_service_query_single_video
-gdata_youtube_service_query_single_video_async
-gdata_youtube_service_query_single_video_finish
 gdata_youtube_service_query_related
 gdata_youtube_service_query_related_async
 gdata_youtube_service_upload_video
@@ -652,7 +649,6 @@ gdata_documents_folder_new
 gdata_documents_service_get_type
 gdata_documents_service_new
 gdata_documents_service_query_documents
-gdata_documents_service_query_single_document
 gdata_documents_service_query_documents_async
 gdata_documents_service_upload_document
 gdata_documents_service_update_document
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index 77c3e29..a1b0c4e 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -55,6 +55,7 @@ static void gdata_contacts_contact_set_property (GObject *object, guint property
 static void get_xml (GDataParsable *parsable, GString *xml_string);
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
 
 struct _GDataContactsContactPrivate {
 	GTimeVal edited;
@@ -95,6 +96,7 @@ gdata_contacts_contact_class_init (GDataContactsContactClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataContactsContactPrivate));
 
@@ -107,6 +109,8 @@ gdata_contacts_contact_class_init (GDataContactsContactClass *klass)
 	parsable_class->get_xml = get_xml;
 	parsable_class->get_namespaces = get_namespaces;
 
+	entry_class->get_entry_uri = get_entry_uri;
+
 	/**
 	 * GDataContactsContact:edited:
 	 *
@@ -560,6 +564,21 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 	g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app";);
 }
 
+static gchar *
+get_entry_uri (const gchar *id)
+{
+	const gchar *base_pos;
+	gchar *uri = g_strdup (id);
+
+	/* The service API sometimes stubbornly insists on using the "base" view instead of the "full" view, which we have
+	 * to fix, or our extended attributes are never visible */
+	base_pos = strstr (uri, "/base/");
+	if (base_pos != NULL)
+		memcpy ((char*) base_pos, "/full/", 6);
+
+	return uri;
+}
+
 /**
  * gdata_contacts_contact_new:
  * @id: the contact's ID, or %NULL
diff --git a/gdata/services/documents/gdata-documents-query.c b/gdata/services/documents/gdata-documents-query.c
index 7e006ea..9051126 100644
--- a/gdata/services/documents/gdata-documents-query.c
+++ b/gdata/services/documents/gdata-documents-query.c
@@ -230,11 +230,10 @@ static void
 get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started)
 {
 	GDataDocumentsQueryPrivate *priv = GDATA_DOCUMENTS_QUERY (self)->priv;
-	const gchar *entry_id = gdata_query_get_entry_id (self);
 
 	#define APPEND_SEP g_string_append_c (query_uri, (*params_started == FALSE) ? '?' : '&'); *params_started = TRUE;
 
-	if (entry_id == NULL && priv->folder_id != NULL) {
+	if (priv->folder_id != NULL) {
 		g_string_append (query_uri, "/folder%3A");
 		g_string_append_uri_escaped (query_uri, priv->folder_id, NULL, FALSE);
 	}
@@ -242,10 +241,6 @@ get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboo
 	/* Chain up to the parent class */
 	GDATA_QUERY_CLASS (gdata_documents_query_parent_class)->get_query_uri (self, feed_uri, query_uri, params_started);
 
-	/* Return if the entry ID has been set, since that's handled in the parent class' get_query_uri() function */
-	if (entry_id != NULL)
-		return;
-
 	if  (priv->collaborator_addresses != NULL) {
 		GList *collaborator_address;
 		APPEND_SEP
diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c
index 3431fe5..e1b4dd9 100644
--- a/gdata/services/documents/gdata-documents-service.c
+++ b/gdata/services/documents/gdata-documents-service.c
@@ -208,72 +208,6 @@ gdata_documents_service_query_documents (GDataDocumentsService *self, GDataDocum
 }
 
 /**
- * gdata_documents_service_query_single_document:
- * @self: a #GDataDocumentsService
- * @document_type: the expected #GType of the queried entry
- * @document_id: the document ID of the queried document
- * @cancellable: a #GCancellable, or %NULL
- * @error: a #GError, or %NULL
- *
- * Retrieves information about a single document with the given document ID.
- *
- * @document_type should be the expected type of the document to be returned. e.g. %GDATA_TYPE_DOCUMENTS_SPREADSHEET if you're querying
- * for a spreadsheet.
- *
- * @document_id should be the ID of the document as returned by gdata_documents_entry_get_document_id().
- *
- * Parameters and errors are as for gdata_service_query().
- *
- * Return value: a #GDataDocumentsEntry, or %NULL; unref with g_object_unref()
- *
- * Since: 0.5.0
- **/
-GDataDocumentsEntry *
-gdata_documents_service_query_single_document (GDataDocumentsService *self, GType document_type, const gchar *document_id,
-					       GCancellable *cancellable, GError **error)
-{
-	GDataDocumentsEntry *document;
-	SoupMessage *message;
-	GDataDocumentsQuery *query;
-	gchar *resource_id, *request_uri;
-
-	g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
-	g_return_val_if_fail (document_id != NULL, NULL);
-	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
-	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-	if (document_type == GDATA_TYPE_DOCUMENTS_FOLDER)
-		resource_id = g_strconcat ("folder:", document_id, NULL);
-	else if (document_type == GDATA_TYPE_DOCUMENTS_SPREADSHEET)
-		resource_id = g_strconcat ("spreadsheet:", document_id, NULL);
-	else if (document_type == GDATA_TYPE_DOCUMENTS_TEXT)
-		resource_id = g_strconcat ("document:", document_id, NULL);
-	else if (document_type == GDATA_TYPE_DOCUMENTS_PRESENTATION)
-		resource_id = g_strconcat ("presentation:", document_id, NULL);
-	else
-		g_assert_not_reached ();
-
-	query = gdata_documents_query_new (NULL);
-	gdata_query_set_entry_id (GDATA_QUERY (query), resource_id);
-	g_free (resource_id);
-
-	request_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/documents/private/full", NULL);
-	message = _gdata_service_query (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query), cancellable, NULL, NULL, error);
-	g_free (request_uri);
-	g_object_unref (query);
-
-	if (message == NULL)
-		return NULL;
-
-	g_assert (message->response_body->data != NULL);
-	document = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_xml (document_type, message->response_body->data,
-								       message->response_body->length, error));
-	g_object_unref (message);
-
-	return document;
-}
-
-/**
  * gdata_documents_service_query_documents_async:
  * @self: a #GDataDocumentsService
  * @query: a #GDataQuery with the query parameters, or %NULL
@@ -744,8 +678,8 @@ gdata_documents_service_remove_document_from_folder (GDataDocumentsService *self
 
 	/* Google's servers don't return an updated copy of the entry, so we have to query for it again.
 	 * See: http://code.google.com/p/gdata-issues/issues/detail?id=1380 */
-	return gdata_documents_service_query_single_document (self, G_OBJECT_TYPE (document), gdata_documents_entry_get_document_id (document),
-	                                                      cancellable, error);
+	return GDATA_DOCUMENTS_ENTRY (gdata_service_query_single_entry (GDATA_SERVICE (self), gdata_entry_get_id (GDATA_ENTRY (document)), NULL,
+	                                                                G_OBJECT_TYPE (document), cancellable, error));
 }
 
 /**
diff --git a/gdata/services/documents/gdata-documents-service.h b/gdata/services/documents/gdata-documents-service.h
index a83e619..78802e7 100644
--- a/gdata/services/documents/gdata-documents-service.h
+++ b/gdata/services/documents/gdata-documents-service.h
@@ -89,8 +89,6 @@ void gdata_documents_service_query_documents_async (GDataDocumentsService *self,
 
 #include <gdata/services/documents/gdata-documents-folder.h>
 
-GDataDocumentsEntry *gdata_documents_service_query_single_document (GDataDocumentsService *self, GType document_type, const gchar *document_id,
-								    GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
 GDataDocumentsEntry *gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file,
 							      GDataDocumentsFolder *folder, GCancellable *cancellable,
 							      GError **error) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/gdata/services/picasaweb/gdata-picasaweb-file.c b/gdata/services/picasaweb/gdata-picasaweb-file.c
index a7db304..2c47d48 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-file.c
+++ b/gdata/services/picasaweb/gdata-picasaweb-file.c
@@ -54,6 +54,7 @@ static void gdata_picasaweb_file_set_property (GObject *object, guint property_i
 static void get_xml (GDataParsable *parsable, GString *xml_string);
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
 
 struct _GDataPicasaWebFilePrivate {
 	gchar *file_id;
@@ -119,6 +120,7 @@ gdata_picasaweb_file_class_init (GDataPicasaWebFileClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataPicasaWebFilePrivate));
 
@@ -131,6 +133,8 @@ gdata_picasaweb_file_class_init (GDataPicasaWebFileClass *klass)
 	parsable_class->parse_xml = parse_xml;
 	parsable_class->get_namespaces = get_namespaces;
 
+	entry_class->get_entry_uri = get_entry_uri;
+
 	/**
 	 * GDataPicasaWebFile:file-id:
 	 *
@@ -990,6 +994,21 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 	GDATA_PARSABLE_GET_CLASS (priv->georss_where)->get_namespaces (GDATA_PARSABLE (priv->georss_where), namespaces);
 }
 
+static gchar *
+get_entry_uri (const gchar *id)
+{
+	/* For files, the ID is of the form: "http://picasaweb.google.com/data/entry/user/liz/albumid/albumID/photoid/photoID";
+	 *   whereas the URI is of the form: "http://picasaweb.google.com/data/entry/api/user/liz/albumid/albumID/photoid/photoID"; */
+	gchar **parts, *uri;
+
+	parts = g_strsplit (id, "/entry/user/", 2);
+	g_assert (parts[0] != NULL && parts[1] != NULL && parts[2] == NULL);
+	uri = g_strconcat (parts[0], "/entry/api/user/", parts[1], NULL);
+	g_strfreev (parts);
+
+	return uri;
+}
+
 /**
  * gdata_picasaweb_file_new:
  * @id: the file's ID, or %NULL
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index 1a54e91..603354a 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -512,165 +512,6 @@ gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQuery
 }
 
 /**
- * gdata_youtube_service_query_single_video:
- * @self: a #GDataYouTubeService
- * @query: a #GDataQuery with the query parameters, or %NULL
- * @video_id: the video ID of the desired video
- * @cancellable: a #GCancellable, or %NULL
- * @error: a #GError, or %NULL
- *
- * Retrieves information about the single video with the given @video_id. @video_id should be as returned by
- * gdata_youtube_video_get_video_id() or as extracted from a YouTube URI.
- *
- * Parameters and errors are as for gdata_service_query().
- *
- * Return value: a #GDataYouTubeVideo, or %NULL; unref with g_object_unref()
- *
- * Since: 0.4.0
- **/
-GDataYouTubeVideo *
-gdata_youtube_service_query_single_video (GDataYouTubeService *self, GDataQuery *query, const gchar *video_id,
-					  GCancellable *cancellable, GError **error)
-{
-	gchar *feed_uri;
-	GDataParsable *video;
-	SoupMessage *message;
-
-	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
-	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
-	g_return_val_if_fail (video_id != NULL, NULL);
-	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
-
-	/* Query for just the specified video ID */
-	feed_uri = g_strconcat ("http://gdata.youtube.com/feeds/api/videos/";, video_id, NULL);
-	message = _gdata_service_query (GDATA_SERVICE (self), feed_uri, query, cancellable, NULL, NULL, error);
-	g_free (feed_uri);
-
-	if (message == NULL)
-		return NULL;
-
-	g_assert (message->response_body->data != NULL);
-	video = gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_VIDEO, message->response_body->data, message->response_body->length, error);
-	g_object_unref (message);
-
-	return GDATA_YOUTUBE_VIDEO (video);
-}
-
-typedef struct {
-	GDataQuery *query;
-	gchar *video_id;
-} QuerySingleVideoAsyncData;
-
-static void
-query_single_video_async_data_free (QuerySingleVideoAsyncData *data)
-{
-	g_free (data->video_id);
-	if (data->query != NULL)
-		g_object_unref (data->query);
-	g_slice_free (QuerySingleVideoAsyncData, data);
-}
-
-static void
-query_single_video_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
-{
-	GDataYouTubeVideo *video;
-	GError *error = NULL;
-	QuerySingleVideoAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
-
-	/* Check to see if it's been cancelled already */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-		return;
-	}
-
-	/* Execute the query and return */
-	video = gdata_youtube_service_query_single_video (GDATA_YOUTUBE_SERVICE (service), data->query, data->video_id,
-							  cancellable, &error);
-	if (video == NULL) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-	}
-
-	g_simple_async_result_set_op_res_gpointer (result, video, (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gdata_youtube_service_query_single_video_async:
- * @self: a #GDataService
- * @query: a #GDataQuery with the query parameters, or %NULL
- * @video_id: the video ID of the desired video
- * @cancellable: optional #GCancellable object, or %NULL
- * @callback: a #GAsyncReadyCallback to call when authentication is finished
- * @user_data: data to pass to the @callback function
- *
- * Retrieves information about the single video with the given @video_id. @video_id should be as returned by
- * gdata_youtube_video_get_video_id() or as extracted from a YouTube URI. @self, @query and @video_id are reffed/copied when this
- * function is called, so can safely be freed after this function returns.
- *
- * For more details, see gdata_youtube_service_query_single_video(), which is the synchronous version of this function.
- *
- * When the operation is finished, @callback will be called. You can then call gdata_youtube_service_query_single_video_finish()
- * to get the results of the operation.
- *
- * Since: 0.4.0
- **/
-void
-gdata_youtube_service_query_single_video_async (GDataYouTubeService *self, GDataQuery *query, const gchar *video_id,
-						GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
-{
-	GSimpleAsyncResult *result;
-	QuerySingleVideoAsyncData *data;
-
-	g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self));
-	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
-	g_return_if_fail (video_id != NULL);
-	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
-
-	data = g_slice_new (QuerySingleVideoAsyncData);
-	data->query = (query != NULL) ? g_object_ref (query) : NULL;
-	data->video_id = g_strdup (video_id);
-
-	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_youtube_service_query_single_video_async);
-	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) query_single_video_async_data_free);
-	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) query_single_video_thread, G_PRIORITY_DEFAULT, cancellable);
-	g_object_unref (result);
-}
-
-/**
- * gdata_youtube_service_query_single_video_finish:
- * @self: a #GDataYouTubeService
- * @async_result: a #GAsyncResult
- * @error: a #GError, or %NULL
- *
- * Finishes an asynchronous query operation for a single video, as started with gdata_youtube_service_query_single_video_async().
- *
- * Return value: a #GDataYouTubeVideo, or %NULL; unref with g_object_unref()
- *
- * Since: 0.4.0
- **/
-GDataYouTubeVideo *
-gdata_youtube_service_query_single_video_finish (GDataYouTubeService *self, GAsyncResult *async_result, GError **error)
-{
-	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
-	GDataYouTubeVideo *video;
-
-	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
-	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
-
-	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_youtube_service_query_single_video_async);
-
-	if (g_simple_async_result_propagate_error (result, error) == TRUE)
-		return NULL;
-
-	video = g_simple_async_result_get_op_res_gpointer (result);
-	if (video != NULL)
-		return g_object_ref (video);
-
-	g_assert_not_reached ();
-}
-
-/**
  * gdata_youtube_service_query_related:
  * @self: a #GDataYouTubeService
  * @video: a #GDataYouTubeVideo for which to find related videos
diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h
index 28bf9ca..8d32b07 100644
--- a/gdata/services/youtube/gdata-youtube-service.h
+++ b/gdata/services/youtube/gdata-youtube-service.h
@@ -123,13 +123,6 @@ void gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQ
 					       GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
 					       GAsyncReadyCallback callback, gpointer user_data);
 
-GDataYouTubeVideo *gdata_youtube_service_query_single_video (GDataYouTubeService *self, GDataQuery *query, const gchar *video_id,
-							     GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
-void gdata_youtube_service_query_single_video_async (GDataYouTubeService *self, GDataQuery *query, const gchar *video_id,
-						     GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
-GDataYouTubeVideo *gdata_youtube_service_query_single_video_finish (GDataYouTubeService *self, GAsyncResult *async_result,
-								    GError **error);
-
 GDataFeed *gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVideo *video, GDataQuery *query,
 						GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
 						GError **error) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index e436e49..0d57a45 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -87,6 +87,7 @@ static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node,
 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 gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
 
 struct _GDataYouTubeVideoPrivate {
 	guint view_count;
@@ -140,6 +141,7 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataYouTubeVideoPrivate));
 
@@ -153,6 +155,8 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
 	parsable_class->get_xml = get_xml;
 	parsable_class->get_namespaces = get_namespaces;
 
+	entry_class->get_entry_uri = get_entry_uri;
+
 	/**
 	 * GDataYouTubeVideo:view-count:
 	 *
@@ -861,6 +865,32 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 	GDATA_PARSABLE_GET_CLASS (priv->youtube_control)->get_namespaces (GDATA_PARSABLE (priv->youtube_control), namespaces);
 }
 
+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 ("http://gdata.youtube.com/feeds/api/videos/";, video_id, NULL);
+	g_strfreev (parts);
+
+	return uri;
+}
+
 /**
  * gdata_youtube_video_get_view_count:
  * @self: a #GDataYouTubeVideo
diff --git a/gdata/tests/contacts.c b/gdata/tests/contacts.c
index 1ee286b..6b14bbf 100644
--- a/gdata/tests/contacts.c
+++ b/gdata/tests/contacts.c
@@ -49,13 +49,9 @@ get_contact (gconstpointer service)
 	GDataEntry *entry;
 	GList *entries;
 	GError *error = NULL;
-	GDataQuery *query = NULL;
 	static gchar *entry_id = NULL;
 
 	/* Make sure we use the same contact throughout */
-	if (entry_id != NULL)
-		query = gdata_query_new_for_id (entry_id);
-
 	feed = gdata_contacts_service_query_contacts (GDATA_CONTACTS_SERVICE (service), NULL, NULL, NULL, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_FEED (feed));
diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c
index 54f17e3..41fb89c 100644
--- a/gdata/tests/documents.c
+++ b/gdata/tests/documents.c
@@ -217,7 +217,8 @@ test_upload_metadata_file (gconstpointer service)
 static void
 test_upload_file_get_entry (gconstpointer service)
 {
-	GDataDocumentsEntry *new_document, *newly_created_presentation;
+	GDataDocumentsEntry *new_document;
+	GDataEntry *new_presentation;
 	GFile *document_file;
 	GDataCategory *category;
 	GError *error = NULL;
@@ -233,14 +234,14 @@ test_upload_file_get_entry (gconstpointer service)
 	g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_document));
 
 	/* Get the entry on the server */
-	newly_created_presentation = gdata_documents_service_query_single_document (GDATA_DOCUMENTS_SERVICE (service), GDATA_TYPE_DOCUMENTS_PRESENTATION,
-										    gdata_documents_entry_get_document_id (new_document), NULL, &error);
+	new_presentation = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL,
+	                                                     GDATA_TYPE_DOCUMENTS_PRESENTATION, NULL, &error);
 	g_assert_no_error (error);
-	g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (newly_created_presentation));
+	g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_presentation));
 
 	g_clear_error (&error);
 	g_object_unref (new_document);
-	g_object_unref (newly_created_presentation);
+	g_object_unref (new_presentation);
 	g_object_unref (document_file);
 }
 
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index a96ce21..b16ffa3 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -637,7 +637,6 @@ test_query_etag (void)
 	CHECK_ETAG (gdata_query_set_start_index (query, 5))
 	CHECK_ETAG (gdata_query_set_is_strict (query, TRUE))
 	CHECK_ETAG (gdata_query_set_max_results (query, 1000))
-	CHECK_ETAG (gdata_query_set_entry_id (query, "id"))
 	CHECK_ETAG (gdata_query_next_page (query))
 	CHECK_ETAG (gdata_query_previous_page (query))
 
diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c
index ee0cbfb..210201b 100644
--- a/gdata/tests/picasaweb.c
+++ b/gdata/tests/picasaweb.c
@@ -849,6 +849,27 @@ test_photo_feed (gconstpointer service)
 }
 
 static void
+test_photo_single (gconstpointer service)
+{
+	GDataEntry *photo;
+	GError *error = NULL;
+
+	photo = gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                          "http://picasaweb.google.com/data/entry/user/libgdata.picasaweb/albumid/5328889949261497249/photoid/5328890138794566386";,
+	                                          NULL, GDATA_TYPE_PICASAWEB_FILE, NULL, &error);
+
+	g_assert_no_error (error);
+	g_assert (photo != NULL);
+	g_assert (GDATA_IS_PICASAWEB_FILE (photo));
+	g_assert_cmpstr (gdata_picasaweb_file_get_id (GDATA_PICASAWEB_FILE (photo)), ==, "5328890138794566386");
+	g_assert_cmpstr (gdata_entry_get_id (photo), ==,
+	                 "http://picasaweb.google.com/data/entry/user/libgdata.picasaweb/albumid/5328889949261497249/photoid/5328890138794566386";);
+	g_clear_error (&error);
+
+	g_object_unref (photo);
+}
+
+static void
 test_album (gconstpointer service)
 {
 	GDataFeed *album_feed;
@@ -1357,6 +1378,7 @@ main (int argc, char *argv[])
 	g_test_add_data_func ("/picasaweb/query/photo_feed", service, test_photo_feed);
 	g_test_add_data_func ("/picasaweb/query/photo_feed_entry", service, test_photo_feed_entry);
 	g_test_add_data_func ("/picasaweb/query/photo", service, test_photo);
+	g_test_add_data_func ("/picasaweb/query/photo_single", service, test_photo_single);
 	g_test_add_data_func ("/picasaweb/upload/photo", service, test_upload_simple);
 	g_test_add_data_func ("/picasaweb/download/photo", service, test_download);
 	g_test_add_data_func ("/picasaweb/download/thumbnails", service, test_download_thumbnails);
diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c
index 2f10a50..b78a904 100644
--- a/gdata/tests/youtube.c
+++ b/gdata/tests/youtube.c
@@ -706,7 +706,8 @@ test_query_single (gconstpointer service)
 	GDataYouTubeVideo *video;
 	GError *error = NULL;
 
-	video = gdata_youtube_service_query_single_video (GDATA_YOUTUBE_SERVICE (service), NULL, "_LeQuMpwbW4", NULL, &error);
+	video = GDATA_YOUTUBE_VIDEO (gdata_service_query_single_entry (GDATA_SERVICE (service), "tag:youtube.com,2008:video:_LeQuMpwbW4", NULL,
+	                                                               GDATA_TYPE_YOUTUBE_VIDEO, NULL, &error));
 
 	g_assert_no_error (error);
 	g_assert (video != NULL);
@@ -724,7 +725,7 @@ test_query_single_async_cb (GDataService *service, GAsyncResult *async_result, G
 	GDataYouTubeVideo *video;
 	GError *error = NULL;
 
-	video = gdata_youtube_service_query_single_video_finish (GDATA_YOUTUBE_SERVICE (service), async_result, &error);
+	video = GDATA_YOUTUBE_VIDEO (gdata_service_query_single_entry_finish (GDATA_SERVICE (service), async_result, &error));
 
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_YOUTUBE_VIDEO (video));
@@ -741,8 +742,8 @@ test_query_single_async (gconstpointer service)
 {
 	GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
 
-	gdata_youtube_service_query_single_video_async (GDATA_YOUTUBE_SERVICE (service), NULL, "_LeQuMpwbW4", NULL,
-							(GAsyncReadyCallback) test_query_single_async_cb, main_loop);
+	gdata_service_query_single_entry_async (GDATA_SERVICE (service), "tag:youtube.com,2008:video:_LeQuMpwbW4", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
+	                                        NULL, (GAsyncReadyCallback) test_query_single_async_cb, main_loop);
 
 	g_main_loop_run (main_loop);
 	g_main_loop_unref (main_loop);



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