[libgdata] [core] Added GDataDownloadStream to allow easy downloading of large files
- From: Philip Withnall <pwithnall src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [libgdata] [core] Added GDataDownloadStream to allow easy downloading of large files
- Date: Mon, 3 Aug 2009 18:06:36 +0000 (UTC)
commit d35e5400ca860a5ea12c734bd6b4a9f8437cd091
Author: Philip Withnall <philip tecnocode co uk>
Date: Mon Aug 3 19:05:27 2009 +0100
[core] Added GDataDownloadStream to allow easy downloading of large files
GDataDownloadStream is a subclass of GInputStream which allows large files to
be downloaded in a more controlled manner than by just specifying the
file to save them to.
docs/reference/Makefile.am | 8 +-
docs/reference/gdata-docs.xml | 1 +
docs/reference/gdata-sections.txt | 25 +
gdata/Makefile.am | 8 +-
gdata/gdata-buffer.c | 289 ++++++++++
gdata/gdata-buffer.h | 58 ++
gdata/gdata-download-stream.c | 562 ++++++++++++++++++++
gdata/gdata-download-stream.h | 75 +++
gdata/gdata-private.h | 1 +
gdata/gdata-service.c | 20 +-
gdata/gdata-service.h | 4 +-
gdata/gdata.h | 1 +
gdata/gdata.symbols | 9 +
gdata/services/documents/gdata-documents-entry.c | 104 ++---
.../documents/gdata-documents-presentation.c | 57 ++-
.../documents/gdata-documents-presentation.h | 1 +
gdata/services/documents/gdata-documents-service.c | 4 +-
.../documents/gdata-documents-spreadsheet.c | 81 ++-
.../documents/gdata-documents-spreadsheet.h | 2 +
gdata/services/documents/gdata-documents-text.c | 59 ++-
gdata/services/documents/gdata-documents-text.h | 1 +
21 files changed, 1222 insertions(+), 148 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index cb19f14..da1e470 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -57,17 +57,13 @@ IGNORE_HFILES = \
gdata-marshal.h \
gdata-enums.h \
gdata-media-enums.h \
- gdata-media-group.c \
gdata-media-group.h \
- gdata-youtube-group.c \
gdata-youtube-group.h \
- gdata-youtube-control.c \
gdata-youtube-control.h \
gdata-picasaweb-enums.h \
- gdata-exif-tags.c \
gdata-exif-tags.h \
- gdata-georss-where.c \
- gdata-georss-where.h
+ gdata-georss-where.h \
+ gdata-buffer.h
# Images to copy into HTML directory.
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 6e5be1a..88cec0d 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -30,6 +30,7 @@
<xi:include href="xml/gdata-entry.xml"/>
<xi:include href="xml/gdata-types.xml"/>
<xi:include href="xml/gdata-parsable.xml"/>
+ <xi:include href="xml/gdata-download-stream.xml"/>
</chapter>
<chapter>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index eb28ccc..de50548 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -1356,6 +1356,7 @@ GDataDocumentsPresentationClass
GDataDocumentsPresentationFormat
gdata_documents_presentation_new
gdata_documents_presentation_download_document
+gdata_documents_presentation_get_download_uri
<SUBSECTION Standard>
gdata_documents_presentation_get_type
GDATA_DOCUMENTS_PRESENTATION
@@ -1410,6 +1411,7 @@ GDataDocumentsSpreadsheetClass
GDataDocumentsSpreadsheetFormat
gdata_documents_spreadsheet_new
gdata_documents_spreadsheet_download_document
+gdata_documents_spreadsheet_get_download_uri
<SUBSECTION Standard>
gdata_documents_spreadsheet_get_type
GDATA_DOCUMENTS_SPREADSHEET
@@ -1432,6 +1434,7 @@ GDataDocumentsTextClass
GDataDocumentsTextFormat
gdata_documents_text_new
gdata_documents_text_download_document
+gdata_documents_text_get_download_uri
<SUBSECTION Standard>
gdata_documents_text_get_type
GDATA_DOCUMENTS_TEXT
@@ -1475,3 +1478,25 @@ gdata_documents_service_error_quark
<SUBSECTION Private>
GDataDocumentsServicePrivate
</SECTION>
+
+<SECTION>
+<FILE>gdata-download-stream</FILE>
+<TITLE>GDataDownloadStream</TITLE>
+GDataDownloadStream
+GDataDownloadStreamClass
+gdata_download_stream_new
+gdata_download_stream_get_service
+gdata_download_stream_get_download_uri
+gdata_download_stream_get_content_type
+gdata_download_stream_get_content_length
+<SUBSECTION Standard>
+GDATA_DOWNLOAD_STREAM
+GDATA_DOWNLOAD_STREAM_CLASS
+GDATA_DOWNLOAD_STREAM_GET_CLASS
+GDATA_IS_DOWNLOAD_STREAM
+GDATA_IS_DOWNLOAD_STREAM_CLASS
+GDATA_TYPE_DOWNLOAD_STREAM
+gdata_download_stream_get_type
+<SUBSECTION Private>
+GDataDownloadStreamPrivate
+</SECTION>
diff --git a/gdata/Makefile.am b/gdata/Makefile.am
index 231054d..be1ec52 100644
--- a/gdata/Makefile.am
+++ b/gdata/Makefile.am
@@ -49,7 +49,8 @@ gdata_headers = \
gdata-query.h \
gdata-access-handler.h \
gdata-access-rule.h \
- gdata-parsable.h
+ gdata-parsable.h \
+ gdata-download-stream.h
gdataincludedir = $(pkgincludedir)/gdata
gdatainclude_HEADERS = \
@@ -69,8 +70,11 @@ libgdata_la_SOURCES = \
gdata-access-handler.c \
gdata-access-rule.c \
gdata-parsable.c \
+ gdata-download-stream.c \
gdata-private.h \
- gdata-parser.h
+ gdata-parser.h \
+ gdata-buffer.c \
+ gdata-buffer.h
libgdata_la_CPPFLAGS = \
-I$(top_srcdir) \
diff --git a/gdata/gdata-buffer.c b/gdata/gdata-buffer.c
new file mode 100644
index 0000000..f5de476
--- /dev/null
+++ b/gdata/gdata-buffer.c
@@ -0,0 +1,289 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-buffer
+ * @short_description: GData buffer to allow threadsafe buffering
+ * @stability: Unstable
+ * @include: gdata/gdata-buffer.h
+ *
+ * #GDataBuffer is a simple object which allows threadsafe buffering of data meaning, for example, data can be received from
+ * the network in a "push" fashion, buffered, then sent out to an output stream in a "pull" fashion.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <string.h>
+
+#include "gdata-buffer.h"
+
+struct _GDataBufferChunk {
+ /*< private >*/
+ guint8 *data;
+ gsize length;
+ GDataBufferChunk *next;
+ /* Note: the data is actually allocated in the same memory block, so it's inside this comment right now.
+ * We simply set chunk->data to point to chunk + sizeof (GDataBufferChunk). */
+};
+
+/**
+ * gdata_buffer_new:
+ *
+ * Creates a new empty #GDataBuffer.
+ *
+ * Return value: a new #GDataBuffer; free with gdata_buffer_free()
+ *
+ * Since: 0.5.0
+ **/
+GDataBuffer *
+gdata_buffer_new (void)
+{
+ GDataBuffer *buffer = g_slice_new0 (GDataBuffer);
+ g_static_mutex_init (&(buffer->mutex));
+ buffer->cond = g_cond_new ();
+
+ return buffer;
+}
+
+/**
+ * gdata_buffer_free:
+ *
+ * Frees a #GDataBuffer. The function isn't threadsafe, so should only be called once
+ * use of the buffer has been reduced to only one thread (the reading thread, after
+ * the EOF has been reached).
+ *
+ * Since: 0.5.0
+ **/
+void
+gdata_buffer_free (GDataBuffer *self)
+{
+ GDataBufferChunk *chunk, *next_chunk;
+
+ for (chunk = self->head; chunk != NULL; chunk = next_chunk) {
+ next_chunk = chunk->next;
+ g_free (chunk);
+ }
+
+ g_cond_free (self->cond);
+ g_static_mutex_free (&(self->mutex));
+ g_slice_free (GDataBuffer, self);
+}
+
+/**
+ * gdata_buffer_push_data:
+ * @self: a #GDataBuffer
+ * @data: the data to push onto the buffer
+ * @length: the length of @data
+ *
+ * Pushes @length bytes of @data onto the buffer, taking a copy of the data. If @data is %NULL and @length is %0,
+ * the buffer will be marked as having reached the EOF, and subsequent calls to gdata_buffer_push_data()
+ * will fail and return %FALSE.
+ *
+ * Assuming the buffer hasn't reached EOF, this operation is guaranteed to succeed (unless memory allocation fails).
+ *
+ * This function holds the lock on the #GDataBuffer, and signals any waiting calls to gdata_buffer_pop_data() once
+ * the new data has been pushed onto the buffer. This function is threadsafe.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ *
+ * Since: 0.5.0
+ **/
+gboolean
+gdata_buffer_push_data (GDataBuffer *self, const guint8 *data, gsize length)
+{
+ GDataBufferChunk *chunk;
+
+ g_static_mutex_lock (&(self->mutex));
+
+ if (G_UNLIKELY (self->reached_eof == TRUE)) {
+ /* If we're marked as having reached EOF, don't accept any more data */
+ g_static_mutex_unlock (&(self->mutex));
+ return FALSE;
+ } else if (G_UNLIKELY (data == NULL && length == 0)) {
+ /* If @data is NULL and @length is 0, mark the buffer as having reached EOF,
+ * and signal any waiting threads. */
+ self->reached_eof = TRUE;
+ g_cond_signal (self->cond);
+ g_static_mutex_unlock (&(self->mutex));
+ return FALSE;
+ }
+
+ /* Create the chunk */
+ chunk = g_malloc (sizeof (GDataBufferChunk) + length);
+ chunk->data = (gpointer) chunk + (gsize) sizeof (GDataBufferChunk);
+ chunk->length = length;
+ chunk->next = NULL;
+
+ /* Copy the data to the chunk */
+ memcpy (chunk->data, data, length);
+
+ /* Add it to the buffer's tail */
+ if (self->tail != NULL)
+ *(self->tail) = chunk;
+ else
+ self->head = chunk;
+ self->tail = &(chunk->next);
+ self->total_length += length;
+
+ /* Signal any threads waiting to pop that data is available */
+ g_cond_signal (self->cond);
+
+ g_static_mutex_unlock (&(self->mutex));
+
+ return TRUE;
+}
+
+typedef struct {
+ GDataBuffer *buffer;
+ gboolean *cancelled;
+} CancelledData;
+
+static void
+pop_cancelled_cb (GCancellable *cancellable, CancelledData *data)
+{
+ /* Signal the pop_data function that it should stop blocking and cancel */
+ *(data->cancelled) = TRUE;
+ g_cond_signal (data->buffer->cond);
+}
+
+/**
+ * gdata_buffer_pop_data:
+ * @self: a #GDataBuffer
+ * @data: return location for the popped data
+ * @length_requested: the number of bytes of data requested
+ * @cancellable: a #GCancellable, or %NULL
+ *
+ * Pops up to @length_requested bytes off the head of the buffer and copies them to @data, which must be allocated by
+ * the caller and have enough space to store at most @length_requested bytes of output.
+ *
+ * If the buffer contains enough data to satisfy @length_requested, this function returns immediately.
+ * Otherwise, this function blocks until data is pushed onto the head of the buffer with gdata_buffer_pop_data(). If
+ * the buffer is marked as having reached the EOF, this function will not block, and will instead return the
+ * remaining data in the buffer.
+ *
+ * This function holds the lock on the #GDataBuffer, and will automatically be signalled of new data pushed onto the
+ * buffer if it's blocking.
+ *
+ * If @cancellable is provided, calling g_cancellable_cancel() on it from another thread will cause the call to
+ * gdata_buffer_pop_data() to return immediately with whatever data it can find.
+ *
+ * Return value: the number of bytes returned in @data
+ *
+ * Since: 0.5.0
+ **/
+gsize
+gdata_buffer_pop_data (GDataBuffer *self, guint8 *data, gsize length_requested, GCancellable *cancellable)
+{
+ GDataBufferChunk *chunk;
+ gsize return_length = 0, length_remaining;
+
+ /* In the case:
+ * - length_requested < amount available: return length_requested
+ * - length_requested > amount available: block until more is available, return length_requested
+ * - length_requested > amount available and we've reached EOF: don't block, return all remaining data
+ * - length_requested is a whole number of chunks: remove those chunks, return length_requested
+ * - length_requested is less than one chunk: remove no chunks, return length_requested, set head_read_offset
+ * - length_requested is a fraction of multiple chunks: remove whole chunks, return length_requested, set head_read_offset for remaining fraction
+ */
+
+ g_static_mutex_lock (&(self->mutex));
+
+ if (self->reached_eof == TRUE && length_requested > self->total_length) {
+ /* Return data up to the EOF */
+ return_length = self->total_length;
+ } else if (length_requested > self->total_length) {
+ gulong cancelled_signal = 0;
+ gboolean cancelled = FALSE;
+
+ /* Set up a handler so we can stop if we're cancelled */
+ if (cancellable != NULL) {
+ CancelledData cancelled_data;
+
+ cancelled_data.buffer = self;
+ cancelled_data.cancelled = &cancelled;
+
+ cancelled_signal = g_signal_connect (cancellable, "cancelled", (GCallback) pop_cancelled_cb, &cancelled_data);
+ }
+
+ /* Block until more data is available */
+ while (length_requested > self->total_length) {
+ g_cond_wait (self->cond, g_static_mutex_get_mutex (&(self->mutex)));
+
+ /* If the g_cond_wait() returned because it was signalled from the GCancellable callback (rather than from
+ * data being pushed into the buffer), stop blocking for data and make do with what we have so far. */
+ if (cancelled == TRUE || self->reached_eof == TRUE) {
+ return_length = MIN (length_requested, self->total_length);
+ break;
+ } else {
+ return_length = length_requested;
+ }
+ }
+
+ /* Disconnect from the cancelled signal */
+ if (cancellable != NULL)
+ g_signal_handler_disconnect (cancellable, cancelled_signal);
+ } else {
+ return_length = length_requested;
+ }
+
+ /* Return if we haven't got any data to pop (i.e. if we were cancelled before even one chunk arrived) */
+ if (return_length == 0)
+ return 0;
+
+ /* Otherwise, get on with things */
+ length_remaining = return_length;
+
+ /* We can't assume we'll have enough data, since we may have reached EOF */
+ chunk = self->head;
+ while (chunk != NULL && length_remaining >= chunk->length) {
+ GDataBufferChunk *next_chunk;
+ gsize chunk_length = chunk->length - self->head_read_offset;
+
+ /* Copy the data to the output */
+ length_remaining -= chunk_length;
+ memcpy (data, chunk->data + self->head_read_offset, chunk_length);
+ data += chunk_length;
+
+ /* Free the chunk and move on */
+ next_chunk = chunk->next;
+ g_free (chunk);
+ chunk = next_chunk;
+
+ /* Reset the head read offset, since we've processed at least the first chunk now */
+ self->head_read_offset = 0;
+ }
+
+ /* If the requested length is still > 0, it must be < chunk->length, and chunk must != NULL (if it does, the cached total_length has
+ * been corrupted somewhere). */
+ if (G_LIKELY (length_remaining > 0)) {
+ /* Copy the requested data to the output */
+ memcpy (data, chunk->data, length_remaining);
+ data += length_remaining;
+ self->head_read_offset = length_remaining;
+ }
+
+ self->head = chunk;
+ if (self->head == NULL)
+ self->tail = NULL;
+ self->total_length -= return_length;
+
+ g_static_mutex_unlock (&(self->mutex));
+
+ return return_length;
+}
diff --git a/gdata/gdata-buffer.h b/gdata/gdata-buffer.h
new file mode 100644
index 0000000..ec807b5
--- /dev/null
+++ b/gdata/gdata-buffer.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_BUFFER_H
+#define GDATA_BUFFER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GDataBufferChunk GDataBufferChunk;
+
+/**
+ * GDataBuffer:
+ *
+ * All the fields in the #GDataBuffer structure are private and should never be accessed directly.
+ *
+ * Since: 0.5.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataBufferChunk *head;
+ gsize head_read_offset; /* number of bytes which have already been popped from the head chunk */
+ gsize total_length; /* total length of all the chunks available to read (i.e. head_read_offset is already subtracted) */
+ gboolean reached_eof; /* set to TRUE only once we've reached EOF */
+ GDataBufferChunk **tail; /* pointer to the GDataBufferChunk->next field of the current tail chunk */
+
+ GStaticMutex mutex; /* mutex protecting the entire structure on push and pop */
+ GCond *cond; /* a GCond to allow a popping thread to block on data being pushed into the buffer */
+} GDataBuffer;
+
+GDataBuffer *gdata_buffer_new (void) G_GNUC_WARN_UNUSED_RESULT;
+void gdata_buffer_free (GDataBuffer *self);
+
+gboolean gdata_buffer_push_data (GDataBuffer *self, const guint8 *data, gsize length);
+gsize gdata_buffer_pop_data (GDataBuffer *self, guint8 *data, gsize length_requested, GCancellable *cancellable);
+
+G_END_DECLS
+
+#endif /* !GDATA_BUFFER_H */
diff --git a/gdata/gdata-download-stream.c b/gdata/gdata-download-stream.c
new file mode 100644
index 0000000..bfd3b58
--- /dev/null
+++ b/gdata/gdata-download-stream.c
@@ -0,0 +1,562 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-download-stream
+ * @short_description: GData download stream object
+ * @stability: Unstable
+ * @include: gdata/gdata-download-stream.h
+ *
+ * #GDataDownloadStream is a #GInputStream subclass to allow downloading of files from GData services with authentication from a #GDataService.
+ *
+ * Once a #GDataDownloadStream is instantiated with gdata_download_stream_new(), the standard #GInputStream API can be used on the stream to download
+ * the file. Network communication may not actually begin until the first call to g_input_stream_read(), so having a #GDataDownloadStream around is no
+ * guarantee that the file is being downloaded.
+ *
+ * The content type and length of the file being downloaded are made available through #GDataDownloadStream:content-type and #GDataDownloadStream:content-length
+ * as soon as the appropriate data is received from the server. Connect to #GDataDownloadStream::notify::content-type and
+ * #GDataDownloadStream::notify::content-length to be notified as soon as the data is available.
+ *
+ * Since: 0.5.0
+ **/
+
+#include <config.h>
+#include <glib.h>
+
+#include "gdata-download-stream.h"
+#include "gdata-buffer.h"
+#include "gdata-private.h"
+
+static void gdata_download_stream_seekable_iface_init (GSeekableIface *seekable_iface);
+static void gdata_download_stream_dispose (GObject *object);
+static void gdata_download_stream_finalize (GObject *object);
+static void gdata_download_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_download_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+
+static gssize gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error);
+static gboolean gdata_download_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error);
+
+static goffset gdata_download_stream_tell (GSeekable *seekable);
+static gboolean gdata_download_stream_can_seek (GSeekable *seekable);
+static gboolean gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error);
+static gboolean gdata_download_stream_can_truncate (GSeekable *seekable);
+static gboolean gdata_download_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error);
+
+static void create_network_thread (GDataDownloadStream *self, GError **error);
+
+struct _GDataDownloadStreamPrivate {
+ gchar *download_uri;
+ GDataService *service;
+ SoupSession *session;
+ SoupMessage *message;
+ GDataBuffer *buffer;
+ gboolean finished;
+ goffset offset; /* seek offset */
+ GThread *network_thread;
+
+ /* Cached data from the SoupMessage */
+ gchar *content_type;
+ gssize content_length;
+ GStaticMutex content_mutex; /* mutex to protect them */
+};
+
+enum {
+ PROP_SERVICE = 1,
+ PROP_DOWNLOAD_URI,
+ PROP_CONTENT_TYPE,
+ PROP_CONTENT_LENGTH
+};
+
+G_DEFINE_TYPE_WITH_CODE (GDataDownloadStream, gdata_download_stream, G_TYPE_INPUT_STREAM,
+ G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, gdata_download_stream_seekable_iface_init))
+#define GDATA_DOWNLOAD_STREAM_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOWNLOAD_STREAM, GDataDownloadStreamPrivate))
+
+static void
+gdata_download_stream_class_init (GDataDownloadStreamClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataDownloadStreamPrivate));
+
+ gobject_class->dispose = gdata_download_stream_dispose;
+ gobject_class->finalize = gdata_download_stream_finalize;
+ gobject_class->get_property = gdata_download_stream_get_property;
+ gobject_class->set_property = gdata_download_stream_set_property;
+
+ /* We use the default implementations of the async functions, which just run
+ * our implementation of the sync function in a thread. */
+ stream_class->read_fn = gdata_download_stream_read;
+ stream_class->close_fn = gdata_download_stream_close;
+
+ /**
+ * GDataDownloadStream:service:
+ *
+ * The service which is used to authenticate the download, and to which the download relates.
+ *
+ * Since: 0.5.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SERVICE,
+ g_param_spec_object ("service",
+ "Service", "The service which is used to authenticate the download.",
+ GDATA_TYPE_SERVICE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDownloadStream:download-uri:
+ *
+ * The URI of the file to download.
+ *
+ * Since: 0.5.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_DOWNLOAD_URI,
+ g_param_spec_string ("download-uri",
+ "Download URI", "The URI of the file to download.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDownloadStream:content-type:
+ *
+ * The content type of the file being downloaded.
+ *
+ * Since: 0.5.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
+ g_param_spec_string ("content-type",
+ "Content type", "The content type of the file being downloaded.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDownloadStream:content-length:
+ *
+ * The length (in bytes) of the file being downloaded.
+ *
+ * Since: 0.5.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH,
+ g_param_spec_long ("content-length",
+ "Content length", "The length (in bytes) of the file being downloaded.",
+ -1, G_MAXSSIZE, -1,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_download_stream_seekable_iface_init (GSeekableIface *seekable_iface)
+{
+ seekable_iface->tell = gdata_download_stream_tell;
+ seekable_iface->can_seek = gdata_download_stream_can_seek;
+ seekable_iface->seek = gdata_download_stream_seek;
+ seekable_iface->can_truncate = gdata_download_stream_can_truncate;
+ seekable_iface->truncate_fn = gdata_download_stream_truncate;
+}
+
+static void
+gdata_download_stream_init (GDataDownloadStream *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOWNLOAD_STREAM, GDataDownloadStreamPrivate);
+ self->priv->content_length = -1;
+ self->priv->buffer = gdata_buffer_new ();
+ g_static_mutex_init (&(self->priv->content_mutex));
+}
+
+static void
+gdata_download_stream_dispose (GObject *object)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM_GET_PRIVATE (object);
+
+ if (priv->service != NULL)
+ g_object_unref (priv->service);
+ priv->service = NULL;
+
+ if (priv->message != NULL)
+ g_object_unref (priv->message);
+ priv->message = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_download_stream_parent_class)->dispose (object);
+}
+
+static void
+gdata_download_stream_finalize (GObject *object)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM_GET_PRIVATE (object);
+
+ g_thread_join (priv->network_thread);
+ g_static_mutex_free (&(priv->content_mutex));
+ gdata_buffer_free (priv->buffer);
+ g_free (priv->download_uri);
+ g_free (priv->content_type);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_download_stream_parent_class)->finalize (object);
+}
+
+static void
+gdata_download_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_SERVICE:
+ g_value_set_object (value, priv->service);
+ break;
+ case PROP_DOWNLOAD_URI:
+ g_value_set_string (value, priv->download_uri);
+ break;
+ case PROP_CONTENT_TYPE:
+ g_static_mutex_lock (&(priv->content_mutex));
+ g_value_set_string (value, priv->content_type);
+ g_static_mutex_unlock (&(priv->content_mutex));
+ break;
+ case PROP_CONTENT_LENGTH:
+ g_static_mutex_lock (&(priv->content_mutex));
+ g_value_set_long (value, priv->content_length);
+ g_static_mutex_unlock (&(priv->content_mutex));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_download_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (object)->priv;
+
+ switch (property_id) {
+ case PROP_SERVICE:
+ priv->service = g_value_dup_object (value);
+ priv->session = _gdata_service_get_session (priv->service);
+ break;
+ case PROP_DOWNLOAD_URI:
+ priv->download_uri = g_value_dup_string (value);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gssize
+gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv;
+ gssize length_read;
+
+ /* We're lazy about starting the network operation so we don't end up with a massive buffer */
+ if (priv->network_thread == NULL) {
+ create_network_thread (GDATA_DOWNLOAD_STREAM (stream), error);
+ if (priv->network_thread == NULL)
+ return 0;
+ }
+
+ /* Read the data off the buffer */
+ length_read = (gssize) gdata_buffer_pop_data (priv->buffer, buffer, count, cancellable);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ /* Handle cancellation */
+ return length_read;
+ } else if (SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code) == FALSE) {
+ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service);
+
+ /* Set an appropriate error */
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (priv->service, GDATA_SERVICE_ERROR_WITH_DOWNLOAD, priv->message->status_code, priv->message->reason_phrase,
+ NULL, 0, error);
+ return 0;
+ }
+
+ return length_read;
+}
+
+static gboolean
+gdata_download_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv;
+
+ if (priv->finished == FALSE)
+ soup_session_cancel_message (priv->session, priv->message, SOUP_STATUS_CANCELLED);
+
+ return TRUE;
+}
+
+static goffset
+gdata_download_stream_tell (GSeekable *seekable)
+{
+ return GDATA_DOWNLOAD_STREAM (seekable)->priv->offset;
+}
+
+static gboolean
+gdata_download_stream_can_seek (GSeekable *seekable)
+{
+ return TRUE;
+}
+
+extern void soup_message_io_cleanup (SoupMessage *msg);
+
+/* Copied from SoupInputStream */
+static gboolean
+gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error)
+{
+ GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (seekable)->priv;
+ gchar *range = NULL;
+
+ if (type == G_SEEK_END) {
+ /* FIXME: we could send "bytes=-offset", but unless we know the
+ * Content-Length, we wouldn't be able to answer a tell() properly.
+ * We could find the Content-Length by doing a HEAD...
+ */
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "G_SEEK_END not currently supported");
+ return FALSE;
+ }
+
+ if (g_input_stream_set_pending (G_INPUT_STREAM (seekable), error) == FALSE)
+ return FALSE;
+
+ soup_session_cancel_message (priv->session, priv->message, SOUP_STATUS_CANCELLED);
+ soup_message_io_cleanup (priv->message);
+
+ switch (type) {
+ case G_SEEK_CUR:
+ offset += priv->offset;
+ /* fall through */
+ case G_SEEK_SET:
+ range = g_strdup_printf ("bytes=%" G_GUINT64_FORMAT "-", (guint64) offset);
+ priv->offset = offset;
+ break;
+ case G_SEEK_END:
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Change the Range header and re-send the message */
+ soup_message_headers_remove (priv->message->request_headers, "Range");
+ soup_message_headers_append (priv->message->request_headers, "Range", range);
+ g_free (range);
+
+ /* Wait for the thread to quit, then launch another one with the modified message */
+ g_thread_join (priv->network_thread);
+ create_network_thread (GDATA_DOWNLOAD_STREAM (seekable), error);
+ if (priv->network_thread == NULL)
+ return FALSE;
+
+ g_input_stream_clear_pending (G_INPUT_STREAM (seekable));
+
+ return TRUE;
+}
+
+static gboolean
+gdata_download_stream_can_truncate (GSeekable *seekable)
+{
+ return FALSE;
+}
+
+static gboolean
+gdata_download_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error)
+{
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Truncate not allowed on input stream");
+ return FALSE;
+}
+
+static gboolean
+notify_content_data_cb (GObject *download_stream)
+{
+ g_object_freeze_notify (download_stream);
+ g_object_notify (download_stream, "content-length");
+ g_object_notify (download_stream, "content-type");
+ g_object_thaw_notify (download_stream);
+
+ g_object_unref (download_stream);
+
+ return FALSE;
+}
+
+static void
+got_headers_cb (SoupMessage *message, GDataDownloadStream *self)
+{
+ /* Don't get the client's hopes up by setting the Content-Type or -Length if the response
+ * is actually unsuccessful. */
+ if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code) == FALSE)
+ return;
+
+ g_static_mutex_lock (&(self->priv->content_mutex));
+ self->priv->content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
+ self->priv->content_length = soup_message_headers_get_content_length (message->response_headers);
+ g_static_mutex_unlock (&(self->priv->content_mutex));
+
+ /* Emit the notifications for the Content-Length and -Type properties in the main thread */
+ g_idle_add ((GSourceFunc) notify_content_data_cb, g_object_ref (self));
+}
+
+static void
+got_chunk_cb (SoupMessage *message, SoupBuffer *buffer, GDataDownloadStream *self)
+{
+ /* Ignore the chunk if the response is unsuccessful or it has zero length */
+ if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code) == FALSE || buffer->length == 0)
+ return;
+
+ /* Push the data onto the buffer immediately */
+ gdata_buffer_push_data (self->priv->buffer, g_memdup (buffer->data, buffer->length), buffer->length);
+}
+
+static void
+finished_cb (SoupMessage *message, GDataDownloadStream *self)
+{
+ self->priv->finished = TRUE;
+
+ /* Mark the buffer as having reached EOF */
+ gdata_buffer_push_data (self->priv->buffer, NULL, 0);
+}
+
+static gpointer
+download_thread (GDataDownloadStream *self)
+{
+ /* Connect to the got-headers signal so we can notify clients of the values of content-type and content-length */
+ g_signal_connect (self->priv->message, "got-headers", (GCallback) got_headers_cb, self);
+ g_signal_connect (self->priv->message, "got-chunk", (GCallback) got_chunk_cb, self);
+ g_signal_connect (self->priv->message, "finished", (GCallback) finished_cb, self);
+
+ soup_session_send_message (self->priv->session, self->priv->message);
+
+ return NULL;
+}
+
+static void
+create_network_thread (GDataDownloadStream *self, GError **error)
+{
+ self->priv->network_thread = g_thread_create ((GThreadFunc) download_thread, self, TRUE, error);
+}
+
+/**
+ * gdata_download_stream_new:
+ * @service: a #GDataService
+ * @download_uri: the URI to download
+ *
+ * Creates a new #GDataDownloadStream, allowing a file to be downloaded from a GData service using standard #GInputStream API.
+ *
+ * As well as the standard GIO errors, calls to the #GInputStream API on a #GDataDownloadStream can also return any relevant specific error from
+ * #GDataServiceError, or %GDATA_SERVICE_ERROR_WITH_DOWNLOAD in the general case.
+ *
+ * Return value: a new #GInputStream, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.5.0
+ **/
+GInputStream *
+gdata_download_stream_new (GDataService *service, const gchar *download_uri)
+{
+ GDataServiceClass *klass;
+ GDataDownloadStream *download_stream;
+ SoupMessage *message;
+
+ g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+ g_return_val_if_fail (download_uri != NULL, NULL);
+
+ /* Build the message */
+ message = soup_message_new (SOUP_METHOD_GET, download_uri);
+
+ /* Make sure the headers are set */
+ klass = GDATA_SERVICE_GET_CLASS (service);
+ if (klass->append_query_headers != NULL)
+ klass->append_query_headers (GDATA_SERVICE (service), message);
+
+ /* We don't want to accumulate chunks */
+ soup_message_body_set_accumulate (message->request_body, FALSE);
+
+ download_stream = g_object_new (GDATA_TYPE_DOWNLOAD_STREAM, "download-uri", download_uri, "service", service, NULL);
+ download_stream->priv->message = message;
+
+ /* Downloading doesn't actually start until the first call to read() */
+
+ return G_INPUT_STREAM (download_stream);
+}
+
+/**
+ * gdata_download_stream_get_service:
+ * @self: a #GDataDownloadStream
+ *
+ * Gets the service used to authenticate the download, as passed to gdata_download_stream_new().
+ *
+ * Return value: the #GDataService used to authenticate the download
+ *
+ * Since: 0.5.0
+ **/
+GDataService *
+gdata_download_stream_get_service (GDataDownloadStream *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
+ return self->priv->service;
+}
+
+/**
+ * gdata_download_stream_get_download_uri:
+ * @self: a #GDataDownloadStream
+ *
+ * Gets the URI of the file being downloaded, as passed to gdata_download_stream_new().
+ *
+ * Return value: the URI of the file being downloaded
+ *
+ * Since: 0.5.0
+ **/
+const gchar *
+gdata_download_stream_get_download_uri (GDataDownloadStream *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
+ return self->priv->download_uri;
+}
+
+/**
+ * gdata_download_stream_get_content_type:
+ * @self: a #GDataDownloadStream
+ *
+ * Gets the content type of the file being downloaded. If the <literal>Content-Type</literal> header has not yet
+ * been received, %NULL will be returned.
+ *
+ * Return value: the content type of the file being downloaded, or %NULL
+ *
+ * Since: 0.5.0
+ **/
+const gchar *
+gdata_download_stream_get_content_type (GDataDownloadStream *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
+ return self->priv->content_type;
+}
+
+/**
+ * gdata_download_stream_get_content_length:
+ * @self: a #GDataDownloadStream
+ *
+ * Gets the length (in bytes) of the file being downloaded. If the <literal>Content-Length</literal> header has not yet
+ * been received from the server, %-1 will be returned.
+ *
+ * Return value: the content length of the file being downloaded, or %-1
+ *
+ * Since: 0.5.0
+ **/
+gssize
+gdata_download_stream_get_content_length (GDataDownloadStream *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), -1);
+ return self->priv->content_length;
+}
diff --git a/gdata/gdata-download-stream.h b/gdata/gdata-download-stream.h
new file mode 100644
index 0000000..5ee4da9
--- /dev/null
+++ b/gdata/gdata-download-stream.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_DOWNLOAD_STREAM_H
+#define GDATA_DOWNLOAD_STREAM_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <gdata/gdata-service.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOWNLOAD_STREAM (gdata_download_stream_get_type ())
+#define GDATA_DOWNLOAD_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOWNLOAD_STREAM, GDataDownloadStream))
+#define GDATA_DOWNLOAD_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOWNLOAD_STREAM, GDataDownloadStreamClass))
+#define GDATA_IS_DOWNLOAD_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOWNLOAD_STREAM))
+#define GDATA_IS_DOWNLOAD_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOWNLOAD_STREAM))
+#define GDATA_DOWNLOAD_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOWNLOAD_STREAM, GDataDownloadStreamClass))
+
+typedef struct _GDataDownloadStreamPrivate GDataDownloadStreamPrivate;
+
+/**
+ * GDataDownloadStream:
+ *
+ * All the fields in the #GDataDownloadStream structure are private and should never be accessed directly.
+ *
+ * Since: 0.5.0
+ **/
+typedef struct {
+ GInputStream parent;
+ GDataDownloadStreamPrivate *priv;
+} GDataDownloadStream;
+
+/**
+ * GDataDownloadStreamClass:
+ *
+ * All the fields in the #GDataDownloadStreamClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.5.0
+ **/
+typedef struct {
+ /*< private >*/
+ GInputStreamClass parent;
+} GDataDownloadStreamClass;
+
+GType gdata_download_stream_get_type (void) G_GNUC_CONST;
+
+GInputStream *gdata_download_stream_new (GDataService *service, const gchar *download_uri) G_GNUC_WARN_UNUSED_RESULT;
+
+GDataService *gdata_download_stream_get_service (GDataDownloadStream *self);
+const gchar *gdata_download_stream_get_download_uri (GDataDownloadStream *self);
+const gchar *gdata_download_stream_get_content_type (GDataDownloadStream *self);
+gssize gdata_download_stream_get_content_length (GDataDownloadStream *self);
+
+G_END_DECLS
+
+#endif /* !GDATA_DOWNLOAD_STREAM_H */
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index 53729b4..c8d007e 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -29,6 +29,7 @@
G_BEGIN_DECLS
#include "gdata-service.h"
+SoupSession *_gdata_service_get_session (GDataService *self);
void _gdata_service_set_authenticated (GDataService *self, gboolean authenticated);
guint _gdata_service_send_message (GDataService *self, SoupMessage *message, GError **error);
SoupMessage *_gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable,
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index 6f44529..74aa76c 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -332,7 +332,6 @@ real_parse_error_response (GDataService *self, GDataServiceError error_type, gui
gint length, GError **error)
{
/* See: http://code.google.com/apis/gdata/docs/2.0/reference.html#HTTPStatusCodes */
-
switch (status) {
case 400:
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
@@ -365,24 +364,29 @@ real_parse_error_response (GDataService *self, GDataServiceError error_type, gui
switch (error_type) {
case GDATA_SERVICE_ERROR_WITH_INSERTION:
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_INSERTION,
- /* Translators: the first parameter is a HTTP status, and the second is an error message returned by the server. */
+ /* Translators: the first parameter is an HTTP status, and the second is an error message returned by the server. */
_("Error code %u when inserting an entry: %s"), status, response_body);
break;
case GDATA_SERVICE_ERROR_WITH_UPDATE:
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_UPDATE,
- /* Translators: the first parameter is a HTTP status, and the second is an error message returned by the server. */
+ /* Translators: the first parameter is an HTTP status, and the second is an error message returned by the server. */
_("Error code %u when updating an entry: %s"), status, response_body);
break;
case GDATA_SERVICE_ERROR_WITH_DELETION:
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_DELETION,
- /* Translators: the first parameter is a HTTP status, and the second is an error message returned by the server. */
+ /* Translators: the first parameter is an HTTP status, and the second is an error message returned by the server. */
_("Error code %u when deleting an entry: %s"), status, response_body);
break;
case GDATA_SERVICE_ERROR_WITH_QUERY:
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_QUERY,
- /* Translators: the first parameter is a HTTP status, and the second is an error message returned by the server. */
+ /* Translators: the first parameter is an HTTP status, and the second is an error message returned by the server. */
_("Error code %u when querying: %s"), status, response_body);
break;
+ case GDATA_SERVICE_ERROR_WITH_DOWNLOAD:
+ g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_DOWNLOAD,
+ /* Translators: the first parameter is an HTTP status, and the second is an error message returned by the server. */
+ _("Error code %u when downloading: %s"), status, response_body);
+ break;
default:
/* We should not be called with anything other than the above four generic error types */
g_assert_not_reached ();
@@ -1720,3 +1724,9 @@ gdata_service_get_password (GDataService *self)
g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
return self->priv->password;
}
+
+SoupSession *
+_gdata_service_get_session (GDataService *self)
+{
+ return self->priv->session;
+}
diff --git a/gdata/gdata-service.h b/gdata/gdata-service.h
index c61f88b..ddb9fc3 100644
--- a/gdata/gdata-service.h
+++ b/gdata/gdata-service.h
@@ -43,6 +43,7 @@ G_BEGIN_DECLS
* @GDATA_SERVICE_ERROR_CONFLICT: There was a conflict when updating an entry on the server; the server-side copy was modified inbetween downloading
* and uploading the modified entry
* @GDATA_SERVICE_ERROR_FORBIDDEN: Generic error for a forbidden action (not due to having insufficient permissions)
+ * @GDATA_SERVICE_ERROR_WITH_DOWNLOAD: Generic error when downloading a file (rather than querying for an entry).
*
* Error codes for #GDataService operations.
**/
@@ -57,7 +58,8 @@ typedef enum {
GDATA_SERVICE_ERROR_WITH_DELETION,
GDATA_SERVICE_ERROR_NOT_FOUND,
GDATA_SERVICE_ERROR_CONFLICT,
- GDATA_SERVICE_ERROR_FORBIDDEN
+ GDATA_SERVICE_ERROR_FORBIDDEN,
+ GDATA_SERVICE_ERROR_WITH_DOWNLOAD
} GDataServiceError;
/**
diff --git a/gdata/gdata.h b/gdata/gdata.h
index d02c3c0..c68e7c6 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -30,6 +30,7 @@
#include <gdata/gdata-access-handler.h>
#include <gdata/gdata-access-rule.h>
#include <gdata/gdata-parsable.h>
+#include <gdata/gdata-download-stream.h>
/* Namespaces */
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index b658280..d2c24ac 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -589,12 +589,15 @@ gdata_picasaweb_visibility_get_type
gdata_documents_presentation_get_type
gdata_documents_presentation_new
gdata_documents_presentation_download_document
+gdata_documents_presentation_get_download_uri
gdata_documents_text_get_type
gdata_documents_text_new
gdata_documents_text_download_document
+gdata_documents_text_get_download_uri
gdata_documents_spreadsheet_get_type
gdata_documents_spreadsheet_new
gdata_documents_spreadsheet_download_document
+gdata_documents_spreadsheet_get_download_uri
gdata_documents_folder_get_type
gdata_documents_folder_new
gdata_documents_service_get_type
@@ -636,3 +639,9 @@ gdata_documents_service_error_get_type
gdata_documents_text_format_get_type
gdata_documents_presentation_format_get_type
gdata_documents_spreadsheet_format_get_type
+gdata_download_stream_get_type
+gdata_download_stream_new
+gdata_download_stream_get_service
+gdata_download_stream_get_download_uri
+gdata_download_stream_get_content_type
+gdata_download_stream_get_content_length
diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c
index aabb2b2..5d4bbf9 100644
--- a/gdata/services/documents/gdata-documents-entry.c
+++ b/gdata/services/documents/gdata-documents-entry.c
@@ -43,6 +43,7 @@
#include "gdata-types.h"
#include "gdata-private.h"
#include "gdata-access-handler.h"
+#include "gdata-download-stream.h"
static void gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface);
static void gdata_documents_entry_finalize (GObject *object);
@@ -550,9 +551,9 @@ gdata_documents_entry_is_deleted (GDataDocumentsEntry *self)
}
static void
-got_chunk_cb (SoupMessage *message, SoupBuffer *chunk, GOutputStream *output_stream)
+notify_content_type_cb (GDataDownloadStream *download_stream, GParamSpec *pspec, gchar **content_type)
{
- g_output_stream_write (output_stream, (void*) chunk->data, chunk->length, NULL, NULL);
+ *content_type = g_strdup (gdata_download_stream_get_content_type (download_stream));
}
/*
@@ -592,10 +593,10 @@ _gdata_documents_entry_download_document (GDataDocumentsEntry *self, GDataServic
GFile *destination_file, const gchar *file_extension, gboolean replace_file_if_exists,
GCancellable *cancellable, GError **error)
{
- GDataServiceClass *klass;
+ GError *child_error = NULL;
+ GFile *output_file;
GFileOutputStream *file_stream;
- SoupMessage *message;
- guint status;
+ GInputStream *download_stream;
/* TODO: async version */
g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL);
@@ -613,74 +614,47 @@ _gdata_documents_entry_download_document (GDataDocumentsEntry *self, GDataServic
}
/* Create a new file */
- file_stream = g_file_create (destination_file, G_FILE_CREATE_NONE, cancellable, error);
- if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
- /* Replace a pre-existing file */
+ file_stream = g_file_create (destination_file, G_FILE_CREATE_NONE, cancellable, &child_error);
+ if (g_error_matches (child_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
if (replace_file_if_exists == TRUE) {
- g_clear_error (error);
- file_stream = g_file_replace (destination_file, NULL, TRUE, G_FILE_CREATE_REPLACE_DESTINATION, cancellable, error);
- }
+ g_error_free (child_error);
+ child_error = NULL;
- if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) {
- GFile *new_destination_file = NULL;
- const gchar *document_title;
- gchar *filename;
+ /* Replace a pre-existing file */
+ file_stream = g_file_replace (destination_file, NULL, TRUE, G_FILE_CREATE_REPLACE_DESTINATION, cancellable, &child_error);
- g_clear_error (error);
+ if (g_error_matches (child_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) {
+ GFile *new_destination_file;
+ const gchar *document_title;
+ gchar *filename;
- /* Prepare the GFile */
- document_title = gdata_entry_get_title (GDATA_ENTRY (self));
- filename = g_strdup_printf ("%s.%s", document_title, file_extension);
- new_destination_file = g_file_get_child (destination_file, filename);
- g_free (filename);
+ g_error_free (child_error);
- return _gdata_documents_entry_download_document (self, service, content_type, download_uri, new_destination_file,
- file_extension, replace_file_if_exists, cancellable, error);
- }
+ /* Prepare a new GFile */
+ document_title = gdata_entry_get_title (GDATA_ENTRY (self));
+ filename = g_strdup_printf ("%s.%s", document_title, file_extension);
+ new_destination_file = g_file_get_child (destination_file, filename);
+ g_free (filename);
- return NULL;
+ file_stream = g_file_replace (new_destination_file, NULL, TRUE, G_FILE_CREATE_REPLACE_DESTINATION, cancellable, error);
+ output_file = new_destination_file;
+ } else {
+ output_file = g_object_ref (destination_file);
+ }
+ } else {
+ g_propagate_error (error, child_error);
+ return NULL;
+ }
+ } else {
+ output_file = g_object_ref (destination_file);
}
- /* Get the document URI */
- message = soup_message_new (SOUP_METHOD_GET, download_uri);
-
- /* We copy the data to disk as it comes through the network pipe */
- soup_message_body_set_accumulate (message->response_body, FALSE);
- g_signal_connect (message, "got-chunk", (GCallback) got_chunk_cb, file_stream);
-
- /* Make sure the headers are set */
- klass = GDATA_SERVICE_GET_CLASS (service);
- if (klass->append_query_headers != NULL)
- klass->append_query_headers (GDATA_SERVICE (service), message);
-
- /* Send the message */
- status = _gdata_service_send_message (GDATA_SERVICE (service), message, error);
+ download_stream = gdata_download_stream_new (GDATA_SERVICE (service), download_uri);
+ g_signal_connect (download_stream, "notify::content-type", (GCallback) notify_content_type_cb, content_type);
+ g_output_stream_splice (G_OUTPUT_STREAM (file_stream), download_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error);
+ g_object_unref (download_stream);
g_object_unref (file_stream);
- if (status == SOUP_STATUS_NONE) {
- g_object_unref (message);
- return NULL;
- }
-
- /* Check for cancellation */
- if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
- g_object_unref (message);
- return NULL;
- }
-
- if (status != 200) {
- /* Error */
- g_assert (klass->parse_error_response != NULL);
- klass->parse_error_response (GDATA_SERVICE (service), GDATA_SERVICE_ERROR_WITH_QUERY, status, message->reason_phrase,
- message->response_body->data, message->response_body->length, error);
- g_object_unref (message);
- return NULL;
- }
-
- /* Sort out the return values */
- if (content_type != NULL)
- *content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
-
- g_object_unref (message);
- return g_object_ref (destination_file);
+ return output_file;
}
diff --git a/gdata/services/documents/gdata-documents-presentation.c b/gdata/services/documents/gdata-documents-presentation.c
index 8d966d8..cfd8b6b 100644
--- a/gdata/services/documents/gdata-documents-presentation.c
+++ b/gdata/services/documents/gdata-documents-presentation.c
@@ -43,6 +43,14 @@
static void get_xml (GDataParsable *parsable, GString *xml_string);
+static const gchar *export_formats[] = {
+ "pdf", /* GDATA_DOCUMENTS_PRESENTATION_PDF */
+ "png", /* GDATA_DOCUMENTS_PRESENTATION_PNG */
+ "ppt", /* GDATA_DOCUMENTS_PRESENTATION_PPT */
+ "swf", /* GDATA_DOCUMENTS_PRESENTATION_SWF */
+ "txt" /* GDATA_DOCUMENTS_PRESENTATION_TXT */
+};
+
G_DEFINE_TYPE (GDataDocumentsPresentation, gdata_documents_presentation, GDATA_TYPE_DOCUMENTS_ENTRY)
#define GDATA_DOCUMENTS_PRESENTATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_PRESENTATION, GDataDocumentsPresentationPrivate))
@@ -119,34 +127,45 @@ gdata_documents_presentation_download_document (GDataDocumentsPresentation *self
GDataDocumentsPresentationFormat export_format, GFile *destination_file,
gboolean replace_file_if_exists, GCancellable *cancellable, GError **error)
{
- const gchar *document_id;
gchar *link_href;
- const gchar *export_formats[] = {
- "pdf", /* GDATA_DOCUMENTS_PRESENTATION_PDF */
- "png", /* GDATA_DOCUMENTS_PRESENTATION_PNG */
- "ppt", /* GDATA_DOCUMENTS_PRESENTATION_PPT */
- "swf", /* GDATA_DOCUMENTS_PRESENTATION_SWF */
- "txt" /* GDATA_DOCUMENTS_PRESENTATION_TXT */
- };
-
- /* TODO: async version */
g_return_val_if_fail (GDATA_IS_DOCUMENTS_PRESENTATION (self), NULL);
g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (service), NULL);
g_return_val_if_fail (G_IS_FILE (destination_file), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
- g_return_val_if_fail (export_format >= 0 && export_format < G_N_ELEMENTS (export_formats), NULL);
-
- document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (self));
- g_assert (document_id != NULL);
-
- link_href = g_strdup_printf ("http://docs.google.com/feeds/download/presentations/Export?exportFormat=%s&docID=%s",
- export_formats[export_format], document_id);
+ g_return_val_if_fail (export_format < G_N_ELEMENTS (export_formats), NULL);
/* Call the common download method on the parent class */
- destination_file = _gdata_documents_entry_download_document (GDATA_DOCUMENTS_ENTRY (self), GDATA_SERVICE (service), content_type,
- link_href, destination_file, export_formats[export_format], replace_file_if_exists, cancellable, error);
+ link_href = gdata_documents_presentation_get_download_uri (self, export_format);
+ destination_file = _gdata_documents_entry_download_document (GDATA_DOCUMENTS_ENTRY (self), GDATA_SERVICE (service), content_type, link_href,
+ destination_file, export_formats[export_format], replace_file_if_exists, cancellable, error);
g_free (link_href);
return destination_file;
}
+
+/**
+ * gdata_documents_presentation_get_download_uri:
+ * @self: a #GDataDocumentsPresentation
+ * @export_format: the format in which the presentation should be exported when downloaded
+ *
+ * Builds and returns the download URI for the given #GDataDocumentsPresentation in the desired format. Note that directly downloading
+ * the document using this URI isn't possible, as authentication is required. You should instead use gdata_download_stream_new() with
+ * the URI, and use the resulting #GInputStream.
+ *
+ * Return value: the download URI; free with g_free()
+ *
+ * Since: 0.5.0
+ **/
+gchar *
+gdata_documents_presentation_get_download_uri (GDataDocumentsPresentation *self, GDataDocumentsPresentationFormat export_format)
+{
+ const gchar *document_id;
+
+ g_return_val_if_fail (export_format < G_N_ELEMENTS (export_formats), NULL);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (self));
+ g_assert (document_id != NULL);
+
+ return g_strdup_printf ("http://docs.google.com/feeds/download/presentations/Export?exportFormat=%s&docID=%s", export_formats[export_format], document_id);
+}
diff --git a/gdata/services/documents/gdata-documents-presentation.h b/gdata/services/documents/gdata-documents-presentation.h
index 211a459..99fda36 100644
--- a/gdata/services/documents/gdata-documents-presentation.h
+++ b/gdata/services/documents/gdata-documents-presentation.h
@@ -90,6 +90,7 @@ GFile *gdata_documents_presentation_download_document (GDataDocumentsPresentatio
GDataDocumentsPresentationFormat export_format, GFile *destination_file,
gboolean replace_file_if_exists, GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
+gchar *gdata_documents_presentation_get_download_uri (GDataDocumentsPresentation *self, GDataDocumentsPresentationFormat export_format) G_GNUC_WARN_UNUSED_RESULT;
G_END_DECLS
diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c
index dfa486d..25d546b 100644
--- a/gdata/services/documents/gdata-documents-service.c
+++ b/gdata/services/documents/gdata-documents-service.c
@@ -293,10 +293,10 @@ gdata_documents_service_query_documents_async (GDataDocumentsService *self, GDat
GDATA_TYPE_DOCUMENTS_ENTRY, cancellable, progress_callback, progress_user_data, callback, user_data);
}
-/**
+/*
* To upload spreasheet documents, another token is needed since the service for it is "wise" as apposed to "writely" for other operations.
* This callback aims to authenticate to this service as a private property (@priv->spreadsheet_service) of #GDataDocumentsService.
- * */
+ */
static void
notify_authenticated_cb (GObject *service, GParamSpec *pspec, GObject *self)
{
diff --git a/gdata/services/documents/gdata-documents-spreadsheet.c b/gdata/services/documents/gdata-documents-spreadsheet.c
index a7c305a..99ede51 100644
--- a/gdata/services/documents/gdata-documents-spreadsheet.c
+++ b/gdata/services/documents/gdata-documents-spreadsheet.c
@@ -42,6 +42,15 @@
static void get_xml (GDataParsable *parsable, GString *xml_string);
+static const struct { const gchar *extension; const gchar *fmcmd; } export_formats[] = {
+ { "xls", "4" }, /* GDATA_DOCUMENTS_SPREADSHEET_XLS */
+ { "csv", "5" }, /* GDATA_DOCUMENTS_SPREADSHEET_CSV */
+ { "pdf", "12" }, /* GDATA_DOCUMENTS_SPREADSHEET_PDF */
+ { "ods", "13" }, /* GDATA_DOCUMENTS_SPREADSHEET_ODS */
+ { "tsv", "23" }, /* GDATA_DOCUMENTS_SPREADSHEET_TSV */
+ { "html", "102" } /* GDATA_DOCUMENTS_SPREADSHEET_HTML */
+};
+
G_DEFINE_TYPE (GDataDocumentsSpreadsheet, gdata_documents_spreadsheet, GDATA_TYPE_DOCUMENTS_ENTRY)
#define GDATA_DOCUMENTS_SPREADSHEET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_SPREADSHEET, GDataDocumentsSpreadsheetPrivate))
@@ -106,8 +115,8 @@ gdata_documents_spreadsheet_new (const gchar *id)
* If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
*
* When requesting a %GDATA_DOCUMENTS_SPREADSHEET_CSV or %GDATA_DOCUMENTS_SPREADSHEET_TSV file you must specify an additional
- * parameter called @gid which indicates which grid, or sheet, you wish to get (the index is %0-based, so gid %1 actually refers
- * to the second sheet sheet on a given spreadsheet).
+ * parameter called @gid which indicates which grid, or sheet, you wish to get (the index is %0-based, so GID %1 actually refers
+ * to the second sheet on a given spreadsheet).
*
* If @destination_file is a directory, then the file will be downloaded in this directory with the #GDataEntry:title with
* the apropriate extension as name.
@@ -124,47 +133,25 @@ gdata_documents_spreadsheet_download_document (GDataDocumentsSpreadsheet *self,
gboolean replace_file_if_exists, GCancellable *cancellable, GError **error)
{
gchar *link_href;
- const gchar *document_id, *extension, *fmcmd;
+ const gchar *extension;
GDataService *spreadsheet_service;
- const struct { const gchar *extension; const gchar *fmcmd; } export_formats[] = {
- { "xls", "4" },
- { "csv", "5" },
- { "pdf", "12" },
- { "ods", "13" },
- { "tsv", "23" },
- { "html", "102" }
- };
-
/* TODO: async version */
g_return_val_if_fail (GDATA_IS_DOCUMENTS_SPREADSHEET (self), NULL);
g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (service), NULL);
- g_return_val_if_fail (export_format >= 0 && export_format < G_N_ELEMENTS (export_formats), NULL);
+ g_return_val_if_fail (export_format < G_N_ELEMENTS (export_formats), NULL);
g_return_val_if_fail (gid >= -1, NULL);
- g_return_val_if_fail ((export_format != GDATA_DOCUMENTS_SPREADSHEET_CSV && export_format != GDATA_DOCUMENTS_SPREADSHEET_TSV) ||
- gid != -1, NULL);
+ g_return_val_if_fail ((export_format != GDATA_DOCUMENTS_SPREADSHEET_CSV && export_format != GDATA_DOCUMENTS_SPREADSHEET_TSV) || gid != -1, NULL);
g_return_val_if_fail (G_IS_FILE (destination_file), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
- document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (self));
- g_assert (document_id != NULL);
-
extension = export_formats[export_format].extension;
- fmcmd = export_formats[export_format].fmcmd;
-
- /* Build the download URI */
- if (gid != -1) {
- link_href = g_strdup_printf ("http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=%s&fmcmd=%s&gid=%d",
- document_id, fmcmd, gid);
- } else {
- link_href = g_strdup_printf ("http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=%s&fmcmd=%s",
- document_id, fmcmd);
- }
/* Get the spreadsheet service */
spreadsheet_service = _gdata_documents_service_get_spreadsheet_service (service);
/* Download the document */
+ link_href = gdata_documents_spreadsheet_get_download_uri (self, export_format, gid);
destination_file = _gdata_documents_entry_download_document (GDATA_DOCUMENTS_ENTRY (self), spreadsheet_service, content_type,
link_href, destination_file, extension, replace_file_if_exists,
cancellable, error);
@@ -172,3 +159,41 @@ gdata_documents_spreadsheet_download_document (GDataDocumentsSpreadsheet *self,
return destination_file;
}
+
+/**
+ * gdata_documents_spreadsheet_get_download_uri:
+ * @self: a #GDataDocumentsSpreadsheet
+ * @export_format: the format in which the spreadsheet should be exported when downloaded
+ * @gid: the %0-based sheet ID to download, or %-1
+ *
+ * Builds and returns the download URI for the given #GDataDocumentsSpreadsheet in the desired format. Note that directly downloading
+ * the document using this URI isn't possible, as authentication is required. You should instead use gdata_download_stream_new() with
+ * the URI, and use the resulting #GInputStream.
+ *
+ * When requesting a %GDATA_DOCUMENTS_SPREADSHEET_CSV or %GDATA_DOCUMENTS_SPREADSHEET_TSV file you must specify an additional
+ * parameter called @gid which indicates which grid, or sheet, you wish to get (the index is %0-based, so GID %1 actually refers
+ * to the second sheet on a given spreadsheet).
+ *
+ * Return value: the download URI; free with g_free()
+ *
+ * Since: 0.5.0
+ **/
+gchar *
+gdata_documents_spreadsheet_get_download_uri (GDataDocumentsSpreadsheet *self, GDataDocumentsSpreadsheetFormat export_format, gint gid)
+{
+ const gchar *document_id, *fmcmd;
+
+ g_return_val_if_fail (export_format < G_N_ELEMENTS (export_formats), NULL);
+ g_return_val_if_fail (gid >= -1, NULL);
+ g_return_val_if_fail ((export_format != GDATA_DOCUMENTS_SPREADSHEET_CSV && export_format != GDATA_DOCUMENTS_SPREADSHEET_TSV) || gid != -1, NULL);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (self));
+ g_assert (document_id != NULL);
+
+ fmcmd = export_formats[export_format].fmcmd;
+
+ if (gid != -1)
+ return g_strdup_printf ("http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=%s&fmcmd=%s&gid=%d", document_id, fmcmd, gid);
+ else
+ return g_strdup_printf ("http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=%s&fmcmd=%s", document_id, fmcmd);
+}
diff --git a/gdata/services/documents/gdata-documents-spreadsheet.h b/gdata/services/documents/gdata-documents-spreadsheet.h
index 30b2045..42ef751 100644
--- a/gdata/services/documents/gdata-documents-spreadsheet.h
+++ b/gdata/services/documents/gdata-documents-spreadsheet.h
@@ -93,6 +93,8 @@ GFile *gdata_documents_spreadsheet_download_document (GDataDocumentsSpreadsheet
GDataDocumentsSpreadsheetFormat export_format, gint gid, GFile *destination_file,
gboolean replace_file_if_exists, GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
+gchar *gdata_documents_spreadsheet_get_download_uri (GDataDocumentsSpreadsheet *self, GDataDocumentsSpreadsheetFormat export_format,
+ gint gid) G_GNUC_WARN_UNUSED_RESULT;
G_END_DECLS
diff --git a/gdata/services/documents/gdata-documents-text.c b/gdata/services/documents/gdata-documents-text.c
index 933059a..8ae323f 100644
--- a/gdata/services/documents/gdata-documents-text.c
+++ b/gdata/services/documents/gdata-documents-text.c
@@ -42,6 +42,17 @@
static void get_xml (GDataParsable *parsable, GString *xml_string);
+static const gchar *export_formats[] = {
+ "doc", /* GDATA_DOCUMENTS_TEXT_DOC */
+ "html", /* GDATA_DOCUMENTS_TEXT_HTML */
+ "odt", /* GDATA_DOCUMENTS_TEXT_ODT */
+ "pdf", /* GDATA_DOCUMENTS_TEXT_PDF */
+ "png", /* GDATA_DOCUMENTS_TEXT_PNG */
+ "rtf", /* GDATA_DOCUMENTS_TEXT_RTF */
+ "txt", /* GDATA_DOCUMENTS_TEXT_TXT */
+ "zip" /* GDATA_DOCUMENTS_TEXT_ZIP */
+};
+
G_DEFINE_TYPE (GDataDocumentsText, gdata_documents_text, GDATA_TYPE_DOCUMENTS_ENTRY)
#define GDATA_DOCUMENTS_TEXT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_TEXT, GDataDocumentsTextClass))
@@ -118,34 +129,16 @@ gdata_documents_text_download_document (GDataDocumentsText *self, GDataDocuments
GDataDocumentsTextFormat export_format, GFile *destination_file,
gboolean replace_file_if_exists, GCancellable *cancellable, GError **error)
{
- const gchar *document_id;
gchar *link_href;
- const gchar *export_formats[] = {
- "doc", /* GDATA_DOCUMENTS_TEXT_DOC */
- "html", /* GDATA_DOCUMENTS_TEXT_HTML */
- "odt", /* GDATA_DOCUMENTS_TEXT_ODT */
- "pdf", /* GDATA_DOCUMENTS_TEXT_PDF */
- "png", /* GDATA_DOCUMENTS_TEXT_PNG */
- "rtf", /* GDATA_DOCUMENTS_TEXT_RTF */
- "txt", /* GDATA_DOCUMENTS_TEXT_TXT */
- "zip" /* GDATA_DOCUMENTS_TEXT_ZIP */
- };
-
- /* TODO: async version */
g_return_val_if_fail (GDATA_IS_DOCUMENTS_TEXT (self), NULL);
g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (service), NULL);
- g_return_val_if_fail (export_format >= 0 && export_format < G_N_ELEMENTS (export_formats), NULL);
+ g_return_val_if_fail (export_format < G_N_ELEMENTS (export_formats), NULL);
g_return_val_if_fail (G_IS_FILE (destination_file), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
- document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (self));
- g_assert (document_id != NULL);
-
- link_href = g_strdup_printf ("http://docs.google.com/feeds/download/presentations/Export?exportFormat=%s&docID=%s",
- export_formats[export_format], document_id);
-
/* Download the file */
+ link_href = gdata_documents_text_get_download_uri (self, export_format);
destination_file = _gdata_documents_entry_download_document (GDATA_DOCUMENTS_ENTRY (self), GDATA_SERVICE (service),
content_type, link_href, destination_file, export_formats[export_format], replace_file_if_exists,
cancellable, error);
@@ -153,3 +146,29 @@ gdata_documents_text_download_document (GDataDocumentsText *self, GDataDocuments
return destination_file;
}
+
+/**
+ * gdata_documents_text_get_download_uri:
+ * @self: a #GDataDocumentsText
+ * @export_format: the format in which the document should be exported when downloaded
+ *
+ * Builds and returns the download URI for the given #GDataDocumentsText in the desired format. Note that directly downloading
+ * the document using this URI isn't possible, as authentication is required. You should instead use gdata_download_stream_new() with
+ * the URI, and use the resulting #GInputStream.
+ *
+ * Return value: the download URI; free with g_free()
+ *
+ * Since: 0.5.0
+ **/
+gchar *
+gdata_documents_text_get_download_uri (GDataDocumentsText *self, GDataDocumentsTextFormat export_format)
+{
+ const gchar *document_id;
+
+ g_return_val_if_fail (export_format < G_N_ELEMENTS (export_formats), NULL);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (self));
+ g_assert (document_id != NULL);
+
+ return g_strdup_printf ("http://docs.google.com/feeds/download/documents/Export?exportFormat=%s&docID=%s", export_formats[export_format], document_id);
+}
diff --git a/gdata/services/documents/gdata-documents-text.h b/gdata/services/documents/gdata-documents-text.h
index f8526df..393dd98 100644
--- a/gdata/services/documents/gdata-documents-text.h
+++ b/gdata/services/documents/gdata-documents-text.h
@@ -97,6 +97,7 @@ GFile *gdata_documents_text_download_document (GDataDocumentsText *self, GDataDo
GDataDocumentsTextFormat export_format, GFile *destination_file,
gboolean replace_file_if_exists, GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
+gchar *gdata_documents_text_get_download_uri (GDataDocumentsText *self, GDataDocumentsTextFormat export_format) G_GNUC_WARN_UNUSED_RESULT;
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]