[libgdata] Bug 587073 – Add Google Documents service



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]