Blame gdata/gdata-upload-stream.c

Packit 4b6dd7
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
Packit 4b6dd7
/*
Packit 4b6dd7
 * GData Client
Packit 4b6dd7
 * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
Packit 4b6dd7
 *
Packit 4b6dd7
 * GData Client is free software; you can redistribute it and/or
Packit 4b6dd7
 * modify it under the terms of the GNU Lesser General Public
Packit 4b6dd7
 * License as published by the Free Software Foundation; either
Packit 4b6dd7
 * version 2.1 of the License, or (at your option) any later version.
Packit 4b6dd7
 *
Packit 4b6dd7
 * GData Client is distributed in the hope that it will be useful,
Packit 4b6dd7
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 4b6dd7
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 4b6dd7
 * Lesser General Public License for more details.
Packit 4b6dd7
 *
Packit 4b6dd7
 * You should have received a copy of the GNU Lesser General Public
Packit 4b6dd7
 * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
Packit 4b6dd7
 */
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * SECTION:gdata-upload-stream
Packit 4b6dd7
 * @short_description: GData upload stream object
Packit 4b6dd7
 * @stability: Stable
Packit 4b6dd7
 * @include: gdata/gdata-upload-stream.h
Packit 4b6dd7
 *
Packit 4b6dd7
 * #GDataUploadStream is a #GOutputStream subclass to allow uploading of files from GData services with authorization from a #GDataService under
Packit 4b6dd7
 * the given #GDataAuthorizationDomain. If authorization is not required to perform the upload, a #GDataAuthorizationDomain doesn't have to be
Packit 4b6dd7
 * specified.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Once a #GDataUploadStream is instantiated with gdata_upload_stream_new(), the standard #GOutputStream API can be used on the stream to upload
Packit 4b6dd7
 * the file. Network communication may not actually begin until the first call to g_output_stream_write(), so having a #GDataUploadStream around is no
Packit 4b6dd7
 * guarantee that data is being uploaded.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Uploads of a file, or a file with associated metadata (a #GDataEntry) should use #GDataUploadStream, but if you want to simply upload a single
Packit 4b6dd7
 * #GDataEntry, use gdata_service_insert_entry() instead. #GDataUploadStream is for large streaming uploads.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Once an upload is complete, the server's response can be retrieved from the #GDataUploadStream using gdata_upload_stream_get_response(). In order
Packit 4b6dd7
 * for network communication to be guaranteed to have stopped (and thus the response definitely available), g_output_stream_close() must be called
Packit 4b6dd7
 * on the #GDataUploadStream first. Otherwise, gdata_upload_stream_get_response() may return saying that the operation is still in progress.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If the server returns an error instead of a success response, the error will be returned by g_output_stream_close() as a #GDataServiceError.
Packit 4b6dd7
 *
Packit 4b6dd7
 * The entire upload operation can be cancelled using the #GCancellable instance provided to gdata_upload_stream_new(), or returned by
Packit 4b6dd7
 * gdata_upload_stream_get_cancellable(). Cancelling this at any time will cause all future #GOutputStream method calls to return
Packit 4b6dd7
 * %G_IO_ERROR_CANCELLED. If any #GOutputStream methods are in the process of being called, they will be cancelled and return %G_IO_ERROR_CANCELLED as
Packit 4b6dd7
 * soon as possible.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Note that cancelling an individual method call (such as a call to g_output_stream_write()) using the #GCancellable parameter of the method will not
Packit 4b6dd7
 * cancel the upload as a whole — just that particular method call. In the case of g_output_stream_write(), this will cause it to return the number of
Packit 4b6dd7
 * bytes it has successfully written up to the point of cancellation (up to the requested number of bytes), or return a %G_IO_ERROR_CANCELLED if it
Packit 4b6dd7
 * had not managed to write any bytes to the network by that point. This is also the behaviour of g_output_stream_write() when the upload operation as
Packit 4b6dd7
 * a whole is cancelled.
Packit 4b6dd7
 *
Packit 4b6dd7
 * In the case of g_output_stream_close(), the call will return immediately if network activity hasn't yet started. If it has, the network activity
Packit 4b6dd7
 * will be cancelled, regardless of whether the call to g_output_stream_close() is cancelled. Cancelling a pending call to g_output_stream_close()
Packit 4b6dd7
 * (either using the method's #GCancellable, or by cancelling the upload stream as a whole) will cause it to stop waiting for the network activity to
Packit 4b6dd7
 * finish, and return %G_IO_ERROR_CANCELLED immediately. Network activity will continue to be shut down in the background.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Any outstanding data is guaranteed to be written to the network successfully even if a call to g_output_stream_close() is cancelled. However, if
Packit 4b6dd7
 * the upload stream as a whole is cancelled using #GDataUploadStream:cancellable, no more data will be sent over the network, and the network
Packit 4b6dd7
 * connection will be closed immediately. i.e. #GDataUploadStream will do its best to instruct the server to cancel the upload and any associated
Packit 4b6dd7
 * server-side changes of state.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If the server returns an error message (for example, if the user is not correctly authenticated/authorized or doesn't have suitable permissions
Packit 4b6dd7
 * to upload from the given URI), it will be returned as a #GDataServiceError by g_output_stream_close().
Packit 4b6dd7
 *
Packit 4b6dd7
 * <example>
Packit 4b6dd7
 * 	<title>Uploading from a File</title>
Packit 4b6dd7
 * 	<programlisting>
Packit 4b6dd7
 *	GDataService *service;
Packit 4b6dd7
 *	GDataAuthorizationDomain *domain;
Packit 4b6dd7
 *	GCancellable *cancellable;
Packit 4b6dd7
 *	GInputStream *input_stream;
Packit 4b6dd7
 *	GOutputStream *upload_stream;
Packit 4b6dd7
 *	GFile *file;
Packit 4b6dd7
 *	GFileInfo *file_info;
Packit 4b6dd7
 *	GError *error = NULL;
Packit 4b6dd7
 *
Packit 4b6dd7
 *	/* Get the file to upload */
Packit 4b6dd7
 *	file = get_file_to_upload ();
Packit 4b6dd7
 *	file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
Packit 4b6dd7
 *	                               G_FILE_ATTRIBUTE_STANDARD_SIZE,
Packit 4b6dd7
 *	                               G_FILE_QUERY_INFO_NONE, NULL, &error);
Packit 4b6dd7
 *
Packit 4b6dd7
 *	if (file_info == NULL) {
Packit 4b6dd7
 *		g_error ("Error getting file info: %s", error->message);
Packit 4b6dd7
 *		g_error_free (error);
Packit 4b6dd7
 *		g_object_unref (file);
Packit 4b6dd7
 *		return;
Packit 4b6dd7
 *	}
Packit 4b6dd7
 *
Packit 4b6dd7
 *	input_stream = g_file_read (file, NULL, &error);
Packit 4b6dd7
 *	g_object_unref (file);
Packit 4b6dd7
 *
Packit 4b6dd7
 *	if (input_stream == NULL) {
Packit 4b6dd7
 *		g_error ("Error getting file input stream: %s", error->message);
Packit 4b6dd7
 *		g_error_free (error);
Packit 4b6dd7
 *		g_object_unref (file_info);
Packit 4b6dd7
 *		return;
Packit 4b6dd7
 *	}
Packit 4b6dd7
 *
Packit 4b6dd7
 *	/* Create the upload stream */
Packit 4b6dd7
 *	service = create_my_service ();
Packit 4b6dd7
 *	domain = get_my_authorization_domain_from_service (service);
Packit 4b6dd7
 *	cancellable = g_cancellable_new (); /* cancel this to cancel the entire upload operation */
Packit 4b6dd7
 *	upload_stream = gdata_upload_stream_new_resumable (service, domain, SOUP_METHOD_POST, upload_uri, NULL,
Packit 4b6dd7
 *	                                                   g_file_info_get_display_name (file_info), g_file_info_get_content_type (file_info),
Packit 4b6dd7
 *	                                                   g_file_info_get_size (file_info), cancellable);
Packit 4b6dd7
 *	g_object_unref (file_info);
Packit 4b6dd7
 *
Packit 4b6dd7
 *	/* Perform the upload asynchronously */
Packit 4b6dd7
 *	g_output_stream_splice_async (upload_stream, input_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
Packit 4b6dd7
 *	                              G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback) upload_splice_cb, NULL);
Packit 4b6dd7
 *
Packit 4b6dd7
 *	g_object_unref (upload_stream);
Packit 4b6dd7
 *	g_object_unref (input_stream);
Packit 4b6dd7
 *	g_object_unref (cancellable);
Packit 4b6dd7
 *	g_object_unref (domain);
Packit 4b6dd7
 *	g_object_unref (service);
Packit 4b6dd7
 *
Packit 4b6dd7
 *	static void
Packit 4b6dd7
 *	upload_splice_cb (GOutputStream *upload_stream, GAsyncResult *result, gpointer user_data)
Packit 4b6dd7
 *	{
Packit 4b6dd7
 *		gssize length;
Packit 4b6dd7
 *		GError *error = NULL;
Packit 4b6dd7
 *
Packit 4b6dd7
 *		g_output_stream_splice_finish (upload_stream, result, &error);
Packit 4b6dd7
 *
Packit 4b6dd7
 *		if (error != NULL && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == FALSE)) {
Packit 4b6dd7
 *			/* Error upload the file; potentially an I/O error (GIOError), or an error response from the server
Packit 4b6dd7
 *			 * (GDataServiceError). */
Packit 4b6dd7
 *			g_error ("Error uploading file: %s", error->message);
Packit 4b6dd7
 *			g_error_free (error);
Packit 4b6dd7
 *		}
Packit 4b6dd7
 *
Packit 4b6dd7
 *		/* If the upload was successful, carry on to parse the result. Note that this will normally be handled by methods like
Packit 4b6dd7
 *		 * gdata_youtube_service_finish_video_upload(), gdata_picasaweb_service_finish_file_upload() and
Packit 4b6dd7
 *		 * gdata_documents_service_finish_upload() */
Packit 4b6dd7
 *		parse_server_result (gdata_upload_stream_get_response (GDATA_UPLOAD_STREAM (upload_stream), &length), length);
Packit 4b6dd7
 *	}
Packit 4b6dd7
 * 	</programlisting>
Packit 4b6dd7
 * </example>
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
Packit 4b6dd7
/*
Packit 4b6dd7
 * We have a network thread which does all the uploading work. We send the message encoded as chunks, but cannot use the SoupMessageBody as a
Packit 4b6dd7
 * data buffer, since it can only ever be touched by the network thread. Instead, we pass data to the network thread through a GDataBuffer, with
Packit 4b6dd7
 * the main thread pushing it on as and when write() is called. The network thread cannot block on popping data off the buffer, as it requests fixed-
Packit 4b6dd7
 * size chunks, and there's no way to notify it that we've reached EOF; so when it gets to popping the last chunk off the buffer, which may well be
Packit 4b6dd7
 * smaller than its chunk size, it would block for more data and therefore hang. Consequently, the network thread instead pops as much data as it can
Packit 4b6dd7
 * off the buffer, up to its chunk size, which is a non-blocking operation.
Packit 4b6dd7
 *
Packit 4b6dd7
 * The write() and close() operations on the output stream are synchronised with the network thread, so that the write() call only returns once the
Packit 4b6dd7
 * network thread has written at least as many bytes as were passed to the write() call, and the close() call only returns once all network activity
Packit 4b6dd7
 * has finished (including receiving the response from the server). Async versions of these calls are provided by GOutputStream.
Packit 4b6dd7
 *
Packit 4b6dd7
 * The number of bytes in the various buffers are recorded using:
Packit 4b6dd7
 *  • message_bytes_outstanding: the number of bytes in the GDataBuffer which are waiting to be written to the SoupMessageBody
Packit 4b6dd7
 *  • network_bytes_outstanding: the number of bytes which have been written to the SoupMessageBody, and are waiting to be written to the network
Packit 4b6dd7
 *  • network_bytes_written: the total number of bytes which have been successfully written to the network
Packit 4b6dd7
 *
Packit 4b6dd7
 * Mutex locking order:
Packit 4b6dd7
 *  1. response_mutex
Packit 4b6dd7
 *  2. write_mutex
Packit 4b6dd7
 */
Packit 4b6dd7
Packit 4b6dd7
#include <config.h>
Packit 4b6dd7
#include <glib.h>
Packit 4b6dd7
#include <glib/gi18n-lib.h>
Packit 4b6dd7
#include <string.h>
Packit 4b6dd7
Packit 4b6dd7
#include "gdata-upload-stream.h"
Packit 4b6dd7
#include "gdata-buffer.h"
Packit 4b6dd7
#include "gdata-private.h"
Packit 4b6dd7
Packit 4b6dd7
#define BOUNDARY_STRING "0003Z5W789deadbeefRTE456KlemsnoZV"
Packit 4b6dd7
#define MAX_RESUMABLE_CHUNK_SIZE (512 * 1024) /* bytes = 512 KiB */
Packit 4b6dd7
Packit 4b6dd7
static void gdata_upload_stream_constructed (GObject *object);
Packit 4b6dd7
static void gdata_upload_stream_dispose (GObject *object);
Packit 4b6dd7
static void gdata_upload_stream_finalize (GObject *object);
Packit 4b6dd7
static void gdata_upload_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
Packit 4b6dd7
static void gdata_upload_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
Packit 4b6dd7
Packit 4b6dd7
static gssize gdata_upload_stream_write (GOutputStream *stream, const void *buffer, gsize count, GCancellable *cancellable, GError **error);
Packit 4b6dd7
static gboolean gdata_upload_stream_flush (GOutputStream *stream, GCancellable *cancellable, GError **error);
Packit 4b6dd7
static gboolean gdata_upload_stream_close (GOutputStream *stream, GCancellable *cancellable, GError **error);
Packit 4b6dd7
Packit 4b6dd7
static void create_network_thread (GDataUploadStream *self, GError **error);
Packit 4b6dd7
Packit 4b6dd7
typedef enum {
Packit 4b6dd7
	STATE_INITIAL_REQUEST, /* initial POST request to the resumable-create-media link (unused for non-resumable uploads) */
Packit 4b6dd7
	STATE_DATA_REQUESTS, /* one or more subsequent PUT requests (only state used for non-resumable uploads) */
Packit 4b6dd7
	STATE_FINISHED, /* finished successfully or in error */
Packit 4b6dd7
} UploadState;
Packit 4b6dd7
Packit 4b6dd7
struct _GDataUploadStreamPrivate {
Packit 4b6dd7
	gchar *method;
Packit 4b6dd7
	gchar *upload_uri;
Packit 4b6dd7
	GDataService *service;
Packit 4b6dd7
	GDataAuthorizationDomain *authorization_domain;
Packit 4b6dd7
	GDataEntry *entry;
Packit 4b6dd7
	gchar *slug;
Packit 4b6dd7
	gchar *content_type;
Packit 4b6dd7
	goffset content_length; /* -1 for non-resumable uploads; 0 or greater for resumable ones */
Packit 4b6dd7
	SoupSession *session;
Packit 4b6dd7
	SoupMessage *message;
Packit 4b6dd7
	GDataBuffer *buffer;
Packit 4b6dd7
Packit 4b6dd7
	GCancellable *cancellable;
Packit 4b6dd7
	GThread *network_thread;
Packit 4b6dd7
Packit 4b6dd7
	UploadState state; /* protected by write_mutex */
Packit 4b6dd7
	GMutex write_mutex; /* mutex for write operations (specifically, write_finished) */
Packit 4b6dd7
	/* This persists across all resumable upload chunks. Note that it doesn't count bytes from the entry XML. */
Packit 4b6dd7
	gsize total_network_bytes_written; /* the number of bytes which have been written to the network in STATE_DATA_REQUESTS */
Packit 4b6dd7
Packit 4b6dd7
	/* All of the following apply only to the current resumable upload chunk. */
Packit 4b6dd7
	gsize message_bytes_outstanding; /* the number of bytes which have been written to the buffer but not libsoup (signalled by write_cond) */
Packit 4b6dd7
	gsize network_bytes_outstanding; /* the number of bytes which have been written to libsoup but not the network (signalled by write_cond) */
Packit 4b6dd7
	gsize network_bytes_written; /* the number of bytes which have been written to the network (signalled by write_cond) */
Packit 4b6dd7
	gsize chunk_size; /* the size of the current chunk (in bytes); 0 iff content_length <= 0; must be <= MAX_RESUMABLE_CHUNK_SIZE */
Packit 4b6dd7
	GCond write_cond; /* signalled when a chunk has been written (protected by write_mutex) */
Packit 4b6dd7
Packit 4b6dd7
	GCond finished_cond; /* signalled when sending the message (and receiving the response) is finished (protected by response_mutex) */
Packit 4b6dd7
	guint response_status; /* set once we finish receiving the response (SOUP_STATUS_NONE otherwise) (protected by response_mutex) */
Packit 4b6dd7
	GError *response_error; /* error asynchronously set by the network thread, and picked up by the main thread when appropriate */
Packit 4b6dd7
	GMutex response_mutex; /* mutex for ->response_error, ->response_status and ->finished_cond */
Packit 4b6dd7
};
Packit 4b6dd7
Packit 4b6dd7
enum {
Packit 4b6dd7
	PROP_SERVICE = 1,
Packit 4b6dd7
	PROP_UPLOAD_URI,
Packit 4b6dd7
	PROP_ENTRY,
Packit 4b6dd7
	PROP_SLUG,
Packit 4b6dd7
	PROP_CONTENT_TYPE,
Packit 4b6dd7
	PROP_METHOD,
Packit 4b6dd7
	PROP_CANCELLABLE,
Packit 4b6dd7
	PROP_AUTHORIZATION_DOMAIN,
Packit 4b6dd7
	PROP_CONTENT_LENGTH,
Packit 4b6dd7
};
Packit 4b6dd7
Packit 4b6dd7
G_DEFINE_TYPE (GDataUploadStream, gdata_upload_stream, G_TYPE_OUTPUT_STREAM)
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_class_init (GDataUploadStreamClass *klass)
Packit 4b6dd7
{
Packit 4b6dd7
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Packit 4b6dd7
	GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
Packit 4b6dd7
Packit 4b6dd7
	g_type_class_add_private (klass, sizeof (GDataUploadStreamPrivate));
Packit 4b6dd7
Packit 4b6dd7
	gobject_class->constructed = gdata_upload_stream_constructed;
Packit 4b6dd7
	gobject_class->dispose = gdata_upload_stream_dispose;
Packit 4b6dd7
	gobject_class->finalize = gdata_upload_stream_finalize;
Packit 4b6dd7
	gobject_class->get_property = gdata_upload_stream_get_property;
Packit 4b6dd7
	gobject_class->set_property = gdata_upload_stream_set_property;
Packit 4b6dd7
Packit 4b6dd7
	/* We use the default implementations of the async functions, which just run
Packit 4b6dd7
	 * our implementation of the sync function in a thread. */
Packit 4b6dd7
	stream_class->write_fn = gdata_upload_stream_write;
Packit 4b6dd7
	stream_class->flush = gdata_upload_stream_flush;
Packit 4b6dd7
	stream_class->close_fn = gdata_upload_stream_close;
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:service:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The service which is used to authorize the upload, and to which the upload relates.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.5.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_SERVICE,
Packit 4b6dd7
	                                 g_param_spec_object ("service",
Packit 4b6dd7
	                                                      "Service", "The service which is used to authorize the upload.",
Packit 4b6dd7
	                                                      GDATA_TYPE_SERVICE,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:authorization-domain:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The authorization domain for the upload, against which the #GDataService:authorizer for the #GDataDownloadStream:service should be
Packit 4b6dd7
	 * authorized. This may be %NULL if authorization is not needed for the upload.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.9.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN,
Packit 4b6dd7
	                                 g_param_spec_object ("authorization-domain",
Packit 4b6dd7
	                                                      "Authorization domain", "The authorization domain for the upload.",
Packit 4b6dd7
	                                                      GDATA_TYPE_AUTHORIZATION_DOMAIN,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:method:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The HTTP request method to use when uploading the file.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.7.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_METHOD,
Packit 4b6dd7
	                                 g_param_spec_string ("method",
Packit 4b6dd7
	                                                      "Method", "The HTTP request method to use when uploading the file.",
Packit 4b6dd7
	                                                      NULL,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:upload-uri:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The URI to upload the data and metadata to. This must be HTTPS.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.5.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_UPLOAD_URI,
Packit 4b6dd7
	                                 g_param_spec_string ("upload-uri",
Packit 4b6dd7
	                                                      "Upload URI", "The URI to upload the data and metadata to.",
Packit 4b6dd7
	                                                      NULL,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:entry:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The entry used for metadata to upload.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.5.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_ENTRY,
Packit 4b6dd7
	                                 g_param_spec_object ("entry",
Packit 4b6dd7
	                                                      "Entry", "The entry used for metadata to upload.",
Packit 4b6dd7
	                                                      GDATA_TYPE_ENTRY,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:slug:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The slug of the file being uploaded. This is usually the display name of the file (i.e. as returned by g_file_info_get_display_name()).
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.5.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_SLUG,
Packit 4b6dd7
	                                 g_param_spec_string ("slug",
Packit 4b6dd7
	                                                      "Slug", "The slug of the file being uploaded.",
Packit 4b6dd7
	                                                      NULL,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:content-length:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The content length (in bytes) of the file being uploaded (i.e. as returned by g_file_info_get_size()). Note that this does not include the
Packit 4b6dd7
	 * length of the XML serialisation of #GDataUploadStream:entry, if set.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * If this is -1 the upload will be non-resumable; if it is non-negative, the upload will be resumable.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.13.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH,
Packit 4b6dd7
	                                 g_param_spec_int64 ("content-length",
Packit 4b6dd7
	                                                     "Content length", "The content length (in bytes) of the file being uploaded.",
Packit 4b6dd7
	                                                     -1, G_MAXINT64, -1,
Packit 4b6dd7
	                                                     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:content-type:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * The content type of the file being uploaded (i.e. as returned by g_file_info_get_content_type()).
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.5.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
Packit 4b6dd7
	                                 g_param_spec_string ("content-type",
Packit 4b6dd7
	                                                      "Content type", "The content type of the file being uploaded.",
Packit 4b6dd7
	                                                      NULL,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataUploadStream:cancellable:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * An optional cancellable used to cancel the entire upload operation. If a #GCancellable instance isn't provided for this property at
Packit 4b6dd7
	 * construction time (i.e. to gdata_upload_stream_new()), one will be created internally and can be retrieved using
Packit 4b6dd7
	 * gdata_upload_stream_get_cancellable() and used to cancel the upload operation with g_cancellable_cancel() just as if it was passed to
Packit 4b6dd7
	 * gdata_upload_stream_new().
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * If the upload operation is cancelled using this #GCancellable, any ongoing network activity will be stopped, and any pending or future calls
Packit 4b6dd7
	 * to #GOutputStream API on the #GDataUploadStream will return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed
Packit 4b6dd7
	 * to individual #GOutputStream operations will not cancel the upload operation proper if cancelled — they will merely cancel that API call.
Packit 4b6dd7
	 * The only way to cancel the upload operation completely is using #GDataUploadStream:cancellable.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.8.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_CANCELLABLE,
Packit 4b6dd7
	                                 g_param_spec_object ("cancellable",
Packit 4b6dd7
	                                                      "Cancellable", "An optional cancellable used to cancel the entire upload operation.",
Packit 4b6dd7
	                                                      G_TYPE_CANCELLABLE,
Packit 4b6dd7
	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_init (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_UPLOAD_STREAM, GDataUploadStreamPrivate);
Packit 4b6dd7
	self->priv->buffer = gdata_buffer_new ();
Packit 4b6dd7
	g_mutex_init (&(self->priv->write_mutex));
Packit 4b6dd7
	g_cond_init (&(self->priv->write_cond));
Packit 4b6dd7
	g_cond_init (&(self->priv->finished_cond));
Packit 4b6dd7
	g_mutex_init (&(self->priv->response_mutex));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static SoupMessage *
Packit 4b6dd7
build_message (GDataUploadStream *self, const gchar *method, const gchar *upload_uri)
Packit 4b6dd7
{
Packit 4b6dd7
	SoupMessage *new_message;
Packit 4b6dd7
	SoupURI *_uri;
Packit 4b6dd7
Packit 4b6dd7
	/* Build the message */
Packit 4b6dd7
	_uri = soup_uri_new (upload_uri);
Packit 4b6dd7
	soup_uri_set_port (_uri, _gdata_service_get_https_port ());
Packit 4b6dd7
	new_message = soup_message_new_from_uri (method, _uri);
Packit 4b6dd7
	soup_uri_free (_uri);
Packit 4b6dd7
Packit 4b6dd7
	/* We don't want to accumulate chunks */
Packit 4b6dd7
	soup_message_body_set_accumulate (new_message->request_body, FALSE);
Packit 4b6dd7
Packit 4b6dd7
	return new_message;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_constructed (GObject *object)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv;
Packit 4b6dd7
	GDataServiceClass *service_klass;
Packit 4b6dd7
	SoupURI *uri = NULL;
Packit 4b6dd7
Packit 4b6dd7
	/* Chain up to the parent class */
Packit 4b6dd7
	G_OBJECT_CLASS (gdata_upload_stream_parent_class)->constructed (object);
Packit 4b6dd7
	priv = GDATA_UPLOAD_STREAM (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* The upload URI must be HTTPS. */
Packit 4b6dd7
	uri = soup_uri_new (priv->upload_uri);
Packit 4b6dd7
	g_assert_cmpstr (soup_uri_get_scheme (uri), ==, SOUP_URI_SCHEME_HTTPS);
Packit 4b6dd7
	soup_uri_free (uri);
Packit 4b6dd7
Packit 4b6dd7
	/* Create a #GCancellable for the entire upload operation if one wasn't specified for #GDataUploadStream:cancellable during construction */
Packit 4b6dd7
	if (priv->cancellable == NULL)
Packit 4b6dd7
		priv->cancellable = g_cancellable_new ();
Packit 4b6dd7
Packit 4b6dd7
	/* Build the message */
Packit 4b6dd7
	priv->message = build_message (GDATA_UPLOAD_STREAM (object), priv->method, priv->upload_uri);
Packit 4b6dd7
Packit 4b6dd7
	if (priv->slug != NULL)
Packit 4b6dd7
		soup_message_headers_append (priv->message->request_headers, "Slug", priv->slug);
Packit 4b6dd7
Packit 4b6dd7
	if (priv->content_length == -1) {
Packit 4b6dd7
		/* Non-resumable upload */
Packit 4b6dd7
		soup_message_headers_set_encoding (priv->message->request_headers, SOUP_ENCODING_CHUNKED);
Packit 4b6dd7
Packit 4b6dd7
		/* The Content-Type should be multipart/related if we're also uploading the metadata (entry != NULL),
Packit 4b6dd7
		 * and the given content_type otherwise. */
Packit 4b6dd7
		if (priv->entry != NULL) {
Packit 4b6dd7
			gchar *first_part_header, *upload_data;
Packit 4b6dd7
			gchar *second_part_header;
Packit 4b6dd7
			GDataParsableClass *parsable_klass;
Packit 4b6dd7
Packit 4b6dd7
			parsable_klass = GDATA_PARSABLE_GET_CLASS (priv->entry);
Packit 4b6dd7
			g_assert (parsable_klass->get_content_type != NULL);
Packit 4b6dd7
Packit 4b6dd7
			soup_message_headers_set_content_type (priv->message->request_headers, "multipart/related; boundary=" BOUNDARY_STRING, NULL);
Packit 4b6dd7
Packit 4b6dd7
			if (g_strcmp0 (parsable_klass->get_content_type (), "application/json") == 0) {
Packit 4b6dd7
				upload_data = gdata_parsable_get_json (GDATA_PARSABLE (priv->entry));
Packit 4b6dd7
			} else {
Packit 4b6dd7
				upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (priv->entry));
Packit 4b6dd7
			}
Packit 4b6dd7
Packit 4b6dd7
			/* Start by writing out the entry; then the thread has something to write to the network when it's created */
Packit 4b6dd7
			first_part_header = g_strdup_printf ("--" BOUNDARY_STRING "\n"
Packit 4b6dd7
			                                     "Content-Type: %s; charset=UTF-8\n\n",
Packit 4b6dd7
			                                     parsable_klass->get_content_type ());
Packit 4b6dd7
			second_part_header = g_strdup_printf ("\n--" BOUNDARY_STRING "\n"
Packit 4b6dd7
			                                      "Content-Type: %s\n"
Packit 4b6dd7
			                                      "Content-Transfer-Encoding: binary\n\n",
Packit 4b6dd7
			                                      priv->content_type);
Packit 4b6dd7
Packit 4b6dd7
			/* Push the message parts onto the message body; we can skip the buffer, since the network thread hasn't yet been created,
Packit 4b6dd7
			 * so we're the sole thread accessing the SoupMessage. */
Packit 4b6dd7
			soup_message_body_append (priv->message->request_body,
Packit 4b6dd7
			                          SOUP_MEMORY_TAKE,
Packit 4b6dd7
			                          first_part_header,
Packit 4b6dd7
			                          strlen (first_part_header));
Packit 4b6dd7
			soup_message_body_append (priv->message->request_body,
Packit 4b6dd7
			                          SOUP_MEMORY_TAKE, upload_data,
Packit 4b6dd7
			                          strlen (upload_data));
Packit 4b6dd7
			soup_message_body_append (priv->message->request_body,
Packit 4b6dd7
			                          SOUP_MEMORY_TAKE,
Packit 4b6dd7
			                          second_part_header,
Packit 4b6dd7
			                          strlen (second_part_header));
Packit 4b6dd7
Packit 4b6dd7
			first_part_header = NULL;
Packit 4b6dd7
			upload_data = NULL;
Packit 4b6dd7
			second_part_header = NULL;
Packit 4b6dd7
Packit 4b6dd7
			priv->network_bytes_outstanding = priv->message->request_body->length;
Packit 4b6dd7
		} else {
Packit 4b6dd7
			soup_message_headers_set_content_type (priv->message->request_headers, priv->content_type, NULL);
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		/* Non-resumable uploads start with the data requests immediately. */
Packit 4b6dd7
		priv->state = STATE_DATA_REQUESTS;
Packit 4b6dd7
	} else {
Packit 4b6dd7
		gchar *content_length_str;
Packit 4b6dd7
Packit 4b6dd7
		/* Resumable upload's initial request */
Packit 4b6dd7
		soup_message_headers_set_encoding (priv->message->request_headers, SOUP_ENCODING_CONTENT_LENGTH);
Packit 4b6dd7
		soup_message_headers_replace (priv->message->request_headers, "X-Upload-Content-Type", priv->content_type);
Packit 4b6dd7
Packit 4b6dd7
		content_length_str = g_strdup_printf ("%" G_GOFFSET_FORMAT, priv->content_length);
Packit 4b6dd7
		soup_message_headers_replace (priv->message->request_headers, "X-Upload-Content-Length", content_length_str);
Packit 4b6dd7
		g_free (content_length_str);
Packit 4b6dd7
Packit 4b6dd7
		if (priv->entry != NULL) {
Packit 4b6dd7
			GDataParsableClass *parsable_klass;
Packit 4b6dd7
			gchar *content_type, *upload_data;
Packit 4b6dd7
Packit 4b6dd7
			parsable_klass = GDATA_PARSABLE_GET_CLASS (priv->entry);
Packit 4b6dd7
			g_assert (parsable_klass->get_content_type != NULL);
Packit 4b6dd7
Packit 4b6dd7
			if (g_strcmp0 (parsable_klass->get_content_type (), "application/json") == 0) {
Packit 4b6dd7
				upload_data = gdata_parsable_get_json (GDATA_PARSABLE (priv->entry));
Packit 4b6dd7
			} else {
Packit 4b6dd7
				upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (priv->entry));
Packit 4b6dd7
			}
Packit 4b6dd7
Packit 4b6dd7
			content_type = g_strdup_printf ("%s; charset=UTF-8",
Packit 4b6dd7
			                                parsable_klass->get_content_type ());
Packit 4b6dd7
			soup_message_headers_set_content_type (priv->message->request_headers,
Packit 4b6dd7
			                                       content_type,
Packit 4b6dd7
			                                       NULL);
Packit 4b6dd7
			g_free (content_type);
Packit 4b6dd7
Packit 4b6dd7
			soup_message_body_append (priv->message->request_body,
Packit 4b6dd7
			                          SOUP_MEMORY_TAKE,
Packit 4b6dd7
			                          upload_data,
Packit 4b6dd7
			                          strlen (upload_data));
Packit 4b6dd7
			upload_data = NULL;
Packit 4b6dd7
Packit 4b6dd7
			priv->network_bytes_outstanding = priv->message->request_body->length;
Packit 4b6dd7
		} else {
Packit 4b6dd7
			soup_message_headers_set_content_length (priv->message->request_headers, 0);
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		/* Resumable uploads always start with an initial request, which either contains the XML or is empty. */
Packit 4b6dd7
		priv->state = STATE_INITIAL_REQUEST;
Packit 4b6dd7
		priv->chunk_size = MIN (priv->content_length, MAX_RESUMABLE_CHUNK_SIZE);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around
Packit 4b6dd7
	 * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */
Packit 4b6dd7
	service_klass = GDATA_SERVICE_GET_CLASS (priv->service);
Packit 4b6dd7
	if (service_klass->append_query_headers != NULL) {
Packit 4b6dd7
		service_klass->append_query_headers (priv->service, priv->authorization_domain, priv->message);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* If the entry exists and has an ETag, we assume we're updating the entry, so we can set the If-Match header */
Packit 4b6dd7
	if (priv->entry != NULL && gdata_entry_get_etag (priv->entry) != NULL)
Packit 4b6dd7
		soup_message_headers_append (priv->message->request_headers, "If-Match", gdata_entry_get_etag (priv->entry));
Packit 4b6dd7
Packit 4b6dd7
	/* Uploading doesn't actually start until the first call to write() */
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_dispose (GObject *object)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* Close the stream before unreffing things like priv->service, which stops crashes like bgo#602156 if the stream is unreffed in the middle
Packit 4b6dd7
	 * of network operations */
Packit 4b6dd7
	g_output_stream_close (G_OUTPUT_STREAM (object), NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	if (priv->cancellable != NULL)
Packit 4b6dd7
		g_object_unref (priv->cancellable);
Packit 4b6dd7
	priv->cancellable = NULL;
Packit 4b6dd7
Packit 4b6dd7
	if (priv->service != NULL)
Packit 4b6dd7
		g_object_unref (priv->service);
Packit 4b6dd7
	priv->service = NULL;
Packit 4b6dd7
Packit 4b6dd7
	if (priv->authorization_domain != NULL)
Packit 4b6dd7
		g_object_unref (priv->authorization_domain);
Packit 4b6dd7
	priv->authorization_domain = NULL;
Packit 4b6dd7
Packit 4b6dd7
	if (priv->message != NULL)
Packit 4b6dd7
		g_object_unref (priv->message);
Packit 4b6dd7
	priv->message = NULL;
Packit 4b6dd7
Packit 4b6dd7
	if (priv->entry != NULL)
Packit 4b6dd7
		g_object_unref (priv->entry);
Packit 4b6dd7
	priv->entry = NULL;
Packit 4b6dd7
Packit 4b6dd7
	/* Chain up to the parent class */
Packit 4b6dd7
	G_OBJECT_CLASS (gdata_upload_stream_parent_class)->dispose (object);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_finalize (GObject *object)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_clear (&(priv->response_mutex));
Packit 4b6dd7
	g_cond_clear (&(priv->finished_cond));
Packit 4b6dd7
	g_cond_clear (&(priv->write_cond));
Packit 4b6dd7
	g_mutex_clear (&(priv->write_mutex));
Packit 4b6dd7
	gdata_buffer_free (priv->buffer);
Packit 4b6dd7
	g_clear_error (&(priv->response_error));
Packit 4b6dd7
	g_free (priv->upload_uri);
Packit 4b6dd7
	g_free (priv->method);
Packit 4b6dd7
	g_free (priv->slug);
Packit 4b6dd7
	g_free (priv->content_type);
Packit 4b6dd7
Packit 4b6dd7
	/* Chain up to the parent class */
Packit 4b6dd7
	G_OBJECT_CLASS (gdata_upload_stream_parent_class)->finalize (object);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	switch (property_id) {
Packit 4b6dd7
		case PROP_SERVICE:
Packit 4b6dd7
			g_value_set_object (value, priv->service);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_AUTHORIZATION_DOMAIN:
Packit 4b6dd7
			g_value_set_object (value, priv->authorization_domain);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_METHOD:
Packit 4b6dd7
			g_value_set_string (value, priv->method);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_UPLOAD_URI:
Packit 4b6dd7
			g_value_set_string (value, priv->upload_uri);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_ENTRY:
Packit 4b6dd7
			g_value_set_object (value, priv->entry);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_SLUG:
Packit 4b6dd7
			g_value_set_string (value, priv->slug);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_CONTENT_TYPE:
Packit 4b6dd7
			g_value_set_string (value, priv->content_type);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_CONTENT_LENGTH:
Packit 4b6dd7
			g_value_set_int64 (value, priv->content_length);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_CANCELLABLE:
Packit 4b6dd7
			g_value_set_object (value, priv->cancellable);
Packit 4b6dd7
			break;
Packit 4b6dd7
		default:
Packit 4b6dd7
			/* We don't have any other property... */
Packit 4b6dd7
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
Packit 4b6dd7
			break;
Packit 4b6dd7
	}
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_upload_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	switch (property_id) {
Packit 4b6dd7
		case PROP_SERVICE:
Packit 4b6dd7
			priv->service = g_value_dup_object (value);
Packit 4b6dd7
			priv->session = _gdata_service_get_session (priv->service);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_AUTHORIZATION_DOMAIN:
Packit 4b6dd7
			priv->authorization_domain = g_value_dup_object (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_METHOD:
Packit 4b6dd7
			priv->method = g_value_dup_string (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_UPLOAD_URI:
Packit 4b6dd7
			priv->upload_uri = g_value_dup_string (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_ENTRY:
Packit 4b6dd7
			priv->entry = g_value_dup_object (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_SLUG:
Packit 4b6dd7
			priv->slug = g_value_dup_string (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_CONTENT_TYPE:
Packit 4b6dd7
			priv->content_type = g_value_dup_string (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_CONTENT_LENGTH:
Packit 4b6dd7
			priv->content_length = g_value_get_int64 (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		case PROP_CANCELLABLE:
Packit 4b6dd7
			/* Construction only */
Packit 4b6dd7
			priv->cancellable = g_value_dup_object (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		default:
Packit 4b6dd7
			/* We don't have any other property... */
Packit 4b6dd7
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
Packit 4b6dd7
			break;
Packit 4b6dd7
	}
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
typedef struct {
Packit 4b6dd7
	GDataUploadStream *upload_stream;
Packit 4b6dd7
	gboolean *cancelled;
Packit 4b6dd7
} CancelledData;
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
write_cancelled_cb (GCancellable *cancellable, CancelledData *data)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = data->upload_stream->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* Signal the gdata_upload_stream_write() function that it should stop blocking and cancel */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	*(data->cancelled) = TRUE;
Packit 4b6dd7
	g_cond_signal (&(priv->write_cond));
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static gssize
Packit 4b6dd7
gdata_upload_stream_write (GOutputStream *stream, const void *buffer, gsize count, GCancellable *cancellable, GError **error_out)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (stream)->priv;
Packit 4b6dd7
	gssize length_written = -1;
Packit 4b6dd7
	gulong cancelled_signal = 0, global_cancelled_signal = 0;
Packit 4b6dd7
	gboolean cancelled = FALSE; /* must only be touched with ->write_mutex held */
Packit 4b6dd7
	gsize old_total_network_bytes_written;
Packit 4b6dd7
	CancelledData data;
Packit 4b6dd7
	GError *error = NULL;
Packit 4b6dd7
Packit 4b6dd7
	/* Listen for cancellation events */
Packit 4b6dd7
	data.upload_stream = GDATA_UPLOAD_STREAM (stream);
Packit 4b6dd7
	data.cancelled = &cancelled;
Packit 4b6dd7
Packit 4b6dd7
	global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) write_cancelled_cb, &data, NULL);
Packit 4b6dd7
Packit 4b6dd7
	if (cancellable != NULL)
Packit 4b6dd7
		cancelled_signal = g_cancellable_connect (cancellable, (GCallback) write_cancelled_cb, &data, NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Check for an error and return if necessary */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	if (cancelled == TRUE) {
Packit 4b6dd7
		g_assert (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE ||
Packit 4b6dd7
		          g_cancellable_set_error_if_cancelled (priv->cancellable, &error) == TRUE);
Packit 4b6dd7
		g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
		length_written = -1;
Packit 4b6dd7
		goto done;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Increment the number of bytes outstanding for the new write, and keep a record of the old number written so we know if the write's
Packit 4b6dd7
	 * finished before we reach write_cond. */
Packit 4b6dd7
	old_total_network_bytes_written = priv->total_network_bytes_written;
Packit 4b6dd7
	priv->message_bytes_outstanding += count;
Packit 4b6dd7
Packit 4b6dd7
	/* Handle the more common case of the network thread already having been created first */
Packit 4b6dd7
	if (priv->network_thread != NULL) {
Packit 4b6dd7
		/* Push the new data into the buffer */
Packit 4b6dd7
		gdata_buffer_push_data (priv->buffer, buffer, count);
Packit 4b6dd7
		goto write;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Write out the first chunk of data, so there's guaranteed to be something in the buffer */
Packit 4b6dd7
	gdata_buffer_push_data (priv->buffer, buffer, count);
Packit 4b6dd7
Packit 4b6dd7
	/* Create the thread and let the writing commence! */
Packit 4b6dd7
	create_network_thread (GDATA_UPLOAD_STREAM (stream), &error);
Packit 4b6dd7
	if (priv->network_thread == NULL) {
Packit 4b6dd7
		length_written = -1;
Packit 4b6dd7
		goto done;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
write:
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Wait for it to be written */
Packit 4b6dd7
	while (priv->total_network_bytes_written - old_total_network_bytes_written < count && cancelled == FALSE && priv->state != STATE_FINISHED) {
Packit 4b6dd7
		g_cond_wait (&(priv->write_cond), &(priv->write_mutex));
Packit 4b6dd7
	}
Packit 4b6dd7
	length_written = MIN (count, priv->total_network_bytes_written - old_total_network_bytes_written);
Packit 4b6dd7
Packit 4b6dd7
	/* Check for an error and return if necessary */
Packit 4b6dd7
	if (cancelled == TRUE && length_written < 1) {
Packit 4b6dd7
		/* Cancellation. */
Packit 4b6dd7
		g_assert (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE ||
Packit 4b6dd7
		          g_cancellable_set_error_if_cancelled (priv->cancellable, &error) == TRUE);
Packit 4b6dd7
		length_written = -1;
Packit 4b6dd7
	} else if (priv->state == STATE_FINISHED && (length_written < 0 || (gsize) length_written < count)) {
Packit 4b6dd7
		/* Resumable upload error. */
Packit 4b6dd7
		g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error received from server after uploading a resumable upload chunk."));
Packit 4b6dd7
		length_written = -1;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
done:
Packit 4b6dd7
	/* Disconnect from the cancelled signals. Note that we have to do this with @write_mutex not held, as g_cancellable_disconnect() blocks
Packit 4b6dd7
	 * until any outstanding cancellation callbacks return, and they will block on @write_mutex. */
Packit 4b6dd7
	if (cancelled_signal != 0)
Packit 4b6dd7
		g_cancellable_disconnect (cancellable, cancelled_signal);
Packit 4b6dd7
	if (global_cancelled_signal != 0)
Packit 4b6dd7
		g_cancellable_disconnect (priv->cancellable, global_cancelled_signal);
Packit 4b6dd7
Packit 4b6dd7
	g_assert (error != NULL || length_written > 0);
Packit 4b6dd7
Packit 4b6dd7
	if (error != NULL) {
Packit 4b6dd7
		g_propagate_error (error_out, error);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	return length_written;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
flush_cancelled_cb (GCancellable *cancellable, CancelledData *data)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = data->upload_stream->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* Signal the gdata_upload_stream_flush() function that it should stop blocking and cancel */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	*(data->cancelled) = TRUE;
Packit 4b6dd7
	g_cond_signal (&(priv->write_cond));
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/* Block until ->network_bytes_outstanding reaches zero. Cancelling the cancellable passed to gdata_upload_stream_flush() breaks out of the wait(),
Packit 4b6dd7
 * but doesn't stop the network thread from continuing to write the remaining bytes to the network.
Packit 4b6dd7
 * The wrapper function, g_output_stream_flush(), calls g_output_stream_set_pending() before calling this function, and calls
Packit 4b6dd7
 * g_output_stream_clear_pending() afterwards, so we don't need to worry about other operations happening concurrently. We also don't need to worry
Packit 4b6dd7
 * about being called after the stream has been closed (though the network activity could finish before or during this function). */
Packit 4b6dd7
static gboolean
Packit 4b6dd7
gdata_upload_stream_flush (GOutputStream *stream, GCancellable *cancellable, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (stream)->priv;
Packit 4b6dd7
	gulong cancelled_signal = 0, global_cancelled_signal = 0;
Packit 4b6dd7
	gboolean cancelled = FALSE; /* must only be touched with ->write_mutex held */
Packit 4b6dd7
	gboolean success = TRUE;
Packit 4b6dd7
	CancelledData data;
Packit 4b6dd7
Packit 4b6dd7
	/* Listen for cancellation events */
Packit 4b6dd7
	data.upload_stream = GDATA_UPLOAD_STREAM (stream);
Packit 4b6dd7
	data.cancelled = &cancelled;
Packit 4b6dd7
Packit 4b6dd7
	global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) flush_cancelled_cb, &data, NULL);
Packit 4b6dd7
Packit 4b6dd7
	if (cancellable != NULL)
Packit 4b6dd7
		cancelled_signal = g_cancellable_connect (cancellable, (GCallback) flush_cancelled_cb, &data, NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Create the thread if it hasn't been created already. This can happen if flush() is called immediately after creating the stream. */
Packit 4b6dd7
	if (priv->network_thread == NULL) {
Packit 4b6dd7
		create_network_thread (GDATA_UPLOAD_STREAM (stream), error);
Packit 4b6dd7
		if (priv->network_thread == NULL) {
Packit 4b6dd7
			success = FALSE;
Packit 4b6dd7
			goto done;
Packit 4b6dd7
		}
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Start the flush operation proper */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Wait for all outstanding bytes to be written to the network */
Packit 4b6dd7
	while (priv->network_bytes_outstanding > 0 && cancelled == FALSE && priv->state != STATE_FINISHED) {
Packit 4b6dd7
		g_cond_wait (&(priv->write_cond), &(priv->write_mutex));
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Check for an error and return if necessary */
Packit 4b6dd7
	if (cancelled == TRUE) {
Packit 4b6dd7
		g_assert (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE ||
Packit 4b6dd7
		          g_cancellable_set_error_if_cancelled (priv->cancellable, error) == TRUE);
Packit 4b6dd7
		success = FALSE;
Packit 4b6dd7
	} else if (priv->state == STATE_FINISHED && priv->network_bytes_outstanding > 0) {
Packit 4b6dd7
		/* Resumable upload error. */
Packit 4b6dd7
		g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error received from server after uploading a resumable upload chunk."));
Packit 4b6dd7
		success = FALSE;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
done:
Packit 4b6dd7
	/* Disconnect from the cancelled signals. Note that we have to do this without @write_mutex held, as g_cancellable_disconnect() blocks
Packit 4b6dd7
	 * until any outstanding cancellation callbacks return, and they will block on @write_mutex. */
Packit 4b6dd7
	if (cancelled_signal != 0)
Packit 4b6dd7
		g_cancellable_disconnect (cancellable, cancelled_signal);
Packit 4b6dd7
	if (global_cancelled_signal != 0)
Packit 4b6dd7
		g_cancellable_disconnect (priv->cancellable, global_cancelled_signal);
Packit 4b6dd7
Packit 4b6dd7
	return success;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
close_cancelled_cb (GCancellable *cancellable, CancelledData *data)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = data->upload_stream->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* Signal the gdata_upload_stream_close() function that it should stop blocking and cancel */
Packit 4b6dd7
	g_mutex_lock (&(priv->response_mutex));
Packit 4b6dd7
	*(data->cancelled) = TRUE;
Packit 4b6dd7
	g_cond_signal (&(priv->finished_cond));
Packit 4b6dd7
	g_mutex_unlock (&(priv->response_mutex));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/* It's guaranteed that we have set ->response_status and ->response_error and are done with *all* network activity before this returns, unless it's
Packit 4b6dd7
 * cancelled. This means that it's safe to call gdata_upload_stream_get_response() once a call to close() has returned without being cancelled.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Even though calling g_output_stream_close() multiple times on this stream is guaranteed to call gdata_upload_stream_close() at most once, other
Packit 4b6dd7
 * GIO methods (notably g_output_stream_splice()) can call gdata_upload_stream_close() directly. Consequently, we need to be careful to be idempotent
Packit 4b6dd7
 * after the first call.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If the network thread hasn't yet been started (i.e. gdata_upload_stream_write() hasn't been called at all yet), %TRUE will be returned immediately.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If the global cancellable, ->cancellable, or @cancellable are cancelled before the call to gdata_upload_stream_close(), gdata_upload_stream_close()
Packit 4b6dd7
 * should return immediately with %G_IO_ERROR_CANCELLED. If they're cancelled during the call, gdata_upload_stream_close() should stop waiting for
Packit 4b6dd7
 * any outstanding data to be flushed to the network and return %G_IO_ERROR_CANCELLED (though the operation to finish off network activity and close
Packit 4b6dd7
 * the stream will still continue).
Packit 4b6dd7
 *
Packit 4b6dd7
 * If the call to gdata_upload_stream_close() is not cancelled by any #GCancellable, it will wait until all the data has been flushed to the network
Packit 4b6dd7
 * and a response has been received. At this point, ->response_status and ->response_error have been set (and won't ever change) and we can return
Packit 4b6dd7
 * either success or an error code. */
Packit 4b6dd7
static gboolean
Packit 4b6dd7
gdata_upload_stream_close (GOutputStream *stream, GCancellable *cancellable, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (stream)->priv;
Packit 4b6dd7
	gboolean success = TRUE;
Packit 4b6dd7
	gboolean cancelled = FALSE; /* must only be touched with ->response_mutex held */
Packit 4b6dd7
	gulong cancelled_signal = 0, global_cancelled_signal = 0;
Packit 4b6dd7
	CancelledData data;
Packit 4b6dd7
	gboolean is_finished;
Packit 4b6dd7
	GError *child_error = NULL;
Packit 4b6dd7
Packit 4b6dd7
	/* If the operation was never started, return successfully immediately */
Packit 4b6dd7
	if (priv->network_thread == NULL)
Packit 4b6dd7
		return TRUE;
Packit 4b6dd7
Packit 4b6dd7
	/* If we've already closed the stream, return G_IO_ERROR_CLOSED */
Packit 4b6dd7
	g_mutex_lock (&(priv->response_mutex));
Packit 4b6dd7
Packit 4b6dd7
	if (priv->response_status != SOUP_STATUS_NONE) {
Packit 4b6dd7
		g_mutex_unlock (&(priv->response_mutex));
Packit 4b6dd7
		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED, _("Stream is already closed"));
Packit 4b6dd7
		return FALSE;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(priv->response_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Allow cancellation */
Packit 4b6dd7
	data.upload_stream = GDATA_UPLOAD_STREAM (stream);
Packit 4b6dd7
	data.cancelled = &cancelled;
Packit 4b6dd7
Packit 4b6dd7
	global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) close_cancelled_cb, &data, NULL);
Packit 4b6dd7
Packit 4b6dd7
	if (cancellable != NULL)
Packit 4b6dd7
		cancelled_signal = g_cancellable_connect (cancellable, (GCallback) close_cancelled_cb, &data, NULL);
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_lock (&(priv->response_mutex));
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	is_finished = (priv->state == STATE_FINISHED);
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* If an operation is still in progress, the upload thread hasn't finished yet… */
Packit 4b6dd7
	if (!is_finished) {
Packit 4b6dd7
		/* We've reached the end of the stream, so append the footer if the entire operation hasn't been cancelled. */
Packit 4b6dd7
		if (priv->entry != NULL && g_cancellable_is_cancelled (priv->cancellable) == FALSE) {
Packit 4b6dd7
			const gchar *footer = "\n--" BOUNDARY_STRING "--";
Packit 4b6dd7
			gsize footer_length = strlen (footer);
Packit 4b6dd7
Packit 4b6dd7
			gdata_buffer_push_data (priv->buffer, (const guint8*) footer, footer_length);
Packit 4b6dd7
Packit 4b6dd7
			g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
			priv->message_bytes_outstanding += footer_length;
Packit 4b6dd7
			g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		/* Mark the buffer as having reached EOF, and the write operation will close in its own time */
Packit 4b6dd7
		gdata_buffer_push_data (priv->buffer, NULL, 0);
Packit 4b6dd7
Packit 4b6dd7
		/* Wait for the signal that we've finished. Cancelling the call to gdata_upload_stream_close() will cause this wait to be aborted,
Packit 4b6dd7
		 * but won't actually prevent the stream being closed (i.e. all it means is that the stream isn't guaranteed to have been closed by
Packit 4b6dd7
		 * the time gdata_upload_stream_close() returns — whereas normally it would be). */
Packit 4b6dd7
		if (cancelled == FALSE) {
Packit 4b6dd7
			g_cond_wait (&(priv->finished_cond), &(priv->response_mutex));
Packit 4b6dd7
		}
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_assert (priv->response_status == SOUP_STATUS_NONE);
Packit 4b6dd7
	g_assert (priv->response_error == NULL);
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	is_finished = (priv->state == STATE_FINISHED);
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Error handling */
Packit 4b6dd7
	if (!is_finished && cancelled == TRUE) {
Packit 4b6dd7
		/* Cancelled? If ->state == STATE_FINISHED, the network activity finished before the gdata_upload_stream_close() operation was
Packit 4b6dd7
		 * cancelled, so we don't need to return an error. */
Packit 4b6dd7
		g_assert (g_cancellable_set_error_if_cancelled (cancellable, &child_error) == TRUE ||
Packit 4b6dd7
		          g_cancellable_set_error_if_cancelled (priv->cancellable, &child_error) == TRUE);
Packit 4b6dd7
		priv->response_status = SOUP_STATUS_CANCELLED;
Packit 4b6dd7
		success = FALSE;
Packit 4b6dd7
	} else if (SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code) == FALSE) {
Packit 4b6dd7
		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service);
Packit 4b6dd7
Packit 4b6dd7
		/* Parse the error */
Packit 4b6dd7
		g_assert (klass->parse_error_response != NULL);
Packit 4b6dd7
		klass->parse_error_response (priv->service, GDATA_OPERATION_UPLOAD, priv->message->status_code, priv->message->reason_phrase,
Packit 4b6dd7
		                             priv->message->response_body->data, priv->message->response_body->length, &child_error);
Packit 4b6dd7
		priv->response_status = priv->message->status_code;
Packit 4b6dd7
		success = FALSE;
Packit 4b6dd7
	} else {
Packit 4b6dd7
		/* Success! Set the response status */
Packit 4b6dd7
		priv->response_status = priv->message->status_code;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_assert (priv->response_status != SOUP_STATUS_NONE && (SOUP_STATUS_IS_SUCCESSFUL (priv->response_status) || child_error != NULL));
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(priv->response_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Disconnect from the signal handler. Note that we have to do this with @response_mutex not held, as g_cancellable_disconnect() blocks
Packit 4b6dd7
	 * until any outstanding cancellation callbacks return, and they will block on @response_mutex. */
Packit 4b6dd7
	if (cancelled_signal != 0)
Packit 4b6dd7
		g_cancellable_disconnect (cancellable, cancelled_signal);
Packit 4b6dd7
	if (global_cancelled_signal != 0)
Packit 4b6dd7
		g_cancellable_disconnect (priv->cancellable, global_cancelled_signal);
Packit 4b6dd7
Packit 4b6dd7
	g_assert ((success == TRUE && child_error == NULL) || (success == FALSE && child_error != NULL));
Packit 4b6dd7
Packit 4b6dd7
	if (child_error != NULL)
Packit 4b6dd7
		g_propagate_error (error, child_error);
Packit 4b6dd7
Packit 4b6dd7
	return success;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/* In the network thread context, called just after writing the headers, or just after writing a chunk, to write the next chunk to libsoup.
Packit 4b6dd7
 * We don't let it return until we've finished pushing all the data into the buffer.
Packit 4b6dd7
 * This is due to http://bugzilla.gnome.org/show_bug.cgi?id=522147, which means that
Packit 4b6dd7
 * we can't use soup_session_(un)?pause_message() with a SoupSessionSync.
Packit 4b6dd7
 * If we don't return from this signal handler, the message is never paused, and thus
Packit 4b6dd7
 * Bad Things don't happen (due to the bug, messages can be paused, but not unpaused).
Packit 4b6dd7
 * Obviously this means that our memory usage will increase, and we'll eventually end
Packit 4b6dd7
 * up storing the entire request body in memory, but that's unavoidable at this point. */
Packit 4b6dd7
static void
Packit 4b6dd7
write_next_chunk (GDataUploadStream *self, SoupMessage *message)
Packit 4b6dd7
{
Packit 4b6dd7
	#define CHUNK_SIZE 8192 /* 8KiB */
Packit 4b6dd7
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = self->priv;
Packit 4b6dd7
	gboolean has_network_bytes_outstanding, is_complete;
Packit 4b6dd7
	gsize length;
Packit 4b6dd7
	gboolean reached_eof = FALSE;
Packit 4b6dd7
	guint8 next_buffer[CHUNK_SIZE];
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	has_network_bytes_outstanding = (priv->network_bytes_outstanding > 0);
Packit 4b6dd7
	is_complete = (priv->state == STATE_INITIAL_REQUEST ||
Packit 4b6dd7
	               (priv->content_length != -1 && priv->network_bytes_written + priv->network_bytes_outstanding == priv->chunk_size));
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* If there are still bytes in libsoup's buffer, don't block on getting new bytes into the stream. Also, if we're making the initial request
Packit 4b6dd7
	 * of a resumable upload, don't push new data onto the network, since all of the XML was pushed into the buffer when we started. */
Packit 4b6dd7
	if (has_network_bytes_outstanding) {
Packit 4b6dd7
		return;
Packit 4b6dd7
	} else if (is_complete) {
Packit 4b6dd7
		soup_message_body_complete (priv->message->request_body);
Packit 4b6dd7
Packit 4b6dd7
		return;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Append the next chunk to the message body so it can join in the fun.
Packit 4b6dd7
	 * Note that this call isn't necessarily blocking, and can return less than the CHUNK_SIZE. This is because
Packit 4b6dd7
	 * we could deadlock if we block on getting CHUNK_SIZE bytes at the end of the stream. write() could
Packit 4b6dd7
	 * easily be called with fewer bytes, but has no way to notify us that we've reached the end of the
Packit 4b6dd7
	 * stream, so we'd happily block on receiving more bytes which weren't forthcoming.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Note also that we can't block on this call with write_mutex locked, or we could get into a deadlock if the stream is flushed at the same
Packit 4b6dd7
	 * time (in the case that we don't know the content length ahead of time). */
Packit 4b6dd7
	if (priv->content_length == -1) {
Packit 4b6dd7
		/* Non-resumable upload. */
Packit 4b6dd7
		length = gdata_buffer_pop_data_limited (priv->buffer, next_buffer, CHUNK_SIZE, &reached_eof);
Packit 4b6dd7
	} else {
Packit 4b6dd7
		/* Resumable upload. Ensure we don't exceed the chunk size. */
Packit 4b6dd7
		length = gdata_buffer_pop_data_limited (priv->buffer, next_buffer,
Packit 4b6dd7
		                                        MIN (CHUNK_SIZE, priv->chunk_size - (priv->network_bytes_written +
Packit 4b6dd7
		                                                                             priv->network_bytes_outstanding)), &reached_eof);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	priv->message_bytes_outstanding -= length;
Packit 4b6dd7
	priv->network_bytes_outstanding += length;
Packit 4b6dd7
Packit 4b6dd7
	/* Append whatever data was returned */
Packit 4b6dd7
	if (length > 0)
Packit 4b6dd7
		soup_message_body_append (priv->message->request_body, SOUP_MEMORY_COPY, next_buffer, length);
Packit 4b6dd7
Packit 4b6dd7
	/* Finish off the request body if we've reached EOF (i.e. the stream has been closed), or if we're doing a resumable upload and we reach
Packit 4b6dd7
	 * the maximum chunk size. */
Packit 4b6dd7
	if (reached_eof == TRUE ||
Packit 4b6dd7
	    (priv->content_length != -1 && priv->network_bytes_written + priv->network_bytes_outstanding == priv->chunk_size)) {
Packit 4b6dd7
		g_assert (reached_eof == FALSE || priv->message_bytes_outstanding == 0);
Packit 4b6dd7
Packit 4b6dd7
		soup_message_body_complete (priv->message->request_body);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
wrote_headers_cb (SoupMessage *message, GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = self->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* Signal the main thread that the headers have been written */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	g_cond_signal (&(priv->write_cond));
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Send the first chunk to libsoup */
Packit 4b6dd7
	write_next_chunk (self, message);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
wrote_body_data_cb (SoupMessage *message, SoupBuffer *buffer, GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = self->priv;
Packit 4b6dd7
Packit 4b6dd7
	/* Signal the main thread that the chunk has been written */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	g_assert (priv->network_bytes_outstanding > 0);
Packit 4b6dd7
	priv->network_bytes_outstanding -= buffer->length;
Packit 4b6dd7
	priv->network_bytes_written += buffer->length;
Packit 4b6dd7
Packit 4b6dd7
	if (priv->state == STATE_DATA_REQUESTS) {
Packit 4b6dd7
		priv->total_network_bytes_written += buffer->length;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_cond_signal (&(priv->write_cond));
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	/* Send the next chunk to libsoup */
Packit 4b6dd7
	write_next_chunk (self, message);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static gpointer
Packit 4b6dd7
upload_thread (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = self->priv;
Packit 4b6dd7
Packit 4b6dd7
	g_assert (priv->cancellable != NULL);
Packit 4b6dd7
Packit 4b6dd7
	while (TRUE) {
Packit 4b6dd7
		GDataServiceClass *klass;
Packit 4b6dd7
		gulong wrote_headers_signal, wrote_body_data_signal;
Packit 4b6dd7
		gchar *new_uri;
Packit 4b6dd7
		SoupMessage *new_message;
Packit 4b6dd7
		gsize next_chunk_length;
Packit 4b6dd7
Packit 4b6dd7
		/* Connect to the wrote-* signals so we can prepare the next chunk for transmission */
Packit 4b6dd7
		wrote_headers_signal = g_signal_connect (priv->message, "wrote-headers", (GCallback) wrote_headers_cb, self);
Packit 4b6dd7
		wrote_body_data_signal = g_signal_connect (priv->message, "wrote-body-data", (GCallback) wrote_body_data_cb, self);
Packit 4b6dd7
Packit 4b6dd7
		_gdata_service_actually_send_message (priv->session, priv->message, priv->cancellable, NULL);
Packit 4b6dd7
Packit 4b6dd7
		g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
		/* If this is a resumable upload, continue to the next chunk. If it's a non-resumable upload, we're done. We have several cases:
Packit 4b6dd7
		 *  • Non-resumable upload:
Packit 4b6dd7
		 *     - Content only: STATE_DATA_REQUESTS → STATE_FINISHED
Packit 4b6dd7
		 *     - Metadata only: not supported
Packit 4b6dd7
		 *     - Content and metadata: STATE_DATA_REQUESTS → STATE_FINISHED
Packit 4b6dd7
		 *  • Resumable upload:
Packit 4b6dd7
		 *     - Content only:
Packit 4b6dd7
		 *        * STATE_INITIAL_REQUEST → STATE_DATA_REQUESTS
Packit 4b6dd7
		 *        * STATE_DATA_REQUESTS → STATE_DATA_REQUESTS
Packit 4b6dd7
		 *        * STATE_DATA_REQUESTS → STATE_FINISHED
Packit 4b6dd7
		 *     - Metadata only: STATE_INITIAL_REQUEST → STATE_FINISHED
Packit 4b6dd7
		 *     - Content and metadata:
Packit 4b6dd7
		 *        * STATE_INITIAL_REQUEST → STATE_DATA_REQUESTS
Packit 4b6dd7
		 *        * STATE_DATA_REQUESTS → STATE_DATA_REQUESTS
Packit 4b6dd7
		 *        * STATE_DATA_REQUESTS → STATE_FINISHED
Packit 4b6dd7
		 */
Packit 4b6dd7
		switch (priv->state) {
Packit 4b6dd7
			case STATE_INITIAL_REQUEST:
Packit 4b6dd7
				/* We're either a content-only or a content-and-metadata resumable upload. */
Packit 4b6dd7
				priv->state = STATE_DATA_REQUESTS;
Packit 4b6dd7
Packit 4b6dd7
				/* Check the response. On success it should be empty, status 200, with a Location header telling us where to upload
Packit 4b6dd7
				 * next. If it's an error response, bail out and let the code in gdata_upload_stream_close() parse the error..*/
Packit 4b6dd7
				if (!SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code)) {
Packit 4b6dd7
					goto finished;
Packit 4b6dd7
				} else if (priv->content_length == 0 && priv->message->status_code == SOUP_STATUS_CREATED) {
Packit 4b6dd7
					/* If this was a metadata-only upload, we're done. */
Packit 4b6dd7
					goto finished;
Packit 4b6dd7
				}
Packit 4b6dd7
Packit 4b6dd7
				/* Fall out and prepare the next message */
Packit 4b6dd7
				g_assert (priv->total_network_bytes_written == 0); /* haven't written any data yet */
Packit 4b6dd7
Packit 4b6dd7
				break;
Packit 4b6dd7
			case STATE_DATA_REQUESTS:
Packit 4b6dd7
				/* Check the response. On completion it should contain the resulting entry's XML, status 201. On continuation it should
Packit 4b6dd7
				 * be empty, status 308, with a Range header and potentially a Location header telling us what/where to upload next.
Packit 4b6dd7
				 * If it's an error response, bail out and let the code in gdata_upload_stream_close() parse the error..*/
Packit 4b6dd7
				if (priv->message->status_code == 308) {
Packit 4b6dd7
					/* Continuation: fall out and prepare the next message */
Packit 4b6dd7
					g_assert (priv->content_length == -1 || priv->total_network_bytes_written < (gsize) priv->content_length);
Packit 4b6dd7
				} else if (SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code)) {
Packit 4b6dd7
					/* Completion. Check the server isn't misbehaving. */
Packit 4b6dd7
					g_assert (priv->content_length == -1 || priv->total_network_bytes_written == (gsize) priv->content_length);
Packit 4b6dd7
Packit 4b6dd7
					goto finished;
Packit 4b6dd7
				} else {
Packit 4b6dd7
					/* Error */
Packit 4b6dd7
					goto finished;
Packit 4b6dd7
				}
Packit 4b6dd7
Packit 4b6dd7
				/* Fall out and prepare the next message */
Packit 4b6dd7
				g_assert (priv->total_network_bytes_written > 0);
Packit 4b6dd7
Packit 4b6dd7
				break;
Packit 4b6dd7
			case STATE_FINISHED:
Packit 4b6dd7
			default:
Packit 4b6dd7
				g_assert_not_reached ();
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		/* Prepare the next message. */
Packit 4b6dd7
		g_assert (priv->content_length != -1);
Packit 4b6dd7
Packit 4b6dd7
		next_chunk_length = MIN (priv->content_length - priv->total_network_bytes_written, MAX_RESUMABLE_CHUNK_SIZE);
Packit 4b6dd7
Packit 4b6dd7
		new_uri = g_strdup (soup_message_headers_get_one (priv->message->response_headers, "Location"));
Packit 4b6dd7
		if (new_uri == NULL) {
Packit 4b6dd7
			new_uri = soup_uri_to_string (soup_message_get_uri (priv->message), FALSE);
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		new_message = build_message (self, SOUP_METHOD_PUT, new_uri);
Packit 4b6dd7
Packit 4b6dd7
		g_free (new_uri);
Packit 4b6dd7
Packit 4b6dd7
		soup_message_headers_set_encoding (new_message->request_headers, SOUP_ENCODING_CONTENT_LENGTH);
Packit 4b6dd7
		soup_message_headers_set_content_type (new_message->request_headers, priv->content_type, NULL);
Packit 4b6dd7
		soup_message_headers_set_content_length (new_message->request_headers, next_chunk_length);
Packit 4b6dd7
		soup_message_headers_set_content_range (new_message->request_headers, priv->total_network_bytes_written,
Packit 4b6dd7
		                                        priv->total_network_bytes_written + next_chunk_length - 1, priv->content_length);
Packit 4b6dd7
Packit 4b6dd7
		/* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around
Packit 4b6dd7
		 * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */
Packit 4b6dd7
		klass = GDATA_SERVICE_GET_CLASS (priv->service);
Packit 4b6dd7
		if (klass->append_query_headers != NULL) {
Packit 4b6dd7
			klass->append_query_headers (priv->service, priv->authorization_domain, new_message);
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		g_signal_handler_disconnect (priv->message, wrote_body_data_signal);
Packit 4b6dd7
		g_signal_handler_disconnect (priv->message, wrote_headers_signal);
Packit 4b6dd7
Packit 4b6dd7
		g_object_unref (priv->message);
Packit 4b6dd7
		priv->message = new_message;
Packit 4b6dd7
Packit 4b6dd7
		/* Reset various counters for the next upload. Note that message_bytes_outstanding may be > 0 at this point, since the client may
Packit 4b6dd7
		 * have pushed some content into the buffer while we were waiting for the response to this request. */
Packit 4b6dd7
		g_assert (priv->network_bytes_outstanding == 0);
Packit 4b6dd7
		priv->chunk_size = next_chunk_length;
Packit 4b6dd7
		priv->network_bytes_written = 0;
Packit 4b6dd7
Packit 4b6dd7
		/* Loop round and upload this chunk now. */
Packit 4b6dd7
		g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
		continue;
Packit 4b6dd7
Packit 4b6dd7
finished:
Packit 4b6dd7
		g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
		goto finished_outer;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
finished_outer:
Packit 4b6dd7
	/* Signal that the operation has finished (either successfully or in error).
Packit 4b6dd7
	 * Also signal write_cond, just in case we errored out and finished sending in the middle of a write. */
Packit 4b6dd7
	g_mutex_lock (&(priv->write_mutex));
Packit 4b6dd7
	priv->state = STATE_FINISHED;
Packit 4b6dd7
	g_cond_signal (&(priv->write_cond));
Packit 4b6dd7
	g_mutex_unlock (&(priv->write_mutex));
Packit 4b6dd7
Packit 4b6dd7
	g_cond_signal (&(priv->finished_cond));
Packit 4b6dd7
Packit 4b6dd7
	/* Referenced in create_network_thread(). */
Packit 4b6dd7
	g_object_unref (self);
Packit 4b6dd7
Packit 4b6dd7
	return NULL;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
create_network_thread (GDataUploadStream *self, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataUploadStreamPrivate *priv = self->priv;
Packit 4b6dd7
Packit 4b6dd7
	g_assert (priv->network_thread == NULL);
Packit 4b6dd7
	g_object_ref (self); /* ownership transferred to thread */
Packit 4b6dd7
	priv->network_thread = g_thread_try_new ("upload-thread", (GThreadFunc) upload_thread, self, error);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_new:
Packit 4b6dd7
 * @service: a #GDataService
Packit 4b6dd7
 * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the upload, or %NULL
Packit 4b6dd7
 * @method: the HTTP method to use
Packit 4b6dd7
 * @upload_uri: the URI to upload, which must be HTTPS
Packit 4b6dd7
 * @entry: (allow-none): the entry to upload as metadata, or %NULL
Packit 4b6dd7
 * @slug: the file's slug (filename)
Packit 4b6dd7
 * @content_type: the content type of the file being uploaded
Packit 4b6dd7
 * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Creates a new #GDataUploadStream, allowing a file to be uploaded from a GData service using standard #GOutputStream API.
Packit 4b6dd7
 *
Packit 4b6dd7
 * The HTTP method to use should be specified in @method, and will typically be either %SOUP_METHOD_POST (for insertions) or %SOUP_METHOD_PUT
Packit 4b6dd7
 * (for updates), according to the server and the @upload_uri.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If @entry is specified, it will be attached to the upload as the entry to which the file being uploaded belongs. Otherwise, just the file
Packit 4b6dd7
 * written to the stream will be uploaded, and given a default entry as determined by the server.
Packit 4b6dd7
 *
Packit 4b6dd7
 * @slug and @content_type must be specified before the upload begins, as they describe the file being streamed. @slug is the filename given to the
Packit 4b6dd7
 * file, which will typically be stored on the server and made available when downloading the file again. @content_type must be the correct
Packit 4b6dd7
 * content type for the file, and should be in the service's list of acceptable content types.
Packit 4b6dd7
 *
Packit 4b6dd7
 * As well as the standard GIO errors, calls to the #GOutputStream API on a #GDataUploadStream can also return any relevant specific error from
Packit 4b6dd7
 * #GDataServiceError, or %GDATA_SERVICE_ERROR_PROTOCOL_ERROR in the general case.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If a #GCancellable is provided in @cancellable, the upload operation may be cancelled at any time from another thread using g_cancellable_cancel().
Packit 4b6dd7
 * In this case, any ongoing network activity will be stopped, and any pending or future calls to #GOutputStream API on the #GDataUploadStream will
Packit 4b6dd7
 * return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed to individual #GOutputStream operations will not cancel the
Packit 4b6dd7
 * upload operation proper if cancelled — they will merely cancel that API call. The only way to cancel the upload operation completely is using this
Packit 4b6dd7
 * @cancellable.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Note that network communication won't begin until the first call to g_output_stream_write() on the #GDataUploadStream.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: a new #GOutputStream, or %NULL; unref with g_object_unref()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.9.0
Packit 4b6dd7
 */
Packit 4b6dd7
GOutputStream *
Packit 4b6dd7
gdata_upload_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, GDataEntry *entry,
Packit 4b6dd7
                         const gchar *slug, const gchar *content_type, GCancellable *cancellable)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
Packit 4b6dd7
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
Packit 4b6dd7
	g_return_val_if_fail (method != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (upload_uri != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL);
Packit 4b6dd7
	g_return_val_if_fail (slug != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (content_type != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Create the upload stream */
Packit 4b6dd7
	return G_OUTPUT_STREAM (g_object_new (GDATA_TYPE_UPLOAD_STREAM,
Packit 4b6dd7
	                                      "method", method,
Packit 4b6dd7
	                                      "upload-uri", upload_uri,
Packit 4b6dd7
	                                      "service", service,
Packit 4b6dd7
	                                      "authorization-domain", domain,
Packit 4b6dd7
	                                      "entry", entry,
Packit 4b6dd7
	                                      "slug", slug,
Packit 4b6dd7
	                                      "content-type", content_type,
Packit 4b6dd7
	                                      "cancellable", cancellable,
Packit 4b6dd7
	                                      NULL));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_new_resumable:
Packit 4b6dd7
 * @service: a #GDataService
Packit 4b6dd7
 * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the upload, or %NULL
Packit 4b6dd7
 * @method: the HTTP method to use
Packit 4b6dd7
 * @upload_uri: the URI to upload
Packit 4b6dd7
 * @entry: (allow-none): the entry to upload as metadata, or %NULL
Packit 4b6dd7
 * @slug: the file's slug (filename)
Packit 4b6dd7
 * @content_type: the content type of the file being uploaded
Packit 4b6dd7
 * @content_length: the size (in bytes) of the file being uploaded
Packit 4b6dd7
 * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Creates a new resumable #GDataUploadStream, allowing a file to be uploaded from a GData service using standard #GOutputStream API. The upload will
Packit 4b6dd7
 * use GData's resumable upload API, so should be more reliable than a normal upload (especially if the file is large). See the
Packit 4b6dd7
 * <ulink type="http" url="http://code.google.com/apis/gdata/docs/resumable_upload.html">GData documentation on resumable uploads</ulink> for more
Packit 4b6dd7
 * information.
Packit 4b6dd7
 *
Packit 4b6dd7
 * The HTTP method to use should be specified in @method, and will typically be either %SOUP_METHOD_POST (for insertions) or %SOUP_METHOD_PUT
Packit 4b6dd7
 * (for updates), according to the server and the @upload_uri.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If @entry is specified, it will be attached to the upload as the entry to which the file being uploaded belongs. Otherwise, just the file
Packit 4b6dd7
 * written to the stream will be uploaded, and given a default entry as determined by the server.
Packit 4b6dd7
 *
Packit 4b6dd7
 * @slug, @content_type and @content_length must be specified before the upload begins, as they describe the file being streamed. @slug is the filename
Packit 4b6dd7
 * given to the file, which will typically be stored on the server and made available when downloading the file again. @content_type must be the
Packit 4b6dd7
 * correct content type for the file, and should be in the service's list of acceptable content types. @content_length must be the size of the file
Packit 4b6dd7
 * being uploaded (not including the XML for any associated #GDataEntry) in bytes. Zero is accepted if a metadata-only upload is being performed.
Packit 4b6dd7
 *
Packit 4b6dd7
 * As well as the standard GIO errors, calls to the #GOutputStream API on a #GDataUploadStream can also return any relevant specific error from
Packit 4b6dd7
 * #GDataServiceError, or %GDATA_SERVICE_ERROR_PROTOCOL_ERROR in the general case.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If a #GCancellable is provided in @cancellable, the upload operation may be cancelled at any time from another thread using g_cancellable_cancel().
Packit 4b6dd7
 * In this case, any ongoing network activity will be stopped, and any pending or future calls to #GOutputStream API on the #GDataUploadStream will
Packit 4b6dd7
 * return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed to individual #GOutputStream operations will not cancel the
Packit 4b6dd7
 * upload operation proper if cancelled — they will merely cancel that API call. The only way to cancel the upload operation completely is using this
Packit 4b6dd7
 * @cancellable.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Note that network communication won't begin until the first call to g_output_stream_write() on the #GDataUploadStream.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: a new #GOutputStream, or %NULL; unref with g_object_unref()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.13.0
Packit 4b6dd7
 */
Packit 4b6dd7
GOutputStream *
Packit 4b6dd7
gdata_upload_stream_new_resumable (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri,
Packit 4b6dd7
                                   GDataEntry *entry, const gchar *slug, const gchar *content_type, goffset content_length, GCancellable *cancellable)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
Packit 4b6dd7
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
Packit 4b6dd7
	g_return_val_if_fail (method != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (upload_uri != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL);
Packit 4b6dd7
	g_return_val_if_fail (slug != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (content_type != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (content_length >= 0, NULL);
Packit 4b6dd7
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Create the upload stream */
Packit 4b6dd7
	return G_OUTPUT_STREAM (g_object_new (GDATA_TYPE_UPLOAD_STREAM,
Packit 4b6dd7
	                                      "method", method,
Packit 4b6dd7
	                                      "upload-uri", upload_uri,
Packit 4b6dd7
	                                      "service", service,
Packit 4b6dd7
	                                      "authorization-domain", domain,
Packit 4b6dd7
	                                      "entry", entry,
Packit 4b6dd7
	                                      "slug", slug,
Packit 4b6dd7
	                                      "content-type", content_type,
Packit 4b6dd7
	                                      "content-length", content_length,
Packit 4b6dd7
	                                      "cancellable", cancellable,
Packit 4b6dd7
	                                      NULL));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_response:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 * @length: (allow-none) (out caller-allocates): return location for the length of the response, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Returns the server's response to the upload operation performed by the #GDataUploadStream. If the operation
Packit 4b6dd7
 * is still underway, or the server's response hasn't been received yet, %NULL is returned and @length is set to -1.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If there was an error during the upload operation (but it is complete), %NULL is returned, and @length is set to 0.
Packit 4b6dd7
 *
Packit 4b6dd7
 * While it is safe to call this function from any thread at any time during the network operation, the only way to guarantee that the response has
Packit 4b6dd7
 * been set before calling this function is to have closed the #GDataUploadStream by calling g_output_stream_close() on it, without cancelling
Packit 4b6dd7
 * the close operation. Once the stream has been closed, all network communication is guaranteed to have finished. Note that if a call to
Packit 4b6dd7
 * g_output_stream_close() is cancelled, g_output_stream_is_closed() will immediately start to return %TRUE, even if the #GDataUploadStream is still
Packit 4b6dd7
 * attempting to flush the network buffers asynchronously — consequently, gdata_upload_stream_get_response() may still return %NULL and a @length of
Packit 4b6dd7
 * -1. The only reliable way to determine if the stream has been fully closed in this situation is to check the results
Packit 4b6dd7
 * of gdata_upload_stream_get_response(), rather than g_output_stream_is_closed().
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the server's response to the upload, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
const gchar *
Packit 4b6dd7
gdata_upload_stream_get_response (GDataUploadStream *self, gssize *length)
Packit 4b6dd7
{
Packit 4b6dd7
	gssize _length;
Packit 4b6dd7
	const gchar *_response;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_lock (&(self->priv->response_mutex));
Packit 4b6dd7
Packit 4b6dd7
	if (self->priv->response_status == SOUP_STATUS_NONE) {
Packit 4b6dd7
		/* We can't touch the message until the network thread has finished using it, since it isn't threadsafe */
Packit 4b6dd7
		_length = -1;
Packit 4b6dd7
		_response = NULL;
Packit 4b6dd7
	} else if (SOUP_STATUS_IS_SUCCESSFUL (self->priv->response_status) == FALSE) {
Packit 4b6dd7
		/* The response has been received, and was unsuccessful */
Packit 4b6dd7
		_length = 0;
Packit 4b6dd7
		_response = NULL;
Packit 4b6dd7
	} else {
Packit 4b6dd7
		/* The response has been received, and was successful */
Packit 4b6dd7
		_length = self->priv->message->response_body->length;
Packit 4b6dd7
		_response = self->priv->message->response_body->data;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_mutex_unlock (&(self->priv->response_mutex));
Packit 4b6dd7
Packit 4b6dd7
	if (length != NULL)
Packit 4b6dd7
		*length = _length;
Packit 4b6dd7
	return _response;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_service:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the service used to authorize the upload, as passed to gdata_upload_stream_new().
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: (transfer none): the #GDataService used to authorize the upload
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
GDataService *
Packit 4b6dd7
gdata_upload_stream_get_service (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->service;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_authorization_domain:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the authorization domain used to authorize the upload, as passed to gdata_upload_stream_new(). It may be %NULL if authorization is not
Packit 4b6dd7
 * needed for the upload.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the upload, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.9.0
Packit 4b6dd7
 */
Packit 4b6dd7
GDataAuthorizationDomain *
Packit 4b6dd7
gdata_upload_stream_get_authorization_domain (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->authorization_domain;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_method:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the HTTP request method being used to upload the file, as passed to gdata_upload_stream_new().
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the HTTP request method in use
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.7.0
Packit 4b6dd7
 */
Packit 4b6dd7
const gchar *
Packit 4b6dd7
gdata_upload_stream_get_method (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->method;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_upload_uri:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the URI the file is being uploaded to, as passed to gdata_upload_stream_new().
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the URI which the file is being uploaded to
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
const gchar *
Packit 4b6dd7
gdata_upload_stream_get_upload_uri (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->upload_uri;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_entry:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the entry being used to upload metadata, if one was passed to gdata_upload_stream_new().
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: (transfer none): the entry used for metadata, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
GDataEntry *
Packit 4b6dd7
gdata_upload_stream_get_entry (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->entry;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_slug:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the slug (filename) of the file being uploaded.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the slug of the file being uploaded
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
const gchar *
Packit 4b6dd7
gdata_upload_stream_get_slug (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->slug;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_content_type:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the content type of the file being uploaded.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the content type of the file being uploaded
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.5.0
Packit 4b6dd7
 */
Packit 4b6dd7
const gchar *
Packit 4b6dd7
gdata_upload_stream_get_content_type (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	return self->priv->content_type;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_content_length:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the size (in bytes) of the file being uploaded. This will be -1 for a non-resumable upload, and zero or greater
Packit 4b6dd7
 * for a resumable upload.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the size of the file being uploaded
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.13.0
Packit 4b6dd7
 */
Packit 4b6dd7
goffset
Packit 4b6dd7
gdata_upload_stream_get_content_length (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), -1);
Packit 4b6dd7
	return self->priv->content_length;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_upload_stream_get_cancellable:
Packit 4b6dd7
 * @self: a #GDataUploadStream
Packit 4b6dd7
 *
Packit 4b6dd7
 * Gets the #GCancellable for the entire upload operation, #GDataUploadStream:cancellable.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: (transfer none): the #GCancellable for the entire upload operation
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.8.0
Packit 4b6dd7
 */
Packit 4b6dd7
GCancellable *
Packit 4b6dd7
gdata_upload_stream_get_cancellable (GDataUploadStream *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
Packit 4b6dd7
	g_assert (self->priv->cancellable != NULL);
Packit 4b6dd7
	return self->priv->cancellable;
Packit 4b6dd7
}