[libgdata] Bug 587073 – Add Google Documents service
- From: Philip Withnall <pwithnall src gnome org>
- To: svn-commits-list gnome org
- Subject: [libgdata] Bug 587073 – Add Google Documents service
- Date: Fri, 17 Jul 2009 18:05:51 +0000 (UTC)
commit 4ee6c030ecdc75086d6317fd0e2e8fa8ae454903
Author: Thibault Saunier <saunierthibault gmail com>
Date: Fri Jul 17 19:04:15 2009 +0100
Bug 587073 â?? Add Google Documents service
Added support for Google Documents, from work done by Thibault Saunier
<saunierthibault gmail com>. Closes bgo#587073 and bgo#588282.
HACKING | 2 +
configure.in | 3 +-
docs/reference/gdata-docs.xml | 13 +-
docs/reference/gdata-sections.txt | 188 +++++
gdata/Makefile.am | 1 +
gdata/atom/gdata-link.c | 6 +-
gdata/gdata-entry.c | 35 +
gdata/gdata-entry.h | 1 +
gdata/gdata-feed.c | 100 ++-
gdata/gdata-parsable.c | 7 +-
gdata/gdata-private.h | 13 +
gdata/gdata.h | 10 +
gdata/gdata.symbols | 48 ++
gdata/services/Makefile.am | 2 +-
gdata/services/documents/Makefile.am | 80 ++
gdata/services/documents/gdata-documents-entry.c | 633 ++++++++++++++++
gdata/services/documents/gdata-documents-entry.h | 81 ++
gdata/services/documents/gdata-documents-feed.c | 119 +++
gdata/services/documents/gdata-documents-feed.h | 67 ++
gdata/services/documents/gdata-documents-folder.c | 88 +++
gdata/services/documents/gdata-documents-folder.h | 69 ++
.../documents/gdata-documents-presentation.c | 151 ++++
.../documents/gdata-documents-presentation.h | 96 +++
gdata/services/documents/gdata-documents-query.c | 563 ++++++++++++++
gdata/services/documents/gdata-documents-query.h | 86 +++
gdata/services/documents/gdata-documents-service.c | 767 ++++++++++++++++++++
gdata/services/documents/gdata-documents-service.h | 105 +++
.../documents/gdata-documents-spreadsheet.c | 172 +++++
.../documents/gdata-documents-spreadsheet.h | 99 +++
gdata/services/documents/gdata-documents-text.c | 154 ++++
gdata/services/documents/gdata-documents-text.h | 103 +++
gdata/services/youtube/gdata-youtube-video.c | 2 +
gdata/tests/Makefile.am | 3 +
gdata/tests/common.h | 1 +
gdata/tests/documents.c | 588 +++++++++++++++
gdata/tests/picasaweb.c | 1 -
po/fr.po | 4 +
37 files changed, 4418 insertions(+), 43 deletions(-)
---
diff --git a/HACKING b/HACKING
index f3a901f..45ea847 100644
--- a/HACKING
+++ b/HACKING
@@ -139,6 +139,8 @@ The short explanation of a commit should always be prefixed by a tag to describe
- [contacts] â?? for the Google Contacts code in gdata/services/contacts.
+ - [documents] â?? for the Google Documents code in gdata/services/documents.
+
- [picasaweb] â?? for the PicasaWeb code in gdata/services/picasaweb.
- [youtube] â?? for the YouTube code in gdata/services/youtube.
diff --git a/configure.in b/configure.in
index 49ab4f6..7aa5e55 100644
--- a/configure.in
+++ b/configure.in
@@ -91,8 +91,9 @@ gdata/media/Makefile
gdata/services/Makefile
gdata/services/calendar/Makefile
gdata/services/contacts/Makefile
-gdata/services/youtube/Makefile
+gdata/services/documents/Makefile
gdata/services/picasaweb/Makefile
+gdata/services/youtube/Makefile
gdata/tests/Makefile
po/Makefile.in
docs/Makefile
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 2e9daa4..6e5be1a 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -47,7 +47,6 @@
<xi:include href="xml/gdata-category.xml"/>
<xi:include href="xml/gdata-generator.xml"/>
<xi:include href="xml/gdata-link.xml"/>
- <xi:include href="xml/gdata-gdata.xml"/>
</chapter>
<chapter>
@@ -105,6 +104,18 @@
</chapter>
<chapter>
+ <title>Google Documents API</title>
+ <xi:include href="xml/gdata-documents-service.xml"/>
+ <xi:include href="xml/gdata-documents-feed.xml"/>
+ <xi:include href="xml/gdata-documents-query.xml"/>
+ <xi:include href="xml/gdata-documents-entry.xml"/>
+ <xi:include href="xml/gdata-documents-folder.xml"/>
+ <xi:include href="xml/gdata-documents-presentation.xml"/>
+ <xi:include href="xml/gdata-documents-spreadsheet.xml"/>
+ <xi:include href="xml/gdata-documents-text.xml"/>
+ </chapter>
+
+ <chapter>
<title>Google PicasaWeb API</title>
<xi:include href="xml/gdata-picasaweb-service.xml"/>
<xi:include href="xml/gdata-picasaweb-query.xml"/>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 94c3b76..229f4cb 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -147,6 +147,7 @@ gdata_entry_add_category
gdata_entry_get_categories
gdata_entry_add_link
gdata_entry_look_up_link
+gdata_entry_look_up_links
gdata_entry_is_inserted
<SUBSECTION Standard>
gdata_entry_get_type
@@ -256,6 +257,7 @@ gdata_contacts_service_new
gdata_contacts_service_query_contacts
gdata_contacts_service_query_contacts_async
gdata_contacts_service_insert_contact
+gdata_contacts_service_update_contact
<SUBSECTION Standard>
gdata_contacts_service_get_type
GDATA_CONTACTS_SERVICE
@@ -1260,3 +1262,189 @@ GDATA_TYPE_PICASAWEB_FILE
<SUBSECTION Private>
GDataPicasaWebFilePrivate
</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-entry</FILE>
+<TITLE>GDataDocumentsEntry</TITLE>
+GDataDocumentsEntry
+GDataDocumentsEntryClass
+gdata_documents_entry_get_path
+gdata_documents_entry_get_document_id
+gdata_documents_entry_get_edited
+gdata_documents_entry_get_last_modified_by
+gdata_documents_entry_get_last_viewed
+gdata_documents_entry_writers_can_invite
+gdata_documents_entry_set_writers_can_invite
+<SUBSECTION Standard>
+gdata_documents_entry_get_type
+GDATA_DOCUMENTS_ENTRY
+GDATA_DOCUMENTS_ENTRY_CLASS
+GDATA_DOCUMENTS_ENTRY_GET_CLASS
+GDATA_IS_DOCUMENTS_ENTRY
+GDATA_IS_DOCUMENTS_ENTRY_CLASS
+GDATA_TYPE_DOCUMENTS_ENTRY
+<SUBSECTION Private>
+GDataDocumentsEntryPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-feed</FILE>
+<TITLE>GDataDocumentsFeed</TITLE>
+GDataDocumentsFeed
+GDataDocumentsFeedClass
+<SUBSECTION Standard>
+gdata_documents_feed_get_type
+GDATA_DOCUMENTS_FEED
+GDATA_DOCUMENTS_FEED_CLASS
+GDATA_DOCUMENTS_FEED_GET_CLASS
+GDATA_IS_DOCUMENTS_FEED
+GDATA_IS_DOCUMENTS_FEED_CLASS
+GDATA_TYPE_DOCUMENTS_FEED
+<SUBSECTION Private>
+GDataDocumentsFeedPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-folder</FILE>
+<TITLE>GDataDocumentsFolder</TITLE>
+GDataDocumentsFolder
+GDataDocumentsFolderClass
+gdata_documents_folder_new
+<SUBSECTION Standard>
+gdata_documents_folder_get_type
+GDATA_DOCUMENTS_FOLDER
+GDATA_DOCUMENTS_FOLDER_CLASS
+GDATA_DOCUMENTS_FOLDER_GET_CLASS
+GDATA_IS_DOCUMENTS_FOLDER
+GDATA_IS_DOCUMENTS_FOLDER_CLASS
+GDATA_TYPE_DOCUMENTS_FOLDER
+<SUBSECTION Private>
+GDataDocumentsFolderPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-presentation</FILE>
+<TITLE>GDataDocumentsPresentation</TITLE>
+GDataDocumentsPresentation
+GDataDocumentsPresentationClass
+GDataDocumentsPresentationFormat
+gdata_documents_presentation_new
+gdata_documents_presentation_download_document
+<SUBSECTION Standard>
+gdata_documents_presentation_get_type
+GDATA_DOCUMENTS_PRESENTATION
+GDATA_DOCUMENTS_PRESENTATION_CLASS
+GDATA_DOCUMENTS_PRESENTATION_GET_CLASS
+GDATA_IS_DOCUMENTS_PRESENTATION
+GDATA_IS_DOCUMENTS_PRESENTATION_CLASS
+GDATA_TYPE_DATA_DOCUMENTS_PRESENTATION_FORMAT
+GDATA_TYPE_DOCUMENTS_PRESENTATION
+gdata_documents_presentation_format_get_type
+<SUBSECTION Private>
+GDataDocumentsPresentationPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-query</FILE>
+<TITLE>GDataDocumentsQuery</TITLE>
+GDataDocumentsQuery
+GDataDocumentsQueryClass
+gdata_documents_query_new
+gdata_documents_query_new_with_limits
+gdata_documents_query_show_deleted
+gdata_documents_query_set_show_deleted
+gdata_documents_query_show_folders
+gdata_documents_query_set_show_folders
+gdata_documents_query_get_folder_id
+gdata_documents_query_set_folder_id
+gdata_documents_query_get_title
+gdata_documents_query_get_exact_title
+gdata_documents_query_set_title
+gdata_documents_query_get_collaborator_addresses
+gdata_documents_query_add_collaborator
+gdata_documents_query_get_reader_addresses
+gdata_documents_query_add_reader
+<SUBSECTION Standard>
+gdata_documents_query_get_type
+GDATA_DOCUMENTS_QUERY
+GDATA_DOCUMENTS_QUERY_CLASS
+GDATA_DOCUMENTS_QUERY_GET_CLASS
+GDATA_IS_DOCUMENTS_QUERY
+GDATA_IS_DOCUMENTS_QUERY_CLASS
+GDATA_TYPE_DOCUMENTS_QUERY
+<SUBSECTION Private>
+GDataDocumentsQueryPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-spreadsheet</FILE>
+<TITLE>GDataDocumentsSpreadsheet</TITLE>
+GDataDocumentsSpreadsheet
+GDataDocumentsSpreadsheetClass
+GDataDocumentsSpreadsheetFormat
+gdata_documents_spreadsheet_new
+gdata_documents_spreadsheet_download_document
+<SUBSECTION Standard>
+gdata_documents_spreadsheet_get_type
+GDATA_DOCUMENTS_SPREADSHEET
+GDATA_DOCUMENTS_SPREADSHEET_CLASS
+GDATA_DOCUMENTS_SPREADSHEET_GET_CLASS
+GDATA_IS_DOCUMENTS_SPREADSHEET
+GDATA_IS_DOCUMENTS_SPREADSHEET_CLASS
+GDATA_TYPE_DATA_DOCUMENTS_SPREADSHEET_FORMAT
+GDATA_TYPE_DOCUMENTS_SPREADSHEET
+gdata_documents_spreadsheet_format_get_type
+<SUBSECTION Private>
+GDataDocumentsSpreadsheetPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-text</FILE>
+<TITLE>GDataDocumentsText</TITLE>
+GDataDocumentsText
+GDataDocumentsTextClass
+GDataDocumentsTextFormat
+gdata_documents_text_new
+gdata_documents_text_download_document
+<SUBSECTION Standard>
+gdata_documents_text_get_type
+GDATA_DOCUMENTS_TEXT
+GDATA_DOCUMENTS_TEXT_CLASS
+GDATA_DOCUMENTS_TEXT_GET_CLASS
+GDATA_IS_DOCUMENTS_TEXT
+GDATA_IS_DOCUMENTS_TEXT_CLASS
+GDATA_TYPE_DATA_DOCUMENTS_TEXT_FORMAT
+GDATA_TYPE_DOCUMENTS_TEXT
+gdata_documents_text_format_get_type
+<SUBSECTION Private>
+GDataDocumentsTextPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-documents-service</FILE>
+<TITLE>GDataDocumentsService</TITLE>
+GDataDocumentsService
+GDataDocumentsServiceClass
+GDataDocumentsServiceError
+gdata_documents_service_new
+gdata_documents_service_query_documents
+gdata_documents_service_query_documents_async
+gdata_documents_service_upload_document
+gdata_documents_service_update_document
+gdata_documents_service_move_document_to_folder
+gdata_documents_service_remove_document_from_folder
+<SUBSECTION Standard>
+gdata_documents_service_get_type
+GDATA_DOCUMENTS_SERVICE
+GDATA_DOCUMENTS_SERVICE_CLASS
+GDATA_DOCUMENTS_SERVICE_ERROR
+GDATA_DOCUMENTS_SERVICE_GET_CLASS
+GDATA_IS_DOCUMENTS_SERVICE
+GDATA_IS_DOCUMENTS_SERVICE_CLASS
+GDATA_TYPE_DATA_DOCUMENTS_SERVICE_ERROR
+GDATA_TYPE_DOCUMENTS_SERVICE
+gdata_documents_service_error_get_type
+gdata_documents_service_error_quark
+<SUBSECTION Private>
+GDataDocumentsServicePrivate
+</SECTION>
diff --git a/gdata/Makefile.am b/gdata/Makefile.am
index e3a085f..ee846bd 100644
--- a/gdata/Makefile.am
+++ b/gdata/Makefile.am
@@ -94,6 +94,7 @@ libgdata_la_LIBADD = \
services/youtube/libgdatayoutube.la \
services/calendar/libgdatacalendar.la \
services/picasaweb/libgdatapicasaweb.la \
+ services/documents/libgdatadocuments.la \
services/contacts/libgdatacontacts.la
libgdata_la_LDFLAGS = \
diff --git a/gdata/atom/gdata-link.c b/gdata/atom/gdata-link.c
index 6025e07..d40c3b1 100644
--- a/gdata/atom/gdata-link.c
+++ b/gdata/atom/gdata-link.c
@@ -322,7 +322,11 @@ pre_get_xml (GDataParsable *parsable, GString *xml_string)
{
GDataLinkPrivate *priv = GDATA_LINK (parsable)->priv;
- g_string_append_printf (xml_string, " href='%s'", priv->uri);
+ {
+ gchar *href = g_markup_escape_text (priv->uri, -1);
+ g_string_append_printf (xml_string, " href='%s'", href);
+ g_free (href);
+ }
if (priv->title != NULL) {
gchar *link_title = g_markup_escape_text (priv->title, -1);
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index 4ce3abc..2a2ecb3 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -705,6 +705,8 @@ link_compare_cb (const GDataLink *link, const gchar *rel)
* Looks up a link by relation type from the list of links in the entry. If the link has one of the standard Atom relation types,
* use one of the defined @rel values, instead of a static string. e.g. %GDATA_LINK_EDIT or %GDATA_LINK_SELF.
*
+ * In the rare event of requiring a list of links with the same @rel value, use gdata_entry_look_up_links().
+ *
* Return value: a #GDataLink, or %NULL if one was not found
*
* Since: 0.1.1
@@ -724,6 +726,39 @@ gdata_entry_look_up_link (GDataEntry *self, const gchar *rel)
}
/**
+ * gdata_entry_look_up_links:
+ * @self: a #GDataEntry
+ * @rel: the value of the <structfield>rel</structfield> attribute of the desired links
+ *
+ * Looks up a list of links by relation type from the list of links in the entry. If the links have one of the standard Atom
+ * relation types, use one of the defined @rel values, instead of a static string. e.g. %GDATA_LINK_EDIT or %GDATA_LINK_SELF.
+ *
+ * If you will only use the first link found, consider calling gdata_entry_look_up_link() instead.
+ *
+ * Return value: a #GList of #GDataLink<!-- -->s, or %NULL if none were found
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_entry_look_up_links (GDataEntry *self, const gchar *rel)
+{
+ GList *element = self->priv->links, *results = NULL;
+
+ g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
+ g_return_val_if_fail (rel != NULL, NULL);
+
+ do {
+ element = g_list_find_custom (element, rel, (GCompareFunc) link_compare_cb);
+ if (element == NULL)
+ return results;
+ results = g_list_prepend (results, element);
+ element = element->next;
+ } while (element != NULL);
+
+ return g_list_reverse (results);
+}
+
+/**
* gdata_entry_add_author:
* @self: a #GDataEntry
* @author: a #GDataAuthor to add
diff --git a/gdata/gdata-entry.h b/gdata/gdata-entry.h
index 50b85f3..b21e8c0 100644
--- a/gdata/gdata-entry.h
+++ b/gdata/gdata-entry.h
@@ -77,6 +77,7 @@ const gchar *gdata_entry_get_content (GDataEntry *self);
void gdata_entry_set_content (GDataEntry *self, const gchar *content);
void gdata_entry_add_link (GDataEntry *self, GDataLink *link);
GDataLink *gdata_entry_look_up_link (GDataEntry *self, const gchar *rel);
+GList *gdata_entry_look_up_links (GDataEntry *self, const gchar *rel);
void gdata_entry_add_author (GDataEntry *self, GDataAuthor *author);
gboolean gdata_entry_is_inserted (GDataEntry *self);
diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c
index 5a38c21..378acf0 100644
--- a/gdata/gdata-feed.c
+++ b/gdata/gdata-feed.c
@@ -376,15 +376,6 @@ typedef struct {
} ProgressCallbackData;
static gboolean
-progress_callback_idle (ProgressCallbackData *data)
-{
- data->progress_callback (data->entry, data->entry_i, data->total_results, data->progress_user_data);
- g_object_unref (data->entry);
- g_slice_free (ProgressCallbackData, data);
- return FALSE;
-}
-
-static gboolean
parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
{
GDataFeed *self;
@@ -401,26 +392,9 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
GDataEntry *entry = GDATA_ENTRY (_gdata_parsable_new_from_xml_node (data->entry_type, doc, node, NULL, error));
if (entry == NULL)
return FALSE;
-
- /* Call the progress callback in the main thread */
- if (data->progress_callback != NULL) {
- ProgressCallbackData *progress_data;
-
- /* Build the data for the callback */
- progress_data = g_slice_new (ProgressCallbackData);
- progress_data->progress_callback = data->progress_callback;
- progress_data->progress_user_data = data->progress_user_data;
- progress_data->entry = g_object_ref (entry);
- progress_data->entry_i = data->entry_i;
- progress_data->total_results = MIN (self->priv->items_per_page, self->priv->total_results);
-
- /* Send the callback; use G_PRIORITY_DEFAULT rather than G_PRIORITY_DEFAULT_IDLE
- * to contend with the priorities used by the callback functions in GAsyncResult */
- g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) progress_callback_idle, progress_data, NULL);
- }
-
- data->entry_i++;
- self->priv->entries = g_list_prepend (self->priv->entries, entry);
+ /*calls the callbacks in the main thread*/
+ _gdata_feed_call_progress_callback (self, data, entry);
+ _gdata_feed_add_entry (self, entry);
} else if (xmlStrcmp (node->name, (xmlChar*) "title") == 0) {
/* atom:title */
if (self->priv->title != NULL)
@@ -583,15 +557,11 @@ _gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType
g_return_val_if_fail (xml != NULL, NULL);
g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, FALSE);
- data = g_slice_new (ParseData);
- data->entry_type = entry_type;
- data->progress_callback = progress_callback;
- data->progress_user_data = progress_user_data;
- data->entry_i = 0;
+ data = _gdata_feed_parse_data_new (entry_type, progress_callback, progress_user_data);
feed = GDATA_FEED (_gdata_parsable_new_from_xml (feed_type, xml, length, data, error));
- g_slice_free (ParseData, data);
+ _gdata_feed_parse_data_free(data);
return feed;
}
@@ -870,3 +840,63 @@ gdata_feed_get_total_results (GDataFeed *self)
g_return_val_if_fail (GDATA_IS_FEED (self), 0);
return self->priv->total_results;
}
+
+void
+_gdata_feed_add_entry (GDataFeed *self, GDataEntry *entry)
+{
+ g_return_if_fail (GDATA_IS_FEED (self));
+ g_return_if_fail (GDATA_IS_ENTRY (entry));
+ self->priv->entries = g_list_prepend (self->priv->entries, entry);
+}
+
+gpointer
+_gdata_feed_parse_data_new (GType entry_type, GDataQueryProgressCallback progress_callback, gpointer progress_user_data)
+{
+ ParseData *data;
+ data = g_slice_new (ParseData);
+ data->entry_type = entry_type;
+ data->progress_callback = progress_callback;
+ data->progress_user_data = progress_user_data;
+ data->entry_i = 0;
+ return data;
+}
+
+void
+_gdata_feed_parse_data_free (gpointer data)
+{
+ g_slice_free (ParseData, data);
+}
+
+
+static gboolean
+progress_callback_idle (ProgressCallbackData *data)
+{
+ data->progress_callback (data->entry, data->entry_i, data->total_results, data->progress_user_data);
+ g_object_unref (data->entry);
+ g_slice_free (ProgressCallbackData, data);
+ return FALSE;
+}
+
+void
+_gdata_feed_call_progress_callback (GDataFeed *self, gpointer user_data, GDataEntry *entry)
+{
+ ParseData *data = user_data;
+
+ if (data->progress_callback != NULL) {
+ ProgressCallbackData *progress_data;
+
+ /* Build the data for the callback */
+ progress_data = g_slice_new (ProgressCallbackData);
+ progress_data->progress_callback = data->progress_callback;
+ progress_data->progress_user_data = data->progress_user_data;
+ progress_data->entry = g_object_ref (entry);
+ progress_data->entry_i = data->entry_i;
+ progress_data->total_results = MIN (self->priv->items_per_page, self->priv->total_results);
+
+ /* Send the callback; use G_PRIORITY_DEFAULT rather than G_PRIORITY_DEFAULT_IDLE
+ * to contend with the priorities used by the callback functions in GAsyncResult */
+ g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) progress_callback_idle, progress_data, NULL);
+ }
+ data->entry_i++;
+}
+
diff --git a/gdata/gdata-parsable.c b/gdata/gdata-parsable.c
index dc5efc3..e45e1b1 100644
--- a/gdata/gdata-parsable.c
+++ b/gdata/gdata-parsable.c
@@ -198,13 +198,14 @@ _gdata_parsable_new_from_xml_node (GType parsable_type, xmlDoc *doc, xmlNode *no
return FALSE;
g_assert (klass->element_name != NULL);
- if (xmlStrcmp (node->name, (xmlChar*) klass->element_name) != 0 ||
+ /* TODO: See gdata-documents-entry.c:260 for an example of where the code below doesn't work */
+ /*if (xmlStrcmp (node->name, (xmlChar*) klass->element_name) != 0 ||
(node->ns != NULL && xmlStrcmp (node->ns->prefix, (xmlChar*) klass->element_namespace) != 0)) {
- /* No <entry> element (required) */
+ * No <entry> element (required) *
xmlFreeDoc (doc);
gdata_parser_error_required_element_missing (klass->element_name, "root", error);
return NULL;
- }
+ }*/
/* Call the pre-parse function first */
if (klass->pre_parse_xml != NULL &&
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index 98e0791..a978d3a 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -51,6 +51,19 @@ void _gdata_parsable_get_xml (GDataParsable *self, GString *xml_string, gboolean
#include "gdata-feed.h"
GDataFeed *_gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType entry_type,
GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+void _gdata_feed_add_entry (GDataFeed *self, GDataEntry *entry);
+gpointer _gdata_feed_parse_data_new(GType entry_type, GDataQueryProgressCallback progress_callback, gpointer progress_user_data);
+void _gdata_feed_parse_data_free (gpointer data);
+void _gdata_feed_call_progress_callback (GDataFeed *self, gpointer user_data, GDataEntry *entry);
+
+#include "gdata/services/documents/gdata-documents-entry.h"
+GFile *_gdata_documents_entry_download_document (GDataDocumentsEntry *self, GDataService *service, gchar **content_type, gchar *download_uri,
+ GFile *destination_directory, const gchar *file_extension, gboolean replace_file_if_exists,
+ GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+#include "gdata/services/documents/gdata-documents-service.h"
+
+GDataService *_gdata_documents_service_get_spreadsheet_service (GDataDocumentsService *self);
#include "gdata-parser.h"
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 1b58c7e..d02c3c0 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -87,4 +87,14 @@
#include <gdata/services/contacts/gdata-contacts-contact.h>
#include <gdata/services/contacts/gdata-contacts-query.h>
+/* Google Documents*/
+#include <gdata/services/documents/gdata-documents-entry.h>
+#include <gdata/services/documents/gdata-documents-text.h>
+#include <gdata/services/documents/gdata-documents-spreadsheet.h>
+#include <gdata/services/documents/gdata-documents-presentation.h>
+#include <gdata/services/documents/gdata-documents-folder.h>
+#include <gdata/services/documents/gdata-documents-query.h>
+#include <gdata/services/documents/gdata-documents-service.h>
+#include <gdata/services/documents/gdata-documents-feed.h>
+
#endif /* !GDATA_H */
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 3bab83b..b666912 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -565,3 +565,51 @@ gdata_picasaweb_query_set_bounding_box
gdata_picasaweb_query_get_location
gdata_picasaweb_query_set_location
gdata_picasaweb_visibility_get_type
+gdata_documents_presentation_get_type
+gdata_documents_presentation_new
+gdata_documents_presentation_download_document
+gdata_documents_text_get_type
+gdata_documents_text_new
+gdata_documents_text_download_document
+gdata_documents_spreadsheet_get_type
+gdata_documents_spreadsheet_new
+gdata_documents_spreadsheet_download_document
+gdata_documents_folder_get_type
+gdata_documents_folder_new
+gdata_documents_service_get_type
+gdata_documents_service_new
+gdata_documents_service_query_documents
+gdata_documents_service_query_documents_async
+gdata_documents_service_upload_document
+gdata_documents_service_update_document
+gdata_documents_service_move_document_to_folder
+gdata_documents_service_remove_document_from_folder
+gdata_documents_feed_get_type
+gdata_documents_entry_get_type
+gdata_documents_entry_get_path
+gdata_documents_entry_get_document_id
+gdata_documents_entry_get_edited
+gdata_documents_entry_get_last_viewed
+gdata_documents_entry_set_writers_can_invite
+gdata_documents_entry_writers_can_invite
+gdata_documents_entry_get_last_modified_by
+gdata_documents_query_get_type
+gdata_documents_query_new
+gdata_documents_query_new_with_limits
+gdata_documents_query_show_deleted
+gdata_documents_query_set_show_deleted
+gdata_documents_query_show_folders
+gdata_documents_query_set_show_folders
+gdata_documents_query_get_folder_id
+gdata_documents_query_set_folder_id
+gdata_documents_query_get_title
+gdata_documents_query_get_exact_title
+gdata_documents_query_set_title
+gdata_documents_query_get_collaborator_addresses
+gdata_documents_query_get_reader_addresses
+gdata_documents_query_add_reader
+gdata_documents_query_add_collaborator
+gdata_documents_service_error_get_type
+gdata_documents_text_format_get_type
+gdata_documents_presentation_format_get_type
+gdata_documents_spreadsheet_format_get_type
diff --git a/gdata/services/Makefile.am b/gdata/services/Makefile.am
index 87d94f1..039829d 100644
--- a/gdata/services/Makefile.am
+++ b/gdata/services/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = youtube calendar contacts picasaweb
+SUBDIRS = youtube calendar contacts picasaweb documents
-include $(top_srcdir)/git.mk
diff --git a/gdata/services/documents/Makefile.am b/gdata/services/documents/Makefile.am
new file mode 100644
index 0000000..0e7a726
--- /dev/null
+++ b/gdata/services/documents/Makefile.am
@@ -0,0 +1,80 @@
+# Enums
+GDATA_DOCUMENTS_ENUM_FILES = \
+ gdata-documents-enums.c \
+ gdata-documents-enums.h
+
+gdata-documents-enums.h: $(gdata_documents_headers) Makefile
+ (cd $(srcdir) && $(GLIB_MKENUMS) \
+ --fhead "#ifndef GDATA_DOCUMENTS_ENUMS_H\n#define GDATA_DOCUMENTS_ENUMS_H\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+ --fprod "/* enumerations from \"@filename \" */\n" \
+ --vhead "GType @enum_name _get_type (void) G_GNUC_CONST;\n#define GDATA_TYPE_ ENUMSHORT@ (@enum_name _get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* !GDATA_DOCUMENTS_ENUMS_H */" $(gdata_documents_headers)) > gdata-documents-enums.h.tmp \
+ && sed "s/g_data/gdata/" gdata-documents-enums.h.tmp > gdata-documents-enums.h \
+ && rm -f gdata-documents-enums.h.tmp
+
+gdata-documents-enums.c: $(gdata_documents_headers) Makefile gdata-documents-enums.h
+ (cd $(srcdir) && $(GLIB_MKENUMS) \
+ --fhead "#include \"gdata-documents-service.h\"\n#include \"gdata-documents-text.h\"\n#include \"gdata-documents-spreadsheet.h\"\n#include \"gdata-documents-presentation.h\"\n#include \"gdata-documents-enums.h\"" \
+ --fprod "\n/* enumerations from \"@filename \" */" \
+ --vhead "GType\n enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G Type@Value values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
+ --vtail " { 0, NULL, NULL }\n };\n etype = g_ type@_register_static (\"@EnumName \", values);\n }\n return etype;\n}\n" \
+ $(gdata_documents_headers)) > gdata-documents-enums.c.tmp \
+ && sed "s/g_data/gdata/" gdata-documents-enums.c.tmp > gdata-documents-enums.c \
+ && rm -f gdata-documents-enums.c.tmp
+
+# Documents library
+gdatadocumentsincludedir = $(pkgincludedir)/gdata/services/documents
+
+gdata_documents_headers = \
+ gdata-documents-service.h \
+ gdata-documents-feed.h \
+ gdata-documents-entry.h \
+ gdata-documents-query.h \
+ gdata-documents-text.h \
+ gdata-documents-presentation.h \
+ gdata-documents-folder.h \
+ gdata-documents-spreadsheet.h
+
+gdatadocumentsinclude_HEADERS = \
+ $(gdata_documents_headers) \
+ gdata-documents-enums.h
+
+noinst_LTLIBRARIES = libgdatadocuments.la
+
+libgdatadocuments_la_SOURCES = \
+ $(GDATA_DOCUMENTS_ENUM_FILES) \
+ gdata-documents-service.c \
+ gdata-documents-feed.c \
+ gdata-documents-entry.c \
+ gdata-documents-text.c \
+ gdata-documents-presentation.c \
+ gdata-documents-spreadsheet.c \
+ gdata-documents-folder.c \
+ gdata-documents-query.c
+
+libgdatadocuments_la_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gdata \
+ -I$(top_srcdir)/gdata/services/documents \
+ $(DISABLE_DEPRECATED) \
+ $(AM_CPPFLAGS)
+
+libgdatadocuments_la_CFLAGS = \
+ $(GDATA_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(AM_CFLAGS) \
+ -D_GNU_SOURCE
+
+libgdatadocuments_la_LIBADD = \
+ $(GDATA_LIBS)
+
+libgdatadocuments_la_LDFLAGS = \
+ -no-undefined \
+ $(AM_LDFLAGS)
+
+# General cleanup
+CLEANFILES = \
+ $(GDATA_DOCUMENTS_ENUM_FILES)
+
+-include $(top_srcdir)/git.mk
diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c
new file mode 100644
index 0000000..0245558
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-entry.c
@@ -0,0 +1,633 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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-documents-entry
+ * @short_description: GData document object abstract class
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-entry.h
+ *
+ * #GDataDocumentsEntry is a subclass of #GDataEntry to represent a Google Documents entry, which is then further subclassed
+ * to give specific document types.
+ *
+ * For more details of Google Documents' GData API, see the <ulink type="http://code.google.com/apis/document/docs/2.0/developers_guide_protocol.html">
+ * online documentation</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-documents-entry.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+#include "gdata-access-handler.h"
+
+static void gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface);
+static void gdata_documents_entry_finalize (GObject *object);
+static void gdata_entry_dispose (GObject *object);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+static void gdata_documents_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_documents_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+
+struct _GDataDocumentsEntryPrivate {
+ GTimeVal edited;
+ GTimeVal last_viewed;
+ gchar *path;
+ gchar *document_id;
+ gboolean writers_can_invite;
+ GDataAuthor *last_modified_by;
+};
+
+enum {
+ PROP_EDITED = 1,
+ PROP_LAST_VIEWED,
+ PROP_PATH,
+ PROP_DOCUMENT_ID,
+ PROP_LAST_MODIFIED_BY,
+ PROP_WRITERS_CAN_INVITE
+};
+
+G_DEFINE_TYPE_WITH_CODE (GDataDocumentsEntry, gdata_documents_entry, GDATA_TYPE_ENTRY,
+ G_IMPLEMENT_INTERFACE (GDATA_TYPE_ACCESS_HANDLER, gdata_documents_entry_access_handler_init))
+#define GDATA_DOCUMENTS_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntryPrivate))
+
+static void
+gdata_documents_entry_class_init (GDataDocumentsEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataDocumentsEntryPrivate));
+
+ gobject_class->get_property = gdata_documents_entry_get_property;
+ gobject_class->set_property = gdata_documents_entry_set_property;
+ gobject_class->finalize = gdata_documents_entry_finalize;
+ gobject_class->dispose = gdata_entry_dispose;
+
+ parsable_class->parse_xml = parse_xml;
+ parsable_class->get_xml = get_xml;
+ parsable_class->get_namespaces = get_namespaces;
+
+ /**
+ * GDataDocumentsEntry:edited
+ *
+ * The last time the document was edited. If the document has not been edited yet, the content indicates the time it was created.
+ *
+ * For more information, see the <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appEdited">
+ * Atom Publishing Protocol specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_EDITED,
+ g_param_spec_boxed ("edited",
+ "Edited", "The last time the document was edited.",
+ GDATA_TYPE_G_TIME_VAL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsEntry:last-viewed
+ *
+ * The last time the document was viewed.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_LAST_VIEWED,
+ g_param_spec_boxed ("last-viewed",
+ "Last viewed", "The last time the document was viewed.",
+ GDATA_TYPE_G_TIME_VAL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsEntry:writers-can-invite:
+ *
+ * Indicates whether the document entry writers can invite others to edit the document.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_WRITERS_CAN_INVITE,
+ g_param_spec_boolean ("writers-can-invite",
+ "Writers can invite?", "Indicates whether writers can invite others to edit.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsEntry:path
+ *
+ * Indicates the folder hierarchy path containing the document.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_PATH,
+ g_param_spec_string ("path",
+ "Path", "Indicates the folder hierarchy path containing the document.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsEntry:document-id
+ *
+ * The document ID of the document, which is different from its entry ID (GDataEntry:id).
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_DOCUMENT_ID,
+ g_param_spec_string ("document-id",
+ "Document ID", "The document ID of the document.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsEntry:last-modified-by
+ *
+ * Indicates the author of the last modification.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_LAST_MODIFIED_BY,
+ g_param_spec_object ("last-modified-by",
+ "Last modified by", "Indicates the author of the last modification.",
+ GDATA_TYPE_AUTHOR,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_documents_entry_init (GDataDocumentsEntry *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntryPrivate);
+}
+
+static gboolean
+is_owner_rule (GDataAccessRule *rule)
+{
+ return (strcmp (gdata_access_rule_get_role (rule), "owner") == 0) ? TRUE : FALSE;
+}
+
+static void
+gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface)
+{
+ iface->is_owner_rule = is_owner_rule;
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+ GDataDocumentsEntry *self;
+
+ self = GDATA_DOCUMENTS_ENTRY (parsable);
+
+ if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
+ xmlChar *edited = xmlNodeListGetString (doc, node->children, TRUE);
+ if (g_time_val_from_iso8601 ((gchar*) edited, &(self->priv->edited)) == FALSE) {
+ gdata_parser_error_not_iso8601_format (node, (gchar*) edited, error);
+ xmlFree (edited);
+ return FALSE;
+ }
+ xmlFree (edited);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "lastViewed") == 0) {
+ xmlChar *last_viewed = xmlNodeListGetString (doc, node->children, TRUE);
+ if (g_time_val_from_iso8601 ((gchar*) last_viewed, &(self->priv->last_viewed)) == FALSE) {
+ gdata_parser_error_not_iso8601_format (node, (gchar*) last_viewed, error);
+ xmlFree (last_viewed);
+ return FALSE;
+ }
+ xmlFree (last_viewed);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "writersCanInvite") == 0) {
+ xmlChar *writers_can_invite = xmlGetProp (node, (xmlChar*) "value");
+ if (xmlStrcmp (writers_can_invite, (xmlChar*) "true") == 0) {
+ self->priv->writers_can_invite = TRUE;
+ } else if (xmlStrcmp (writers_can_invite, (xmlChar*) "false") == 0) {
+ self->priv->writers_can_invite = FALSE;
+ } else {
+ gdata_parser_error_unknown_property_value (node, "value", (gchar*) writers_can_invite, error);
+ xmlFree (writers_can_invite);
+ return FALSE;
+ }
+ xmlFree (writers_can_invite);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "resourceId") == 0) {
+ gchar **document_id_parts;
+ xmlChar *resource_id;
+
+ if (self->priv->document_id != NULL)
+ return gdata_parser_error_duplicate_element (node, error);
+
+ resource_id = xmlNodeListGetString (doc, node->children, TRUE);
+ if (resource_id == NULL || *resource_id == '\0') {
+ xmlFree (resource_id);
+ return gdata_parser_error_required_content_missing (node, error);
+ }
+
+ document_id_parts = g_strsplit ((gchar*) resource_id, ":", 2);
+ if (document_id_parts == NULL) {
+ gdata_parser_error_unknown_content (node, (gchar*) resource_id, error);
+ xmlFree (resource_id);
+ return FALSE;
+ }
+ xmlFree (resource_id);
+
+ self->priv->document_id = g_strdup (document_id_parts[1]);
+ g_strfreev (document_id_parts);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "feedLink") == 0) {
+ GDataLink *link = GDATA_LINK (_gdata_parsable_new_from_xml_node (GDATA_TYPE_LINK, doc, node, NULL, error));
+ if (link == NULL)
+ return FALSE;
+ gdata_entry_add_link (GDATA_ENTRY (self), link);
+ g_object_unref (link);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "lastModifiedBy") == 0) {
+ GDataAuthor *last_modified_by = GDATA_AUTHOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_AUTHOR, doc, node, NULL, error));
+ if (last_modified_by == NULL)
+ return FALSE;
+ self->priv->last_modified_by = last_modified_by;
+ } else if (GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+ /* Error! */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gdata_documents_entry_finalize (GObject *object)
+{
+ GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY_GET_PRIVATE (object);
+
+ g_free (priv->path);
+ g_free (priv->document_id);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_documents_entry_parent_class)->finalize (object);
+}
+
+static void
+gdata_entry_dispose (GObject *object)
+{
+ GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY_GET_PRIVATE (object);
+
+ if (priv->last_modified_by != NULL)
+ g_object_unref (priv->last_modified_by);
+ priv->last_modified_by = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_documents_entry_parent_class)->dispose (object);
+}
+
+static void
+gdata_documents_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_PATH:
+ g_value_set_string (value, priv->path);
+ break;
+ case PROP_DOCUMENT_ID:
+ g_value_set_string (value, priv->document_id);
+ break;
+ case PROP_WRITERS_CAN_INVITE:
+ g_value_set_boolean (value, priv->writers_can_invite);
+ break;
+ case PROP_EDITED:
+ g_value_set_boxed (value, &(priv->edited));
+ break;
+ case PROP_LAST_VIEWED:
+ g_value_set_boxed (value, &(priv->last_viewed));
+ break;
+ case PROP_LAST_MODIFIED_BY:
+ g_value_set_object (value, priv->last_modified_by);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_documents_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataDocumentsEntry *self = GDATA_DOCUMENTS_ENTRY (object);
+
+ switch (property_id) {
+ case PROP_WRITERS_CAN_INVITE:
+ gdata_documents_entry_set_writers_can_invite (self, g_value_get_boolean (value));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY (parsable)->priv;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->get_xml (parsable, xml_string);
+
+ /* TODO: Only output "kind" categories? */
+
+ if (priv->writers_can_invite == TRUE)
+ g_string_append (xml_string, "<docs:writersCanInvite value='true'/>");
+ else
+ g_string_append (xml_string, "<docs:writersCanInvite value='false'/>");
+}
+
+
+static void
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
+{
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->get_namespaces (parsable, namespaces);
+
+ g_hash_table_insert (namespaces, (gchar*) "docs", (gchar*) "http://schemas.google.com/docs/2007");
+}
+
+/**
+ * gdata_documents_entry_get_edited:
+ * @self: a #GDataDocumentsEntry
+ * @edited: a #GTimeVal
+ *
+ * Gets the #GDataDocumentsEntry:edited property and puts it in @edited. If the property is unset,
+ * both fields in the #GTimeVal will be set to %0.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_entry_get_edited (GDataDocumentsEntry *self, GTimeVal *edited)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self));
+ g_return_if_fail (edited != NULL);
+ edited = &(self->priv->edited);
+}
+
+/**
+ * gdata_documents_entry_get_last_viewed:
+ * @self: a #GDataDocumentsEntry
+ * @last_viewed: a #GTimeVal
+ *
+ * Gets the #GDataDocumentsEntry:last-viewed property and puts it in @last_viewed. If the property is unset,
+ * both fields in the #GTimeVal will be set to %0.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_entry_get_last_viewed (GDataDocumentsEntry *self, GTimeVal *last_viewed)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self));
+ g_return_if_fail (last_viewed != NULL);
+ *last_viewed = self->priv->last_viewed;
+}
+
+/**
+ * gdata_documents_entry_get_path:
+ * @self: a #GDataDocumentsEntry
+ *
+ * Gets the #GDataDocumentsEntry:path property.
+ *
+ * Return value: the folder hierarchy path containing the entry
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_documents_entry_get_path (GDataDocumentsEntry *self)
+{
+ GList *element, *parent_folders;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL);
+
+ if (self->priv->path != NULL)
+ return self->priv->path;
+
+ parent_folders = gdata_entry_look_up_links (GDATA_ENTRY (self), "http://schemas.google.com/docs/2007#parent");
+ for (element = parent_folders; element != NULL; element = element->next) {
+ if (self->priv->path == NULL)
+ self->priv->path = g_strdup (gdata_link_get_title (((GDataLink*) element->data)));
+ else
+ self->priv->path = g_strconcat (self->priv->path, gdata_link_get_title (((GDataLink*) element->data)), NULL);
+ }
+
+ return self->priv->path;
+}
+
+/**
+ * gdata_documents_entry_get_document_id:
+ * @self: a #GDataDocumentsEntry
+ *
+ * Gets the #GDataDocumentsEntry:document-id property.
+ *
+ * Return value: the document's document ID
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_documents_entry_get_document_id (GDataDocumentsEntry *self )
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL);
+ return self->priv->document_id;
+}
+
+/**
+ * gdata_documents_entry_set_writers_can_invite:
+ * @self: a #GDataDocumentsEntry
+ * @writers_can_invite: %TRUE if writers can invite other people to edit the document, %FALSE otherwise
+ *
+ * Sets the #GDataDocumentsEntry:writers-can-invite property to @writers_can_invite.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_entry_set_writers_can_invite (GDataDocumentsEntry *self, gboolean writers_can_invite)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self));
+ self->priv->writers_can_invite = writers_can_invite;
+ g_object_notify (G_OBJECT (self), "writers-can-invite");
+}
+
+/**
+ * gdata_documents_entry_writers_can_invite:
+ * @self: a #GDataDocumentsEntry
+ *
+ * Gets the #GDataDocumentsEntry:writers-can-invite property.
+ *
+ * Return value: %TRUE if writers can invite other people to edit the document, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_documents_entry_writers_can_invite (GDataDocumentsEntry *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self ), FALSE);
+ return self->priv->writers_can_invite;
+}
+
+/**
+ * gdata_documents_entry_get_last_modified_by:
+ * @self: a #GDataDocumentsEntry
+ *
+ * Gets the #GDataDocumentsEntry:last-modified-by property.
+ *
+ * Return value: the author who last modified the document
+ *
+ * Since: 0.4.0
+ **/
+GDataAuthor *
+gdata_documents_entry_get_last_modified_by (GDataDocumentsEntry *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL);
+ return self->priv->last_modified_by;
+}
+
+static void
+got_chunk_cb (SoupMessage *message, SoupBuffer *chunk, GOutputStream *output_stream)
+{
+ g_output_stream_write (output_stream, (void*) chunk->data, chunk->length, NULL, NULL);
+}
+
+/*
+ * _gdata_documents_entry_download_document:
+ * @self: a #GDataDocumentsEntry
+ * @service: an authenticated #GDataDocumentsService
+ * @content_type: return location for the document's content type, or %NULL; free with g_free()
+ * @download_uri: the URI to download the document
+ * @destination_directory: the directory into which the file should be downloaded
+ * @file_extension: the extension with which to save the downloaded file
+ * @replace_file_if_exists: %TRUE if you want to replace the file if it exists, %FALSE otherwise
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Downloads and returns the actual file which comprises the document here. If the document doesn't exist,
+ * the downloaded document will be an HTML file containing the error explanation.
+ * TODO: Is that still true?
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If @replace_file_if_exists is set to %FALSE and the destination file already exists, a %G_IO_ERROR_EXISTS will be returned.
+ *
+ * If @service isn't authenticated, a %GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED is returned.
+ *
+ * If there is an error downloading the document, a %GDATA_SERVICE_ERROR_WITH_QUERY error will be returned.
+ *
+ * Return value: a #GFile pointing to the downloaded document, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ */
+GFile *
+_gdata_documents_entry_download_document (GDataDocumentsEntry *self, GDataService *service, gchar **content_type, gchar *download_uri,
+ GFile *destination_directory, const gchar *file_extension, gboolean replace_file_if_exists,
+ GCancellable *cancellable, GError **error)
+{
+ GDataServiceClass *klass;
+ GFileOutputStream *file_stream;
+ GFile *destination_file;
+ SoupMessage *message;
+ guint status;
+ const gchar *document_title;
+ gchar *filename;
+
+ /* TODO: async version */
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL);
+ g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+ g_return_val_if_fail (download_uri != NULL, NULL);
+ g_return_val_if_fail (G_IS_FILE (destination_directory), NULL);
+ g_return_val_if_fail (file_extension != NULL, NULL);
+
+ /* Ensure we're authenticated first */
+ if (gdata_service_is_authenticated (GDATA_SERVICE (service)) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to download documents."));
+ return NULL;
+ }
+
+ /* Prepare the GFile */
+ document_title = gdata_entry_get_title (GDATA_ENTRY (self));
+ filename = g_strdup_printf ("%s.%s", document_title, file_extension);
+ destination_file = g_file_get_child (destination_directory, filename);
+ g_free (filename);
+
+ /* Check if the file exists */
+ if (g_file_query_exists (destination_file, cancellable) == TRUE) {
+ /* Replace a pre-existing file */
+ if (replace_file_if_exists == TRUE) {
+ file_stream = g_file_replace (destination_file, NULL, TRUE, G_FILE_CREATE_REPLACE_DESTINATION,
+ cancellable, error);
+ } else {
+ g_set_error (error, G_IO_ERROR_EXISTS, 1, NULL);
+ return NULL;
+ }
+ } else {
+ /* Create a new file */
+ file_stream = g_file_create (destination_file, G_FILE_CREATE_NONE, cancellable, error);
+ }
+
+ /* 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);
+ g_object_unref (file_stream);
+ if (status == SOUP_STATUS_NONE) {
+ g_object_unref (message);
+ g_object_unref (destination_file);
+ return NULL;
+ }
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ g_object_unref (destination_file);
+ 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);
+ g_object_unref (destination_file);
+ 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 destination_file;
+}
diff --git a/gdata/services/documents/gdata-documents-entry.h b/gdata/services/documents/gdata-documents-entry.h
new file mode 100644
index 0000000..81e64dc
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-entry.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_ENTRY_H
+#define GDATA_DOCUMENTS_ENTRY_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-entry.h>
+#include <gdata/gdata-service.h>
+#include <gdata/atom/gdata-author.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_ENTRY (gdata_documents_entry_get_type ())
+#define GDATA_DOCUMENTS_ENTRY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntry))
+#define GDATA_DOCUMENTS_ENTRY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntryClass))
+#define GDATA_IS_DOCUMENTS_ENTRY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_ENTRY))
+#define GDATA_IS_DOCUMENTS_ENTRY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_ENTRY))
+#define GDATA_DOCUMENTS_ENTRY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntryClass))
+
+typedef struct _GDataDocumentsEntryPrivate GDataDocumentsEntryPrivate;
+
+/**
+ * GDataDocumentsEntry:
+ *
+ * All the fields in the #GDataDocumentsEntry structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataEntry parent;
+ GDataDocumentsEntryPrivate *priv;
+} GDataDocumentsEntry;
+
+/**
+ * GDataDocumentsEntryClass:
+ *
+ * All the fields in the #GDataDocumentsEntryClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataEntryClass parent;
+} GDataDocumentsEntryClass;
+
+GType gdata_documents_entry_get_type (void) G_GNUC_CONST;
+
+const gchar *gdata_documents_entry_get_path (GDataDocumentsEntry *self);
+
+const gchar *gdata_documents_entry_get_document_id (GDataDocumentsEntry *self);
+
+void gdata_documents_entry_get_edited (GDataDocumentsEntry *self, GTimeVal *edited);
+void gdata_documents_entry_get_last_viewed (GDataDocumentsEntry *self, GTimeVal *last_viewed);
+
+void gdata_documents_entry_set_writers_can_invite (GDataDocumentsEntry *self, gboolean writers_can_invite);
+gboolean gdata_documents_entry_writers_can_invite (GDataDocumentsEntry *self);
+
+GDataAuthor *gdata_documents_entry_get_last_modified_by (GDataDocumentsEntry *self);
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_ENTRY_H */
diff --git a/gdata/services/documents/gdata-documents-feed.c b/gdata/services/documents/gdata-documents-feed.c
new file mode 100644
index 0000000..4ee04af
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-feed.c
@@ -0,0 +1,119 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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-documents-feed
+ * @short_description: GData documents feed object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-feed.h
+ *
+ * #GDataDocumentsFeed is a list of entries (#GDataDocumentsEntry subclasses) returned as the result of a query to a #GDataDocumentsService,
+ * or given as the input to another operation on the online service.
+ *
+ * Each #GDataDocumentsEntry represents a single object on the Google Documents online service, such as a text document, presentation document,
+ * spreadsheet document or a folder, and the #GDataDocumentsFeed represents a collection of those objects.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-documents-feed.h"
+#include "gdata-documents-entry.h"
+#include "gdata-documents-spreadsheet.h"
+#include "gdata-documents-text.h"
+#include "gdata-documents-presentation.h"
+#include "gdata-documents-folder.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+#include "gdata-service.h"
+
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
+static gboolean is_entry_type (xmlDoc *doc, xmlNode *node, const gchar *entry_type);
+
+G_DEFINE_TYPE (GDataDocumentsFeed, gdata_documents_feed, GDATA_TYPE_FEED)
+#define GDATA_DOCUMENTS_FEED_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_FEED, GDataDocumentsFeedPrivate))
+
+static void
+gdata_documents_feed_class_init (GDataDocumentsFeedClass *klass)
+{
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+ parsable_class->parse_xml = parse_xml;
+}
+
+static void
+gdata_documents_feed_init (GDataDocumentsFeed *self)
+{
+ /* Why am I writing it? */
+}
+
+static gboolean
+is_entry_type (xmlDoc *doc, xmlNode *node, const gchar *entry_type)
+{
+ xmlNode *entry_node;
+
+ for (entry_node = node->children; entry_node != NULL; entry_node = entry_node->next) {
+ xmlChar *label;
+
+ if (xmlStrcmp (entry_node->name, (xmlChar*) "category") != 0)
+ continue;
+
+ label = xmlGetProp (entry_node, (xmlChar*) "label");
+ if (xmlStrcmp (label, (xmlChar*) entry_type) == 0) {
+ xmlFree (label);
+ return TRUE;
+ }
+ xmlFree (label);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+ GDataDocumentsFeed *self = GDATA_DOCUMENTS_FEED (parsable);
+
+ if (xmlStrcmp (node->name, (xmlChar*) "entry") == 0) {
+ GDataEntry *entry = NULL;
+
+ if (is_entry_type (doc, node, "spreadsheet"))
+ entry = GDATA_ENTRY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_DOCUMENTS_SPREADSHEET, doc, node, NULL, error));
+ else if (is_entry_type (doc, node, "document"))
+ entry = GDATA_ENTRY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_DOCUMENTS_TEXT, doc, node, NULL, error));
+ else if (is_entry_type (doc, node, "presentation"))
+ entry = GDATA_ENTRY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_DOCUMENTS_PRESENTATION, doc, node, NULL, error));
+ else if (is_entry_type (doc, node, "folder"))
+ entry = GDATA_ENTRY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_DOCUMENTS_FOLDER, doc, node, NULL, error));
+
+ if (entry == NULL)
+ return FALSE;
+
+ /* Call the progress callback in the main thread */
+ _gdata_feed_call_progress_callback (GDATA_FEED (self), user_data, entry);
+ _gdata_feed_add_entry (GDATA_FEED (self), entry);
+ } else if (GDATA_PARSABLE_CLASS (gdata_documents_feed_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+ /* Error! */
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/gdata/services/documents/gdata-documents-feed.h b/gdata/services/documents/gdata-documents-feed.h
new file mode 100644
index 0000000..0499e98
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-feed.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier <saunierthibault gmail com
+ *
+ * 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_DOCUMENTS_FEED_H
+#define GDATA_DOCUMENTS_FEED_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gdata/gdata-feed.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_FEED (gdata_documents_feed_get_type ())
+#define GDATA_DOCUMENTS_FEED(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_FEED, GDataDocumentsFeed))
+#define GDATA_DOCUMENTS_FEED_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_FEED, GDataDocumentsFeedClass))
+#define GDATA_IS_DOCUMENTS_FEED(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_FEED))
+#define GDATA_IS_DOCUMENTS_FEED_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_FEED))
+#define GDATA_DOCUMENTS_FEED_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_FEED, GDataDocumentsFeedClass))
+
+typedef struct _GDataDocumentsFeedPrivate GDataDocumentsFeedPrivate;
+
+/**
+ * GDataDocumentsFeed:
+ *
+ * All the fields in the #GDataDocumentsFeed structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataFeed parent;
+ GDataDocumentsFeedPrivate *priv;
+} GDataDocumentsFeed;
+
+/**
+ * GDataDocumentsFeedClass:
+ *
+ * All the fields in the #GDataDocumentsFeedClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataFeedClass parent;
+} GDataDocumentsFeedClass;
+
+GType gdata_documents_feed_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_FEED_H */
diff --git a/gdata/services/documents/gdata-documents-folder.c b/gdata/services/documents/gdata-documents-folder.c
new file mode 100644
index 0000000..e843cea
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-folder.c
@@ -0,0 +1,88 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier <saunierthibault gmail com
+ *
+ * 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-documents-folder
+ * @short_description: GData documents folder object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-folder.h
+ *
+ * #GDataDocumentsFolder is a subclass of #GDataDocumentsEntry to represent a folder from Google Documents.
+ *
+ * For more details of Google Documents' GData API, see the
+ * <ulink type="http://code.google.com/apis/document/docs/2.0/developers_guide_protocol.html">online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-documents-folder.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+
+G_DEFINE_TYPE (GDataDocumentsFolder, gdata_documents_folder, GDATA_TYPE_DOCUMENTS_ENTRY)
+#define GDATA_DOCUMENTS_FOLDER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntryPrivate))
+
+static void
+gdata_documents_folder_class_init (GDataDocumentsFolderClass *klass)
+{
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+ parsable_class->get_xml = get_xml;
+}
+
+/**
+ * gdata_documents_folder_new:
+ * @id: the entry's ID (not the document ID of the folder), or %NULL
+ *
+ * Creates a new #GDataDocumentsFolder with the given entry ID (#GDataEntry:id).
+ *
+ * Return value: a new #GDataDocumentsFolder, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsFolder *
+gdata_documents_folder_new (const gchar *id)
+{
+ return g_object_new (GDATA_TYPE_DOCUMENTS_FOLDER, "id", id, NULL);
+}
+
+static void
+gdata_documents_folder_init (GDataDocumentsFolder *self)
+{
+ /* Why am I writing it? */
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ const gchar *document_id;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_documents_folder_parent_class)->get_xml (parsable, xml_string);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (parsable));
+ if (document_id != NULL)
+ g_string_append_printf (xml_string, "<gd:resourceId>folder:%s</gd:resourceId>", document_id);
+}
diff --git a/gdata/services/documents/gdata-documents-folder.h b/gdata/services/documents/gdata-documents-folder.h
new file mode 100644
index 0000000..dbf7dc3
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-folder.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_FOLDER_H
+#define GDATA_DOCUMENTS_FOLDER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/services/documents/gdata-documents-entry.h>
+#include <gdata/gdata-types.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_FOLDER (gdata_documents_folder_get_type ())
+#define GDATA_DOCUMENTS_FOLDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_FOLDER, GDataDocumentsFolder))
+#define GDATA_DOCUMENTS_FOLDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_FOLDER, GDataDocumentsFolderClass))
+#define GDATA_IS_DOCUMENTS_FOLDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_FOLDER))
+#define GDATA_IS_DOCUMENTS_FOLDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_FOLDER))
+#define GDATA_DOCUMENTS_FOLDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_FOLDER, GDataDocumentsFolderClass))
+
+typedef struct _GDataDocumentsFolderPrivate GDataDocumentsFolderPrivate;
+
+/**
+ * GDataDocumentsFolder:
+ *
+ * All the fields in the #GDataDocumentsFolder structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataDocumentsEntry parent;
+ GDataDocumentsFolderPrivate *priv;
+} GDataDocumentsFolder;
+
+/**
+ * GDataDocumentsFolderClass:
+ *
+ * All the fields in the #GDataDocumentsFolderClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataDocumentsEntryClass parent;
+} GDataDocumentsFolderClass;
+
+GType gdata_documents_folder_get_type (void) G_GNUC_CONST;
+GDataDocumentsFolder *gdata_documents_folder_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_FOLDER_H */
diff --git a/gdata/services/documents/gdata-documents-presentation.c b/gdata/services/documents/gdata-documents-presentation.c
new file mode 100644
index 0000000..4bc840c
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-presentation.c
@@ -0,0 +1,151 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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-documents-presentation
+ * @short_description: GData documents presentation object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-presentation.h
+ *
+ * #GDataDocumentsPresentation is a subclass of #GDataDocumentsEntry to represent a Google Documents presentation.
+ *
+ * For more details of Google Documents' GData API, see the
+ * <ulink type="http://code.google.com/apis/document/docs/2.0/developers_guide_protocol.html">online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-documents-presentation.h"
+#include "gdata-documents-service.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+
+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))
+
+static void
+gdata_documents_presentation_class_init (GDataDocumentsPresentationClass *klass)
+{
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+ parsable_class->get_xml = get_xml;
+}
+
+static void
+gdata_documents_presentation_init (GDataDocumentsPresentation *self)
+{
+ /* Why am I writing it? */
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ const gchar *document_id;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_documents_presentation_parent_class)->get_xml (parsable, xml_string);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (parsable));
+ if (document_id != NULL)
+ g_string_append_printf (xml_string, "<gd:resourceId>presentation:%s</gd:resourceId>", document_id);
+}
+
+/**
+ * gdata_documents_presentation_new:
+ * @id: the entry's ID (not the document ID of the presentation), or %NULL
+ *
+ * Creates a new #GDataDocumentsPresentation with the given entry ID (#GDataEntry:id).
+ *
+ * Return value: a new #GDataDocumentsPresentation, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsPresentation *
+gdata_documents_presentation_new (const gchar *id)
+{
+ return g_object_new (GDATA_TYPE_DOCUMENTS_PRESENTATION, "id", id, NULL);
+}
+
+/**
+ * gdata_documents_presentation_download_document:
+ * @self: a #GDataDocumentsPresentation
+ * @service: a #GDataDocumentsService
+ * @content_type: return location for the document's content type, or %NULL; free with g_free()
+ * @export_format: the format in which the presentation should be exported
+ * @destination_directory: the directory into which the presentation file should be saved
+ * @replace_file_if_exists: %TRUE if the file should be replaced if it already exists, %FALSE otherwise
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Downloads and returns the presentation file represented by the #GDataDocumentsPresentation. If the document doesn't exist,
+ * %NULL is returned, but no error is set in @error. TODO: What?
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If there is an error getting the document, a %GDATA_SERVICE_ERROR_WITH_QUERY error will be returned.
+ *
+ * Return value: the document's data, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GFile *
+gdata_documents_presentation_download_document (GDataDocumentsPresentation *self, GDataDocumentsService *service, gchar **content_type,
+ GDataDocumentsPresentationFormat export_format, GFile *destination_directory,
+ gboolean replace_file_if_exists, GCancellable *cancellable, GError **error)
+{
+ GFile *destination_file;
+ 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_directory), 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);
+
+ /* 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_directory, export_formats[export_format],
+ replace_file_if_exists, cancellable, error);
+ g_free (link_href);
+
+ return destination_file;
+}
diff --git a/gdata/services/documents/gdata-documents-presentation.h b/gdata/services/documents/gdata-documents-presentation.h
new file mode 100644
index 0000000..f8f02f6
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-presentation.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_PRESENTATION_H
+#define GDATA_DOCUMENTS_PRESENTATION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/services/documents/gdata-documents-entry.h>
+#include <gdata/gdata-types.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_PRESENTATION (gdata_documents_presentation_get_type ())
+#define GDATA_DOCUMENTS_PRESENTATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_PRESENTATION, GDataDocumentsPresentation))
+#define GDATA_DOCUMENTS_PRESENTATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_PRESENTATION, GDataDocumentsPresentationClass))
+#define GDATA_IS_DOCUMENTS_PRESENTATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_PRESENTATION))
+#define GDATA_IS_DOCUMENTS_PRESENTATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_PRESENTATION))
+#define GDATA_DOCUMENTS_PRESENTATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_PRESENTATION, GDataDocumentsPresentationClass))
+
+typedef struct _GDataDocumentsPresentationPrivate GDataDocumentsPresentationPrivate;
+
+/**
+ * GDataDocumentsPresentationFormat:
+ * @GDATA_DOCUMENTS_PRESENTATION_PDF: the document in PDF format
+ * @GDATA_DOCUMENTS_PRESENTATION_PNG: the document in PNG image format
+ * @GDATA_DOCUMENTS_PRESENTATION_PPT: the document in Microsoft PowerPoint PPT format
+ * @GDATA_DOCUMENTS_PRESENTATION_SWF: the document in Adobe Flash SWF format
+ * @GDATA_DOCUMENTS_PRESENTATION_TXT: the document in text format
+ *
+ * The various different file formats in which a presentation can be downloaded, with conversation happening on the server.
+ *
+ * Since: 0.4.0
+ **/
+typedef enum {
+ GDATA_DOCUMENTS_PRESENTATION_PDF = 0,
+ GDATA_DOCUMENTS_PRESENTATION_PNG,
+ GDATA_DOCUMENTS_PRESENTATION_PPT,
+ GDATA_DOCUMENTS_PRESENTATION_SWF,
+ GDATA_DOCUMENTS_PRESENTATION_TXT,
+} GDataDocumentsPresentationFormat;
+
+/**
+ * GDataDocumentsPresentation:
+ *
+ * All the fields in the #GDataDocumentsPresentation structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataDocumentsEntry parent;
+ GDataDocumentsPresentationPrivate *priv;
+} GDataDocumentsPresentation;
+
+/**
+ * GDataDocumentsPresentationClass:
+ *
+ * All the fields in the #GDataDocumentsPresentationClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataDocumentsEntryClass parent;
+} GDataDocumentsPresentationClass;
+
+GType gdata_documents_presentation_get_type (void) G_GNUC_CONST;
+
+GDataDocumentsPresentation *gdata_documents_presentation_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+
+#include <gdata/services/documents/gdata-documents-service.h>
+GFile *gdata_documents_presentation_download_document (GDataDocumentsPresentation *self, GDataDocumentsService *service, gchar **content_type,
+ GDataDocumentsPresentationFormat export_format, GFile *destination_directory,
+ gboolean replace_file_if_exists, GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_PRESENTATION_H */
diff --git a/gdata/services/documents/gdata-documents-query.c b/gdata/services/documents/gdata-documents-query.c
new file mode 100644
index 0000000..55f5275
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-query.c
@@ -0,0 +1,563 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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-documents-query
+ * @short_description: GData Documents query object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-query.h
+ *
+ * #GDataDocumentsQuery represents a collection of query parameters specific to the Google Documents service, which go above and beyond
+ * those catered for by #GDataQuery.
+ *
+ * For more information on the custom GData query parameters supported by #GDataDocumentsQuery, see the <ulink type="http"
+ * url="http://code.google.com/apis/documents/docs/2.0/reference.html#Parameters">online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+#include "gd/gdata-gd-email-address.h"
+#include "gdata-documents-query.h"
+#include "gdata-query.h"
+
+static void gdata_documents_query_finalize (GObject *object);
+static void gdata_documents_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_documents_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started);
+
+struct _GDataDocumentsQueryPrivate {
+ gboolean show_deleted;
+ gboolean show_folders;
+ gboolean exact_title;
+ gchar *folder_id;
+ gchar *title;
+ GList *collaborator_addresses; /* GDataGDEmailAddress */
+ GList *reader_addresses; /* GDataGDEmailAddress */
+};
+
+enum {
+ PROP_SHOW_DELETED = 1,
+ PROP_SHOW_FOLDERS,
+ PROP_EXACT_TITLE,
+ PROP_FOLDER_ID,
+ PROP_TITLE
+};
+
+G_DEFINE_TYPE (GDataDocumentsQuery, gdata_documents_query, GDATA_TYPE_QUERY)
+#define GDATA_DOCUMENTS_QUERY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_QUERY, GDataDocumentsQueryPrivate))
+
+static void
+gdata_documents_query_class_init (GDataDocumentsQueryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataQueryClass *query_class = GDATA_QUERY_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataDocumentsQueryPrivate));
+
+ gobject_class->get_property = gdata_documents_query_get_property;
+ gobject_class->set_property = gdata_documents_query_set_property;
+ gobject_class->finalize = gdata_documents_query_finalize;
+
+ query_class->get_query_uri = get_query_uri;
+
+ /**
+ * GDataDocumentsQuery:show-deleted:
+ *
+ * A shortcut to request all documents that have been deleted.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SHOW_DELETED,
+ g_param_spec_boolean ("show-deleted",
+ "Show deleted?", "A shortcut to request all documents that have been deleted.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsQuery:show-folders:
+ *
+ * Specifies if the request also returns folders.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SHOW_FOLDERS,
+ g_param_spec_boolean ("show-folders",
+ "Show folders?", "Specifies if the request also returns folders.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsQuery:exact-title:
+ *
+ * Specifies whether the query should search for an exact title match for the #GDataDocumentsQuery:title parameter.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_EXACT_TITLE,
+ g_param_spec_boolean ("exact-title",
+ "Exact title?", "Specifies whether the query should search for an exact title match.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsQuery:folder-id:
+ *
+ * Specifies the ID of the folder in which to search.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_FOLDER_ID,
+ g_param_spec_string ("folder-id",
+ "Folder ID", "Specifies the ID of the folder in which to search.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataDocumentsQuery:title:
+ *
+ * A title (or title fragment) to be searched for. If #GDataDocumentsQuery:exact-title is %TRUE, an exact
+ * title match will be searched for, otherwise substring matches will also be returned.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_TITLE,
+ g_param_spec_string ("title",
+ "Title", "A title (or title fragment) to be searched for.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_documents_query_init (GDataDocumentsQuery *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOCUMENTS_QUERY, GDataDocumentsQueryPrivate);
+}
+
+static void
+gdata_documents_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataDocumentsQueryPrivate *priv = GDATA_DOCUMENTS_QUERY_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_SHOW_DELETED:
+ g_value_set_boolean (value, priv->show_deleted);
+ break;
+ case PROP_SHOW_FOLDERS:
+ g_value_set_boolean (value, priv->show_folders);
+ break;
+ case PROP_FOLDER_ID:
+ g_value_set_string (value, priv->folder_id);
+ break;
+ case PROP_EXACT_TITLE:
+ g_value_set_boolean (value, priv->exact_title);
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, priv->title);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_documents_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataDocumentsQuery *self = GDATA_DOCUMENTS_QUERY (object);
+
+ switch (property_id) {
+ case PROP_SHOW_DELETED:
+ gdata_documents_query_set_show_deleted (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_FOLDERS:
+ gdata_documents_query_set_show_folders (self, g_value_get_boolean (value));
+ break;
+ case PROP_FOLDER_ID:
+ gdata_documents_query_set_folder_id (self, g_value_get_string (value));
+ break;
+ case PROP_EXACT_TITLE:
+ self->priv->exact_title = g_value_get_boolean (value);
+ break;
+ case PROP_TITLE:
+ gdata_documents_query_set_title (self, g_value_get_string (value), TRUE);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_documents_query_finalize (GObject *object)
+{
+ GDataDocumentsQueryPrivate *priv = GDATA_DOCUMENTS_QUERY_GET_PRIVATE (object);
+
+ g_free (priv->folder_id);
+ g_free (priv->title);
+
+ G_OBJECT_CLASS (gdata_documents_query_parent_class)->finalize (object);
+}
+
+static void
+get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started)
+{
+ GDataDocumentsQueryPrivate *priv = GDATA_DOCUMENTS_QUERY (self)->priv;
+
+ #define APPEND_SEP g_string_append_c (query_uri, (*params_started == FALSE) ? '?' : '&'); *params_started = TRUE;
+
+ if (priv->folder_id != NULL) {
+ g_string_append (query_uri, "/folder%%3A");
+ g_string_append_uri_escaped (query_uri, priv->folder_id, NULL, TRUE);
+ }
+
+ /* Chain up to the parent class */
+ GDATA_QUERY_CLASS (gdata_documents_query_parent_class)->get_query_uri (self, feed_uri, query_uri, params_started);
+
+ if (priv->collaborator_addresses != NULL) {
+ GList *collaborator_address;
+ APPEND_SEP
+ collaborator_address = priv->collaborator_addresses;
+
+ g_string_append (query_uri, "writer=");
+ g_string_append_uri_escaped (query_uri, gdata_gd_email_address_get_address (collaborator_address->data), NULL, TRUE);
+ for (collaborator_address = collaborator_address->next; collaborator_address != NULL; collaborator_address = collaborator_address->next) {
+ g_string_append_c (query_uri, ';');
+ g_string_append_uri_escaped (query_uri, gdata_gd_email_address_get_address (collaborator_address->data), NULL, TRUE);
+ }
+ }
+
+ if (priv->reader_addresses != NULL) {
+ GList *reader_address;
+ APPEND_SEP
+ reader_address = priv->reader_addresses;
+
+ g_string_append (query_uri, "reader=");
+ g_string_append_uri_escaped (query_uri, gdata_gd_email_address_get_address (reader_address->data), NULL, TRUE);
+ for (reader_address = reader_address->next; reader_address != NULL; reader_address = reader_address->next) {
+ g_string_append_c (query_uri, ';');
+ g_string_append_uri_escaped (query_uri, gdata_gd_email_address_get_address (reader_address->data), NULL, TRUE);
+ }
+ }
+
+ if (priv->title != NULL) {
+ APPEND_SEP
+ g_string_append (query_uri, "title=");
+ g_string_append_uri_escaped (query_uri, priv->title, NULL, TRUE);
+ if (priv->exact_title == TRUE)
+ g_string_append (query_uri, "&title-exact=true");
+ }
+
+ APPEND_SEP
+ if (priv->show_deleted == TRUE)
+ g_string_append (query_uri, "showdeleted=true");
+ else
+ g_string_append (query_uri, "showdeleted=false");
+
+ if (priv->show_folders == TRUE)
+ g_string_append (query_uri, "&showfolders=true");
+ else
+ g_string_append (query_uri, "&showfolders=false");
+}
+
+/**
+ * gdata_documents_query_new:
+ * @q: a query string
+ *
+ * Creates a new #GDataDocumentsQuery with its #GDataQuery:q property set to @q.
+ *
+ * Return value: a new #GDataDocumentsQuery
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsQuery *
+gdata_documents_query_new (const gchar *q)
+{
+ return g_object_new (GDATA_TYPE_DOCUMENTS_QUERY, "q", q, NULL);
+}
+
+/**
+ * gdata_documents_query_new_with_limits:
+ * @q: a query string
+ * @start_index: a one-based start index for the results
+ * @max_results: the maximum number of results to return
+ *
+ * Creates a new #GDataDocumentsQuery with its #GDataQuery:q property set to @q, and the limits @start_index and @max_results
+ * applied.
+ *
+ * Return value: a new #GDataDocumentsQuery
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsQuery *
+gdata_documents_query_new_with_limits (const gchar *q, gint start_index, gint max_results)
+{
+ return g_object_new (GDATA_TYPE_DOCUMENTS_QUERY,
+ "q", q,
+ "start-index", start_index,
+ "max-results", max_results,
+ NULL);
+}
+
+/**
+ * gdata_documents_query_show_deleted:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets the #GDataDocumentsQuery:show_deleted property.
+ *
+ * Return value: %TRUE if the request should return deleted entries, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_documents_query_show_deleted (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), FALSE);
+ return self->priv->show_deleted;
+}
+
+/**
+ * gdata_documents_query_set_show_deleted:
+ * @self: a #GDataDocumentsQuery
+ * @show_deleted: %TRUE if the request should return deleted entries, %FALSE otherwise
+ *
+ * Sets the #GDataDocumentsQuery:show_deleted property to @show_deleted.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_query_set_show_deleted (GDataDocumentsQuery *self, gboolean show_deleted)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_QUERY (self));
+ self->priv->show_deleted = show_deleted;
+ g_object_notify (G_OBJECT (self), "show-deleted");
+}
+
+/**
+ * gdata_documents_query_show_folders:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets the #GDataDocumentsQuery:show-folders property.
+ *
+ * Return value: %TRUE if the request should return folders, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_documents_query_show_folders (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), FALSE);
+ return self->priv->show_folders;
+}
+
+/**
+ * gdata_documents_query_set_show_folders:
+ * @self: a #GDataDocumentsQuery
+ * @show_folders: %TRUE if the request should return folders, %FALSE otherwise
+ *
+ * Sets the #GDataDocumentsQuery:show-folders property to show_folders.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_query_set_show_folders (GDataDocumentsQuery *self, gboolean show_folders)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_QUERY (self));
+ self->priv->show_folders = show_folders;
+ g_object_notify (G_OBJECT (self), "show-folders");
+}
+
+/**
+ * gdata_documents_query_get_folder_id:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets the #GDataDocumentsQuery:folder-id property.
+ *
+ * Return value: the ID of the folder to be queried, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_documents_query_get_folder_id (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), NULL);
+ return self->priv->folder_id;
+}
+
+/**
+ * gdata_documents_query_set_folder_id:
+ * @self: a #GDataDocumentsQuery
+ * @folder_id: the ID of the folder to be queried, or %NULL
+ *
+ * Sets the #GDataDocumentsQuery:folder-id property to @folder_id.
+ *
+ * Set @folder_id to %NULL to unset the property in the query URI.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_query_set_folder_id (GDataDocumentsQuery *self, const gchar *folder_id)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_QUERY (self));
+
+ g_free (self->priv->folder_id);
+ self->priv->folder_id = g_strdup (folder_id);
+ g_object_notify (G_OBJECT (self), "folder-id");
+}
+
+/**
+ * gdata_documents_query_get_title:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets the #GDataDocumentsQuery:title property.
+ *
+ * Return value: the title (or title fragment) being queried for, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_documents_query_get_title (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), NULL);
+ return self->priv->title;
+}
+
+/**
+ * gdata_documents_query_get_exact_title:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets the #GDataDocumentsQuery:exact-title property.
+ *
+ * Return value: %TRUE if the query matches the exact title of documents with #GDataDocumentsQuery:title, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_documents_query_get_exact_title (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), FALSE);
+ return self->priv->exact_title;
+}
+
+/**
+ * gdata_documents_query_set_title:
+ * @self: a #GDataDocumentsQuery
+ * @title: the title (or title fragment) to query for, or %NULL
+ * @exact_title: %TRUE if the query should match the exact @title, %FALSE otherwise
+ *
+ * Sets the #GDataDocumentsQuery:title property to @title.
+ *
+ * Set @title to %NULL to unset the property in the query URI.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_query_set_title (GDataDocumentsQuery *self, const gchar *title, gboolean exact_title)
+{
+ g_return_if_fail (GDATA_IS_DOCUMENTS_QUERY (self));
+
+ g_free (self->priv->title);
+ self->priv->title = g_strdup (title);
+ self->priv->exact_title = exact_title;
+
+ g_object_freeze_notify (G_OBJECT (self));
+ g_object_notify (G_OBJECT (self), "exact-title");
+ g_object_notify (G_OBJECT (self), "title");
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gdata_documents_query_get_collaborator_addresses:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets a list of #GDataGDEmailAddress<!-- -->es of the document collaborators whose documents will be queried.
+ *
+ * Return value: a list of #GDataGDEmailAddress<!-- -->es of the collaborators concerned by the query, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_documents_query_get_collaborator_addresses (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), NULL);
+ return self->priv->collaborator_addresses;
+}
+
+/**
+ * gdata_documents_query_get_reader_addresses:
+ * @self: a #GDataDocumentsQuery
+ *
+ * Gets a list of #GDataGDEmailAddress<!-- -->es of the document readers whose documents will be queried.
+ *
+ * Return value: a list of #GDataGDEmailAddress<!-- -->es of the readers concerned by the query, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_documents_query_get_reader_addresses (GDataDocumentsQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_QUERY (self), NULL);
+ return self->priv->reader_addresses;
+}
+
+/**
+ * gdata_documents_query_add_reader:
+ * @self: a #GDataDocumentsQuery
+ * @email_address: the e-mail address of the reader to add
+ *
+ * Add @email_address as a #GDataGDEmailAddress to the list of readers, the documents readable by whom will be queried.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_query_add_reader (GDataDocumentsQuery *self, const gchar *email_address)
+{
+ GDataGDEmailAddress *address;
+
+ g_return_if_fail (GDATA_IS_DOCUMENTS_QUERY (self));
+ g_return_if_fail (email_address != NULL && *email_address != '\0');
+
+ address = gdata_gd_email_address_new (email_address, "reader", NULL, FALSE);
+ self->priv->reader_addresses = g_list_append (self->priv->reader_addresses, address);
+}
+
+/**
+ * gdata_documents_query_add_collaborator:
+ * @self: a #GDataDocumentsQuery
+ * @email_address: the e-mail address of the collaborator to add
+ *
+ * Add @email_address as a #GDataGDEmailAddress to the list of collaborators whose edited documents will be queried.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_query_add_collaborator (GDataDocumentsQuery *self, const gchar *email_address)
+{
+ GDataGDEmailAddress *address;
+
+ g_return_if_fail (GDATA_IS_DOCUMENTS_QUERY (self));
+ g_return_if_fail (email_address != NULL && *email_address != '\0');
+
+ address = gdata_gd_email_address_new (email_address, "collaborator", NULL, FALSE);
+ self->priv->collaborator_addresses = g_list_append (self->priv->collaborator_addresses, address);
+}
diff --git a/gdata/services/documents/gdata-documents-query.h b/gdata/services/documents/gdata-documents-query.h
new file mode 100644
index 0000000..8ebb8cc
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-query.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_QUERY_H
+#define GDATA_DOCUMENTS_QUERY_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-types.h>
+#include <gdata/gdata-query.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_QUERY (gdata_documents_query_get_type ())
+#define GDATA_DOCUMENTS_QUERY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_QUERY, GDataDocumentsQuery))
+#define GDATA_DOCUMENTS_QUERY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_QUERY, GDataDocumentsQueryClass))
+#define GDATA_IS_DOCUMENTS_QUERY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_QUERY))
+#define GDATA_IS_DOCUMENTS_QUERY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_QUERY))
+#define GDATA_DOCUMENTS_QUERY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_QUERY, GDataDocumentsQueryClass))
+
+typedef struct _GDataDocumentsQueryPrivate GDataDocumentsQueryPrivate;
+
+/**
+ * GDataDocumentsQuery:
+ *
+ * All the fields in the #GDataDocumentsQuery structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataQuery parent;
+ GDataDocumentsQueryPrivate *priv;
+} GDataDocumentsQuery;
+
+/**
+ * GDataDocumentsQueryClass:
+ *
+ * All the fields in the #GDataDocumentsQueryClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataQueryClass parent;
+ GDataDocumentsQueryPrivate *priv;
+} GDataDocumentsQueryClass;
+
+GType gdata_documents_query_get_type (void) G_GNUC_CONST;
+
+GDataDocumentsQuery *gdata_documents_query_new (const gchar *q) G_GNUC_WARN_UNUSED_RESULT;
+GDataDocumentsQuery *gdata_documents_query_new_with_limits (const gchar *q, gint start_index, gint max_results) G_GNUC_WARN_UNUSED_RESULT;
+
+gboolean gdata_documents_query_show_deleted (GDataDocumentsQuery *self);
+void gdata_documents_query_set_show_deleted (GDataDocumentsQuery *self, gboolean show_deleted);
+gboolean gdata_documents_query_show_folders (GDataDocumentsQuery *self);
+void gdata_documents_query_set_show_folders (GDataDocumentsQuery *self, gboolean show_folders);
+const gchar *gdata_documents_query_get_folder_id (GDataDocumentsQuery *self);
+void gdata_documents_query_set_folder_id (GDataDocumentsQuery *self, const gchar *folder_id);
+const gchar *gdata_documents_query_get_title (GDataDocumentsQuery *self);
+gboolean gdata_documents_query_get_exact_title (GDataDocumentsQuery *self);
+void gdata_documents_query_set_title (GDataDocumentsQuery *self, const gchar *title, gboolean exact_title);
+GList *gdata_documents_query_get_collaborator_addresses (GDataDocumentsQuery *self);
+GList *gdata_documents_query_get_reader_addresses (GDataDocumentsQuery *self);
+void gdata_documents_query_add_reader (GDataDocumentsQuery *self, const gchar *email_address);
+void gdata_documents_query_add_collaborator (GDataDocumentsQuery *self, const gchar *email_address);
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_QUERY_H */
diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c
new file mode 100644
index 0000000..92aa5d8
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-service.c
@@ -0,0 +1,767 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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-documents-service
+ * @short_description: GData Documents service object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-service.h
+ *
+ * #GDataDocumentsService is a subclass of #GDataService for communicating with the GData API of Google Documents. It supports querying
+ * for, inserting, editing and deleting documents, as well as a folder hierarchy.
+ *
+ * For more details of Google Documents' GData API, see the <ulink type="http" url="http://code.google.com/apis/document/docs/2.0/reference.html">
+ * online documentation</ulink>.
+ *
+ * Fore more details about the spreadsheet downloads handling, see the
+ * <ulink type="http" url="http://groups.google.com/group/Google-Docs-Data-APIs/browse_thread/thread/bfc50e94e303a29a?pli=1">
+ * online explanation about the problem</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libsoup/soup.h>
+#include <string.h>
+
+#include "gdata-documents-service.h"
+#include "gdata-documents-spreadsheet.h"
+#include "gdata-documents-text.h"
+#include "gdata-documents-presentation.h"
+#include "gdata-service.h"
+#include "gdata-private.h"
+
+GQuark
+gdata_documents_service_error_quark (void)
+{
+ return g_quark_from_static_string ("gdata-documents-service-error-quark");
+}
+
+static void gdata_documents_service_dispose (GObject *object);
+static void gdata_documents_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void notify_authenticated_cb (GObject *service, GParamSpec *pspec, GObject *self);
+static void notify_proxy_uri_cb (GObject *service, GParamSpec *pspec, GObject *self);
+GDataDocumentsEntry *upload_update_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file,
+ SoupMessage *message, guint good_status_code,
+ GCancellable *cancellable, GError **error);
+
+struct _GDataDocumentsServicePrivate {
+ GDataService *spreadsheet_service;
+};
+
+enum {
+ PROP_SPREADSHEET_SERVICE = 1
+};
+
+G_DEFINE_TYPE (GDataDocumentsService, gdata_documents_service, GDATA_TYPE_SERVICE)
+#define GDATA_DOCUMENTS_SERVICE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_DOCUMENTS_SERVICE, GDataDocumentsServicePrivate))
+
+static void
+gdata_documents_service_class_init (GDataDocumentsServiceClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataDocumentsServicePrivate));
+
+ gobject_class->get_property = gdata_documents_service_get_property;
+ gobject_class->dispose = gdata_documents_service_dispose;
+
+ service_class->service_name = "writely";
+ service_class->feed_type = GDATA_TYPE_DOCUMENTS_FEED;
+
+ /**
+ * GDataService:spreadsheet-service:
+ *
+ * Another service for spreadsheets, required to be able to handle downloads.
+ *
+ * For more details about the spreadsheet downloads handling, see the
+ * <ulink type="http" url="http://groups.google.com/group/Google-Docs-Data-APIs/browse_thread/thread/bfc50e94e303a29a?pli=1">
+ * online explanation about the problem</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SPREADSHEET_SERVICE,
+ g_param_spec_object ("spreadsheet-service",
+ "Spreadsheet service", "Another service for spreadsheets.",
+ GDATA_TYPE_SERVICE,
+ G_PARAM_READABLE));
+}
+
+static void
+gdata_documents_service_init (GDataDocumentsService *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOCUMENTS_SERVICE, GDataDocumentsServicePrivate);
+ g_signal_connect (self, "notify::authenticated", G_CALLBACK (notify_authenticated_cb), NULL);
+ g_signal_connect (self, "notify::proxy-uri", G_CALLBACK (notify_proxy_uri_cb), NULL);
+}
+
+/**
+ * gdata_documents_service_new:
+ * @client_id: your application's client ID
+ *
+ * Creates a new #GDataDocumentsService. The @client_id must be unique for your application, and as registered with Google.
+ *
+ * Return value: a new #GDataDocumentsService, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsService *
+gdata_documents_service_new (const gchar *client_id)
+{
+ g_return_val_if_fail (client_id != NULL, NULL);
+
+ return g_object_new (GDATA_TYPE_DOCUMENTS_SERVICE,
+ "client-id", client_id,
+ NULL);
+}
+
+static void
+gdata_documents_service_dispose (GObject *object)
+{
+ GDataDocumentsServicePrivate *priv = GDATA_DOCUMENTS_SERVICE_GET_PRIVATE (object);
+
+ if (priv->spreadsheet_service != NULL)
+ g_object_unref (priv->spreadsheet_service);
+ priv->spreadsheet_service = NULL;
+
+ G_OBJECT_CLASS (gdata_documents_service_parent_class)->dispose (object);
+}
+
+static void
+gdata_documents_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataDocumentsServicePrivate *priv = GDATA_DOCUMENTS_SERVICE_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_SPREADSHEET_SERVICE:
+ g_value_set_object (value, priv->spreadsheet_service);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gdata_documents_service_query_documents:
+ * @self: a #GDataDocumentsService
+ * @query: a #GDataQuery with the query parameters, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @progress_callback: a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
+ * @progress_user_data: data to pass to the @progress_callback function
+ * @error: a #GError, or %NULL
+ *
+ * Queries the service to return a list of documents matching the given @query.
+ *
+ * For more details, see gdata_service_query().
+ *
+ * Return value: a #GDataDocumentsFeed of query results; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsFeed *
+gdata_documents_service_query_documents (GDataDocumentsService *self, GDataDocumentsQuery *query, GCancellable *cancellable,
+ GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GError **error)
+{
+ /* Ensure we're authenticated first */
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to query documents."));
+ return NULL;
+ }
+
+ return GDATA_DOCUMENTS_FEED (gdata_service_query (GDATA_SERVICE (self), "http://docs.google.com/feeds/documents/private/full", GDATA_QUERY (query),
+ GDATA_TYPE_DOCUMENTS_ENTRY, cancellable, progress_callback, progress_user_data, error));
+}
+
+/**
+ * gdata_documents_service_query_documents_async:
+ * @self: a #GDataDocumentsService
+ * @query: a #GDataQuery with the query parameters, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @progress_callback: a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
+ * @progress_user_data: data to pass to the @progress_callback function
+ * @callback: a #GAsyncReadyCallback to call when authentication is finished
+ * @user_data: data to pass to the @callback function
+ *
+ * Queries the service to return a list of documents matching the given @query. @self and
+ * @query are both reffed when this function is called, so can safely be unreffed after this function returns.
+ *
+ * For more details, see gdata_documents_service_query_documents(), which is the synchronous version of this function,
+ * and gdata_service_query_async(), which is the base asynchronous query function.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_documents_service_query_documents_async (GDataDocumentsService *self, GDataDocumentsQuery *query, GCancellable *cancellable,
+ GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GAsyncReadyCallback callback, gpointer user_data)
+{
+ /* Ensure we're authenticated first */
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to query documents."));
+ return;
+ }
+
+ gdata_service_query_async (GDATA_SERVICE (self), "http://docs.google.com/feeds/documents/private/full", GDATA_QUERY (query),
+ 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)
+{
+ GDataService *spreadsheet_service;
+ GDataDocumentsServicePrivate *priv = GDATA_DOCUMENTS_SERVICE_GET_PRIVATE (GDATA_DOCUMENTS_SERVICE (service));
+
+ if (priv->spreadsheet_service != NULL)
+ g_object_unref (priv->spreadsheet_service);
+
+ spreadsheet_service = g_object_new (GDATA_TYPE_SERVICE, "client-id", gdata_service_get_client_id (GDATA_SERVICE (service)), NULL);
+ GDATA_SERVICE_GET_CLASS (spreadsheet_service)->service_name = "wise";
+ gdata_service_authenticate (spreadsheet_service, gdata_service_get_username (GDATA_SERVICE (service)),
+ gdata_service_get_password (GDATA_SERVICE (service)), NULL, NULL);
+ priv->spreadsheet_service = spreadsheet_service;
+}
+
+/* Sets the proxy on @spreadsheet_service when it is set on the service */
+static void
+notify_proxy_uri_cb (GObject *service, GParamSpec *pspec, GObject *self)
+{
+ SoupURI *proxy_uri;
+
+ if (GDATA_DOCUMENTS_SERVICE (self)->priv->spreadsheet_service == NULL)
+ return;
+
+ proxy_uri = gdata_service_get_proxy_uri (GDATA_SERVICE (service));
+ gdata_service_set_proxy_uri (GDATA_DOCUMENTS_SERVICE (self)->priv->spreadsheet_service, proxy_uri);
+}
+
+GDataDocumentsEntry *
+upload_update_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file, SoupMessage *message,
+ guint good_status_code, GCancellable *cancellable, GError **error)
+{
+ /* TODO: Async variant */
+ #define BOUNDARY_STRING "0003Z5W789RTE456KlemsnoZV"
+
+ GDataServiceClass *klass;
+ GType new_document_type = GDATA_TYPE_DOCUMENTS_ENTRY; /* If the document type is wrong we get an error from the server anyway */
+ gchar *entry_xml, *second_chunk_header, *upload_data, *document_contents = NULL, *i;
+ const gchar *first_chunk_header, *footer;
+ GFileInfo *document_file_info = NULL;
+ guint status;
+ gsize content_length, first_chunk_header_length, second_chunk_header_length, entry_xml_length, document_length, footer_length;
+
+ g_return_val_if_fail (document != NULL || document_file != NULL, NULL);
+ g_return_val_if_fail (message != NULL, NULL);
+
+ /* Make sure subclasses set their headers */
+ klass = GDATA_SERVICE_GET_CLASS (self);
+ if (klass->append_query_headers != NULL)
+ klass->append_query_headers (GDATA_SERVICE (self), message);
+
+ /* Gets document file information */
+ if (document_file != NULL) {
+ /* Get the data early so we can calculate the content length */
+ if (g_file_load_contents (document_file, NULL, &document_contents, &document_length, NULL, error) == FALSE)
+ return NULL;
+
+ document_file_info = g_file_query_info (document_file, "standard::display-name,standard::content-type", G_FILE_QUERY_INFO_NONE, NULL, error);
+ if (document_file_info == NULL) {
+ g_free (document_contents);
+ return NULL;
+ }
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_free (document_contents);
+ g_object_unref (document_file_info);
+ return NULL;
+ }
+
+ /* Add document-upload--specific headers */
+ soup_message_headers_append (message->request_headers, "Slug", g_file_info_get_display_name (document_file_info));
+
+ if (document == NULL) {
+ const gchar *content_type = g_file_info_get_content_type (document_file_info);
+
+ /* Corrects a bug on spreadsheet content types handling
+ * The content type for ODF spreasheets is "application/vnd.oasis.opendocument.spreadsheet" for my ODF spreadsheet;
+ * but Google Documents' spreadsheet service is waiting for "application/x-vnd.oasis.opendocument.spreadsheet"
+ * and nothing else.
+ * Bug filed with Google: http://code.google.com/p/gdata-issues/issues/detail?id=1127 */
+ if (strcmp (content_type, "application/vnd.oasis.opendocument.spreadsheet") == 0)
+ content_type = "application/x-vnd.oasis.opendocument.spreadsheet";
+ soup_message_set_request (message, content_type, SOUP_MEMORY_TAKE, document_contents, document_length);
+
+ /* We get the document type of the document which is being uploaded */
+ if (strcmp (content_type, "application/x-vnd.oasis.opendocument.spreadsheet") == 0 ||
+ strcmp (content_type, "text/tab-separated-values") == 0 ||
+ strcmp (content_type, "application/x-vnd.oasis.opendocument.spreadsheet") == 0 ||
+ strcmp (content_type, "application/vnd.ms-excel") == 0) {
+ new_document_type = GDATA_TYPE_DOCUMENTS_SPREADSHEET;
+ } else if (strcmp (content_type, "application/msword") == 0 ||
+ strcmp (content_type, "application/vnd.oasis.opendocument.text") == 0 ||
+ strcmp (content_type, "application/rtf") == 0 ||
+ strcmp (content_type, "text/html") == 0 ||
+ strcmp (content_type, "application/vnd.sun.xml.writer") == 0 ||
+ strcmp (content_type, "text/plain") == 0) {
+ new_document_type = GDATA_TYPE_DOCUMENTS_TEXT;
+ } else if (strcmp (content_type, "application/vnd.ms-powerpoint") == 0) {
+ new_document_type = GDATA_TYPE_DOCUMENTS_PRESENTATION;
+ } else {
+ g_set_error_literal (error, GDATA_DOCUMENTS_SERVICE_ERROR, GDATA_DOCUMENTS_SERVICE_ERROR_INVALID_CONTENT_TYPE,
+ _("The supplied document had an invalid content type."));
+ g_free (document_contents);
+ g_object_unref (document_file_info);
+ return NULL;
+ }
+ }
+ }
+
+ /* Prepare XML file to upload metadata */
+ if (document != NULL) {
+ /* Test on the document entry */
+ new_document_type = G_OBJECT_TYPE (document);
+
+ /* Get the XML content */
+ entry_xml = gdata_parsable_get_xml (GDATA_PARSABLE (document));
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_free (entry_xml);
+ g_free (document_contents);
+ if (document_file_info != NULL)
+ g_object_unref (document_file_info);
+ return NULL;
+ }
+
+ /* Prepare the data to upload containg both metadata and document content */
+ if (document_file != NULL) {
+ const gchar *content_type = g_file_info_get_content_type (document_file_info);
+
+ first_chunk_header = "--" BOUNDARY_STRING "\nContent-Type: application/atom+xml; charset=UTF-8\n\n<?xml version='1.0'?>";
+ /* Corrects a bug on spreadsheet content types handling (see above) */
+ if (strcmp (content_type, "application/vnd.oasis.opendocument.spreadsheet") == 0)
+ content_type = "application/x-vnd.oasis.opendocument.spreadsheet";
+
+ second_chunk_header = g_strdup_printf ("\n--" BOUNDARY_STRING "\nContent-Type: %s\n\n", content_type);
+ footer = "\n--" BOUNDARY_STRING "--";
+
+ first_chunk_header_length = strlen (first_chunk_header);
+ second_chunk_header_length = strlen (second_chunk_header);
+ entry_xml_length = strlen (entry_xml);
+ footer_length = strlen (footer);
+ content_length = first_chunk_header_length + entry_xml_length + second_chunk_header_length + document_length + footer_length;
+
+ /* Build the upload data */
+ upload_data = i = g_malloc (content_length);
+
+ memcpy (upload_data, first_chunk_header, first_chunk_header_length);
+ i += first_chunk_header_length;
+
+ memcpy (i, entry_xml, entry_xml_length);
+ i += entry_xml_length;
+ g_free (entry_xml);
+
+ memcpy (i, second_chunk_header, second_chunk_header_length);
+ g_free (second_chunk_header);
+ i += second_chunk_header_length;
+
+ memcpy (i, document_contents, document_length);
+ g_free (document_contents);
+ i += document_length;
+
+ memcpy (i, footer, footer_length);
+
+ /* Append the data */
+ soup_message_set_request (message, "multipart/related; boundary=" BOUNDARY_STRING, SOUP_MEMORY_TAKE, upload_data, content_length);
+ } else {
+ /* Send only metadata */
+ upload_data = g_strconcat ("<?xml version='1.0' encoding='UTF-8'?>", entry_xml, NULL);
+ g_free (entry_xml);
+ soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
+ }
+
+ if (document_file_info != NULL)
+ g_object_unref (document_file_info);
+ }
+
+ /* Send the message */
+ status = _gdata_service_send_message (GDATA_SERVICE (self), message, error);
+ if (status == SOUP_STATUS_NONE)
+ return NULL;
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE)
+ return NULL;
+
+ if (status != good_status_code) {
+ /* Error */
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (GDATA_SERVICE (self), GDATA_SERVICE_ERROR_WITH_INSERTION, status, message->reason_phrase, message->response_body->data,
+ message->response_body->length, error);
+ return NULL;
+ }
+
+ /* Build the updated entry */
+ g_assert (message->response_body->data != NULL);
+
+ /* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+ return GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_xml (new_document_type, message->response_body->data, (gint) message->response_body->length, error));
+}
+
+/**
+ * gdata_documents_service_upload_document:
+ * @self: an authenticated #GDataDocumentsService
+ * @document: the #GDataDocumentsEntry to insert, or %NULL
+ * @document_file: the document to upload, or %NULL
+ * @folder: the folder to which the document should be uploaded, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Uploads a document to Google Documents, using the properties from @document and the document file pointed to by @document_file.
+ *
+ * If @document is %NULL, only the document file will be uploaded. The new document entry will be named after the document file's name,
+ * and will have default metadata.
+ *
+ * If @document_file is %NULL, only the document metadata will be uploaded. A blank document file will be created with the name
+ * <literal>new document</literal> and the specified metadata. @document and @document_file cannot both be %NULL, but can both have values.
+ *
+ * The updated @document_entry will be returned on success, containing updated metadata.
+ *
+ * If there is a problem reading @document_file, an error from g_file_load_contents() or g_file_query_info() will be returned. Other errors from
+ * #GDataServiceError can be returned for other exceptional conditions, as determined by the server.
+ *
+ * Return value: an updated #GDataDocumentsEntry, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsEntry *
+gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file, GDataDocumentsFolder *folder,
+ GCancellable *cancellable, GError **error)
+{
+ GDataDocumentsEntry *new_document;
+ SoupMessage *message;
+ gchar *upload_uri, *tmp_str = NULL;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ g_return_val_if_fail (document == NULL || GDATA_IS_DOCUMENTS_ENTRY (document), NULL);
+ g_return_val_if_fail (document_file == NULL || G_IS_FILE (document_file), NULL);
+ g_return_val_if_fail (document != NULL || document_file != NULL, NULL);
+ g_return_val_if_fail (folder == NULL || GDATA_IS_DOCUMENTS_FOLDER (folder), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to upload documents."));
+ return NULL;
+ }
+
+ if (document != NULL && gdata_entry_is_inserted (GDATA_ENTRY (document)) == TRUE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
+ _("The document has already been uploaded."));
+ return NULL;
+ }
+
+ /* Get the upload URI */
+ if (folder != NULL) {
+ const gchar *folder_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (folder));
+ g_assert (folder_id != NULL);
+ upload_uri = tmp_str = g_strconcat ("http://docs.google.com/feeds/folders/private/full/folder%3A", folder_id, NULL);
+ } else {
+ upload_uri = "http://docs.google.com/feeds/documents/private/full";
+ }
+
+ message = soup_message_new (SOUP_METHOD_POST, upload_uri);
+ g_free (tmp_str);
+
+ new_document = upload_update_document (self, document, document_file, message, 201, cancellable, error);
+ g_object_unref (message);
+
+ return new_document;
+}
+
+/**
+ * gdata_documents_service_update_document:
+ * @self: a #GDataDocumentsService
+ * @document: the #GDataDocumentsEntry to update
+ * @document_file: the local document file containing the new data, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Update the document using the properties from @document and the document file pointed to by @document_file.
+ *
+ * If there is a problem reading @document_file, an error from g_file_load_contents() or g_file_query_info() will be returned. Other errors from
+ * #GDataServiceError can be returned for other exceptional conditions, as determined by the server.
+ *
+ * For more details, see gdata_service_insert_entry().
+ *
+ * Return value: an updated #GDataDocumentsEntry, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsEntry *
+gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file,
+ GCancellable *cancellable, GError **error)
+{
+ GDataDocumentsEntry *updated_document;
+ SoupMessage *message;
+ GDataLink *update_uri;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (document), NULL);
+ g_return_val_if_fail (document_file == NULL || G_IS_FILE (document_file), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to update documents."));
+ return NULL;
+ }
+
+ if (document_file == NULL)
+ update_uri = gdata_entry_look_up_link (GDATA_ENTRY (document), GDATA_LINK_EDIT);
+ else
+ update_uri = gdata_entry_look_up_link (GDATA_ENTRY (document), GDATA_LINK_EDIT_MEDIA);
+ g_assert (update_uri != NULL);
+
+ message = soup_message_new (SOUP_METHOD_PUT, gdata_link_get_uri (update_uri));
+ soup_message_headers_append (message->request_headers, "If-Match", gdata_entry_get_etag (GDATA_ENTRY (document)));
+
+ updated_document = upload_update_document (self, document, document_file, message, 200, cancellable, error);
+ g_object_unref (message);
+
+ return updated_document;
+}
+
+/**
+ * gdata_documents_service_move_document_to_folder:
+ * @self: an authenticated #GDataDocumentsService
+ * @document: the #GDataDocumentsEntry to move
+ * @folder: the #GDataDocumentsFolder to move @document into
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Move the given @document to the specified @folder. If the document is already in another folder, it will be added to
+ * the new folder, but will also remain in any previous folders.
+ *
+ * Errors from #GDataServiceError can be returned for other exceptional conditions, as determined by the server.
+ *
+ * Return value: an updated #GDataDocumentsEntry, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsEntry *
+gdata_documents_service_move_document_to_folder (GDataDocumentsService *self, GDataDocumentsEntry *document, GDataDocumentsFolder *folder,
+ GCancellable *cancellable, GError **error)
+{
+ GDataServiceClass *klass;
+ GDataDocumentsEntry *new_document;
+ gchar *uri, *entry_xml, *upload_data;
+ const gchar *folder_id;
+ SoupMessage *message;
+ guint status;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (document), NULL);
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_FOLDER (folder), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to move documents."));
+ return NULL;
+ }
+
+ folder_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (folder));
+ g_assert (folder_id != NULL);
+ uri = g_strconcat ("http://docs.google.com/feeds/folders/private/full/folder%3A", folder_id, NULL);
+
+ message = soup_message_new (SOUP_METHOD_POST, uri);
+ g_free (uri);
+
+ /* Make sure subclasses set their headers */
+ klass = GDATA_SERVICE_GET_CLASS (self);
+ if (klass->append_query_headers != NULL)
+ klass->append_query_headers (GDATA_SERVICE (self), message);
+
+ /* Get the XML content */
+ entry_xml = gdata_parsable_get_xml (GDATA_PARSABLE (document));
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ g_free (entry_xml);
+ return NULL;
+ }
+
+ upload_data = g_strconcat ("<?xml version='1.0' encoding='UTF-8'?>", entry_xml, NULL);
+ g_free (entry_xml);
+ soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
+
+ /* Send the message */
+ status = _gdata_service_send_message (GDATA_SERVICE (self), message, error);
+ 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 != 201) {
+ /* Error */
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (GDATA_SERVICE (self), GDATA_SERVICE_ERROR_WITH_INSERTION, status, message->reason_phrase, message->response_body->data,
+ message->response_body->length, error);
+ g_object_unref (message);
+ return NULL;
+ }
+
+ /* Build the updated entry */
+ g_assert (message->response_body->data != NULL);
+
+ /* Parse the XML; and update the document*/
+ new_document = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (document), message->response_body->data,
+ message->response_body->length, error));
+ g_object_unref (message);
+
+ return new_document;
+}
+
+/**
+ * gdata_documents_service_remove_document_from_folder:
+ * @self: a #GDataDocumentsService
+ * @document : the #GDataDocumentsEntry to remove
+ * @folder : the #GDataDocumentsFolder from wich we should remove @document
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Remove the #GDataDocumentsEntry @document from the GDataDocumentsFolder @folder, and updates the document entry @document.
+ *
+ * Errors from #GDataServiceError can be returned for other exceptional conditions, as determined by the server.
+ *
+ * Return value: an updated #GDataDocumentsEntry, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsEntry *
+gdata_documents_service_remove_document_from_folder (GDataDocumentsService *self, GDataDocumentsEntry *document, GDataDocumentsFolder *folder,
+ GCancellable *cancellable, GError **error)
+{
+ GDataServiceClass *klass;
+ gchar *uri;
+ const gchar *document_id, *folder_id;
+ SoupMessage *message;
+ GDataDocumentsEntry *new_document;
+ guint status;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (document), NULL);
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_FOLDER (folder), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to move documents."));
+ return NULL;
+ }
+
+ /* Get the document ID */
+ folder_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (folder));
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (document));
+ g_assert (folder_id != NULL);
+ g_assert (document_id != NULL);
+
+ if (GDATA_IS_DOCUMENTS_PRESENTATION (document))
+ uri = g_strdup_printf ("http://docs.google.com/feeds/folders/private/full/folder%%3A%s/presentation%%3A%s", folder_id, document_id);
+ else if (GDATA_IS_DOCUMENTS_SPREADSHEET (document))
+ uri = g_strdup_printf ("http://docs.google.com/feeds/folders/private/full/folder%%3A%s/spreadsheet%%3A%s", folder_id, document_id);
+ else if (GDATA_IS_DOCUMENTS_TEXT (document))
+ uri = g_strdup_printf ("http://docs.google.com/feeds/folders/private/full/folder%%3A%s/document%%3A%s", folder_id, document_id);
+ else
+ g_assert_not_reached ();
+
+ message = soup_message_new (SOUP_METHOD_DELETE, uri);
+ g_free (uri);
+
+ /* Make sure subclasses set their headers */
+ klass = GDATA_SERVICE_GET_CLASS (self);
+ if (klass->append_query_headers != NULL)
+ klass->append_query_headers (GDATA_SERVICE (self), message);
+
+ soup_message_headers_append (message->request_headers, "If-Match", gdata_entry_get_etag (GDATA_ENTRY (document)));
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ return NULL;
+ }
+
+ /* Send the message */
+ status = _gdata_service_send_message (GDATA_SERVICE (self), message, error);
+ 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 (self), GDATA_SERVICE_ERROR_WITH_INSERTION, status, message->reason_phrase, message->response_body->data,
+ message->response_body->length, error);
+ g_object_unref (message);
+ return NULL;
+ }
+
+ /* Build the updated entry */
+ g_assert (message->response_body->data != NULL);
+
+ /* Parse the XML; and update the document*/
+ new_document = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (document), message->response_body->data,
+ message->response_body->length, error));
+ g_object_unref (message);
+
+ return new_document;
+}
+
+GDataService *
+_gdata_documents_service_get_spreadsheet_service (GDataDocumentsService *self)
+{
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ return self->priv->spreadsheet_service;
+}
diff --git a/gdata/services/documents/gdata-documents-service.h b/gdata/services/documents/gdata-documents-service.h
new file mode 100644
index 0000000..0378b21
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-service.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_SERVICE_H
+#define GDATA_DOCUMENTS_SERVICE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gdata/gdata-service.h>
+#include <gdata/services/documents/gdata-documents-query.h>
+#include <gdata/services/documents/gdata-documents-feed.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GDataDocumentsServiceError:
+ * @GDATA_DOCUMENTS_SERVICE_ERROR_INVALID_CONTENT_TYPE: the content type of a provided file was invalid
+ *
+ * Error codes for #GDataDocumentsService operations.
+ *
+ * Since: 0.4.0
+ **/
+typedef enum {
+ GDATA_DOCUMENTS_SERVICE_ERROR_INVALID_CONTENT_TYPE
+} GDataDocumentsServiceError;
+
+#define GDATA_TYPE_DOCUMENTS_SERVICE (gdata_documents_service_get_type ())
+#define GDATA_DOCUMENTS_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_SERVICE, GDataDocumentsService))
+#define GDATA_DOCUMENTS_SERVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_SERVICE, GDataDocumentsServiceClass))
+#define GDATA_IS_DOCUMENTS_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_SERVICE))
+#define GDATA_IS_DOCUMENTS_SERVICE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_SERVICE))
+#define GDATA_DOCUMENTS_SERVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_SERVICE, GDataDocumentsServiceClass))
+
+#define GDATA_DOCUMENTS_SERVICE_ERROR gdata_documents_service_error_quark ()
+
+typedef struct _GDataDocumentsServicePrivate GDataDocumentsServicePrivate;
+
+/**
+ * GDataDocumentsService:
+ *
+ * All the fields in the #GDataDocumentsService structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataService parent;
+ GDataDocumentsServicePrivate *priv;
+} GDataDocumentsService;
+
+/**
+ * GDataDocumentsServiceClass:
+ *
+ * All the fields in the #GDataDocumentsServiceClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataServiceClass parent;
+} GDataDocumentsServiceClass;
+
+GType gdata_documents_service_get_type (void) G_GNUC_CONST;
+GQuark gdata_documents_service_error_quark (void) G_GNUC_CONST;
+
+GDataDocumentsService *gdata_documents_service_new (const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT;
+GDataDocumentsFeed *gdata_documents_service_query_documents (GDataDocumentsService *self, GDataDocumentsQuery *query, GCancellable *cancellable,
+ GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+void gdata_documents_service_query_documents_async (GDataDocumentsService *self, GDataDocumentsQuery *query, GCancellable *cancellable,
+ GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GAsyncReadyCallback callback, gpointer user_data);
+
+#include <gdata/services/documents/gdata-documents-folder.h>
+
+GDataDocumentsEntry *gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file,
+ GDataDocumentsFolder *folder, GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+GDataDocumentsEntry *gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocumentsEntry *document, GFile *document_file,
+ GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+GDataDocumentsEntry *gdata_documents_service_move_document_to_folder (GDataDocumentsService *self, GDataDocumentsEntry *document,
+ GDataDocumentsFolder *folder, GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+GDataDocumentsEntry *gdata_documents_service_remove_document_from_folder (GDataDocumentsService *self, GDataDocumentsEntry *document,
+ GDataDocumentsFolder *folder,
+ GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+#endif /* !GDATA_DOCUMENTS_SERVICE_H */
diff --git a/gdata/services/documents/gdata-documents-spreadsheet.c b/gdata/services/documents/gdata-documents-spreadsheet.c
new file mode 100644
index 0000000..45c494d
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-spreadsheet.c
@@ -0,0 +1,172 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier <saunierthibault gmail com
+ *
+ * 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-documents-spreadsheet
+ * @short_description: GData Documents spreadsheet object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-spreadsheet.h
+ *
+ * #GDataDocumentsSpreadsheet is a subclass of #GDataDocumentsEntry to represent a spreadsheet from Google Documents.
+ *
+ * For more details of Google Documents' GData API, see the <ulink type="http://code.google.com/apis/document/docs/2.0/developers_guide_protocol.html">
+ * online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-documents-spreadsheet.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+
+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))
+
+static void
+gdata_documents_spreadsheet_class_init (GDataDocumentsSpreadsheetClass *klass)
+{
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+ parsable_class->get_xml = get_xml;
+}
+
+static void
+gdata_documents_spreadsheet_init (GDataDocumentsSpreadsheet *self)
+{
+ /* Why am I writing it? */
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ const gchar *document_id;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_documents_spreadsheet_parent_class)->get_xml (parsable, xml_string);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (parsable));
+ if (document_id != NULL)
+ g_string_append_printf (xml_string, "<gd:resourceId>spreadsheet:%s</gd:resourceId>", document_id);
+}
+
+/**
+ * gdata_documents_spreadsheet_new:
+ * @id: the entry's ID (not the document ID of the spreadsheet), or %NULL
+ *
+ * Creates a new #GDataDocumentsSpreadsheet with the given entry ID (#GDataEntry:id).
+ *
+ * Return value: a new #GDataDocumentsSpreadsheet, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsSpreadsheet *
+gdata_documents_spreadsheet_new (const gchar *id)
+{
+ return g_object_new (GDATA_TYPE_DOCUMENTS_SPREADSHEET, "id", id, NULL);
+}
+
+/**
+ * gdata_documents_spreadsheet_download_document:
+ * @self: a #GDataDocumentsPresentation
+ * @service: a #GDataDocumentsService
+ * @content_type: return location for the document's content type, or %NULL; free with g_free()
+ * @export_format: the format in which the presentation should be exported
+ * @gid: the %0-based sheet ID to download, or %-1
+ * @destination_directory: the directory into which the presentation file should be saved
+ * @replace_file_if_exists: %TRUE if the file should be replaced if it already exists, %FALSE otherwise
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Downloads and returns the spreadsheet file represented by the #GDataDocumentsSpreadsheet. If the document doesn't exist,
+ * %NULL is returned, but no error is set in @error. TODO: What?
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * 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).
+ *
+ * If there is an error getting the document, a %GDATA_SERVICE_ERROR_WITH_QUERY error will be returned.
+ *
+ * Return value: the document's data, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GFile *
+gdata_documents_spreadsheet_download_document (GDataDocumentsSpreadsheet *self, GDataDocumentsService *service, gchar **content_type,
+ GDataDocumentsSpreadsheetFormat export_format, gint gid, GFile *destination_directory,
+ gboolean replace_file_if_exists, GCancellable *cancellable, GError **error)
+{
+ gchar *link_href;
+ GFile *destination_file;
+ const gchar *document_id, *extension, *fmcmd;
+ 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 (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_directory), 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 */
+ destination_file = _gdata_documents_entry_download_document (GDATA_DOCUMENTS_ENTRY (self), spreadsheet_service, content_type,
+ link_href, destination_directory, extension, replace_file_if_exists,
+ cancellable, error);
+ g_free (link_href);
+
+ return destination_file;
+}
diff --git a/gdata/services/documents/gdata-documents-spreadsheet.h b/gdata/services/documents/gdata-documents-spreadsheet.h
new file mode 100644
index 0000000..0f56493
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-spreadsheet.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_SPREADSHEET_H
+#define GDATA_DOCUMENTS_SPREADSHEET_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/services/documents/gdata-documents-entry.h>
+#include <gdata/gdata-types.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_SPREADSHEET (gdata_documents_spreadsheet_get_type ())
+#define GDATA_DOCUMENTS_SPREADSHEET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_SPREADSHEET, GDataDocumentsSpreadsheet))
+#define GDATA_DOCUMENTS_SPREADSHEET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_SPREADSHEET, GDataDocumentsSpreadsheetClass))
+#define GDATA_IS_DOCUMENTS_SPREADSHEET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_SPREADSHEET))
+#define GDATA_IS_DOCUMENTS_SPREADSHEET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_SPREADSHEET))
+#define GDATA_DOCUMENTS_SPREADSHEET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_SPREADSHEET, GDataDocumentsSpreadsheetClass))
+
+typedef struct _GDataDocumentsSpreadsheetPrivate GDataDocumentsSpreadsheetPrivate;
+
+/**
+ * GDataDocumentsSpreadsheetFormat:
+ * @GDATA_DOCUMENTS_SPREADSHEET_XLS: Microsoft Excel spreadsheet (XLS) format
+ * @GDATA_DOCUMENTS_SPREADSHEET_CSV: Comma-Separated Values (CSV) format
+ * @GDATA_DOCUMENTS_SPREADSHEET_PDF: Portable Document Format (PDF)
+ * @GDATA_DOCUMENTS_SPREADSHEET_ODS: OpenDocument Spreadsheet (ODS) format
+ * @GDATA_DOCUMENTS_SPREADSHEET_TSV: Tab-Separated Values (TSV) format
+ * @GDATA_DOCUMENTS_SPREADSHEET_HTML: HyperText Markup Language (HTML) format
+ *
+ * The different available download formats for spreadsheets.
+ *
+ * Since: 0.4.0
+ **/
+typedef enum {
+ GDATA_DOCUMENTS_SPREADSHEET_XLS = 0,
+ GDATA_DOCUMENTS_SPREADSHEET_CSV,
+ GDATA_DOCUMENTS_SPREADSHEET_PDF,
+ GDATA_DOCUMENTS_SPREADSHEET_ODS,
+ GDATA_DOCUMENTS_SPREADSHEET_TSV,
+ GDATA_DOCUMENTS_SPREADSHEET_HTML
+} GDataDocumentsSpreadsheetFormat;
+
+/**
+ * GDataDocumentsSpreadsheet:
+ *
+ * All the fields in the #GDataDocumentsSpreadsheet structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataDocumentsEntry parent;
+ GDataDocumentsSpreadsheetPrivate *priv;
+} GDataDocumentsSpreadsheet;
+
+/**
+ * GDataDocumentsSpreadsheetClass:
+ *
+ * All the fields in the #GDataDocumentsSpreadsheetClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataDocumentsEntryClass parent;
+} GDataDocumentsSpreadsheetClass;
+
+GType gdata_documents_spreadsheet_get_type (void) G_GNUC_CONST;
+
+GDataDocumentsSpreadsheet *gdata_documents_spreadsheet_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+
+#include <gdata/services/documents/gdata-documents-service.h>
+
+GFile *gdata_documents_spreadsheet_download_document (GDataDocumentsSpreadsheet *self, GDataDocumentsService *service, gchar **content_type,
+ GDataDocumentsSpreadsheetFormat export_format, gint gid, GFile *destination_directory,
+ gboolean replace_file_if_exists, GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_SPREADSHEET_H */
diff --git a/gdata/services/documents/gdata-documents-text.c b/gdata/services/documents/gdata-documents-text.c
new file mode 100644
index 0000000..731ed24
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-text.c
@@ -0,0 +1,154 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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-documents-text
+ * @short_description: GData Documents text object
+ * @stability: Unstable
+ * @include: gdata/services/documents/gdata-documents-text.h
+ *
+ * #GDataDocumentsText is a subclass of #GDataDocumentsEntry to represent a text document from Google Documents.
+ *
+ * For more details of Google Documents' GData API, see the <ulink type="http://code.google.com/apis/document/docs/2.0/developers_guide_protocol.html">
+ * online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-documents-text.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+
+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))
+
+static void
+gdata_documents_text_class_init (GDataDocumentsTextClass *klass)
+{
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+ parsable_class->get_xml = get_xml;
+}
+
+static void
+gdata_documents_text_init (GDataDocumentsText *self)
+{
+ /* Why am I writing it? */
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ const gchar *document_id;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_documents_text_parent_class)->get_xml (parsable, xml_string);
+
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (parsable));
+ if (document_id != NULL)
+ g_string_append_printf (xml_string, "<gd:resourceId>document:%s</gd:resourceId>", document_id);
+}
+
+/**
+ * gdata_documents_text_new:
+ * @id: the entry's ID (not the document ID of the text document), or %NULL
+ *
+ * Creates a new #GDataDocumentsText with the given entry ID (#GDataEntry:id).
+ *
+ * Return value: a new #GDataDocumentsText, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataDocumentsText *
+gdata_documents_text_new (const gchar *id)
+{
+ return g_object_new (GDATA_TYPE_DOCUMENTS_TEXT, "id", id, NULL);
+}
+
+/**
+ * gdata_documents_text_download_document:
+ * @self: a #GDataDocumentsText
+ * @service: a #GDataDocumentsService
+ * @content_type: return location for the document's content type, or %NULL; free with g_free()
+ * @export_format: the format in which the text document should be exported
+ * @destination_directory: the directory into which the text document file should be saved
+ * @replace_file_if_exists: %TRUE if the file should be replaced if it already exists, %FALSE otherwise
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Downloads and returns the text document file represented by the #GDataDocumentsText. If the document doesn't exist,
+ * %NULL is returned, but no error is set in @error. TODO: What?
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If there is an error getting the document, a %GDATA_SERVICE_ERROR_WITH_QUERY error will be returned.
+ *
+ * Return value: the document's data, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GFile *
+gdata_documents_text_download_document (GDataDocumentsText *self, GDataDocumentsService *service, gchar **content_type,
+ GDataDocumentsTextFormat export_format, GFile *destination_directory,
+ gboolean replace_file_if_exists, GCancellable *cancellable, GError **error)
+{
+ GFile *destination_file;
+ 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 (G_IS_FILE (destination_directory), 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 */
+ destination_file = _gdata_documents_entry_download_document (GDATA_DOCUMENTS_ENTRY (self), GDATA_SERVICE (service),
+ content_type, link_href, destination_directory,
+ export_formats[export_format], replace_file_if_exists,
+ cancellable, error);
+ g_free (link_href);
+
+ return destination_file;
+}
diff --git a/gdata/services/documents/gdata-documents-text.h b/gdata/services/documents/gdata-documents-text.h
new file mode 100644
index 0000000..b7015da
--- /dev/null
+++ b/gdata/services/documents/gdata-documents-text.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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_DOCUMENTS_TEXT_H
+#define GDATA_DOCUMENTS_TEXT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/services/documents/gdata-documents-entry.h>
+#include <gdata/gdata-types.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_DOCUMENTS_TEXT (gdata_documents_text_get_type ())
+#define GDATA_DOCUMENTS_TEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_DOCUMENTS_TEXT, GDataDocumentsText))
+#define GDATA_DOCUMENTS_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_DOCUMENTS_TEXT, GDataDocumentsTextClass))
+#define GDATA_IS_DOCUMENTS_TEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_DOCUMENTS_TEXT))
+#define GDATA_IS_DOCUMENTS_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_DOCUMENTS_TEXT))
+#define GDATA_DOCUMENTS_TEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_DOCUMENTS_TEXT, GDataDocumentsTextClass))
+
+typedef struct _GDataDocumentsTextPrivate GDataDocumentsTextPrivate;
+
+/**
+ * GDataDocumentsTextFormat:
+ * @GDATA_DOCUMENTS_TEXT_DOC: Microsoft Word (DOC) format
+ * @GDATA_DOCUMENTS_TEXT_HTML: HyperText Markup Language (HTML) format
+ * @GDATA_DOCUMENTS_TEXT_ODT: OpenDocument Text (ODT) format
+ * @GDATA_DOCUMENTS_TEXT_PDF: Portable Document Format (PDF)
+ * @GDATA_DOCUMENTS_TEXT_PNG: Portable Network Graphics (PNG) image format
+ * @GDATA_DOCUMENTS_TEXT_RTF: Rich Text Format (RTF)
+ * @GDATA_DOCUMENTS_TEXT_TXT: plain text format
+ * @GDATA_DOCUMENTS_TEXT_ZIP: ZIP archive containing images and exported HTML
+ *
+ * The different available download formats for text documents.
+ *
+ * Since: 0.4.0
+ **/
+typedef enum {
+ GDATA_DOCUMENTS_TEXT_DOC = 0,
+ GDATA_DOCUMENTS_TEXT_HTML,
+ GDATA_DOCUMENTS_TEXT_ODT,
+ GDATA_DOCUMENTS_TEXT_PDF,
+ GDATA_DOCUMENTS_TEXT_PNG,
+ GDATA_DOCUMENTS_TEXT_RTF,
+ GDATA_DOCUMENTS_TEXT_TXT,
+ GDATA_DOCUMENTS_TEXT_ZIP,
+} GDataDocumentsTextFormat;
+
+/**
+ * GDataDocumentsText:
+ *
+ * All the fields in the #GDataDocumentsText structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataDocumentsEntry parent;
+ GDataDocumentsTextPrivate *priv;
+} GDataDocumentsText;
+
+/**
+ * GDataDocumentsTextClass:
+ *
+ * All the fields in the #GDataDocumentsTextClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataDocumentsEntryClass parent;
+} GDataDocumentsTextClass;
+
+GType gdata_documents_text_get_type (void) G_GNUC_CONST;
+
+GDataDocumentsText *gdata_documents_text_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+
+#include <gdata/services/documents/gdata-documents-service.h>
+
+GFile *gdata_documents_text_download_document (GDataDocumentsText *self, GDataDocumentsService *service, gchar **content_type,
+ GDataDocumentsTextFormat export_format, GFile *destination_directory,
+ gboolean replace_file_if_exists, GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+
+#endif /* !GDATA_DOCUMENTS_TEXT_H */
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index ed92851..1ba7ca4 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -1350,6 +1350,8 @@ gdata_youtube_video_set_recorded (GDataYouTubeVideo *self, GTimeVal *recorded)
* </programlisting></informalexample>
*
* Since: 0.4.0
+ *
+ * Return value: the video ID, or %NULL; free with g_free()
**/
gchar *
gdata_youtube_video_get_video_id_from_uri (const gchar *video_uri)
diff --git a/gdata/tests/Makefile.am b/gdata/tests/Makefile.am
index 7db3992..0291531 100644
--- a/gdata/tests/Makefile.am
+++ b/gdata/tests/Makefile.am
@@ -32,6 +32,9 @@ contacts_SOURCES = contacts.c $(TEST_SRCS)
TEST_PROGS += picasaweb
picasaweb_SOURCES = picasaweb.c $(TEST_SRCS)
+TEST_PROGS += documents
+documents_SOURCES = documents.c $(TEST_SRCS)
+
TEST_PROGS += memory
memory_SOURCES = memory.c $(TEST_SRCS)
diff --git a/gdata/tests/common.h b/gdata/tests/common.h
index 78af20a..745b9e8 100644
--- a/gdata/tests/common.h
+++ b/gdata/tests/common.h
@@ -24,6 +24,7 @@ G_BEGIN_DECLS
#define CLIENT_ID "ytapi-GNOME-libgdata-444fubtt-0"
#define USERNAME "libgdata test gmail com"
+#define DOCUMENTS_USERNAME "libgdata documents gmail com"
#define PASSWORD "gdata-libgdata"
G_END_DECLS
diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c
new file mode 100644
index 0000000..8cc9388
--- /dev/null
+++ b/gdata/tests/documents.c
@@ -0,0 +1,588 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ *
+ * 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/>.
+ */
+
+#include <glib.h>
+#include <unistd.h>
+
+#include "gdata.h"
+#include "common.h"
+
+/* TODO: probably a better way to do this; some kind of data associated with the test suite? */
+static GDataDocumentsService *service = NULL;
+
+static void
+test_authentication (void)
+{
+ gboolean retval;
+ GError *error = NULL;
+
+ /* Create a service */
+ service = gdata_documents_service_new (CLIENT_ID);
+
+ g_assert (service != NULL);
+ g_assert (GDATA_IS_SERVICE (service));
+ g_assert_cmpstr (gdata_service_get_client_id (GDATA_SERVICE (service)), ==, CLIENT_ID);
+
+ /* Log in */
+ retval = gdata_service_authenticate (GDATA_SERVICE (service), DOCUMENTS_USERNAME, PASSWORD, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (retval == TRUE);
+ g_clear_error (&error);
+
+ /* Check all is as it should be */
+ g_assert (gdata_service_is_authenticated (GDATA_SERVICE (service)) == TRUE);
+ g_assert_cmpstr (gdata_service_get_username (GDATA_SERVICE (service)), ==, DOCUMENTS_USERNAME);
+ g_assert_cmpstr (gdata_service_get_password (GDATA_SERVICE (service)), ==, PASSWORD);
+}
+
+static void
+test_remove_all_documents_and_folders (GDataService *service)
+{
+ GDataDocumentsFeed *feed;
+ GDataDocumentsQuery *query;
+ GError *error = NULL;
+ GList *i;
+
+ g_assert (service != NULL);
+
+ query = gdata_documents_query_new (NULL);
+ gdata_documents_query_set_show_folders (query, TRUE);
+
+ feed = gdata_documents_service_query_documents (GDATA_DOCUMENTS_SERVICE (service), query, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_FEED (feed));
+
+ for (i = gdata_feed_get_entries (GDATA_FEED (feed)); i != NULL; i = i->next) {
+ gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (i->data), NULL, &error);
+ g_assert_no_error (error);
+ }
+
+ g_clear_error (&error);
+
+ /* TODO: check entries and feed properties */
+ g_object_unref (feed);
+}
+
+static void
+test_query_all_documents_with_folder (GDataService *service)
+{
+ GDataDocumentsFeed *feed;
+ GDataDocumentsQuery *query;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ query = gdata_documents_query_new (NULL);
+ gdata_documents_query_set_show_folders (query, TRUE);
+
+ feed = gdata_documents_service_query_documents (GDATA_DOCUMENTS_SERVICE (service), query, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (feed));
+
+ g_clear_error (&error);
+ g_object_unref (feed);
+}
+
+static void
+test_query_all_documents (GDataService *service)
+{
+ GDataDocumentsFeed *feed;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ feed = gdata_documents_service_query_documents (GDATA_DOCUMENTS_SERVICE (service), NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (feed));
+ g_clear_error (&error);
+
+ /* TODO: check entries and feed properties */
+
+ g_object_unref (feed);
+}
+
+static void
+test_query_all_documents_async_cb (GDataService *service, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+ GDataDocumentsFeed *feed;
+ GError *error = NULL;
+
+ feed = GDATA_DOCUMENTS_FEED (gdata_service_query_finish (GDATA_SERVICE (service), async_result, &error));
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (feed));
+ g_clear_error (&error);
+
+ /* TODO: Tests? */
+ g_main_loop_quit (main_loop);
+ g_object_unref (feed);
+}
+
+static void
+test_query_all_documents_async (GDataService *service)
+{
+ GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
+
+ g_assert (service != NULL);
+
+ gdata_documents_service_query_documents_async (GDATA_DOCUMENTS_SERVICE (service), NULL, NULL, NULL,
+ NULL, (GAsyncReadyCallback) test_query_all_documents_async_cb, main_loop);
+
+ g_main_loop_run (main_loop);
+ g_main_loop_unref (main_loop);
+}
+
+static void
+test_upload_metadata (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document;
+ GDataCategory *category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_spreadsheet_new (NULL));
+ category = gdata_category_new ("http://schemas.google.com/docs/2007#spreadsheet", "http://schemas.google.com/g/2005#kind", "spreadsheet");
+
+ gdata_entry_set_title (GDATA_ENTRY (document), "myNewSpreadsheet");
+ gdata_entry_add_category (GDATA_ENTRY (document), category);
+
+ /* Insert the document */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), GDATA_DOCUMENTS_ENTRY (document), NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_SPREADSHEET (new_document));
+
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+}
+
+static void
+test_upload_metadata_file (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document;
+ GFile *document_file;
+ GDataCategory *category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document_file = g_file_new_for_path ("test.odt");
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_text_new (NULL));
+ category = gdata_category_new ("http://schemas.google.com/docs/2007#document", "http://schemas.google.com/g/2005#kind", "document");
+ gdata_entry_set_title (GDATA_ENTRY (document), "upload_metadata_file");
+ gdata_entry_add_category (GDATA_ENTRY (document), category);
+
+ /* Insert the document */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, document_file, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document));
+
+ g_clear_error (&error);
+ g_object_unref (document_file);
+ g_object_unref (document);
+ g_object_unref (new_document);
+}
+
+static void
+test_upload_file (GDataService *service)
+{
+ GDataDocumentsEntry *new_document;
+ GFile *document_file;
+ GDataCategory *category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document_file = g_file_new_for_path ("test.ppt");
+
+ category = gdata_category_new ("http://schemas.google.com/docs/2007#presentation", "http://schemas.google.com/g/2005#kind", "presentation");
+
+ /* Insert the document */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), NULL, document_file, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_document));
+
+ g_clear_error (&error);
+ g_object_unref (new_document);
+ g_object_unref (document_file);
+}
+
+static void
+test_add_remove_file_from_folder (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document, *new_document2;
+ GDataDocumentsFolder *folder, *new_folder;
+ GFile *document_file;
+ GDataCategory *folder_category, *document_category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ folder = gdata_documents_folder_new (NULL);
+ folder_category = gdata_category_new ("http://schemas.google.com/docs/2007#folder", "http://schemas.google.com/g/2005#kind", "folder");
+ gdata_entry_set_title (GDATA_ENTRY (folder), "add_remove_from_folder_folder");
+ gdata_entry_add_category (GDATA_ENTRY (folder), folder_category);
+
+ document_file = g_file_new_for_path ("test.ppt");
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_presentation_new (NULL));
+ document_category = gdata_category_new ("http://schemas.google.com/docs/2007#presentation", "http://schemas.google.com/g/2005#kind", "presentation");
+ gdata_entry_set_title (GDATA_ENTRY (document), "add_remove_from_folder_presentation");
+ gdata_entry_add_category (GDATA_ENTRY (document), document_category);
+
+ /* Insert the folder */
+ new_folder = GDATA_DOCUMENTS_FOLDER (gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), GDATA_DOCUMENTS_ENTRY (folder), NULL, NULL, NULL, &error));
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_FOLDER (new_folder));
+
+ /* Insert the document in the new folder */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, document_file, new_folder, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_document));
+
+ /*remove document from the folder*/
+ new_document2 = gdata_documents_service_remove_document_from_folder (GDATA_DOCUMENTS_SERVICE (service), new_document, new_folder, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_document2));
+
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+ g_object_unref (new_document2);
+ g_object_unref (folder);
+ g_object_unref (new_folder);
+ g_object_unref (document_file);
+}
+
+static void
+test_add_file_folder_and_move (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document, *new_document2;
+ GDataDocumentsFolder *folder, *new_folder;
+ GFile *document_file;
+ GDataCategory *folder_category, *document_category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document_file = g_file_new_for_path ("test.odt");
+
+ folder = gdata_documents_folder_new (NULL);
+ folder_category = gdata_category_new ("http://schemas.google.com/docs/2007#folder", "http://schemas.google.com/g/2005#kind", "folder");
+ gdata_entry_set_title (GDATA_ENTRY (folder), "add_file_folder_move_folder");
+ gdata_entry_add_category (GDATA_ENTRY (folder), folder_category);
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_text_new (NULL));
+ document_category = gdata_category_new ("http://schemas.google.com/docs/2007#document", "http://schemas.google.com/g/2005#kind", "document");
+ gdata_entry_set_title (GDATA_ENTRY (document), "add_file_folder_move_text");
+ gdata_entry_add_category (GDATA_ENTRY (document), document_category);
+
+ /* Insert the folder */
+ new_folder = GDATA_DOCUMENTS_FOLDER (gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), GDATA_DOCUMENTS_ENTRY (folder), NULL, NULL, NULL, &error));
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_FOLDER (new_folder));
+
+ /* Insert the document in the new folder */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, document_file, NULL, NULL, &error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document));
+
+ /* Remove the document from the folder */
+ new_document2 = gdata_documents_service_move_document_to_folder (GDATA_DOCUMENTS_SERVICE (service), new_document, new_folder, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document2));
+
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+ g_object_unref (new_document2);
+ g_object_unref (folder);
+ g_object_unref (new_folder);
+ g_object_unref (document_file);
+}
+
+static void
+test_upload_file_metadata_in_new_folder (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document;
+ GDataDocumentsFolder *folder, *new_folder;
+ GFile *document_file;
+ GDataCategory *folder_category, *document_category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document_file = g_file_new_for_path ("test.odt");
+
+ folder = gdata_documents_folder_new (NULL);
+ folder_category = gdata_category_new ("http://schemas.google.com/docs/2007#folder", "http://schemas.google.com/g/2005#kind", "folder");
+ gdata_entry_set_title (GDATA_ENTRY (folder), "upload_file_metadata_in_new_folder_folder");
+ gdata_entry_add_category (GDATA_ENTRY (folder), folder_category);
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_text_new (NULL));
+ document_category = gdata_category_new ("http://schemas.google.com/docs/2007#document", "http://schemas.google.com/g/2005#kind", "document");
+ gdata_entry_set_title (GDATA_ENTRY (document), "upload_file_metadata_in_new_folder_text");
+ gdata_entry_add_category (GDATA_ENTRY (document), document_category);
+
+ /* Insert the folder */
+ new_folder = GDATA_DOCUMENTS_FOLDER (gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), GDATA_DOCUMENTS_ENTRY (folder), NULL, NULL, NULL, &error));
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_FOLDER (new_folder));
+ g_clear_error (&error);
+
+ /* Insert the document in the new folder */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, document_file, new_folder, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document));
+
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+ g_object_unref (folder);
+ g_object_unref (new_folder);
+ g_object_unref (document_file);
+}
+
+static void
+test_update_metadata (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document, *updated_document;
+ GDataCategory *category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_text_new (NULL));
+ category = gdata_category_new ("http://schemas.google.com/docs/2007#document", "http://schemas.google.com/g/2005#kind", "document");
+ gdata_entry_set_title (GDATA_ENTRY (document), "update_metadata_first_title");
+ gdata_entry_add_category (GDATA_ENTRY (document), category);
+
+ /* Insert the document */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document));
+
+ /* Change the title */
+ gdata_entry_set_title (GDATA_ENTRY (document), "update_metadata_updated_title");
+
+ /* Update the document */
+ updated_document = gdata_documents_service_update_document (GDATA_DOCUMENTS_SERVICE (service), new_document, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (updated_document));
+
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+ g_object_unref (updated_document);
+}
+
+static void
+test_update_metadata_file (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document, *updated_document;
+ GFile *document_file, *updated_document_file;
+ GDataCategory *category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document_file = g_file_new_for_path ("test.odt");
+ updated_document_file = g_file_new_for_path ("test_updated.odt");
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_text_new (NULL));
+ category = gdata_category_new ("http://schemas.google.com/docs/2007#document", "http://schemas.google.com/g/2005#kind", "document");
+ gdata_entry_set_title (GDATA_ENTRY (document), "update_metadata_file_first_title");
+ gdata_entry_add_category (GDATA_ENTRY (document), category);
+
+ /* Insert the documents metadata*/
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document));
+
+ /* Change the title of the document */
+ gdata_entry_set_title (GDATA_ENTRY (new_document), "update_metadata_file_updated_title");
+
+ /* Update the document */
+ updated_document = gdata_documents_service_update_document (GDATA_DOCUMENTS_SERVICE (service), new_document, updated_document_file, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (updated_document));
+
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+ g_object_unref (updated_document);
+ g_object_unref (document_file);
+}
+
+static void
+test_update_file (GDataService *service)
+{
+ GDataDocumentsEntry *new_document, *updated_document;
+ GFile *document_file, *updated_document_file;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document_file = g_file_new_for_path ("test.ppt");
+ updated_document_file = g_file_new_for_path ("test_updated_file.ppt");
+
+ /* Insert the document */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), NULL, document_file, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_document));
+
+ /* Update the document */
+ updated_document = gdata_documents_service_update_document (GDATA_DOCUMENTS_SERVICE (service), new_document, document_file, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (updated_document));
+ g_clear_error (&error);
+
+ g_object_unref (document_file);
+ g_object_unref (updated_document_file);
+ g_object_unref (new_document);
+ g_object_unref (updated_document);
+}
+
+static void
+test_download_all_documents (GDataService *service)
+{
+ GDataDocumentsFeed *feed;
+ GError *error = NULL;
+ gchar *content_type = NULL;
+ GFile *destination_directory;
+ GList *i;
+
+ destination_directory = g_file_new_for_path ("/tmp");
+ feed = gdata_documents_service_query_documents (GDATA_DOCUMENTS_SERVICE (service), NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (feed));
+
+ for (i = gdata_feed_get_entries (GDATA_FEED (feed)); i != NULL; i = i->next) {
+ GFile *destination_file = NULL;
+
+ if (GDATA_IS_DOCUMENTS_PRESENTATION (i->data)) {
+ destination_file = gdata_documents_presentation_download_document (GDATA_DOCUMENTS_PRESENTATION (i->data), GDATA_DOCUMENTS_SERVICE (service),
+ &content_type, GDATA_DOCUMENTS_PRESENTATION_PPT, destination_directory,
+ TRUE, NULL, &error);
+ } else if (GDATA_IS_DOCUMENTS_SPREADSHEET (i->data)) {
+ destination_file = gdata_documents_spreadsheet_download_document (i->data, GDATA_DOCUMENTS_SERVICE (service),
+ &content_type, GDATA_DOCUMENTS_SPREADSHEET_ODS, -1, destination_directory,
+ TRUE, NULL, &error);
+ } else if (GDATA_IS_DOCUMENTS_TEXT (i->data)) {
+ destination_file = gdata_documents_text_download_document (i->data, GDATA_DOCUMENTS_SERVICE (service), &content_type, GDATA_DOCUMENTS_TEXT_ODT,
+ destination_directory, TRUE, NULL, &error);
+ }
+
+ g_assert_no_error (error);
+ if (destination_file != NULL)
+ g_object_unref (destination_file);
+ g_free (content_type);
+ }
+
+ g_object_unref (destination_directory);
+ g_object_unref (feed);
+ g_clear_error (&error);
+}
+
+static void
+test_new_document_with_collaborator (GDataService *service)
+{
+ GDataDocumentsEntry *document, *new_document;
+ GDataAccessRule *access_rule, *new_access_rule;
+ GDataCategory *category;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ document = GDATA_DOCUMENTS_ENTRY (gdata_documents_spreadsheet_new (NULL));
+ category = gdata_category_new ("http://schemas.google.com/docs/2007#spreadsheet", "http://schemas.google.com/g/2005#kind", "spreadsheet");
+
+ gdata_entry_set_title (GDATA_ENTRY (document), "new_with_collaborator");
+ gdata_entry_add_category (GDATA_ENTRY (document), category);
+
+ /* Insert the document */
+ new_document = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), GDATA_DOCUMENTS_ENTRY (document), NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_DOCUMENTS_SPREADSHEET (new_document));
+
+ /* New access rule */
+ access_rule = gdata_access_rule_new (NULL);
+ gdata_access_rule_set_role (access_rule, "writer");
+ gdata_access_rule_set_scope (access_rule, "user", "libgdata test gmail com");
+
+ /* Set access rules */
+ new_access_rule = gdata_access_handler_insert_rule (GDATA_ACCESS_HANDLER (new_document), GDATA_SERVICE (service), access_rule, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_ACCESS_RULE (new_access_rule));
+
+ /* Check if everything is as it should be */
+ g_clear_error (&error);
+ g_object_unref (document);
+ g_object_unref (new_document);
+ g_object_unref (access_rule);
+ g_object_unref (new_access_rule);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GDataService *service;
+ gint retval;
+
+ g_type_init ();
+ g_thread_init (NULL);
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=");
+
+ service = GDATA_SERVICE (gdata_documents_service_new (CLIENT_ID));
+ gdata_service_authenticate (service, DOCUMENTS_USERNAME, PASSWORD, NULL, NULL);
+
+ g_test_add_func ("/documents/authentication", test_authentication);
+
+ g_test_add_data_func ("/documents/remove/all", service, test_remove_all_documents_and_folders);
+
+ g_test_add_data_func ("/documents/upload/only_file", service, test_upload_file);
+ g_test_add_data_func ("/documents/upload/metadata_file", service, test_upload_metadata_file);
+ g_test_add_data_func ("/documents/upload/only_metadata", service, test_upload_metadata);
+ g_test_add_data_func ("/documents/upload/metadata_file_in_new_folder", service, test_upload_file_metadata_in_new_folder);
+
+ g_test_add_data_func ("/documents/download/download_all_documents", service, test_download_all_documents);
+
+ g_test_add_data_func ("/documents/update/only_metadata", service, test_update_metadata);
+ g_test_add_data_func ("/documents/update/only_file", service, test_update_file);
+ g_test_add_data_func ("/documents/update/metadata_file", service, test_update_metadata_file);
+
+ g_test_add_data_func ("/documents/access_rules/add_document_with_a_collaborator", service,
+ test_new_document_with_collaborator);
+
+ g_test_add_data_func ("/documents/query/all_documents_with_folder", service, test_query_all_documents_with_folder);
+ g_test_add_data_func ("/documents/query/all_documents", service, test_query_all_documents);
+ if (g_test_thorough () == TRUE)
+ g_test_add_data_func ("/documents/query/all_documents_async", service, test_query_all_documents_async);
+
+ g_test_add_data_func ("/documents/move/move_to_folder", service, test_add_file_folder_and_move);
+
+ g_test_add_data_func ("/documents/move/remove_from_folder", service, test_add_remove_file_from_folder);
+
+ g_test_add_data_func ("/documents/remove/all", service, test_remove_all_documents_and_folders);
+ retval = g_test_run ();
+
+ g_object_unref (service);
+
+ return retval;
+}
diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c
index c72ed67..1542cdf 100644
--- a/gdata/tests/picasaweb.c
+++ b/gdata/tests/picasaweb.c
@@ -552,4 +552,3 @@ main (int argc, char *argv[])
return retval;
}
-
diff --git a/po/fr.po b/po/fr.po
index 17f3524..f81691a 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -290,3 +290,7 @@ msgstr "La vidéo n'a pas de vidéo connexe <link>."
#: ../gdata/services/youtube/gdata-youtube-service.c:634
msgid "You must be authenticated to upload a video."
msgstr "Vous devez vous authentifier pour envoyer une vidéo."
+
+#: ../gdata/services/documents/gdata-documents-service.c:652
+msgid "You must be authenticated to upload documents."
+msgstr "Vous devez vous authentifier pour envoyer un document."
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]