/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * GData Client
 * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
 * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
 *
 * GData Client is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * GData Client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:gdata-picasaweb-service
 * @short_description: GData PicasaWeb service object
 * @stability: Stable
 * @include: gdata/services/picasaweb/gdata-picasaweb-service.h
 *
 * #GDataPicasaWebService is a subclass of #GDataService for communicating with the GData API of Google PicasaWeb. It supports querying for files
 * and albums, and uploading files.
 *
 * For more details of PicasaWeb's GData API, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/developers_guide_protocol.html">
 * online documentation</ulink>.
 *
 * <example>
 * 	<title>Authenticating and Creating a New Album</title>
 * 	<programlisting>
 *	GDataClientLoginAuthorizer *authorizer;
 *	GDataPicasaWebService *service;
 *	GDataPicasaWebAlbum *album, *inserted_album;
 *
 *	/<!-- -->* Create a service object and authorize against the PicasaWeb service *<!-- -->/
 *	authorizer = gdata_client_login_authorizer_new ("companyName-applicationName-versionID", GDATA_TYPE_PICASAWEB_SERVICE);
 *	gdata_client_login_authorizer_authenticate (authorizer, username, password, NULL, NULL);
 *	service = gdata_picasaweb_service_new (GDATA_AUTHORIZER (authorizer));
 *
 *	/<!-- -->* Create a GDataPicasaWebAlbum entry for the new album, setting some information about it *<!-- -->/
 *	album = gdata_picasaweb_album_new (NULL);
 *	gdata_entry_set_title (GDATA_ENTRY (album), "Photos from the Rhine");
 *	gdata_entry_set_summary (GDATA_ENTRY (album), "An album of our adventures on the great river.");
 *	gdata_picasaweb_album_set_location (album, "The Rhine, Germany");
 *
 *	/<!-- -->* Insert the new album on the server. Note that this is a blocking operation. *<!-- -->/
 *	inserted_album = gdata_picasaweb_service_insert_album (service, album, NULL, NULL);
 *
 *	g_object_unref (album);
 *	g_object_unref (inserted_album);
 *	g_object_unref (service);
 *	g_object_unref (authorizer);
 *	</programlisting>
 * </example>
 *
 * <example>
 * 	<title>Uploading a Photo or Video</title>
 * 	<programlisting>
 *	GDataPicasaWebFile *file_entry, *uploaded_file_entry;
 *	GDataUploadStream *upload_stream;
 *	GFile *file_data;
 *	GFileInfo *file_info;
 *	GFileInputStream *file_stream;
 *
 *	/<!-- -->* Specify the GFile image on disk to upload *<!-- -->/
 *	file_data = g_file_new_for_path (path);
 *
 *	/<!-- -->* Get the file information for the file being uploaded. If another data source was being used for the upload, it would have to
 *	 * provide an appropriate slug and content type. Note that this is a blocking operation. *<!-- -->/
 *	file_info = g_file_query_info (file_data, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
 *	                               G_FILE_QUERY_INFO_NONE, NULL, NULL);
 *
 *	/<!-- -->* Create a GDataPicasaWebFile entry for the image, setting a title and caption/summary *<!-- -->/
 *	file_entry = gdata_picasaweb_file_new (NULL);
 *	gdata_entry_set_title (GDATA_ENTRY (file_entry), "Black Cat");
 *	gdata_entry_set_summary (GDATA_ENTRY (file_entry), "Photo of the world's most beautiful cat.");
 *
 *	/<!-- -->* Create an upload stream for the file. This is non-blocking. *<!-- -->/
 *	upload_stream = gdata_picasaweb_service_upload_file (service, album, file_entry, g_file_info_get_display_name (file_info),
 *	                                                     g_file_info_get_content_type (file_info), NULL, NULL);
 *	g_object_unref (file_info);
 *	g_object_unref (file_entry);
 *
 *	/<!-- -->* Prepare a file stream for the file to be uploaded. This is a blocking operation. *<!-- -->/
 *	file_stream = g_file_read (file_data, NULL, NULL);
 *	g_object_unref (file_data);
 *
 *	/<!-- -->* Upload the file to the server. Note that this is a blocking operation. *<!-- -->/
 *	g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
 *	                        G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, NULL);
 *
 *	/<!-- -->* Parse the resulting updated entry. This is a non-blocking operation. *<!-- -->/
 *	uploaded_file_entry = gdata_picasaweb_service_finish_file_upload (service, upload_stream, NULL);
 *	g_object_unref (file_stream);
 *	g_object_unref (upload_stream);
 *
 *	/<!-- -->* ... *<!-- -->/
 *
 *	g_object_unref (uploaded_file_entry);
 * 	</programlisting>
 * </example>
 *
 *
 * Since: 0.4.0
 */

#include <config.h>
#include <glib.h>
#include <glib/gi18n-lib.h>

#include "gdata-service.h"
#include "gdata-picasaweb-service.h"
#include "gdata-private.h"
#include "gdata-parser.h"
#include "atom/gdata-link.h"
#include "gdata-upload-stream.h"
#include "gdata-picasaweb-feed.h"

static GList *get_authorization_domains (void);

_GDATA_DEFINE_AUTHORIZATION_DOMAIN (picasaweb, "lh2", "http://picasaweb.google.com/data/")
G_DEFINE_TYPE (GDataPicasaWebService, gdata_picasaweb_service, GDATA_TYPE_SERVICE)

static void
gdata_picasaweb_service_class_init (GDataPicasaWebServiceClass *klass)
{
	GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
	service_class->feed_type = GDATA_TYPE_PICASAWEB_FEED;
	service_class->get_authorization_domains = get_authorization_domains;
}

static void
gdata_picasaweb_service_init (GDataPicasaWebService *self)
{
	/* Nothing to see here */
}

static GList *
get_authorization_domains (void)
{
	return g_list_prepend (NULL, get_picasaweb_authorization_domain ());
}

/**
 * gdata_picasaweb_service_new:
 * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
 *
 * Creates a new #GDataPicasaWebService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
 *
 * Return value: a new #GDataPicasaWebService, or %NULL; unref with g_object_unref()
 *
 * Since: 0.9.0
 */
GDataPicasaWebService *
gdata_picasaweb_service_new (GDataAuthorizer *authorizer)
{
	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);

	return g_object_new (GDATA_TYPE_PICASAWEB_SERVICE,
	                     "authorizer", authorizer,
	                     NULL);
}

/**
 * gdata_picasaweb_service_get_primary_authorization_domain:
 *
 * The primary #GDataAuthorizationDomain for interacting with PicasaWeb. This will not normally need to be used, as it's used internally
 * by the #GDataPicasaWebService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
 * does not support natively, then this domain may be needed to authorize the requests.
 *
 * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
 *
 * Return value: (transfer none): the service's authorization domain
 *
 * Since: 0.9.0
 */
GDataAuthorizationDomain *
gdata_picasaweb_service_get_primary_authorization_domain (void)
{
	return get_picasaweb_authorization_domain ();
}

/*
 * create_uri:
 * @self: a #GDataPicasaWebService
 * @username: (allow-none): the username to use, or %NULL to use the currently logged in user
 * @type: the type of object to access: "entry" for a user, or "feed" for an album
 *
 * Builds a URI to use when querying for albums or a user.
 *
 * Return value: a constructed URI; free with g_free()
 *
 * Since: 0.4.0
 */
static gchar *
create_uri (GDataPicasaWebService *self, const gchar *username, const gchar *type)
{
	if (username == NULL) {
		/* Ensure we're authorized first */
		if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
		                                               get_picasaweb_authorization_domain ()) == FALSE) {
			return NULL;
		}

		/* Querying Picasa albums for the "default" user when logged in returns the albums for the authenticated user */
		username = "default";
	}

	return _gdata_service_build_uri ("https://picasaweb.google.com/data/%s/api/user/%s", type, username);
}

/**
 * gdata_picasaweb_service_get_user:
 * @self: a #GDataPicasaWebService
 * @username: (allow-none): the username of the user whose information you wish to retrieve, or %NULL for the currently authenticated user.
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @error: a #GError, or %NULL
 *
 * Queries the service to return the user specified by @username.
 *
 * Return value: (transfer full): a #GDataPicasaWebUser; unref with g_object_unref()
 *
 * Since: 0.6.0
 */
GDataPicasaWebUser *
gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *username, GCancellable *cancellable, GError **error)
{
	gchar *uri;
	GDataParsable *user;
	SoupMessage *message;

	g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	uri = create_uri (self, username, "entry");
	if (uri == NULL) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
		                     _("You must specify a username or be authenticated to query a user."));
		return NULL;
	}

	message = _gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, NULL, cancellable, error);
	g_free (uri);

	if (message == NULL)
		return NULL;

	g_assert (message->response_body->data != NULL);
	user = gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_USER, message->response_body->data, message->response_body->length, error);
	g_object_unref (message);

	return GDATA_PICASAWEB_USER (user);
}

static void
get_user_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
{
	GDataPicasaWebService *service = GDATA_PICASAWEB_SERVICE (source_object);
	const gchar *username = task_data;
	g_autoptr(GDataPicasaWebUser) user = NULL;
	g_autoptr(GError) error = NULL;

	/* Get the user and return */
	user = gdata_picasaweb_service_get_user (service, username, cancellable, &error);

	if (error != NULL)
		g_task_return_error (task, g_steal_pointer (&error));
	else
		g_task_return_pointer (task, g_steal_pointer (&user), g_object_unref);
}

/**
 * gdata_picasaweb_service_get_user_async:
 * @self: a #GDataPicasaWebService
 * @username: (allow-none): the username of the user whose information you wish to retrieve, or %NULL for the currently authenticated user
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @callback: a #GAsyncReadyCallback to call when the query is finished
 * @user_data: (closure): data to pass to the @callback function
 *
 * Queries the service to return the user specified by @username.
 *
 * For more details, see gdata_picasaweb_service_get_user() which is the synchronous version of this method.
 *
 * When the operation is finished, @callback will be called. You can then call gdata_picasaweb_service_get_user_finish() to get the results of the
 * operation.
 *
 * Since: 0.9.1
 */
void
gdata_picasaweb_service_get_user_async (GDataPicasaWebService *self, const gchar *username, GCancellable *cancellable,
                                        GAsyncReadyCallback callback, gpointer user_data)
{
	g_autoptr(GTask) task = NULL;

	g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (callback != NULL);

	task = g_task_new (self, cancellable, callback, user_data);
	g_task_set_source_tag (task, gdata_picasaweb_service_get_user_async);
	g_task_set_task_data (task, g_strdup (username), (GDestroyNotify) g_free);
	g_task_run_in_thread (task, get_user_thread);
}

/**
 * gdata_picasaweb_service_get_user_finish:
 * @self: a #GDataPicasaWebService
 * @result: a #GAsyncResult
 * @error: a #GError, or %NULL
 *
 * Finishes an asynchronous user retrieval operation started with gdata_picasaweb_service_get_user_async().
 *
 * Return value: (transfer full): a #GDataPicasaWebUser; unref with g_object_unref()
 *
 * Since: 0.9.1
 */
GDataPicasaWebUser *
gdata_picasaweb_service_get_user_finish (GDataPicasaWebService *self, GAsyncResult *async_result, GError **error)
{
	g_return_val_if_fail (GDATA_IS_PICASAWEB_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_return_val_if_fail (g_task_is_valid (async_result, self), NULL);
	g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_picasaweb_service_get_user_async), NULL);

	return g_task_propagate_pointer (G_TASK (async_result), error);
}

/**
 * gdata_picasaweb_service_query_all_albums:
 * @self: a #GDataPicasaWebService
 * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
 * @username: (allow-none): the username of the user whose albums you wish to retrieve, or %NULL
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @progress_callback: (allow-none) (scope call) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
 * @progress_user_data: (closure): data to pass to the @progress_callback function
 * @error: a #GError, or %NULL
 *
 * Queries the service to return a list of all albums belonging to the specified @username which match the given
 * @query. If a user is authenticated with the service, @username can be set as %NULL to return a list of albums belonging
 * to the currently-authenticated user.
 *
 * Note that the #GDataQuery:q query parameter cannot be set on @query for album queries.
 *
 * For more details, see gdata_service_query().
 *
 * Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref()
 *
 * Since: 0.4.0
 */
GDataFeed *
gdata_picasaweb_service_query_all_albums (GDataPicasaWebService *self, GDataQuery *query, const gchar *username, GCancellable *cancellable,
                                          GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
{
	gchar *uri;
	GDataFeed *album_feed;

	g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	if (query != NULL && gdata_query_get_q (query) != NULL) {
		/* Bug #593336 — Query parameter "q=..." isn't valid for album kinds */
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_BAD_QUERY_PARAMETER,
		                     _("Query parameter not allowed for albums."));
		return NULL;
	}

	uri = create_uri (self, username, "feed");
	if (uri == NULL) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
		                     _("You must specify a username or be authenticated to query all albums."));
		return NULL;
	}

	/* Execute the query */
	album_feed = gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, query, GDATA_TYPE_PICASAWEB_ALBUM,
	                                  cancellable, progress_callback, progress_user_data, error);
	g_free (uri);

	return album_feed;
}

/**
 * gdata_picasaweb_service_query_all_albums_async:
 * @self: a #GDataPicasaWebService
 * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
 * @username: (allow-none): the username of the user whose albums you wish to retrieve, or %NULL
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @progress_callback: (allow-none) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
 * @progress_user_data: (closure): data to pass to the @progress_callback function
 * @destroy_progress_user_data: (allow-none): the function to call when @progress_callback will not be called any more, or %NULL. This function will be
 * called with @progress_user_data as a parameter and can be used to free any memory allocated for it.
 * @callback: a #GAsyncReadyCallback to call when authentication is finished
 * @user_data: (closure): data to pass to the @callback function
 *
 * Queries the service to return a list of all albums belonging to the specified @username which match the given
 * @query. @self, @query and @username are all reffed/copied when this function is called, so can safely be unreffed/freed after
 * this function returns.
 *
 * For more details, see gdata_picasaweb_service_query_all_albums(), which is the synchronous version of
 * this function, and gdata_service_query_async(), which is the base asynchronous query function.
 *
 * Since: 0.9.1
 */
void
gdata_picasaweb_service_query_all_albums_async (GDataPicasaWebService *self, GDataQuery *query, const gchar *username,
                                                GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                                                GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data)
{
	gchar *uri;

	g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self));
	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (callback != NULL);

	if (query != NULL && gdata_query_get_q (query) != NULL) {
		/* Bug #593336 — Query parameter "q=..." isn't valid for album kinds */
		g_autoptr(GTask) task = NULL;

		task = g_task_new (self, cancellable, callback, user_data);
		g_task_set_source_tag (task, gdata_service_query_async);
		g_task_return_new_error (task, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_BAD_QUERY_PARAMETER, "%s",
		                         _("Query parameter not allowed for albums."));

		return;
	}

	uri = create_uri (self, username, "feed");
	if (uri == NULL) {
		g_autoptr(GTask) task = NULL;

		task = g_task_new (self, cancellable, callback, user_data);
		g_task_set_source_tag (task, gdata_service_query_async);
		g_task_return_new_error (task, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, "%s",
		                         _("You must specify a username or be authenticated to query all albums."));

		return;
	}

	/* Schedule the async query */
	gdata_service_query_async (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, query, GDATA_TYPE_PICASAWEB_ALBUM, cancellable,
	                           progress_callback, progress_user_data, destroy_progress_user_data, callback, user_data);
	g_free (uri);
}

static const gchar *
get_query_files_uri (GDataPicasaWebAlbum *album, GError **error)
{
	if (album != NULL) {
		GDataLink *_link = gdata_entry_look_up_link (GDATA_ENTRY (album), "http://schemas.google.com/g/2005#feed");
		if (_link == NULL) {
			/* Error */
			g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			                     _("The album did not have a feed link."));
			return NULL;
		}

		return gdata_link_get_uri (_link);
	} else {
		/* Default URI */
		return "https://picasaweb.google.com/data/feed/api/user/default/albumid/default";
	}
}

/**
 * gdata_picasaweb_service_query_files:
 * @self: a #GDataPicasaWebService
 * @album: (allow-none): a #GDataPicasaWebAlbum from which to retrieve the files, or %NULL
 * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @progress_callback: (allow-none) (scope call) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
 * @progress_user_data: (closure): data to pass to the @progress_callback function
 * @error: a #GError, or %NULL
 *
 * Queries the specified @album for a list of the files which match the given @query. If @album is %NULL and a user is
 * authenticated with the service, the user's default album will be queried.
 *
 * For more details, see gdata_service_query().
 *
 * Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref()
 *
 * Since: 0.4.0
 */
GDataFeed *
gdata_picasaweb_service_query_files (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataQuery *query, GCancellable *cancellable,
                                     GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
{
	const gchar *uri;

	g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
	g_return_val_if_fail (album == NULL || GDATA_IS_PICASAWEB_ALBUM (album), NULL);
	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	uri = get_query_files_uri (album, error);
	if (uri == NULL)
		return NULL;

	/* Execute the query */
	return gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, GDATA_QUERY (query), GDATA_TYPE_PICASAWEB_FILE,
	                            cancellable, progress_callback, progress_user_data, error);
}

/**
 * gdata_picasaweb_service_query_files_async:
 * @self: a #GDataPicasaWebService
 * @album: (allow-none): a #GDataPicasaWebAlbum from which to retrieve the files, or %NULL
 * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @progress_callback: (allow-none) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
 * @progress_user_data: (closure): data to pass to the @progress_callback function
 * @destroy_progress_user_data: (allow-none): the function to call when @progress_callback will not be called any more, or %NULL. This function will be
 * called with @progress_user_data as a parameter and can be used to free any memory allocated for it.
 * @callback: a #GAsyncReadyCallback to call when the query is finished
 * @user_data: (closure): data to pass to the @callback function
 *
 * Queries the specified @album for a list of the files which match the given @query. If @album is %NULL and a user is authenticated with the service,
 * the user's default album will be queried. @self, @album and @query are all reffed when this function is called, so can safely be unreffed after
 * this function returns.
 *
 * For more details, see gdata_picasaweb_service_query_files(), which is the synchronous version of this function, and gdata_service_query_async(),
 * which is the base asynchronous query function.
 *
 * Since: 0.9.1
 */
void
gdata_picasaweb_service_query_files_async (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataQuery *query, GCancellable *cancellable,
                                           GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                                           GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data)
{
	const gchar *request_uri;
	GError *child_error = NULL;

	g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self));
	g_return_if_fail (album == NULL || GDATA_IS_PICASAWEB_ALBUM (album));
	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (callback != NULL);

	request_uri = get_query_files_uri (album, &child_error);
	if (request_uri == NULL) {
		g_task_report_error (self, callback, user_data, gdata_service_query_async, g_steal_pointer (&child_error));
		return;
	}

	gdata_service_query_async (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), request_uri, GDATA_QUERY (query),
	                           GDATA_TYPE_PICASAWEB_FILE, cancellable, progress_callback, progress_user_data, destroy_progress_user_data,
	                           callback, user_data);
}

/**
 * gdata_picasaweb_service_upload_file:
 * @self: a #GDataPicasaWebService
 * @album: (allow-none): a #GDataPicasaWebAlbum into which to insert the file, or %NULL
 * @file_entry: a #GDataPicasaWebFile to insert
 * @slug: the filename to give to the uploaded file
 * @content_type: the content type of the uploaded data
 * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL
 * @error: a #GError, or %NULL
 *
 * Uploads a file (photo or video) to the given PicasaWeb @album, using the metadata from @file and the file data written to the resulting
 * #GDataUploadStream. If @album is %NULL, the file will be uploaded to the currently-authenticated user's "Drop Box" album. A user must be
 * authenticated to use this function.
 *
 * If @file has already been inserted, a %GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED error will be returned.
 *
 * If no user is authenticated with the service, %GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED will be returned. It is recommended to retry the
 * upload after refreshing the authorization tokens held by the associated #GDataAuthorizer using gdata_authorizer_refresh_authorization().
 *
 * The stream returned by this function should be written to using the standard #GOutputStream methods, asychronously or synchronously. Once the stream
 * is closed (using g_output_stream_close()), gdata_picasaweb_service_finish_file_upload() should be called on it to parse and return the updated
 * #GDataPicasaWebFile for the uploaded file. This must be done, as @file_entry isn't updated in-place.
 *
 * In order to cancel the upload, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual
 * #GOutputStream operations on the #GDataUploadStream will not cancel the entire upload; merely the write or close operation in question. See the
 * #GDataUploadStream:cancellable for more details.
 *
 * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain.
 *
 * Return value: (transfer full): a #GDataUploadStream to write the file data to, or %NULL; unref with g_object_unref()
 *
 * Since: 0.8.0
 */
GDataUploadStream *
gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, const gchar *slug,
                                     const gchar *content_type, GCancellable *cancellable, GError **error)
{
	const gchar *album_id = NULL;
	GDataUploadStream *upload_stream;
	gchar *upload_uri;

	g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
	g_return_val_if_fail (album == NULL || GDATA_IS_PICASAWEB_ALBUM (album), NULL);
	g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (file_entry), NULL);
	g_return_val_if_fail (slug != NULL && *slug != '\0', NULL);
	g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	if (gdata_entry_is_inserted (GDATA_ENTRY (file_entry)) == TRUE) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
		                     _("The entry has already been inserted."));
		return NULL;
	}

	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
	                                               get_picasaweb_authorization_domain ()) == FALSE) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
		                     _("You must be authenticated to upload a file."));
		return NULL;
	}

	/* PicasaWeb allows you to post to a default Dropbox */
	album_id = (album != NULL) ? gdata_picasaweb_album_get_id (album) : "default";

	/* Build the upload URI and upload stream */
	upload_uri = _gdata_service_build_uri ("https://picasaweb.google.com/data/feed/api/user/default/albumid/%s", album_id);
	upload_stream = GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), SOUP_METHOD_POST,
	                                                              upload_uri, GDATA_ENTRY (file_entry), slug, content_type, cancellable));
	g_free (upload_uri);

	return upload_stream;
}

/**
 * gdata_picasaweb_service_finish_file_upload:
 * @self: a #GDataPicasaWebService
 * @upload_stream: the #GDataUploadStream from the operation
 * @error: a #GError, or %NULL
 *
 * Finish off a file upload operation started by gdata_picasaweb_service_upload_file(), parsing the result and returning the new #GDataPicasaWebFile.
 *
 * If an error occurred during the upload operation, it will have been returned during the operation (e.g. by g_output_stream_splice() or one
 * of the other stream methods). In such a case, %NULL will be returned but @error will remain unset. @error is only set in the case that the server
 * indicates that the operation was successful, but an error is encountered in parsing the result sent by the server.
 *
 * Return value: (transfer full): the new #GDataPicasaWebFile, or %NULL; unref with g_object_unref()
 *
 * Since: 0.8.0
 */
GDataPicasaWebFile *
gdata_picasaweb_service_finish_file_upload (GDataPicasaWebService *self, GDataUploadStream *upload_stream, GError **error)
{
	const gchar *response_body;
	gssize response_length;

	g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (upload_stream), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	/* Get the response from the server */
	response_body = gdata_upload_stream_get_response (upload_stream, &response_length);
	if (response_body == NULL || response_length == 0)
		return NULL;

	/* Parse the response to produce a GDataPicasaWebFile */
	return GDATA_PICASAWEB_FILE (gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_FILE, response_body, (gint) response_length, error));
}

/**
 * gdata_picasaweb_service_insert_album:
 * @self: a #GDataPicasaWebService
 * @album: a #GDataPicasaWebAlbum to create on the server
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @error: a #GError, or %NULL
 *
 * Inserts a new album described by @album. A user must be
 * authenticated to use this function.
 *
 * Return value: (transfer full): the inserted #GDataPicasaWebAlbum; unref with
 * g_object_unref()
 *
 * Since: 0.6.0
 */
GDataPicasaWebAlbum *
gdata_picasaweb_service_insert_album (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GCancellable *cancellable, GError **error)
{
	g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
	g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (album), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	if (gdata_entry_is_inserted (GDATA_ENTRY (album)) == TRUE) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
		                     _("The album has already been inserted."));
		return NULL;
	}

	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
	                                               get_picasaweb_authorization_domain ()) == FALSE) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
		                     _("You must be authenticated to insert an album."));
		return NULL;
	}

	return GDATA_PICASAWEB_ALBUM (gdata_service_insert_entry (GDATA_SERVICE (self), get_picasaweb_authorization_domain (),
	                                                          "https://picasaweb.google.com/data/feed/api/user/default",
	                                                          GDATA_ENTRY (album), cancellable, error));
}

/**
 * gdata_picasaweb_service_insert_album_async:
 * @self: a #GDataPicasaWebService
 * @album: a #GDataPicasaWebAlbum to create on the server
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @callback: a #GAsyncReadyCallback to call when insertion is finished
 * @user_data: (closure): data to pass to the @callback function
 *
 * Inserts a new album described by @album. The user must be authenticated to use this function. @self and @album are both reffed when this function
 * is called, so can safely be unreffed after this function returns.
 *
 * @callback should call gdata_service_insert_entry_finish() to obtain a #GDataPicasaWebAlbum representing the inserted album and to check for
 * possible errors.
 *
 * For more details, see gdata_picasaweb_service_insert_album(), which is the synchronous version of this function, and
 * gdata_service_insert_entry_async(), which is the base asynchronous insertion function.
 *
 * Since: 0.8.0
 */
void
gdata_picasaweb_service_insert_album_async (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GCancellable *cancellable,
                                            GAsyncReadyCallback callback, gpointer user_data)
{
	g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self));
	g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (album));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	gdata_service_insert_entry_async (GDATA_SERVICE (self), get_picasaweb_authorization_domain (),
	                                  "https://picasaweb.google.com/data/feed/api/user/default", GDATA_ENTRY (album), cancellable, callback,
	                                  user_data);
}
