|
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-download-stream
|
|
Packit |
4b6dd7 |
* @short_description: GData download stream object
|
|
Packit |
4b6dd7 |
* @stability: Stable
|
|
Packit |
4b6dd7 |
* @include: gdata/gdata-download-stream.h
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* #GDataDownloadStream is a #GInputStream subclass to allow downloading of files from GData services with authorization from a #GDataService under
|
|
Packit |
4b6dd7 |
* the given #GDataAuthorizationDomain. If authorization is not required to perform the download, a #GDataAuthorizationDomain doesn't have to be
|
|
Packit |
4b6dd7 |
* specified.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Once a #GDataDownloadStream is instantiated with gdata_download_stream_new(), the standard #GInputStream API can be used on the stream to download
|
|
Packit |
4b6dd7 |
* the file. Network communication may not actually begin until the first call to g_input_stream_read(), so having a #GDataDownloadStream around is no
|
|
Packit |
4b6dd7 |
* guarantee that the file is being downloaded.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The content type and length of the file being downloaded are made available through #GDataDownloadStream:content-type and
|
|
Packit |
4b6dd7 |
* #GDataDownloadStream:content-length as soon as the appropriate data is received from the server. Connect to the
|
|
Packit |
4b6dd7 |
* #GObject::notify content-type and content-length details to be notified as
|
|
Packit |
4b6dd7 |
* soon as the data is available.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The entire download operation can be cancelled using the #GCancellable instance provided to gdata_download_stream_new(), or returned by
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_cancellable(). Cancelling this at any time will cause all future #GInputStream method calls to return
|
|
Packit |
4b6dd7 |
* %G_IO_ERROR_CANCELLED. If any #GInputStream 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_input_stream_read()) using the #GCancellable parameter of the method will not
|
|
Packit |
4b6dd7 |
* cancel the download as a whole — just that particular method call. In the case of g_input_stream_read(), this will cause it to successfully return
|
|
Packit |
4b6dd7 |
* any data that it has in memory at the moment (up to the requested number of bytes), or return a %G_IO_ERROR_CANCELLED if it was blocking on receiving
|
|
Packit |
4b6dd7 |
* data from the network. This is also the behaviour of g_input_stream_read() when the download operation as a whole is cancelled.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* In the case of g_input_stream_close(), the call will return immediately if network activity hasn't yet started. If it has, the network activity will
|
|
Packit |
4b6dd7 |
* be cancelled, regardless of whether the call to g_input_stream_close() is cancelled. Cancelling a pending call to g_input_stream_close() (either
|
|
Packit |
4b6dd7 |
* using the method's #GCancellable, or by cancelling the download stream as a whole) will cause it to stop waiting for the network activity to finish,
|
|
Packit |
4b6dd7 |
* and return %G_IO_ERROR_CANCELLED immediately. Network activity will continue to be shut down in the background.
|
|
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 to
|
|
Packit |
4b6dd7 |
* download from the given URI), it will be returned as a #GDataServiceError by the first call to g_input_stream_read().
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* <example>
|
|
Packit |
4b6dd7 |
* <title>Downloading to a File</title>
|
|
Packit |
4b6dd7 |
* <programlisting>
|
|
Packit |
4b6dd7 |
* GDataService *service;
|
|
Packit |
4b6dd7 |
* GDataAuthorizationDomain *domain;
|
|
Packit |
4b6dd7 |
* GCancellable *cancellable;
|
|
Packit |
4b6dd7 |
* GInputStream *download_stream;
|
|
Packit |
4b6dd7 |
* GOutputStream *output_stream;
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* /* Create the download 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 download operation */
|
|
Packit |
4b6dd7 |
* download_stream = gdata_download_stream_new (service, domain, download_uri, cancellable);
|
|
Packit |
4b6dd7 |
* output_stream = create_file_and_return_output_stream ();
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* /* Perform the download asynchronously */
|
|
Packit |
4b6dd7 |
* g_output_stream_splice_async (output_stream, download_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
|
Packit |
4b6dd7 |
* G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback) download_splice_cb, NULL);
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* g_object_unref (output_stream);
|
|
Packit |
4b6dd7 |
* g_object_unref (download_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 |
* download_splice_cb (GOutputStream *output_stream, GAsyncResult *result, gpointer user_data)
|
|
Packit |
4b6dd7 |
* {
|
|
Packit |
4b6dd7 |
* GError *error = NULL;
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* g_output_stream_splice_finish (output_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 downloading the file; potentially an I/O error (GIOError), or an error response from the server
|
|
Packit |
4b6dd7 |
* * (GDataServiceError). You might want to delete the newly created file because of the error. */
|
|
Packit |
4b6dd7 |
* g_error ("Error downloading file: %s", error->message);
|
|
Packit |
4b6dd7 |
* g_error_free (error);
|
|
Packit |
4b6dd7 |
* }
|
|
Packit |
4b6dd7 |
* }
|
|
Packit |
4b6dd7 |
* </programlisting>
|
|
Packit |
4b6dd7 |
* </example>
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
#include <config.h>
|
|
Packit |
4b6dd7 |
#include <glib.h>
|
|
Packit |
4b6dd7 |
#include <glib/gi18n-lib.h>
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
#include "gdata-download-stream.h"
|
|
Packit |
4b6dd7 |
#include "gdata-buffer.h"
|
|
Packit |
4b6dd7 |
#include "gdata-private.h"
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void gdata_download_stream_seekable_iface_init (GSeekableIface *seekable_iface);
|
|
Packit |
4b6dd7 |
static GObject *gdata_download_stream_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
|
|
Packit |
4b6dd7 |
static void gdata_download_stream_dispose (GObject *object);
|
|
Packit |
4b6dd7 |
static void gdata_download_stream_finalize (GObject *object);
|
|
Packit |
4b6dd7 |
static void gdata_download_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
|
|
Packit |
4b6dd7 |
static void gdata_download_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gssize gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error);
|
|
Packit |
4b6dd7 |
static gboolean gdata_download_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static goffset gdata_download_stream_tell (GSeekable *seekable);
|
|
Packit |
4b6dd7 |
static gboolean gdata_download_stream_can_seek (GSeekable *seekable);
|
|
Packit |
4b6dd7 |
static gboolean gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error);
|
|
Packit |
4b6dd7 |
static gboolean gdata_download_stream_can_truncate (GSeekable *seekable);
|
|
Packit |
4b6dd7 |
static gboolean gdata_download_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void create_network_thread (GDataDownloadStream *self, GError **error);
|
|
Packit |
4b6dd7 |
static void reset_network_thread (GDataDownloadStream *self);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/*
|
|
Packit |
4b6dd7 |
* The GDataDownloadStream can be in one of several states:
|
|
Packit |
4b6dd7 |
* 1. Pre-network activity. This is the state that the stream is created in. @network_thread and @cancellable are both %NULL, and @finished is %FALSE.
|
|
Packit |
4b6dd7 |
* The stream will remain in this state until gdata_download_stream_read() or gdata_download_stream_seek() are called for the first time.
|
|
Packit |
4b6dd7 |
* @content_type and @content_length are at their default values (NULL and -1, respectively).
|
|
Packit |
4b6dd7 |
* 2. Network activity. This state is entered when gdata_download_stream_read() is called for the first time.
|
|
Packit |
4b6dd7 |
* @network_thread, @buffer and @cancellable are created, while @finished remains %FALSE.
|
|
Packit |
4b6dd7 |
* As soon as the headers are downloaded, which is guaranteed to be before the first call to gdata_download_stream_read() returns, @content_type
|
|
Packit |
4b6dd7 |
* and @content_length are set from the headers. From this point onwards, they are immutable.
|
|
Packit |
4b6dd7 |
* 3. Reset network activity. This state is entered only if case 3 is encountered in a call to gdata_download_stream_seek(): a seek to an offset which
|
|
Packit |
4b6dd7 |
* has already been read out of the buffer. In this state, @buffer is freed and set to %NULL, @network_thread is cancelled (then set to %NULL),
|
|
Packit |
4b6dd7 |
* and @offset is set to the seeked-to offset. @finished remains at %FALSE.
|
|
Packit |
4b6dd7 |
* When the next call to gdata_download_stream_read() is made, the download stream will go back to state 2 as if this was the first call to
|
|
Packit |
4b6dd7 |
* gdata_download_stream_read().
|
|
Packit |
4b6dd7 |
* 4. Post-network activity. This state is reached once the download thread finishes downloading, due to having downloaded everything.
|
|
Packit |
4b6dd7 |
* @buffer is non-%NULL, @network_thread is non-%NULL, but meaningless; @cancellable is still a valid #GCancellable instance; and @finished is set
|
|
Packit |
4b6dd7 |
* to %TRUE. At the same time, @finished_cond is signalled.
|
|
Packit |
4b6dd7 |
* This state can be exited either by making a call to gdata_download_stream_seek(), in which case the stream will go back to state 3; or by
|
|
Packit |
4b6dd7 |
* calling gdata_download_stream_close(), in which case the stream will return errors for all operations, as the underlying %GInputStream will be
|
|
Packit |
4b6dd7 |
* marked as closed.
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
struct _GDataDownloadStreamPrivate {
|
|
Packit |
4b6dd7 |
gchar *download_uri;
|
|
Packit |
4b6dd7 |
GDataService *service;
|
|
Packit |
4b6dd7 |
GDataAuthorizationDomain *authorization_domain;
|
|
Packit |
4b6dd7 |
SoupSession *session;
|
|
Packit |
4b6dd7 |
SoupMessage *message;
|
|
Packit |
4b6dd7 |
GDataBuffer *buffer;
|
|
Packit |
4b6dd7 |
goffset offset; /* current position in the stream */
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
GThread *network_thread;
|
|
Packit |
4b6dd7 |
GCancellable *cancellable;
|
|
Packit |
4b6dd7 |
GCancellable *network_cancellable; /* see the comment in gdata_download_stream_constructor() about the relationship between these two */
|
|
Packit |
4b6dd7 |
gulong network_cancellable_id;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
gboolean finished;
|
|
Packit |
4b6dd7 |
GCond finished_cond;
|
|
Packit |
4b6dd7 |
GMutex finished_mutex; /* mutex for ->finished, protected by ->finished_cond */
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Cached data from the SoupMessage */
|
|
Packit |
4b6dd7 |
gchar *content_type;
|
|
Packit |
4b6dd7 |
gssize content_length;
|
|
Packit |
4b6dd7 |
GMutex content_mutex; /* mutex to protect them */
|
|
Packit |
4b6dd7 |
};
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
enum {
|
|
Packit |
4b6dd7 |
PROP_SERVICE = 1,
|
|
Packit |
4b6dd7 |
PROP_DOWNLOAD_URI,
|
|
Packit |
4b6dd7 |
PROP_CONTENT_TYPE,
|
|
Packit |
4b6dd7 |
PROP_CONTENT_LENGTH,
|
|
Packit |
4b6dd7 |
PROP_CANCELLABLE,
|
|
Packit |
4b6dd7 |
PROP_AUTHORIZATION_DOMAIN,
|
|
Packit |
4b6dd7 |
};
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
G_DEFINE_TYPE_WITH_CODE (GDataDownloadStream, gdata_download_stream, G_TYPE_INPUT_STREAM,
|
|
Packit |
4b6dd7 |
G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, gdata_download_stream_seekable_iface_init))
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
gdata_download_stream_class_init (GDataDownloadStreamClass *klass)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
Packit |
4b6dd7 |
GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_type_class_add_private (klass, sizeof (GDataDownloadStreamPrivate));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
gobject_class->constructor = gdata_download_stream_constructor;
|
|
Packit |
4b6dd7 |
gobject_class->dispose = gdata_download_stream_dispose;
|
|
Packit |
4b6dd7 |
gobject_class->finalize = gdata_download_stream_finalize;
|
|
Packit |
4b6dd7 |
gobject_class->get_property = gdata_download_stream_get_property;
|
|
Packit |
4b6dd7 |
gobject_class->set_property = gdata_download_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->read_fn = gdata_download_stream_read;
|
|
Packit |
4b6dd7 |
stream_class->close_fn = gdata_download_stream_close;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* GDataDownloadStream:service:
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The service which is used to authorize the download, and to which the download 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 download.",
|
|
Packit |
4b6dd7 |
GDATA_TYPE_SERVICE,
|
|
Packit |
4b6dd7 |
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* GDataDownloadStream:authorization-domain:
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The authorization domain for the download, 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 download.
|
|
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 download.",
|
|
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 |
* GDataDownloadStream:download-uri:
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The URI of the file to download. This must be HTTPS.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
g_object_class_install_property (gobject_class, PROP_DOWNLOAD_URI,
|
|
Packit |
4b6dd7 |
g_param_spec_string ("download-uri",
|
|
Packit |
4b6dd7 |
"Download URI", "The URI of the file to download.",
|
|
Packit |
4b6dd7 |
NULL,
|
|
Packit |
4b6dd7 |
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* GDataDownloadStream:content-type:
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The content type of the file being downloaded. This will initially be %NULL, and will be populated as soon as the appropriate header is
|
|
Packit |
4b6dd7 |
* received from the server. Its value will never change after this.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Note that change notifications for this property (#GObject::notify emissions) may be emitted in threads other than the one which created
|
|
Packit |
4b6dd7 |
* the #GDataDownloadStream. It is the client's responsibility to ensure that any notification signal handlers are either multi-thread safe
|
|
Packit |
4b6dd7 |
* or marshal the notification to the thread which owns the #GDataDownloadStream as appropriate.
|
|
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 downloaded.",
|
|
Packit |
4b6dd7 |
NULL,
|
|
Packit |
4b6dd7 |
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* GDataDownloadStream:content-length:
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* The length (in bytes) of the file being downloaded. This will initially be -1 , and will be populated as soon as
|
|
Packit |
4b6dd7 |
* the appropriate header is received from the server. Its value will never change after this.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Note that change notifications for this property (#GObject::notify emissions) may be emitted in threads other than the one which created
|
|
Packit |
4b6dd7 |
* the #GDataDownloadStream. It is the client's responsibility to ensure that any notification signal handlers are either multi-thread safe
|
|
Packit |
4b6dd7 |
* or marshal the notification to the thread which owns the #GDataDownloadStream as appropriate.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH,
|
|
Packit |
4b6dd7 |
g_param_spec_long ("content-length",
|
|
Packit |
4b6dd7 |
"Content length", "The length (in bytes) of the file being downloaded.",
|
|
Packit |
4b6dd7 |
-1, G_MAXSSIZE, -1,
|
|
Packit |
4b6dd7 |
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* GDataDownloadStream:cancellable:
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* An optional cancellable used to cancel the entire download operation. If a #GCancellable instance isn't provided for this property at
|
|
Packit |
4b6dd7 |
* construction time (i.e. to gdata_download_stream_new()), one will be created internally and can be retrieved using
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_cancellable() and used to cancel the download operation with g_cancellable_cancel() just as if it was passed to
|
|
Packit |
4b6dd7 |
* gdata_download_stream_new().
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* If the download operation is cancelled using this #GCancellable, any ongoing network activity will be stopped, and any pending or future
|
|
Packit |
4b6dd7 |
* calls to #GInputStream API on the #GDataDownloadStream will return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be
|
|
Packit |
4b6dd7 |
* passed to individual #GInputStream operations will not cancel the download operation proper if cancelled — they will merely cancel that API
|
|
Packit |
4b6dd7 |
* call. The only way to cancel the download operation completely is using #GDataDownloadStream: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 download 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_download_stream_seekable_iface_init (GSeekableIface *seekable_iface)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
seekable_iface->tell = gdata_download_stream_tell;
|
|
Packit |
4b6dd7 |
seekable_iface->can_seek = gdata_download_stream_can_seek;
|
|
Packit |
4b6dd7 |
seekable_iface->seek = gdata_download_stream_seek;
|
|
Packit |
4b6dd7 |
seekable_iface->can_truncate = gdata_download_stream_can_truncate;
|
|
Packit |
4b6dd7 |
seekable_iface->truncate_fn = gdata_download_stream_truncate;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
gdata_download_stream_init (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOWNLOAD_STREAM, GDataDownloadStreamPrivate);
|
|
Packit |
4b6dd7 |
self->priv->buffer = NULL; /* created when the network thread is started and destroyed when the stream is closed */
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
self->priv->finished = FALSE;
|
|
Packit |
4b6dd7 |
g_cond_init (&(self->priv->finished_cond));
|
|
Packit |
4b6dd7 |
g_mutex_init (&(self->priv->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
self->priv->content_type = NULL;
|
|
Packit |
4b6dd7 |
self->priv->content_length = -1;
|
|
Packit |
4b6dd7 |
g_mutex_init (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
cancellable_cancel_cb (GCancellable *cancellable, GCancellable *network_cancellable)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_cancellable_cancel (network_cancellable);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static GObject *
|
|
Packit |
4b6dd7 |
gdata_download_stream_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv;
|
|
Packit |
4b6dd7 |
GDataServiceClass *klass;
|
|
Packit |
4b6dd7 |
GObject *object;
|
|
Packit |
4b6dd7 |
SoupURI *_uri;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Chain up to the parent class */
|
|
Packit |
4b6dd7 |
object = G_OBJECT_CLASS (gdata_download_stream_parent_class)->constructor (type, n_construct_params, construct_params);
|
|
Packit |
4b6dd7 |
priv = GDATA_DOWNLOAD_STREAM (object)->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Create a #GCancellable for the network. Cancellation of ->cancellable is chained to this one, so that if ->cancellable is cancelled,
|
|
Packit |
4b6dd7 |
* ->network_cancellable is also cancelled. However, if ->network_cancellable is cancelled, the cancellation doesn't propagate back upwards
|
|
Packit |
4b6dd7 |
* to ->cancellable. This allows closing the stream part-way through a download to be implemented by cancelling ->network_cancellable, without
|
|
Packit |
4b6dd7 |
* causing ->cancellable to be unnecessarily cancelled (which would be a nasty side-effect of closing the stream early otherwise). */
|
|
Packit |
4b6dd7 |
priv->network_cancellable = g_cancellable_new ();
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Create a #GCancellable for the entire download operation if one wasn't specified for #GDataDownloadStream:cancellable during construction */
|
|
Packit |
4b6dd7 |
if (priv->cancellable == NULL)
|
|
Packit |
4b6dd7 |
priv->cancellable = g_cancellable_new ();
|
|
Packit |
4b6dd7 |
priv->network_cancellable_id = g_cancellable_connect (priv->cancellable, (GCallback) cancellable_cancel_cb, priv->network_cancellable, NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Build the message. The URI must be HTTPS. */
|
|
Packit |
4b6dd7 |
_uri = soup_uri_new (priv->download_uri);
|
|
Packit |
4b6dd7 |
soup_uri_set_port (_uri, _gdata_service_get_https_port ());
|
|
Packit |
4b6dd7 |
g_assert_cmpstr (soup_uri_get_scheme (_uri), ==, SOUP_URI_SCHEME_HTTPS);
|
|
Packit |
4b6dd7 |
priv->message = soup_message_new_from_uri (SOUP_METHOD_GET, _uri);
|
|
Packit |
4b6dd7 |
soup_uri_free (_uri);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Make sure the headers are set */
|
|
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, priv->message);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* We don't want to accumulate chunks */
|
|
Packit |
4b6dd7 |
soup_message_body_set_accumulate (priv->message->request_body, FALSE);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Downloading doesn't actually start until the first call to read() */
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
return object;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
gdata_download_stream_dispose (GObject *object)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (object)->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Block on closing the stream */
|
|
Packit |
4b6dd7 |
g_input_stream_close (G_INPUT_STREAM (object), NULL, NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (priv->cancellable != NULL) {
|
|
Packit |
4b6dd7 |
if (priv->network_cancellable_id != 0) {
|
|
Packit |
4b6dd7 |
g_cancellable_disconnect (priv->cancellable, priv->network_cancellable_id);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_object_unref (priv->cancellable);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
priv->network_cancellable_id = 0;
|
|
Packit |
4b6dd7 |
priv->cancellable = NULL;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (priv->network_cancellable != NULL)
|
|
Packit |
4b6dd7 |
g_object_unref (priv->network_cancellable);
|
|
Packit |
4b6dd7 |
priv->network_cancellable = 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->service != NULL)
|
|
Packit |
4b6dd7 |
g_object_unref (priv->service);
|
|
Packit |
4b6dd7 |
priv->service = 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 |
/* Chain up to the parent class */
|
|
Packit |
4b6dd7 |
G_OBJECT_CLASS (gdata_download_stream_parent_class)->dispose (object);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
gdata_download_stream_finalize (GObject *object)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (object)->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
reset_network_thread (GDATA_DOWNLOAD_STREAM (object));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_cond_clear (&(priv->finished_cond));
|
|
Packit |
4b6dd7 |
g_mutex_clear (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_clear (&(priv->content_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_free (priv->download_uri);
|
|
Packit |
4b6dd7 |
g_free (priv->content_type);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Chain up to the parent class */
|
|
Packit |
4b6dd7 |
G_OBJECT_CLASS (gdata_download_stream_parent_class)->finalize (object);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_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_DOWNLOAD_URI:
|
|
Packit |
4b6dd7 |
g_value_set_string (value, priv->download_uri);
|
|
Packit |
4b6dd7 |
break;
|
|
Packit |
4b6dd7 |
case PROP_CONTENT_TYPE:
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(priv->content_mutex));
|
|
Packit |
4b6dd7 |
g_value_set_string (value, priv->content_type);
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->content_mutex));
|
|
Packit |
4b6dd7 |
break;
|
|
Packit |
4b6dd7 |
case PROP_CONTENT_LENGTH:
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(priv->content_mutex));
|
|
Packit |
4b6dd7 |
g_value_set_long (value, priv->content_length);
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->content_mutex));
|
|
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_download_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_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_DOWNLOAD_URI:
|
|
Packit |
4b6dd7 |
priv->download_uri = g_value_dup_string (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 |
static void
|
|
Packit |
4b6dd7 |
read_cancelled_cb (GCancellable *cancellable, GCancellable *child_cancellable)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_cancellable_cancel (child_cancellable);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gssize
|
|
Packit |
4b6dd7 |
gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv;
|
|
Packit |
4b6dd7 |
gssize length_read = -1;
|
|
Packit |
4b6dd7 |
gboolean reached_eof = FALSE;
|
|
Packit |
4b6dd7 |
gulong cancelled_signal = 0, global_cancelled_signal = 0;
|
|
Packit |
4b6dd7 |
GCancellable *child_cancellable;
|
|
Packit |
4b6dd7 |
GError *child_error = NULL;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Listen for cancellation from either @cancellable or @priv->cancellable. We have to multiplex cancellation signals from the two sources
|
|
Packit |
4b6dd7 |
* into a single #GCancellabel, @child_cancellable. */
|
|
Packit |
4b6dd7 |
child_cancellable = g_cancellable_new ();
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) read_cancelled_cb, child_cancellable, NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (cancellable != NULL)
|
|
Packit |
4b6dd7 |
cancelled_signal = g_cancellable_connect (cancellable, (GCallback) read_cancelled_cb, child_cancellable, NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* We're lazy about starting the network operation so we don't end up with a massive buffer */
|
|
Packit |
4b6dd7 |
if (priv->network_thread == NULL) {
|
|
Packit |
4b6dd7 |
/* Handle early cancellation so that we don't create the network thread unnecessarily */
|
|
Packit |
4b6dd7 |
if (g_cancellable_set_error_if_cancelled (child_cancellable, &child_error) == TRUE) {
|
|
Packit |
4b6dd7 |
length_read = -1;
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Create the network thread */
|
|
Packit |
4b6dd7 |
create_network_thread (GDATA_DOWNLOAD_STREAM (stream), &child_error);
|
|
Packit |
4b6dd7 |
if (priv->network_thread == NULL) {
|
|
Packit |
4b6dd7 |
length_read = -1;
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Read the data off the buffer. If the operation is cancelled, it'll probably still return a positive number of bytes read — if it does, we
|
|
Packit |
4b6dd7 |
* can return without error. Iff it returns a non-positive number of bytes should we return an error. */
|
|
Packit |
4b6dd7 |
g_assert (priv->buffer != NULL);
|
|
Packit |
4b6dd7 |
length_read = (gssize) gdata_buffer_pop_data (priv->buffer, buffer, count, &reached_eof, child_cancellable);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (length_read < 1 && g_cancellable_set_error_if_cancelled (child_cancellable, &child_error) == TRUE) {
|
|
Packit |
4b6dd7 |
/* Handle cancellation */
|
|
Packit |
4b6dd7 |
length_read = -1;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
goto done;
|
|
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 |
/* Set an appropriate error */
|
|
Packit |
4b6dd7 |
g_assert (klass->parse_error_response != NULL);
|
|
Packit |
4b6dd7 |
klass->parse_error_response (priv->service, GDATA_OPERATION_DOWNLOAD, priv->message->status_code, priv->message->reason_phrase,
|
|
Packit |
4b6dd7 |
NULL, 0, &child_error);
|
|
Packit |
4b6dd7 |
length_read = -1;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
done:
|
|
Packit |
4b6dd7 |
/* Disconnect from the cancelled signals. */
|
|
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_object_unref (child_cancellable);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_assert ((reached_eof == FALSE && length_read > 0 && length_read <= (gssize) count && child_error == NULL) ||
|
|
Packit |
4b6dd7 |
(reached_eof == TRUE && length_read >= 0 && length_read <= (gssize) count && child_error == NULL) ||
|
|
Packit |
4b6dd7 |
(length_read == -1 && child_error != NULL));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (child_error != NULL)
|
|
Packit |
4b6dd7 |
g_propagate_error (error, child_error);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Update our internal offset */
|
|
Packit |
4b6dd7 |
if (length_read > 0) {
|
|
Packit |
4b6dd7 |
priv->offset += length_read;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
return length_read;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
typedef struct {
|
|
Packit |
4b6dd7 |
GDataDownloadStream *download_stream;
|
|
Packit |
4b6dd7 |
gboolean *cancelled;
|
|
Packit |
4b6dd7 |
} CancelledData;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
close_cancelled_cb (GCancellable *cancellable, CancelledData *data)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = data->download_stream->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
*(data->cancelled) = TRUE;
|
|
Packit |
4b6dd7 |
g_cond_signal (&(priv->finished_cond));
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Even though calling g_input_stream_close() multiple times on this stream is guaranteed to call gdata_download_stream_close() at most once, other
|
|
Packit |
4b6dd7 |
* GIO methods (notably g_output_stream_splice()) can call gdata_download_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_download_stream_read() 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_download_stream_close(),
|
|
Packit |
4b6dd7 |
* gdata_download_stream_close() should return immediately with %G_IO_ERROR_CANCELLED. If they're cancelled during the call,
|
|
Packit |
4b6dd7 |
* gdata_download_stream_close() should stop waiting for any outstanding network activity to finish and return %G_IO_ERROR_CANCELLED (though the
|
|
Packit |
4b6dd7 |
* operation to finish off network activity and close the stream will still continue).
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* If the call to gdata_download_stream_close() is not cancelled by any #GCancellable, it will cancel the ongoing network activity, and wait until
|
|
Packit |
4b6dd7 |
* the operation has been cleaned up before returning success. */
|
|
Packit |
4b6dd7 |
static gboolean
|
|
Packit |
4b6dd7 |
gdata_download_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv;
|
|
Packit |
4b6dd7 |
gulong cancelled_signal = 0, global_cancelled_signal = 0;
|
|
Packit |
4b6dd7 |
gboolean cancelled = FALSE; /* must only be touched with ->finished_mutex held */
|
|
Packit |
4b6dd7 |
gboolean success = TRUE;
|
|
Packit |
4b6dd7 |
CancelledData data;
|
|
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 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Allow cancellation */
|
|
Packit |
4b6dd7 |
data.download_stream = GDATA_DOWNLOAD_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->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* If the operation has started but hasn't already finished, cancel the network thread and wait for it to finish before returning */
|
|
Packit |
4b6dd7 |
if (priv->finished == FALSE) {
|
|
Packit |
4b6dd7 |
g_cancellable_cancel (priv->network_cancellable);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Allow the close() call to be cancelled by cancelling either @cancellable or ->cancellable. Note that this won't prevent the stream
|
|
Packit |
4b6dd7 |
* from continuing to be closed in the background — it'll just stop waiting on the operation to finish being cancelled. */
|
|
Packit |
4b6dd7 |
if (cancelled == FALSE) {
|
|
Packit |
4b6dd7 |
g_cond_wait (&(priv->finished_cond), &(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Error handling */
|
|
Packit |
4b6dd7 |
if (priv->finished == FALSE && cancelled == TRUE) {
|
|
Packit |
4b6dd7 |
/* Cancelled? If ->finished is TRUE, the network activity finished before the gdata_download_stream_close() operation was cancelled,
|
|
Packit |
4b6dd7 |
* 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 |
success = FALSE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Disconnect from the signal handlers. Note that we have to do this without @finished_mutex held, as g_cancellable_disconnect() blocks
|
|
Packit |
4b6dd7 |
* until any outstanding cancellation callbacks return, and they will block on @finished_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 |
done:
|
|
Packit |
4b6dd7 |
/* If we were successful, tidy up various bits of state */
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (success == TRUE && priv->finished == TRUE) {
|
|
Packit |
4b6dd7 |
reset_network_thread (GDATA_DOWNLOAD_STREAM (stream));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->finished_mutex));
|
|
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 |
static goffset
|
|
Packit |
4b6dd7 |
gdata_download_stream_tell (GSeekable *seekable)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
return GDATA_DOWNLOAD_STREAM (seekable)->priv->offset;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gboolean
|
|
Packit |
4b6dd7 |
gdata_download_stream_can_seek (GSeekable *seekable)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
return TRUE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gboolean
|
|
Packit |
4b6dd7 |
gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (seekable)->priv;
|
|
Packit |
4b6dd7 |
GError *child_error = NULL;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (type == G_SEEK_END && priv->content_length == -1) {
|
|
Packit |
4b6dd7 |
/* If we don't have the Content-Length, we can't calculate the offset from the start of the stream properly, so just give up.
|
|
Packit |
4b6dd7 |
* We could technically use a HEAD request to get the Content-Length, but this hasn't been tried yet (FIXME). */
|
|
Packit |
4b6dd7 |
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "G_SEEK_END not currently supported");
|
|
Packit |
4b6dd7 |
return FALSE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (g_input_stream_set_pending (G_INPUT_STREAM (seekable), error) == FALSE) {
|
|
Packit |
4b6dd7 |
return FALSE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Ensure that offset is relative to the start of the stream. */
|
|
Packit |
4b6dd7 |
switch (type) {
|
|
Packit |
4b6dd7 |
case G_SEEK_CUR:
|
|
Packit |
4b6dd7 |
offset += priv->offset;
|
|
Packit |
4b6dd7 |
break;
|
|
Packit |
4b6dd7 |
case G_SEEK_SET:
|
|
Packit |
4b6dd7 |
/* Nothing needs doing */
|
|
Packit |
4b6dd7 |
break;
|
|
Packit |
4b6dd7 |
case G_SEEK_END:
|
|
Packit |
4b6dd7 |
offset += priv->content_length;
|
|
Packit |
4b6dd7 |
break;
|
|
Packit |
4b6dd7 |
default:
|
|
Packit |
4b6dd7 |
g_assert_not_reached ();
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* There are three cases to consider:
|
|
Packit |
4b6dd7 |
* 1. The network thread hasn't been started. In this case, we need to set the offset and do nothing. When the network thread is started
|
|
Packit |
4b6dd7 |
* (in the next read() call), a Range header will be set on it which will give the correct seek.
|
|
Packit |
4b6dd7 |
* 2. The network thread has been started and the seek is to a position greater than our current position (i.e. one which already does, or
|
|
Packit |
4b6dd7 |
* will soon, exist in the buffer). In this case, we need to pop the intervening bytes off the buffer (which may block) and update the
|
|
Packit |
4b6dd7 |
* offset.
|
|
Packit |
4b6dd7 |
* 3. The network thread has been started and the seek is to a position which has already been popped off the buffer. In this case, we need
|
|
Packit |
4b6dd7 |
* to set the offset and cancel the network thread. When the network thread is restarted (in the next read() call), a Range header will
|
|
Packit |
4b6dd7 |
* be set on it which will give the correct seek.
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (priv->network_thread == NULL) {
|
|
Packit |
4b6dd7 |
/* Case 1. Set the offset and we're done. */
|
|
Packit |
4b6dd7 |
priv->offset = offset;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Cases 2 and 3. The network thread has already been started. */
|
|
Packit |
4b6dd7 |
if (offset >= priv->offset) {
|
|
Packit |
4b6dd7 |
goffset num_intervening_bytes;
|
|
Packit |
4b6dd7 |
gssize length_read;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Case 2. Pop off the intervening bytes and update the offset. If we can't pop enough bytes off, we throw an error. */
|
|
Packit |
4b6dd7 |
num_intervening_bytes = offset - priv->offset;
|
|
Packit |
4b6dd7 |
g_assert (priv->buffer != NULL);
|
|
Packit |
4b6dd7 |
length_read = (gssize) gdata_buffer_pop_data (priv->buffer, NULL, num_intervening_bytes, NULL, cancellable);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (length_read != num_intervening_bytes) {
|
|
Packit |
4b6dd7 |
if (g_cancellable_set_error_if_cancelled (cancellable, &child_error) == FALSE) {
|
|
Packit |
4b6dd7 |
/* Tried to seek too far */
|
|
Packit |
4b6dd7 |
g_set_error_literal (&child_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid seek request"));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Update the offset */
|
|
Packit |
4b6dd7 |
priv->offset = offset;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
} else {
|
|
Packit |
4b6dd7 |
/* Case 3. Cancel the current network thread. Note that we don't allow cancellation of this call, as we depend on it waiting for
|
|
Packit |
4b6dd7 |
* the network thread to join. */
|
|
Packit |
4b6dd7 |
if (gdata_download_stream_close (G_INPUT_STREAM (seekable), NULL, &child_error) == FALSE) {
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Update the offset */
|
|
Packit |
4b6dd7 |
priv->offset = offset;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Mark the thread as unfinished */
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
priv->finished = FALSE;
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
goto done;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
done:
|
|
Packit |
4b6dd7 |
g_input_stream_clear_pending (G_INPUT_STREAM (seekable));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (child_error != NULL) {
|
|
Packit |
4b6dd7 |
g_propagate_error (error, child_error);
|
|
Packit |
4b6dd7 |
return FALSE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
return TRUE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gboolean
|
|
Packit |
4b6dd7 |
gdata_download_stream_can_truncate (GSeekable *seekable)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
return FALSE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gboolean
|
|
Packit |
4b6dd7 |
gdata_download_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Truncate not allowed on input stream");
|
|
Packit |
4b6dd7 |
return FALSE;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
got_headers_cb (SoupMessage *message, GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
goffset end;
|
|
Packit |
4b6dd7 |
goffset start;
|
|
Packit |
4b6dd7 |
goffset total_length;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Don't get the client's hopes up by setting the Content-Type or -Length if the response
|
|
Packit |
4b6dd7 |
* is actually unsuccessful. */
|
|
Packit |
4b6dd7 |
if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code) == FALSE)
|
|
Packit |
4b6dd7 |
return;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
self->priv->content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
|
|
Packit |
4b6dd7 |
self->priv->content_length = soup_message_headers_get_content_length (message->response_headers);
|
|
Packit |
4b6dd7 |
if (soup_message_headers_get_content_range (message->response_headers, &start, &end, &total_length)) {
|
|
Packit |
4b6dd7 |
self->priv->content_length = (gssize) total_length;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Emit the notifications for the Content-Length and -Type properties */
|
|
Packit |
4b6dd7 |
g_object_freeze_notify (G_OBJECT (self));
|
|
Packit |
4b6dd7 |
g_object_notify (G_OBJECT (self), "content-length");
|
|
Packit |
4b6dd7 |
g_object_notify (G_OBJECT (self), "content-type");
|
|
Packit |
4b6dd7 |
g_object_thaw_notify (G_OBJECT (self));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
got_chunk_cb (SoupMessage *message, SoupBuffer *buffer, GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
/* Ignore the chunk if the response is unsuccessful or it has zero length */
|
|
Packit |
4b6dd7 |
if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code) == FALSE || buffer->length == 0)
|
|
Packit |
4b6dd7 |
return;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Push the data onto the buffer immediately */
|
|
Packit |
4b6dd7 |
g_assert (self->priv->buffer != NULL);
|
|
Packit |
4b6dd7 |
gdata_buffer_push_data (self->priv->buffer, (const guint8*) buffer->data, buffer->length);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static gpointer
|
|
Packit |
4b6dd7 |
download_thread (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = self->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_object_ref (self);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_assert (priv->network_cancellable != NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Connect to the got-headers signal so we can notify clients of the values of content-type and content-length */
|
|
Packit |
4b6dd7 |
g_signal_connect (priv->message, "got-headers", (GCallback) got_headers_cb, self);
|
|
Packit |
4b6dd7 |
g_signal_connect (priv->message, "got-chunk", (GCallback) got_chunk_cb, self);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Set a Range header if our starting offset is non-zero */
|
|
Packit |
4b6dd7 |
if (priv->offset > 0) {
|
|
Packit |
4b6dd7 |
soup_message_headers_set_range (priv->message->request_headers, priv->offset, -1);
|
|
Packit |
4b6dd7 |
} else {
|
|
Packit |
4b6dd7 |
soup_message_headers_remove (priv->message->request_headers, "Range");
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
_gdata_service_actually_send_message (priv->session, priv->message, priv->network_cancellable, NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Mark the buffer as having reached EOF */
|
|
Packit |
4b6dd7 |
g_assert (priv->buffer != NULL);
|
|
Packit |
4b6dd7 |
gdata_buffer_push_data (priv->buffer, NULL, 0);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* Mark the download as finished */
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
priv->finished = TRUE;
|
|
Packit |
4b6dd7 |
g_cond_signal (&(priv->finished_cond));
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(priv->finished_mutex));
|
|
Packit |
4b6dd7 |
|
|
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 (GDataDownloadStream *self, GError **error)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = self->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_assert (priv->buffer == NULL);
|
|
Packit |
4b6dd7 |
priv->buffer = gdata_buffer_new ();
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_assert (priv->network_thread == NULL);
|
|
Packit |
4b6dd7 |
priv->network_thread = g_thread_try_new ("download-thread", (GThreadFunc) download_thread, self, error);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
static void
|
|
Packit |
4b6dd7 |
reset_network_thread (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
GDataDownloadStreamPrivate *priv = self->priv;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
priv->network_thread = NULL;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (priv->buffer != NULL) {
|
|
Packit |
4b6dd7 |
gdata_buffer_free (priv->buffer);
|
|
Packit |
4b6dd7 |
priv->buffer = NULL;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (priv->message != NULL) {
|
|
Packit |
4b6dd7 |
soup_session_cancel_message (priv->session, priv->message, SOUP_STATUS_CANCELLED);
|
|
Packit |
4b6dd7 |
g_signal_handlers_disconnect_by_func (priv->message, got_headers_cb, self);
|
|
Packit |
4b6dd7 |
g_signal_handlers_disconnect_by_func (priv->message, got_chunk_cb, self);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
priv->offset = 0;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
if (priv->network_cancellable != NULL) {
|
|
Packit |
4b6dd7 |
g_cancellable_reset (priv->network_cancellable);
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_new:
|
|
Packit |
4b6dd7 |
* @service: a #GDataService
|
|
Packit |
4b6dd7 |
* @domain: (allow-none): the #GDataAuthorizationDomain to authorize the download, or %NULL
|
|
Packit |
4b6dd7 |
* @download_uri: the URI to download; this must be HTTPS
|
|
Packit |
4b6dd7 |
* @cancellable: (allow-none): a #GCancellable for the entire download stream, or %NULL
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Creates a new #GDataDownloadStream, allowing a file to be downloaded from a GData service using standard #GInputStream API.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* As well as the standard GIO errors, calls to the #GInputStream API on a #GDataDownloadStream 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 download 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 #GInputStream API on the #GDataDownloadStream will
|
|
Packit |
4b6dd7 |
* return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed to individual #GInputStream operations will not cancel the
|
|
Packit |
4b6dd7 |
* download operation proper if cancelled — they will merely cancel that API call. The only way to cancel the download operation completely is using
|
|
Packit |
4b6dd7 |
* this @cancellable.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: a new #GInputStream, or %NULL; unref with g_object_unref()
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.9.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
GInputStream *
|
|
Packit |
4b6dd7 |
gdata_download_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *download_uri, 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 (download_uri != NULL, NULL);
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
return G_INPUT_STREAM (g_object_new (GDATA_TYPE_DOWNLOAD_STREAM,
|
|
Packit |
4b6dd7 |
"download-uri", download_uri,
|
|
Packit |
4b6dd7 |
"service", service,
|
|
Packit |
4b6dd7 |
"authorization-domain", domain,
|
|
Packit |
4b6dd7 |
"cancellable", cancellable,
|
|
Packit |
4b6dd7 |
NULL));
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_service:
|
|
Packit |
4b6dd7 |
* @self: a #GDataDownloadStream
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Gets the service used to authorize the download, as passed to gdata_download_stream_new().
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: (transfer none): the #GDataService used to authorize the download
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
GDataService *
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_service (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
|
|
Packit |
4b6dd7 |
return self->priv->service;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_authorization_domain:
|
|
Packit |
4b6dd7 |
* @self: a #GDataDownloadStream
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Gets the authorization domain used to authorize the download, as passed to gdata_download_stream_new(). It may be %NULL if authorization is not
|
|
Packit |
4b6dd7 |
* needed for the download.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the download, or %NULL
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.9.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
GDataAuthorizationDomain *
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_authorization_domain (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
|
|
Packit |
4b6dd7 |
return self->priv->authorization_domain;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_download_uri:
|
|
Packit |
4b6dd7 |
* @self: a #GDataDownloadStream
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Gets the URI of the file being downloaded, as passed to gdata_download_stream_new().
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: the URI of the file being downloaded
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
const gchar *
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_download_uri (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
|
|
Packit |
4b6dd7 |
return self->priv->download_uri;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_content_type:
|
|
Packit |
4b6dd7 |
* @self: a #GDataDownloadStream
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Gets the content type of the file being downloaded. If the <literal>Content-Type</literal> header has not yet
|
|
Packit |
4b6dd7 |
* been received, %NULL will be returned.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: the content type of the file being downloaded, or %NULL
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
const gchar *
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_content_type (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
const gchar *content_type;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
content_type = self->priv->content_type;
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/* It's safe to return this, even though we're not taking a copy of it, as it's immutable once set. */
|
|
Packit |
4b6dd7 |
return content_type;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_content_length:
|
|
Packit |
4b6dd7 |
* @self: a #GDataDownloadStream
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Gets the length (in bytes) of the file being downloaded. If the <literal>Content-Length</literal> header has not yet
|
|
Packit |
4b6dd7 |
* been received from the server, -1 will be returned.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: the content length of the file being downloaded, or -1
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.5.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
gssize
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_content_length (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
gssize content_length;
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), -1);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_mutex_lock (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
content_length = self->priv->content_length;
|
|
Packit |
4b6dd7 |
g_mutex_unlock (&(self->priv->content_mutex));
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
g_assert (content_length >= -1);
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
return content_length;
|
|
Packit |
4b6dd7 |
}
|
|
Packit |
4b6dd7 |
|
|
Packit |
4b6dd7 |
/**
|
|
Packit |
4b6dd7 |
* gdata_download_stream_get_cancellable:
|
|
Packit |
4b6dd7 |
* @self: a #GDataDownloadStream
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Gets the #GCancellable for the entire download operation, #GDataDownloadStream:cancellable.
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Return value: (transfer none): the #GCancellable for the entire download operation
|
|
Packit |
4b6dd7 |
*
|
|
Packit |
4b6dd7 |
* Since: 0.8.0
|
|
Packit |
4b6dd7 |
*/
|
|
Packit |
4b6dd7 |
GCancellable *
|
|
Packit |
4b6dd7 |
gdata_download_stream_get_cancellable (GDataDownloadStream *self)
|
|
Packit |
4b6dd7 |
{
|
|
Packit |
4b6dd7 |
g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
|
|
Packit |
4b6dd7 |
g_assert (self->priv->cancellable != NULL);
|
|
Packit |
4b6dd7 |
return self->priv->cancellable;
|
|
Packit |
4b6dd7 |
}
|