summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.in1
-rw-r--r--docs/reference/Makefile.am3
-rw-r--r--docs/reference/gdata-docs.xml8
-rw-r--r--docs/reference/gdata-sections.txt141
-rw-r--r--gdata/Makefile.am1
-rw-r--r--gdata/gdata-access-rule.c6
-rw-r--r--gdata/gdata-entry.c74
-rw-r--r--gdata/gdata-entry.h2
-rw-r--r--gdata/gdata-feed.c3
-rw-r--r--gdata/gdata-parser.c17
-rw-r--r--gdata/gdata-parser.h1
-rw-r--r--gdata/gdata-service.c1
-rw-r--r--gdata/gdata.h7
-rw-r--r--gdata/gdata.symbols83
-rw-r--r--gdata/media/gdata-media-content.c12
-rw-r--r--gdata/media/gdata-media-credit.c6
-rw-r--r--gdata/media/gdata-media-group.c21
-rw-r--r--gdata/media/gdata-media-group.h1
-rw-r--r--gdata/services/Makefile.am2
-rw-r--r--gdata/services/picasaweb/Makefile.am69
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-album.c1097
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-album.h109
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-file.c1303
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-file.h110
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-query.c547
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-query.h84
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-service.c397
-rw-r--r--gdata/services/picasaweb/gdata-picasaweb-service.h87
-rw-r--r--gdata/tests/Makefile.am3
-rw-r--r--gdata/tests/picasaweb.c554
-rw-r--r--po/POTFILES.in1
31 files changed, 4736 insertions, 15 deletions
diff --git a/configure.in b/configure.in
index fbbf5934..49ab4f67 100644
--- a/configure.in
+++ b/configure.in
@@ -92,6 +92,7 @@ gdata/services/Makefile
gdata/services/calendar/Makefile
gdata/services/contacts/Makefile
gdata/services/youtube/Makefile
+gdata/services/picasaweb/Makefile
gdata/tests/Makefile
po/Makefile.in
docs/Makefile
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 1602c352..2f2ac2f6 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -62,7 +62,8 @@ IGNORE_HFILES = \
gdata-youtube-group.c \
gdata-youtube-group.h \
gdata-youtube-control.c \
- gdata-youtube-control.h
+ gdata-youtube-control.h \
+ gdata-picasaweb-enums.h
# Images to copy into HTML directory.
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index b6e3d572..2e9daa45 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -103,6 +103,14 @@
<xi:include href="xml/gdata-contacts-query.xml"/>
<xi:include href="xml/gdata-contacts-contact.xml"/>
</chapter>
+
+ <chapter>
+ <title>Google PicasaWeb API</title>
+ <xi:include href="xml/gdata-picasaweb-service.xml"/>
+ <xi:include href="xml/gdata-picasaweb-query.xml"/>
+ <xi:include href="xml/gdata-picasaweb-album.xml"/>
+ <xi:include href="xml/gdata-picasaweb-file.xml"/>
+ </chapter>
</part>
<part>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index efc928f3..8b22ce21 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -135,6 +135,8 @@ gdata_entry_new
gdata_entry_new_from_xml
gdata_entry_get_title
gdata_entry_set_title
+gdata_entry_get_summary
+gdata_entry_set_summary
gdata_entry_get_id
gdata_entry_get_etag
gdata_entry_get_content
@@ -1115,3 +1117,142 @@ GDATA_TYPE_YOUTUBE_STATE
<SUBSECTION Private>
GDataYouTubeStatePrivate
</SECTION>
+
+<SECTION>
+<FILE>gdata-picasaweb-service</FILE>
+<TITLE>GDataPicasaWebService</TITLE>
+GDataPicasaWebService
+GDataPicasaWebServiceClass
+gdata_picasaweb_service_new
+gdata_picasaweb_service_query_all_albums
+gdata_picasaweb_service_query_all_albums_async
+gdata_picasaweb_service_query_files
+gdata_picasaweb_service_upload_file
+<SUBSECTION Standard>
+gdata_picasaweb_service_get_type
+GDATA_IS_PICASAWEB_SERVICE
+GDATA_IS_PICASAWEB_SERVICE_CLASS
+GDATA_PICASAWEB_SERVICE
+GDATA_PICASAWEB_SERVICE_CLASS
+GDATA_PICASAWEB_SERVICE_GET_CLASS
+GDATA_TYPE_PICASAWEB_SERVICE
+</SECTION>
+
+<SECTION>
+<FILE>gdata-picasaweb-query</FILE>
+<TITLE>GDataPicasaWebQuery</TITLE>
+GDataPicasaWebQuery
+GDataPicasaWebQueryClass
+gdata_picasaweb_query_new
+gdata_picasaweb_query_get_visibility
+gdata_picasaweb_query_set_visibility
+gdata_picasaweb_query_get_thumbnail_size
+gdata_picasaweb_query_set_thumbnail_size
+gdata_picasaweb_query_get_image_size
+gdata_picasaweb_query_set_image_size
+gdata_picasaweb_query_get_tag
+gdata_picasaweb_query_set_tag
+gdata_picasaweb_query_get_bounding_box
+gdata_picasaweb_query_set_bounding_box
+gdata_picasaweb_query_get_location
+gdata_picasaweb_query_set_location
+<SUBSECTION Standard>
+gdata_picasaweb_query_get_type
+GDATA_IS_PICASAWEB_QUERY
+GDATA_IS_PICASAWEB_QUERY_CLASS
+GDATA_PICASAWEB_QUERY
+GDATA_PICASAWEB_QUERY_CLASS
+GDATA_PICASAWEB_QUERY_GET_CLASS
+GDATA_TYPE_PICASAWEB_QUERY
+<SUBSECTION Private>
+GDataPicasaWebQueryPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-picasaweb-album</FILE>
+<TITLE>GDataPicasaWebAlbum</TITLE>
+GDataPicasaWebAlbum
+GDataPicasaWebAlbumClass
+GDataPicasaWebVisibility
+gdata_picasaweb_album_new
+gdata_picasaweb_album_new_from_xml
+gdata_picasaweb_album_get_user
+gdata_picasaweb_album_get_nickname
+gdata_picasaweb_album_get_edited
+gdata_picasaweb_album_get_name
+gdata_picasaweb_album_get_location
+gdata_picasaweb_album_set_location
+gdata_picasaweb_album_get_visibility
+gdata_picasaweb_album_set_visibility
+gdata_picasaweb_album_get_timestamp
+gdata_picasaweb_album_set_timestamp
+gdata_picasaweb_album_get_num_photos
+gdata_picasaweb_album_get_num_photos_remaining
+gdata_picasaweb_album_get_bytes_used
+gdata_picasaweb_album_is_commenting_enabled
+gdata_picasaweb_album_set_is_commenting_enabled
+gdata_picasaweb_album_get_comment_count
+gdata_picasaweb_album_get_tags
+gdata_picasaweb_album_set_tags
+gdata_picasaweb_album_get_description
+gdata_picasaweb_album_set_description
+gdata_picasaweb_album_get_contents
+gdata_picasaweb_album_get_thumbnails
+<SUBSECTION Standard>
+gdata_picasaweb_album_get_type
+GDATA_IS_PICASAWEB_ALBUM
+GDATA_IS_PICASAWEB_ALBUM_CLASS
+GDATA_PICASAWEB_ALBUM
+GDATA_PICASAWEB_ALBUM_CLASS
+GDATA_PICASAWEB_ALBUM_GET_CLASS
+GDATA_TYPE_PICASAWEB_ALBUM
+<SUBSECTION Private>
+GDataPicasaWebAlbumPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-picasaweb-file</FILE>
+<TITLE>GDataPicasaWebFile</TITLE>
+GDataPicasaWebFile
+GDataPicasaWebFileClass
+gdata_picasaweb_file_new
+gdata_picasaweb_file_new_from_xml
+gdata_picasaweb_file_get_edited
+gdata_picasaweb_file_get_version
+gdata_picasaweb_file_get_position
+gdata_picasaweb_file_set_position
+gdata_picasaweb_file_get_album_id
+gdata_picasaweb_file_set_album_id
+gdata_picasaweb_file_get_width
+gdata_picasaweb_file_get_height
+gdata_picasaweb_file_get_size
+gdata_picasaweb_file_get_client
+gdata_picasaweb_file_set_client
+gdata_picasaweb_file_get_checksum
+gdata_picasaweb_file_set_checksum
+gdata_picasaweb_file_get_timestamp
+gdata_picasaweb_file_set_timestamp
+gdata_picasaweb_file_is_commenting_enabled
+gdata_picasaweb_file_set_is_commenting_enabled
+gdata_picasaweb_file_get_comment_count
+gdata_picasaweb_file_get_rotation
+gdata_picasaweb_file_set_rotation
+gdata_picasaweb_file_get_video_status
+gdata_picasaweb_file_get_tags
+gdata_picasaweb_file_set_tags
+gdata_picasaweb_file_get_credit
+gdata_picasaweb_file_get_caption
+gdata_picasaweb_file_set_caption
+gdata_picasaweb_file_get_contents
+gdata_picasaweb_file_get_thumbnails
+<SUBSECTION Standard>
+gdata_picasaweb_file_get_type
+GDATA_IS_PICASAWEB_FILE
+GDATA_IS_PICASAWEB_FILE_CLASS
+GDATA_PICASAWEB_FILE
+GDATA_PICASAWEB_FILE_CLASS
+GDATA_PICASAWEB_FILE_GET_CLASS
+GDATA_TYPE_PICASAWEB_FILE
+<SUBSECTION Private>
+GDataPicasaWebFilePrivate
+</SECTION>
diff --git a/gdata/Makefile.am b/gdata/Makefile.am
index ed034651..e3a085ff 100644
--- a/gdata/Makefile.am
+++ b/gdata/Makefile.am
@@ -93,6 +93,7 @@ libgdata_la_LIBADD = \
media/libgdatamedia.la \
services/youtube/libgdatayoutube.la \
services/calendar/libgdatacalendar.la \
+ services/picasaweb/libgdatapicasaweb.la \
services/contacts/libgdatacontacts.la
libgdata_la_LDFLAGS = \
diff --git a/gdata/gdata-access-rule.c b/gdata/gdata-access-rule.c
index 45852b69..1778967a 100644
--- a/gdata/gdata-access-rule.c
+++ b/gdata/gdata-access-rule.c
@@ -88,7 +88,7 @@ gdata_access_rule_class_init (GDataAccessRuleClass *klass)
g_param_spec_string ("role",
"Role", "The role of the person concerned by this ACL.",
NULL,
- G_PARAM_READWRITE ));
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GDataAccessRule:scope-type:
@@ -101,7 +101,7 @@ gdata_access_rule_class_init (GDataAccessRuleClass *klass)
g_param_spec_string ("scope-type",
"Scope type", "Specifies to whom this access rule applies.",
NULL,
- G_PARAM_READWRITE ));
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GDataAccessRule:scope-value:
@@ -115,7 +115,7 @@ gdata_access_rule_class_init (GDataAccessRuleClass *klass)
g_param_spec_string ("scope-value",
"Scope value", "The scope value for this access rule.",
NULL,
- G_PARAM_READWRITE ));
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
/**
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index e1a15bbf..9fed168d 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -55,6 +55,7 @@ static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
struct _GDataEntryPrivate {
gchar *title;
+ gchar *summary;
gchar *id;
gchar *etag;
GTimeVal updated;
@@ -67,6 +68,7 @@ struct _GDataEntryPrivate {
enum {
PROP_TITLE = 1,
+ PROP_SUMMARY,
PROP_ETAG,
PROP_ID,
PROP_UPDATED,
@@ -86,8 +88,8 @@ gdata_entry_class_init (GDataEntryClass *klass)
g_type_class_add_private (klass, sizeof (GDataEntryPrivate));
- gobject_class->set_property = gdata_entry_set_property;
gobject_class->get_property = gdata_entry_get_property;
+ gobject_class->set_property = gdata_entry_set_property;
gobject_class->dispose = gdata_entry_dispose;
gobject_class->finalize = gdata_entry_finalize;
@@ -103,6 +105,23 @@ gdata_entry_class_init (GDataEntryClass *klass)
"Title", "The title for this entry.",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataEntry:summary:
+ *
+ * A short summary, abstract, or excerpt of the entry.
+ *
+ * For more information, see the <ulink type="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.summary">
+ * Atom specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SUMMARY,
+ g_param_spec_string ("summary",
+ "Summary", "A short summary, abstract, or excerpt of the entry.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
g_object_class_install_property (gobject_class, PROP_ID,
g_param_spec_string ("id",
"ID", "The ID for this entry.",
@@ -175,6 +194,7 @@ gdata_entry_finalize (GObject *object)
GDataEntryPrivate *priv = GDATA_ENTRY_GET_PRIVATE (object);
g_free (priv->title);
+ g_free (priv->summary);
xmlFree (priv->id);
xmlFree (priv->etag);
g_free (priv->content);
@@ -192,6 +212,9 @@ gdata_entry_get_property (GObject *object, guint property_id, GValue *value, GPa
case PROP_TITLE:
g_value_set_string (value, priv->title);
break;
+ case PROP_SUMMARY:
+ g_value_set_string (value, priv->summary);
+ break;
case PROP_ID:
g_value_set_string (value, priv->id);
break;
@@ -234,6 +257,9 @@ gdata_entry_set_property (GObject *object, guint property_id, const GValue *valu
case PROP_TITLE:
gdata_entry_set_title (self, g_value_get_string (value));
break;
+ case PROP_SUMMARY:
+ gdata_entry_set_summary (self, g_value_get_string (value));
+ break;
case PROP_CONTENT:
gdata_entry_set_content (self, g_value_get_string (value));
break;
@@ -334,6 +360,11 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
return FALSE;
self->priv->authors = g_list_prepend (self->priv->authors, author);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "summary") == 0) {
+ /* atom:summary */
+ xmlChar *summary = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_entry_set_summary (self, (gchar*) summary);
+ xmlFree (summary);
} else if (GDATA_PARSABLE_CLASS (gdata_entry_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
/* Error! */
return FALSE;
@@ -409,6 +440,12 @@ get_xml (GDataParsable *parsable, GString *xml_string)
g_free (published);
}
+ if (priv->summary != NULL) {
+ gchar *summary = g_markup_escape_text (priv->summary, -1);
+ g_string_append_printf (xml_string, "<summary type='text'>%s</summary>", summary);
+ g_free (summary);
+ }
+
if (priv->content != NULL) {
gchar *content = g_markup_escape_text (priv->content, -1);
g_string_append_printf (xml_string, "<content type='text'>%s</content>", content);
@@ -497,6 +534,41 @@ gdata_entry_set_title (GDataEntry *self, const gchar *title)
}
/**
+ * gdata_entry_get_summary:
+ * @self: a #GDataEntry
+ *
+ * Returns the summary of the entry.
+ *
+ * Return value: the entry's summary, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_entry_get_summary (GDataEntry *self)
+{
+ g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
+ return self->priv->summary;
+}
+
+/**
+ * gdata_entry_set_summary:
+ * @self: a #GDataEntry
+ * @summary: the new entry summary, or %NULL
+ *
+ * Sets the summary of the entry.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_entry_set_summary (GDataEntry *self, const gchar *summary)
+{
+ g_return_if_fail (GDATA_IS_ENTRY (self));
+ g_free (self->priv->summary);
+ self->priv->summary = g_strdup (summary);
+ g_object_notify (G_OBJECT (self), "summary");
+}
+
+/**
* gdata_entry_get_id:
* @self: a #GDataEntry
*
diff --git a/gdata/gdata-entry.h b/gdata/gdata-entry.h
index d8d190d2..46329e30 100644
--- a/gdata/gdata-entry.h
+++ b/gdata/gdata-entry.h
@@ -66,6 +66,8 @@ GDataEntry *gdata_entry_new_from_xml (const gchar *xml, gint length, GError **er
const gchar *gdata_entry_get_title (GDataEntry *self);
void gdata_entry_set_title (GDataEntry *self, const gchar *title);
+const gchar *gdata_entry_get_summary (GDataEntry *self);
+void gdata_entry_set_summary (GDataEntry *self, const gchar *summary);
const gchar *gdata_entry_get_id (GDataEntry *self);
const gchar *gdata_entry_get_etag (GDataEntry *self);
void gdata_entry_get_updated (GDataEntry *self, GTimeVal *updated);
diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c
index d0964531..2b6a9c4b 100644
--- a/gdata/gdata-feed.c
+++ b/gdata/gdata-feed.c
@@ -432,8 +432,9 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
return gdata_parser_error_duplicate_element (node, error);
self->priv->subtitle = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
- } else if (xmlStrcmp (node->name, (xmlChar*) "id") == 0) {
+ } else if (xmlStrcmp (node->name, (xmlChar*) "id") == 0 && xmlStrcmp (node->ns->href, (xmlChar*) "http://www.w3.org/2005/Atom") == 0) {
/* atom:id */
+ /* The namespace check is necessary because there's an "id" element in the gphoto namespace (PicasaWeb service) */
if (self->priv->id != NULL)
return gdata_parser_error_duplicate_element (node, error);
diff --git a/gdata/gdata-parser.c b/gdata/gdata-parser.c
index 2adc56f2..dd241e5d 100644
--- a/gdata/gdata-parser.c
+++ b/gdata/gdata-parser.c
@@ -106,6 +106,23 @@ gdata_parser_error_unknown_property_value (xmlNode *element, const gchar *proper
}
gboolean
+gdata_parser_error_unknown_content (xmlNode *element, const gchar *actual_content, GError **error)
+{
+ gchar *element_string = print_element (element);
+
+ g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+ /* Translators: the first parameter is the name of an XML element (including the angle brackets ("<" and ">")),
+ * and the second parameter is the unknown content of that element.
+ *
+ * For example:
+ * The content of a <gphoto:access> element ("protected") was unknown. */
+ _("The content of a %s element (\"%s\") was unknown."), element_string, actual_content);
+ g_free (element_string);
+
+ return FALSE;
+}
+
+gboolean
gdata_parser_error_required_property_missing (xmlNode *element, const gchar *property_name, GError **error)
{
gchar *property_string, *element_string;
diff --git a/gdata/gdata-parser.h b/gdata/gdata-parser.h
index 61b4708f..b794c5c2 100644
--- a/gdata/gdata-parser.h
+++ b/gdata/gdata-parser.h
@@ -27,6 +27,7 @@ G_BEGIN_DECLS
gboolean gdata_parser_error_required_content_missing (xmlNode *element, GError **error);
gboolean gdata_parser_error_not_iso8601_format (xmlNode *element, const gchar *actual_value, GError **error);
gboolean gdata_parser_error_unknown_property_value (xmlNode *element, const gchar *property_name, const gchar *actual_value, GError **error);
+gboolean gdata_parser_error_unknown_content (xmlNode *element, const gchar *actual_content, GError **error);
gboolean gdata_parser_error_required_property_missing (xmlNode *element, const gchar *property_name, GError **error);
gboolean gdata_parser_error_required_element_missing (const gchar *element_name, const gchar *parent_element_name, GError **error);
gboolean gdata_parser_error_duplicate_element (xmlNode *element, GError **error);
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index 48114fc4..d94d0ca3 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -896,6 +896,7 @@ gdata_service_query_async (GDataService *self, const gchar *feed_uri, GDataQuery
g_return_if_fail (GDATA_IS_SERVICE (self));
g_return_if_fail (feed_uri != NULL);
g_return_if_fail (entry_type != G_TYPE_INVALID);
+ g_return_if_fail (callback != NULL);
data = g_slice_new (QueryAsyncData);
data->feed_uri = g_strdup (feed_uri);
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 5275fe6a..1b58c7e9 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -75,6 +75,13 @@
#include <gdata/services/calendar/gdata-calendar-event.h>
#include <gdata/services/calendar/gdata-calendar-query.h>
+/* Google PicasaWeb */
+#include <gdata/services/picasaweb/gdata-picasaweb-service.h>
+#include <gdata/services/picasaweb/gdata-picasaweb-query.h>
+#include <gdata/services/picasaweb/gdata-picasaweb-album.h>
+#include <gdata/services/picasaweb/gdata-picasaweb-file.h>
+#include <gdata/services/picasaweb/gdata-picasaweb-enums.h>
+
/* Google Contacts */
#include <gdata/services/contacts/gdata-contacts-service.h>
#include <gdata/services/contacts/gdata-contacts-contact.h>
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index a6f4adfe..17446fc6 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -6,6 +6,8 @@ gdata_entry_new
gdata_entry_new_from_xml
gdata_entry_get_title
gdata_entry_set_title
+gdata_entry_get_summary
+gdata_entry_set_summary
gdata_entry_get_id
gdata_entry_get_etag
gdata_entry_get_updated
@@ -242,6 +244,7 @@ gdata_calendar_service_insert_event
gdata_service_error_get_type
gdata_authentication_error_get_type
gdata_media_expression_get_type
+gdata_media_medium_get_type
gdata_parser_error_get_type
gdata_parser_error_quark
gdata_contacts_service_get_type
@@ -481,3 +484,83 @@ gdata_youtube_state_get_name
gdata_youtube_state_get_reason_code
gdata_youtube_state_get_help_uri
gdata_youtube_state_get_message
+gdata_picasaweb_album_get_type
+gdata_picasaweb_album_new
+gdata_picasaweb_album_new_from_xml
+gdata_picasaweb_album_get_user
+gdata_picasaweb_album_get_nickname
+gdata_picasaweb_album_get_edited
+gdata_picasaweb_album_get_name
+gdata_picasaweb_album_get_location
+gdata_picasaweb_album_set_location
+gdata_picasaweb_album_get_visibility
+gdata_picasaweb_album_set_visibility
+gdata_picasaweb_album_get_timestamp
+gdata_picasaweb_album_set_timestamp
+gdata_picasaweb_album_get_num_photos
+gdata_picasaweb_album_get_num_photos_remaining
+gdata_picasaweb_album_get_bytes_used
+gdata_picasaweb_album_is_commenting_enabled
+gdata_picasaweb_album_set_is_commenting_enabled
+gdata_picasaweb_album_get_comment_count
+gdata_picasaweb_album_get_tags
+gdata_picasaweb_album_set_tags
+gdata_picasaweb_album_get_description
+gdata_picasaweb_album_set_description
+gdata_picasaweb_album_get_contents
+gdata_picasaweb_album_get_thumbnails
+gdata_picasaweb_file_get_type
+gdata_picasaweb_file_new
+gdata_picasaweb_file_new_from_xml
+gdata_picasaweb_file_get_edited
+gdata_picasaweb_file_get_version
+gdata_picasaweb_file_get_position
+gdata_picasaweb_file_set_position
+gdata_picasaweb_file_get_album_id
+gdata_picasaweb_file_set_album_id
+gdata_picasaweb_file_get_width
+gdata_picasaweb_file_get_height
+gdata_picasaweb_file_get_size
+gdata_picasaweb_file_get_client
+gdata_picasaweb_file_set_client
+gdata_picasaweb_file_get_checksum
+gdata_picasaweb_file_set_checksum
+gdata_picasaweb_file_get_timestamp
+gdata_picasaweb_file_set_timestamp
+gdata_picasaweb_file_is_commenting_enabled
+gdata_picasaweb_file_set_is_commenting_enabled
+gdata_picasaweb_file_get_comment_count
+gdata_picasaweb_file_get_rotation
+gdata_picasaweb_file_set_rotation
+gdata_picasaweb_file_get_video_status
+gdata_picasaweb_file_get_tags
+gdata_picasaweb_file_set_tags
+gdata_picasaweb_file_get_credit
+gdata_picasaweb_file_get_caption
+gdata_picasaweb_file_set_caption
+gdata_picasaweb_file_get_contents
+gdata_picasaweb_file_get_thumbnails
+gdata_picasaweb_query_get_type
+gdata_picasaweb_query_new
+gdata_picasaweb_query_new_with_limits
+gdata_picasaweb_service_get_type
+gdata_picasaweb_service_new
+gdata_picasaweb_service_query_all_albums
+gdata_picasaweb_service_query_all_albums_async
+gdata_picasaweb_service_query_files
+gdata_picasaweb_service_upload_file
+gdata_picasaweb_query_get_type
+gdata_picasaweb_query_new
+gdata_picasaweb_query_get_visibility
+gdata_picasaweb_query_set_visibility
+gdata_picasaweb_query_get_thumbnail_size
+gdata_picasaweb_query_set_thumbnail_size
+gdata_picasaweb_query_get_image_size
+gdata_picasaweb_query_set_image_size
+gdata_picasaweb_query_get_tag
+gdata_picasaweb_query_set_tag
+gdata_picasaweb_query_get_bounding_box
+gdata_picasaweb_query_set_bounding_box
+gdata_picasaweb_query_get_location
+gdata_picasaweb_query_set_location
+gdata_picasaweb_visibility_get_type
diff --git a/gdata/media/gdata-media-content.c b/gdata/media/gdata-media-content.c
index 18d0e01e..de10bdf3 100644
--- a/gdata/media/gdata-media-content.c
+++ b/gdata/media/gdata-media-content.c
@@ -182,7 +182,7 @@ gdata_media_content_class_init (GDataMediaContentClass *klass)
*
* Since: 0.4.0
**/
- g_object_class_install_property (gobject_class, PROP_EXPRESSION,
+ g_object_class_install_property (gobject_class, PROP_DURATION,
g_param_spec_int64 ("duration",
"Duration", "The number of seconds for which the media object plays.",
0, G_MAXINT64, 0,
@@ -304,10 +304,10 @@ pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointe
/* Parse expression */
expression = xmlGetProp (root_node, (xmlChar*) "expression");
- if (xmlStrcmp (expression, (xmlChar*) "sample") == 0)
- expression_enum = GDATA_MEDIA_EXPRESSION_SAMPLE;
- else if (xmlStrcmp (expression, (xmlChar*) "full") == 0)
+ if (expression == NULL || xmlStrcmp (expression, (xmlChar*) "full") == 0)
expression_enum = GDATA_MEDIA_EXPRESSION_FULL;
+ else if (xmlStrcmp (expression, (xmlChar*) "sample") == 0)
+ expression_enum = GDATA_MEDIA_EXPRESSION_SAMPLE;
else if (xmlStrcmp (expression, (xmlChar*) "nonstop") == 0)
expression_enum = GDATA_MEDIA_EXPRESSION_NONSTOP;
else {
@@ -319,7 +319,9 @@ pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointe
/* Parse medium */
medium = xmlGetProp (root_node, (xmlChar*) "medium");
- if (xmlStrcmp (medium, (xmlChar*) "image") == 0)
+ if (medium == NULL)
+ medium_enum = GDATA_MEDIA_UNKNOWN;
+ else if (xmlStrcmp (medium, (xmlChar*) "image") == 0)
medium_enum = GDATA_MEDIA_IMAGE;
else if (xmlStrcmp (medium, (xmlChar*) "audio") == 0)
medium_enum = GDATA_MEDIA_AUDIO;
diff --git a/gdata/media/gdata-media-credit.c b/gdata/media/gdata-media-credit.c
index 768d5878..5af0cbd8 100644
--- a/gdata/media/gdata-media-credit.c
+++ b/gdata/media/gdata-media-credit.c
@@ -188,8 +188,10 @@ pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointe
priv->role = g_strdup ((gchar*) role);
/* Convert the role to lower case */
- for (i = 0; priv->role[i] != '\0'; i++)
- priv->role[i] = g_ascii_tolower (priv->role[i]);
+ if (priv->role != NULL) {
+ for (i = 0; priv->role[i] != '\0'; i++)
+ priv->role[i] = g_ascii_tolower (priv->role[i]);
+ }
xmlFree (credit);
xmlFree (scheme);
diff --git a/gdata/media/gdata-media-group.c b/gdata/media/gdata-media-group.c
index 0b754590..f2d66c25 100644
--- a/gdata/media/gdata-media-group.c
+++ b/gdata/media/gdata-media-group.c
@@ -263,7 +263,11 @@ get_xml (GDataParsable *parsable, GString *xml_string)
GDataMediaGroupPrivate *priv = GDATA_MEDIA_GROUP (parsable)->priv;
/* Media category */
- g_string_append (xml_string, _gdata_parsable_get_xml (GDATA_PARSABLE (priv->category), "media:category", FALSE));
+ if (priv->category != NULL) {
+ gchar *xml = _gdata_parsable_get_xml (GDATA_PARSABLE (priv->category), "media:category", FALSE);
+ g_string_append (xml_string, xml);
+ g_free (xml);
+ }
if (priv->title != NULL) {
gchar *title = g_markup_escape_text (priv->title, -1);
@@ -462,6 +466,21 @@ gdata_media_group_look_up_content (GDataMediaGroup *self, const gchar *type)
return GDATA_MEDIA_CONTENT (element->data);
}
+/**
+ * gdata_media_group_get_contents:
+ * @self: a #GDataMediaGroup
+ *
+ * Returns a list of #GDataMediaContent<!-- -->s, giving the content enclosed by the group.
+ *
+ * Return value: a #GList of #GDataMediaContent<!-- -->s, or %NULL
+ **/
+GList *
+gdata_media_group_get_contents (GDataMediaGroup *self)
+{
+ g_return_val_if_fail (GDATA_IS_MEDIA_GROUP (self), NULL);
+ return self->priv->contents;
+}
+
void
_gdata_media_group_add_content (GDataMediaGroup *self, GDataMediaContent *content)
{
diff --git a/gdata/media/gdata-media-group.h b/gdata/media/gdata-media-group.h
index 17adb0f5..58912cc8 100644
--- a/gdata/media/gdata-media-group.h
+++ b/gdata/media/gdata-media-group.h
@@ -72,6 +72,7 @@ void gdata_media_group_set_keywords (GDataMediaGroup *self, const gchar *keyword
GDataMediaCategory *gdata_media_group_get_category (GDataMediaGroup *self);
void gdata_media_group_set_category (GDataMediaGroup *self, GDataMediaCategory *category);
GDataMediaContent *gdata_media_group_look_up_content (GDataMediaGroup *self, const gchar *type);
+GList *gdata_media_group_get_contents (GDataMediaGroup *self);
void _gdata_media_group_add_content (GDataMediaGroup *self, GDataMediaContent *content);
GDataMediaCredit *gdata_media_group_get_credit (GDataMediaGroup *self);
void _gdata_media_group_set_credit (GDataMediaGroup *self, GDataMediaCredit *credit);
diff --git a/gdata/services/Makefile.am b/gdata/services/Makefile.am
index 6d224f1d..87d94f17 100644
--- a/gdata/services/Makefile.am
+++ b/gdata/services/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = youtube calendar contacts
+SUBDIRS = youtube calendar contacts picasaweb
-include $(top_srcdir)/git.mk
diff --git a/gdata/services/picasaweb/Makefile.am b/gdata/services/picasaweb/Makefile.am
new file mode 100644
index 00000000..9b7b32ec
--- /dev/null
+++ b/gdata/services/picasaweb/Makefile.am
@@ -0,0 +1,69 @@
+# Enums
+GDATA_PICASAWEB_ENUM_FILES = \
+ gdata-picasaweb-enums.c \
+ gdata-picasaweb-enums.h
+
+gdata-picasaweb-enums.h: $(gdatapicasawebinclude_HEADERS) Makefile
+ (cd $(srcdir) && $(GLIB_MKENUMS) \
+ --fhead "#ifndef GDATA_PICASAWEB_ENUMS_H\n#define GDATA_PICASAWEB_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_PICASAWEB_ENUMS_H */" $(gdatapicasawebinclude_HEADERS)) > gdata-picasaweb-enums.h.tmp \
+ && sed "s/g_data_picasa_web/gdata_picasaweb/" gdata-picasaweb-enums.h.tmp > gdata-picasaweb-enums.h.tmp2 \
+ && sed "s/GDATA_TYPE_DATA_PICASA_WEB/GDATA_TYPE_PICASAWEB/" gdata-picasaweb-enums.h.tmp2 > gdata-picasaweb-enums.h \
+ && rm -f gdata-picasaweb-enums.h.tmp \
+ && rm -f gdata-picasaweb-enums.h.tmp2
+
+gdata-picasaweb-enums.c: $(gdatapicasawebinclude_HEADERS) Makefile gdata-picasaweb-enums.h
+ (cd $(srcdir) && $(GLIB_MKENUMS) \
+ --fhead "#include \"gdata-picasaweb-album.h\"\n#include \"gdata-picasaweb-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" \
+ $(gdatapicasawebinclude_HEADERS)) > gdata-picasaweb-enums.c.tmp \
+ && sed "s/g_data_picasa_web/gdata_picasaweb/" gdata-picasaweb-enums.c.tmp > gdata-picasaweb-enums.c \
+ && rm -f gdata-picasaweb-enums.c.tmp
+
+# Library
+gdatapicasawebincludedir = $(pkgincludedir)/gdata/services/picasaweb
+gdatapicasawebinclude_HEADERS = \
+ gdata-picasaweb-service.h \
+ gdata-picasaweb-query.h \
+ gdata-picasaweb-file.h \
+ gdata-picasaweb-album.h \
+ gdata-picasaweb-enums.h
+
+noinst_LTLIBRARIES = libgdatapicasaweb.la
+
+libgdatapicasaweb_la_SOURCES = \
+ gdata-picasaweb-enums.c \
+ gdata-picasaweb-album.c \
+ gdata-picasaweb-query.c \
+ gdata-picasaweb-file.c \
+ gdata-picasaweb-service.c
+
+libgdatapicasaweb_la_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gdata \
+ -I$(top_srcdir)/gdata/services/picasaweb \
+ $(DISABLE_DEPRECATED) \
+ $(AM_CPPFLAGS)
+
+libgdatapicasaweb_la_CFLAGS = \
+ $(GDATA_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(AM_CFLAGS) \
+ -D_GNU_SOURCE
+
+libgdatapicasaweb_la_LIBADD = \
+ $(GDATA_LIBS)
+
+libgdatapicasaweb_la_LDFLAGS = \
+ -no-undefined \
+ $(AM_LDFLAGS)
+
+# General cleanup
+CLEANFILES = $(GDATA_PICASAWEB_ENUM_FILES)
+
+-include $(top_srcdir)/git.mk
diff --git a/gdata/services/picasaweb/gdata-picasaweb-album.c b/gdata/services/picasaweb/gdata-picasaweb-album.c
new file mode 100644
index 00000000..2e200339
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-album.c
@@ -0,0 +1,1097 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-picasaweb-album
+ * @short_description: GData PicasaWeb album object
+ * @stability: Unstable
+ * @include: gdata/services/picasaweb/gdata-picasaweb-album.h
+ *
+ * #GDataPicasaWebAlbum is a subclass of #GDataEntry to represent an album from Google PicasaWeb.
+ *
+ * For more details of Google PicasaWeb's GData API, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html">
+ * online documentation</ulink>.
+ **/
+
+/* TODO: support the album cover/icon ? */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-picasaweb-album.h"
+#include "gdata-private.h"
+#include "gdata-service.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "media/gdata-media-group.h"
+#include "gdata-picasaweb-enums.h"
+
+static void gdata_picasaweb_album_dispose (GObject *object);
+static void gdata_picasaweb_album_finalize (GObject *object);
+static void gdata_picasaweb_album_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_picasaweb_album_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+
+struct _GDataPicasaWebAlbumPrivate {
+ gchar *user;
+ gchar *nickname;
+ GTimeVal edited;
+ gchar *name; /* album title, usable in URIs */
+ gchar *location;
+ GDataPicasaWebVisibility visibility;
+ GTimeVal timestamp;
+ guint num_photos;
+ guint num_photos_remaining;
+ glong bytes_used;
+ gboolean is_commenting_enabled;
+ guint comment_count;
+
+ /* media:group */
+ GDataMediaGroup *media_group;
+};
+
+enum {
+ PROP_USER = 1,
+ PROP_NICKNAME,
+ PROP_EDITED,
+ PROP_NAME,
+ PROP_LOCATION,
+ PROP_VISIBILITY,
+ PROP_TIMESTAMP,
+ PROP_NUM_PHOTOS,
+ PROP_NUM_PHOTOS_REMAINING,
+ PROP_BYTES_USED,
+ PROP_IS_COMMENTING_ENABLED,
+ PROP_COMMENT_COUNT,
+ PROP_DESCRIPTION,
+ PROP_TAGS
+};
+
+G_DEFINE_TYPE (GDataPicasaWebAlbum, gdata_picasaweb_album, GDATA_TYPE_ENTRY)
+#define GDATA_PICASAWEB_ALBUM_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_PICASAWEB_ALBUM, GDataPicasaWebAlbumPrivate))
+
+static void
+gdata_picasaweb_album_class_init (GDataPicasaWebAlbumClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataPicasaWebAlbumPrivate));
+
+ gobject_class->get_property = gdata_picasaweb_album_get_property;
+ gobject_class->set_property = gdata_picasaweb_album_set_property;
+ gobject_class->dispose = gdata_picasaweb_album_dispose;
+ gobject_class->finalize = gdata_picasaweb_album_finalize;
+
+ parsable_class->parse_xml = parse_xml;
+ parsable_class->get_xml = get_xml;
+ parsable_class->get_namespaces = get_namespaces;
+
+ /**
+ * GDataPicasaWeb:user
+ *
+ * The username of the album owner.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_user">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_USER,
+ g_param_spec_string ("user",
+ "User", "The username of the album owner.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWeb:nickname
+ *
+ * The user's nickname. This is a user-specified value that should be used when referring to the user by name.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_nickname">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_NICKNAME,
+ g_param_spec_string ("nickname",
+ "Nickname", "The user's nickname.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWeb:edited
+ *
+ * The time this album was last edited. If the album 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 time this album was last edited.",
+ GDATA_TYPE_G_TIME_VAL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWeb:name
+ *
+ * The name of the album, which is the URI-usable name derived from the album title (#GDataEntry:title).
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_name">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_NAME,
+ g_param_spec_string ("name",
+ "Name", "The name of the album.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWeb:location
+ *
+ * The user-specified location associated with the album. A place name.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_location">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_LOCATION,
+ g_param_spec_string ("location",
+ "Location", "The user-specified location associated with the album.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWeb:visibility
+ *
+ * The visibility (or access rights) of the album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_access">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ /* TODO: atom:rights duplicates this? */
+ g_object_class_install_property (gobject_class, PROP_VISIBILITY,
+ g_param_spec_enum ("visibility",
+ "Visibility", "The visibility (or access rights) of the album.",
+ GDATA_TYPE_PICASAWEB_VISIBILITY, GDATA_PICASAWEB_PUBLIC,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWeb:timestamp
+ *
+ * The timestamp of when the album occurred, settable by the user.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_timestamp">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_TIMESTAMP,
+ g_param_spec_boxed ("timestamp",
+ "Timestamp", "The timestamp of when the album occurred, settable by the user.",
+ GDATA_TYPE_G_TIME_VAL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* TODO: Change to photo-count? */
+ /**
+ * GDataPicasaWebAlbum:num-photos
+ *
+ * The number of photos and videos in the album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_numphotos">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_NUM_PHOTOS,
+ g_param_spec_uint ("num-photos",
+ "Number of photos", "The number of photos and videos in the album.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /* TODO: Change to remaining-photos-count? */
+ /**
+ * GDataPicasaWebAlbum:num-photos-remaining
+ *
+ * The number of photos and videos that can still be uploaded to this album.
+ * This doesn't account for quota, just a hardcoded maximum number per album set by Google.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_numphotosremaining">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_NUM_PHOTOS_REMAINING,
+ g_param_spec_uint ("num-photos-remaining",
+ "Number of photo spaces remaining", "The number of photos and videos that can still be"
+ " uploaded to this album.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebAlbum:bytes-used:
+ *
+ * The number of bytes consumed by this album and its contents. Note that this is only set if the authenticated user is the owner of the
+ * album; it's otherwise %-1.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_bytesUsed">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_BYTES_USED,
+ g_param_spec_long ("bytes-used",
+ "Number of bytes used", "The number of bytes consumed by this album and its contents.",
+ -1, G_MAXLONG, -1,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebAlbum:commenting-enabled:
+ *
+ * Whether commenting is enabled for this album.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_IS_COMMENTING_ENABLED,
+ g_param_spec_boolean ("is-commenting-enabled",
+ "Commenting enabled?", "Whether commenting is enabled for this album.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebAlbum:comment-count:
+ *
+ * The number of comments on the album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_commentCount">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_COMMENT_COUNT,
+ g_param_spec_uint ("comment-count",
+ "Comment count", "The number of comments on the album.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebAlbum:description:
+ *
+ * Description of the album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#media_description">
+ * Media RSS specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ "Description", "Description of the album.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebAlbum:tags:
+ *
+ * A comma-separated list of tags associated with the album; all the tags associated with the individual photos in the album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#media_keywords">
+ * Media RSS specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_TAGS,
+ g_param_spec_string ("tags",
+ "Tags", "A comma-separated list of tags associated with the album",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+notify_title_cb (GDataPicasaWebAlbum *self, GParamSpec *pspec, gpointer user_data)
+{
+ /* Update our media:group title */
+ if (self->priv->media_group != NULL)
+ gdata_media_group_set_title (self->priv->media_group, gdata_entry_get_title (GDATA_ENTRY (self)));
+}
+
+static void
+gdata_picasaweb_album_init (GDataPicasaWebAlbum *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_PICASAWEB_ALBUM, GDataPicasaWebAlbumPrivate);
+ self->priv->media_group = g_object_new (GDATA_TYPE_MEDIA_GROUP, NULL);
+
+ /* Connect to the notify::title signal from GDataEntry so our media:group title can be kept in sync */
+ g_signal_connect (GDATA_ENTRY (self), "notify::title", G_CALLBACK (notify_title_cb), NULL);
+}
+
+static void
+gdata_picasaweb_album_dispose (GObject *object)
+{
+ GDataPicasaWebAlbumPrivate *priv = GDATA_PICASAWEB_ALBUM_GET_PRIVATE (object);
+
+ if (priv->media_group != NULL)
+ g_object_unref (priv->media_group);
+ priv->media_group = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_picasaweb_album_parent_class)->dispose (object);
+}
+
+static void
+gdata_picasaweb_album_finalize (GObject *object)
+{
+ GDataPicasaWebAlbumPrivate *priv = GDATA_PICASAWEB_ALBUM_GET_PRIVATE (object);
+
+ xmlFree (priv->user);
+ xmlFree (priv->nickname);
+ xmlFree (priv->name);
+ g_free (priv->location);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_picasaweb_album_parent_class)->finalize (object);
+}
+
+static void
+gdata_picasaweb_album_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataPicasaWebAlbumPrivate *priv = GDATA_PICASAWEB_ALBUM_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_USER:
+ g_value_set_string (value, priv->user);
+ break;
+ case PROP_NICKNAME:
+ g_value_set_string (value, priv->nickname);
+ break;
+ case PROP_EDITED:
+ g_value_set_boxed (value, &(priv->edited));
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_LOCATION:
+ g_value_set_string (value, priv->location);
+ break;
+ case PROP_VISIBILITY:
+ g_value_set_enum (value, priv->visibility);
+ break;
+ case PROP_TIMESTAMP:
+ g_value_set_boxed (value, &(priv->timestamp));
+ break;
+ case PROP_NUM_PHOTOS:
+ g_value_set_uint (value, priv->num_photos);
+ break;
+ case PROP_NUM_PHOTOS_REMAINING:
+ g_value_set_uint (value, priv->num_photos_remaining);
+ break;
+ case PROP_BYTES_USED:
+ g_value_set_long (value, priv->bytes_used);
+ break;
+ case PROP_IS_COMMENTING_ENABLED:
+ g_value_set_boolean (value, priv->is_commenting_enabled);
+ break;
+ case PROP_COMMENT_COUNT:
+ g_value_set_uint (value, priv->comment_count);
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, gdata_media_group_get_description (priv->media_group));
+ break;
+ case PROP_TAGS:
+ g_value_set_string (value, gdata_media_group_get_keywords (priv->media_group));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_picasaweb_album_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataPicasaWebAlbum *self = GDATA_PICASAWEB_ALBUM (object);
+
+ switch (property_id) {
+ case PROP_LOCATION:
+ gdata_picasaweb_album_set_location (self, g_value_get_string (value));
+ break;
+ case PROP_VISIBILITY:
+ gdata_picasaweb_album_set_visibility (self, g_value_get_enum (value));
+ break;
+ case PROP_TIMESTAMP:
+ gdata_picasaweb_album_set_timestamp (self, g_value_get_boxed (value));
+ break;
+ case PROP_IS_COMMENTING_ENABLED:
+ gdata_picasaweb_album_set_is_commenting_enabled (self, g_value_get_boolean (value));
+ break;
+ case PROP_DESCRIPTION:
+ gdata_picasaweb_album_set_description (self, g_value_get_string (value));
+ break;
+ case PROP_TAGS:
+ gdata_picasaweb_album_set_tags (self, g_value_get_string (value));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+ GDataPicasaWebAlbum *self = GDATA_PICASAWEB_ALBUM (parsable);
+
+ if (xmlStrcmp (node->name, (xmlChar*) "group") == 0) {
+ /* media:group */
+ GDataMediaGroup *group = GDATA_MEDIA_GROUP (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_GROUP, "group", doc,
+ node, NULL, error));
+ if (group == NULL)
+ return FALSE;
+
+ if (self->priv->media_group != NULL)
+ /* We should really error here, but we can't, as priv->media_group has to be pre-populated
+ * in order for things like gdata_picasaweb_album_get_tags() to work. */
+ g_object_unref (self->priv->media_group);
+
+ self->priv->media_group = group;
+ } else if (xmlStrcmp (node->name, (xmlChar*) "user") == 0) {
+ /* gphoto:user */
+ xmlChar *user = xmlNodeListGetString (doc, node->children, TRUE);
+ if (user == NULL || *user == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ xmlFree (self->priv->user);
+ self->priv->user = (gchar*) user;
+ } else if (xmlStrcmp (node->name, (xmlChar*) "nickname") == 0) {
+ /* gphoto:nickname */
+ xmlChar *nickname = xmlNodeListGetString (doc, node->children, TRUE);
+ if (nickname == NULL || *nickname == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ xmlFree (self->priv->nickname);
+ self->priv->nickname = (gchar*) nickname;
+ } else if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
+ /* app:edited */
+ xmlChar *edited = xmlNodeListGetString (doc, node->children, TRUE);
+ if (g_time_val_from_iso8601 ((gchar*) edited, &(self->priv->edited)) == FALSE) {
+ /* Error */
+ gdata_parser_error_not_iso8601_format (node, (gchar*) edited, error);
+ xmlFree (edited);
+ return FALSE;
+ }
+ xmlFree (edited);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "summary") == 0) {
+ /* gphoto:summary */
+ /* @summary and @description are the same, so they're combined to @description */
+ xmlChar *summary = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_album_set_description (self, (gchar*) summary);
+ xmlFree (summary);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "name") == 0) {
+ /* gphoto:name */
+ xmlChar *name = xmlNodeListGetString (doc, node->children, TRUE);
+ if (name == NULL || *name == '\0') {
+ xmlFree (name);
+ return gdata_parser_error_required_content_missing (node, error);
+ }
+ xmlFree (self->priv->name);
+ self->priv->name = (gchar*) name;
+ } else if (xmlStrcmp (node->name, (xmlChar*) "location") == 0) {
+ /* gphoto:location */
+ xmlChar *location = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_album_set_location (self, (gchar*) location);
+ xmlFree (location);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "access") == 0) {
+ /* gphoto:access */
+ xmlChar *access = xmlNodeListGetString (doc, node->children, TRUE);
+ if (xmlStrcmp (access, (xmlChar*) "public") == 0) {
+ gdata_picasaweb_album_set_visibility (self, GDATA_PICASAWEB_PUBLIC);
+ } else if (xmlStrcmp (access, (xmlChar*) "private") == 0) {
+ gdata_picasaweb_album_set_visibility (self, GDATA_PICASAWEB_PRIVATE);
+ } else {
+ gdata_parser_error_unknown_content (node, (gchar*) access, error);
+ xmlFree (access);
+ return FALSE;
+ }
+ xmlFree (access);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "timestamp") == 0) {
+ /* gphoto:timestamp */
+ xmlChar *timestamp_str;
+ gulong milliseconds;
+ GTimeVal timestamp;
+
+ timestamp_str = xmlNodeListGetString (doc, node->children, TRUE);
+ milliseconds = strtoull ((char*) timestamp_str, NULL, 10);
+ xmlFree (timestamp_str);
+
+ timestamp.tv_sec = (glong) (milliseconds / 1000);
+ timestamp.tv_usec = (glong) ((milliseconds % 1000) * 1000);
+ gdata_picasaweb_album_set_timestamp (self, &timestamp);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "numphotos") == 0) {
+ /* gphoto:numphotos */
+ xmlChar *num_photos = xmlNodeListGetString (doc, node->children, TRUE);
+ if (num_photos == NULL || *num_photos == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ self->priv->num_photos = strtoul ((char*) num_photos, NULL, 10);
+ xmlFree (num_photos);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "numphotosremaining") == 0) {
+ /* gphoto:numphotosremaining */
+ xmlChar *num_photos_remaining = xmlNodeListGetString (doc, node->children, TRUE);
+ if (num_photos_remaining == NULL || *num_photos_remaining == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ self->priv->num_photos_remaining = strtoul ((char*) num_photos_remaining, NULL, 10);
+ xmlFree (num_photos_remaining);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "bytesUsed") == 0) {
+ /* gphoto:bytesUsed */
+ xmlChar *bytes_used = xmlNodeListGetString (doc, node->children, TRUE);
+ if (bytes_used == NULL || *bytes_used == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ self->priv->bytes_used = strtol ((char*) bytes_used, NULL, 10);
+ xmlFree (bytes_used);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "commentingEnabled") == 0) {
+ /* gphoto:commentingEnabled */
+ xmlChar *commenting_enabled = xmlNodeListGetString (doc, node->children, TRUE);
+ if (commenting_enabled == NULL || *commenting_enabled == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ gdata_picasaweb_album_set_is_commenting_enabled (self, (xmlStrcmp (commenting_enabled, (xmlChar*) "true") == 0) ? TRUE : FALSE);
+ xmlFree (commenting_enabled);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "commentCount") == 0) {
+ xmlChar *comment_count = xmlNodeListGetString (doc, node->children, TRUE);
+ if (comment_count == NULL || *comment_count == '\0')
+ return gdata_parser_error_required_content_missing (node, error);
+ self->priv->comment_count = strtoul ((char*) comment_count, NULL, 10);
+ xmlFree (comment_count);
+ } else if (GDATA_PARSABLE_CLASS (gdata_picasaweb_album_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+ /* Error! */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ gchar *xml;
+ GDataPicasaWebAlbumPrivate *priv = GDATA_PICASAWEB_ALBUM (parsable)->priv;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_picasaweb_album_parent_class)->get_xml (parsable, xml_string);
+
+ /* Add all the album-specific XML */
+ /* TODO: gphoto:name?, gphoto:id */
+ if (priv->location != NULL) {
+ gchar *location = g_markup_escape_text (priv->location, -1);
+ g_string_append_printf (xml_string, "<gphoto:location>%s</gphoto:location>", location);
+ g_free (location);
+ }
+
+ if (priv->visibility == GDATA_PICASAWEB_PUBLIC)
+ g_string_append (xml_string, "<gphoto:access>public</gphoto:access>");
+ else if (priv->visibility == GDATA_PICASAWEB_PRIVATE)
+ g_string_append (xml_string, "<gphoto:access>private</gphoto:access>");
+ else
+ g_assert_not_reached ();
+
+ if (priv->timestamp.tv_sec != 0 || priv->timestamp.tv_usec != 0) {
+ /* in milliseconds */
+ g_string_append_printf (xml_string, "<gphoto:timestamp>%lu</gphoto:timestamp>",
+ priv->timestamp.tv_sec * 1000 + priv->timestamp.tv_usec);
+ }
+
+ if (priv->is_commenting_enabled == FALSE)
+ g_string_append (xml_string, "<gphoto:commentingEnabled>false</gphoto:commentingEnabled>");
+ else
+ g_string_append (xml_string, "<gphoto:commentingEnabled>true</gphoto:commentingEnabled>");
+
+ /* media:group */
+ xml = _gdata_parsable_get_xml (GDATA_PARSABLE (priv->media_group), "media:group", FALSE);
+ g_string_append (xml_string, xml);
+ g_free (xml);
+
+ /* TODO: add GML support */
+ /* TODO:
+ * - Finish supporting all tags
+ * - Check all tags here are valid for insertions and updates
+ * - Check things are escaped (or not) as appropriate
+ * - Write a function to encapsulate g_markup_escape_text and
+ * g_string_append_printf to reduce the number of allocations
+ */
+}
+
+static void
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
+{
+ GDataPicasaWebAlbumPrivate *priv = GDATA_PICASAWEB_ALBUM (parsable)->priv;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_picasaweb_album_parent_class)->get_namespaces (parsable, namespaces);
+
+ g_hash_table_insert (namespaces, (gchar*) "gphoto", (gchar*) "http://schemas.google.com/photos/2007");
+ g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app");
+
+ /* Add the media:group namespaces */
+ GDATA_PARSABLE_GET_CLASS (priv->media_group)->get_namespaces (GDATA_PARSABLE (priv->media_group), namespaces);
+}
+
+/**
+ * gdata_picasaweb_album_new:
+ * @id: the album's ID, or %NULL
+ *
+ * Creates a new #GDataPicasaWebAlbum with the given ID and default properties.
+ *
+ * Return value: a new #GDataPicasaWebAlbum; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebAlbum *
+gdata_picasaweb_album_new (const gchar *id)
+{
+ return g_object_new (GDATA_TYPE_PICASAWEB_ALBUM, "id", id, NULL);
+}
+
+/**
+ * gdata_picasaweb_album_new_from_xml:
+ * @xml: an XML string
+ * @length: the length in characters of @xml, or %-1
+ * @error: a #GError, or %NULL
+ *
+ * Creates a new #GDataPicasaWebAlbum from an XML string. If @length is %-1, the length of
+ * the string will be calculated.
+ *
+ * Errors from #GDataParserError can be returned if problems are found in the XML.
+ *
+ * Return value: a new #GDataPicasaWebAlbum, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebAlbum *
+gdata_picasaweb_album_new_from_xml (const gchar *xml, gint length, GError **error)
+{
+ return GDATA_PICASAWEB_ALBUM (_gdata_entry_new_from_xml (GDATA_TYPE_PICASAWEB_ALBUM, xml, length, error));
+}
+
+/**
+ * gdata_picasaweb_album_get_user:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:user property.
+ *
+ * Return value: the album owner's username
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_album_get_user (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return self->priv->user;
+}
+
+/**
+ * gdata_picasaweb_album_get_nickname:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:nickname property.
+ *
+ * Return value: the album owner's nickname
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_album_get_nickname (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return self->priv->nickname;
+}
+
+/**
+ * gdata_picasaweb_album_get_edited:
+ * @self: a #GDataPicasaWebAlbum
+ * @edited: a #GTimeVal
+ *
+ * Gets the #GDataPicasaWebAlbum: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_picasaweb_album_get_edited (GDataPicasaWebAlbum *self, GTimeVal *edited)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+ g_return_if_fail (edited != NULL);
+ *edited = self->priv->edited;
+}
+
+/**
+ * gdata_picasaweb_album_get_name:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:name property.
+ *
+ * Return value: the album's name, as usable in URIs, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_album_get_name (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return self->priv->name;
+}
+
+/**
+ * gdata_picasaweb_album_get_location:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:location property.
+ *
+ * Return value: the album's location, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_album_get_location (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return self->priv->location;
+}
+
+/**
+ * gdata_picasaweb_album_set_location:
+ * @self: a #GDataPicasaWebAlbum
+ * @location: the new album location
+ *
+ * Sets the #GDataPicasaWebAlbum:location property to @location.
+ *
+ * Set @location to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_set_location (GDataPicasaWebAlbum *self, const gchar *location)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+
+ g_free (self->priv->location);
+ self->priv->location = g_strdup (location);
+ g_object_notify (G_OBJECT (self), "location");
+}
+
+/**
+ * gdata_picasaweb_album_get_visibility:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:visibility property.
+ *
+ * Return value: the album's visibility level
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebVisibility
+gdata_picasaweb_album_get_visibility (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), GDATA_PICASAWEB_PUBLIC);
+ return self->priv->visibility;
+}
+
+/**
+ * gdata_picasaweb_album_set_visibility:
+ * @self: a #GDataPicasaWebAlbum
+ * @visibility: the new album visibility level
+ *
+ * Sets the #GDataPicasaWebAlbum:visibility property to @visibility.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_set_visibility (GDataPicasaWebAlbum *self, GDataPicasaWebVisibility visibility)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+
+ self->priv->visibility = visibility;
+ g_object_notify (G_OBJECT (self), "visibility");
+}
+
+/**
+ * gdata_picasaweb_album_get_timestamp:
+ * @self: a #GDataPicasaWebAlbum
+ * @timestamp: a #GTimeVal
+ *
+ * Gets the #GDataPicasaWebAlbum:timestamp property and puts it in @timestamp. If the property is unset,
+ * both fields in the #GTimeVal will be set to %0.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_get_timestamp (GDataPicasaWebAlbum *self, GTimeVal *timestamp)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+ g_return_if_fail (timestamp != NULL);
+ *timestamp = self->priv->timestamp;
+}
+
+/**
+ * gdata_picasaweb_album_set_timestamp:
+ * @self: a #GDataPicasaWebAlbum
+ * @timestamp: a #GTimeVal, or %NULL
+ *
+ * Sets the #GDataPicasaWebAlbum:timestamp property from values supplied by @timestamp.
+ *
+ * Set @timestamp to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_set_timestamp (GDataPicasaWebAlbum *self, GTimeVal *timestamp)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+ if (timestamp == NULL)
+ self->priv->timestamp.tv_sec = self->priv->timestamp.tv_usec = 0;
+ else
+ self->priv->timestamp = *timestamp;
+ g_object_notify (G_OBJECT (self), "timestamp");
+}
+
+/**
+ * gdata_picasaweb_album_get_num_photos:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:num-photos property.
+ *
+ * Return value: the number of photos currently in the album
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_album_get_num_photos (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), 0);
+ return self->priv->num_photos;
+}
+
+/**
+ * gdata_picasaweb_album_get_num_photos_remaining:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:num-photos-remaining property.
+ *
+ * Return value: the number of photos that can still be uploaded to the album
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_album_get_num_photos_remaining (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), 0);
+ return self->priv->num_photos_remaining;
+}
+
+/**
+ * gdata_picasaweb_album_get_bytes_used:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:bytes-used property. It will return %-1 if the current authenticated
+ * user is not the owner of the album.
+ *
+ * Return value: the number of bytes used by the album and its contents, or %-1
+ *
+ * Since: 0.4.0
+ **/
+glong
+gdata_picasaweb_album_get_bytes_used (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), -1);
+ return self->priv->bytes_used;
+}
+
+/**
+ * gdata_picasaweb_album_is_commenting_enabled:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:is-commenting-enabled property.
+ *
+ * Return value: %TRUE if commenting is enabled for the album, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_picasaweb_album_is_commenting_enabled (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), FALSE);
+ return self->priv->is_commenting_enabled;
+}
+
+/**
+ * gdata_picasaweb_album_set_is_commenting_enabled:
+ * @self: a #GDataPicasaWebAlbum
+ * @is_commenting_enabled: %TRUE if commenting should be enabled for the album, %FALSE otherwise
+ *
+ * Sets the #GDataPicasaWebAlbum:is-commenting-enabled property to @is_commenting_enabled.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_set_is_commenting_enabled (GDataPicasaWebAlbum *self, gboolean is_commenting_enabled)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+ self->priv->is_commenting_enabled = is_commenting_enabled;
+ g_object_notify (G_OBJECT (self), "is-commenting-enabled");
+}
+
+/**
+ * gdata_picasaweb_album_get_comment_count:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:comment-count property.
+ *
+ * Return value: the number of comments on the album
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_album_get_comment_count (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), 0);
+ return self->priv->comment_count;
+}
+
+/**
+ * gdata_picasaweb_album_get_tags:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:tags property.
+ *
+ * Return value: a comma-separated list of tags associated with all the photos in the album, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_album_get_tags (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return gdata_media_group_get_keywords (self->priv->media_group);
+}
+
+/**
+ * gdata_picasaweb_album_set_tags:
+ * @self: a #GDataPicasaWebAlbum
+ * @tags: the new comma-separated list of tags, or %NULL
+ *
+ * Sets the #GDataPicasaWebAlbum:tags property to @tags.
+ *
+ * Set @tags to %NULL to unset the album's tag list.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_set_tags (GDataPicasaWebAlbum *self, const gchar *tags)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+
+ gdata_media_group_set_keywords (self->priv->media_group, tags);
+ g_object_notify (G_OBJECT (self), "tags");
+}
+
+/**
+ * gdata_picasaweb_album_get_description:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Gets the #GDataPicasaWebAlbum:description property.
+ *
+ * Return value: the album's long text description, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_album_get_description (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return gdata_media_group_get_description (self->priv->media_group);
+}
+
+/**
+ * gdata_picasaweb_album_set_description:
+ * @self: a #GDataPicasaWebAlbum
+ * @description: the album's new description, or %NULL
+ *
+ * Sets the #GDataPicasaWebAlbum:description property to the new description, @description.
+ *
+ * Set @description to %NULL to unset the album's description.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_album_set_description (GDataPicasaWebAlbum *self, const gchar *description)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (self));
+
+ /* media:group/media:description is the same as atom:summary */
+ gdata_media_group_set_description (self->priv->media_group, description);
+ /*gdata_entry_set_summary (GDATA_ENTRY (self), description); TODO function doesn't exist yet */
+ g_object_notify (G_OBJECT (self), "description");
+}
+
+/**
+ * gdata_picasaweb_album_get_contents:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Returns a list of media content, such as the cover image for the album.
+ *
+ * Return value: a #GList of #GDataMediaContent items
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_picasaweb_album_get_contents (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return gdata_media_group_get_contents (self->priv->media_group);
+}
+
+/**
+ * gdata_picasaweb_album_get_thumbnails:
+ * @self: a #GDataPicasaWebAlbum
+ *
+ * Returns a list of thumbnails, often at different sizes, for this album.
+ *
+ * Return value: a #GList of #GDataMediaThumbnail<!-- -->s, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_picasaweb_album_get_thumbnails (GDataPicasaWebAlbum *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_ALBUM (self), NULL);
+ return gdata_media_group_get_thumbnails (self->priv->media_group);
+}
diff --git a/gdata/services/picasaweb/gdata-picasaweb-album.h b/gdata/services/picasaweb/gdata-picasaweb-album.h
new file mode 100644
index 00000000..efc57efe
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-album.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_PICASWEB_ALBUM_H
+#define GDATA_PICASWEB_ALBUM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-entry.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GDataPicasaWebVisibility:
+ * @GDATA_PICASAWEB_PUBLIC: the album is visible to everyone, regardless of whether they're authenticated
+ * @GDATA_PICASAWEB_PRIVATE: the album is visible only to authenticated users in a whitelist
+ *
+ * Visibility statuses available for albums on PicasaWeb. For more information, see the <ulink type="http"
+ * url="http://code.google.com/apis/picasaweb/reference.html#Visibility">online documentation</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+typedef enum {
+ GDATA_PICASAWEB_PUBLIC = 1,
+ GDATA_PICASAWEB_PRIVATE
+} GDataPicasaWebVisibility;
+
+#define GDATA_TYPE_PICASAWEB_ALBUM (gdata_picasaweb_album_get_type ())
+#define GDATA_PICASAWEB_ALBUM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_PICASAWEB_ALBUM, GDataPicasaWebAlbum))
+#define GDATA_PICASAWEB_ALBUM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_PICASAWEB_ALBUM, GDataPicasaWebAlbumClass))
+#define GDATA_IS_PICASAWEB_ALBUM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_PICASAWEB_ALBUM))
+#define GDATA_IS_PICASAWEB_ALBUM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_PICASAWEB_ALBUM))
+#define GDATA_PICASAWEB_ALBUM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_PICASAWEB_ALBUM, GDataPicasaWebAlbumClass))
+
+typedef struct _GDataPicasaWebAlbumPrivate GDataPicasaWebAlbumPrivate;
+
+/**
+ * GDataPicasaWebAlbum:
+ *
+ * All the fields in the #GDataPicasaWebAlbum structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataEntry parent;
+ GDataPicasaWebAlbumPrivate *priv;
+} GDataPicasaWebAlbum;
+
+/**
+ * GDataPicasaWebAlbumClass:
+ *
+ * All the fields in the #GDataPicasaWebAlbumClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataEntryClass parent;
+} GDataPicasaWebAlbumClass;
+
+GType gdata_picasaweb_album_get_type (void) G_GNUC_CONST;
+
+GDataPicasaWebAlbum *gdata_picasaweb_album_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+GDataPicasaWebAlbum *gdata_picasaweb_album_new_from_xml (const gchar *xml, gint length, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+const gchar *gdata_picasaweb_album_get_user (GDataPicasaWebAlbum *self);
+const gchar *gdata_picasaweb_album_get_nickname (GDataPicasaWebAlbum *self);
+void gdata_picasaweb_album_get_edited (GDataPicasaWebAlbum *self, GTimeVal *edited);
+const gchar *gdata_picasaweb_album_get_name (GDataPicasaWebAlbum *self);
+const gchar *gdata_picasaweb_album_get_location (GDataPicasaWebAlbum *self);
+void gdata_picasaweb_album_set_location (GDataPicasaWebAlbum *self, const gchar *location);
+GDataPicasaWebVisibility gdata_picasaweb_album_get_visibility (GDataPicasaWebAlbum *self);
+void gdata_picasaweb_album_set_visibility (GDataPicasaWebAlbum *self, GDataPicasaWebVisibility visibility);
+void gdata_picasaweb_album_get_timestamp (GDataPicasaWebAlbum *self, GTimeVal *timestamp);
+void gdata_picasaweb_album_set_timestamp (GDataPicasaWebAlbum *self, GTimeVal *timestamp);
+guint gdata_picasaweb_album_get_num_photos (GDataPicasaWebAlbum *self);
+guint gdata_picasaweb_album_get_num_photos_remaining (GDataPicasaWebAlbum *self);
+glong gdata_picasaweb_album_get_bytes_used (GDataPicasaWebAlbum *self);
+gboolean gdata_picasaweb_album_is_commenting_enabled (GDataPicasaWebAlbum *self);
+void gdata_picasaweb_album_set_is_commenting_enabled (GDataPicasaWebAlbum *self, gboolean is_commenting_enabled);
+guint gdata_picasaweb_album_get_comment_count (GDataPicasaWebAlbum *self);
+const gchar *gdata_picasaweb_album_get_tags (GDataPicasaWebAlbum *self);
+void gdata_picasaweb_album_set_tags (GDataPicasaWebAlbum *self, const gchar *tags);
+const gchar *gdata_picasaweb_album_get_description (GDataPicasaWebAlbum *self);
+void gdata_picasaweb_album_set_description (GDataPicasaWebAlbum *self, const gchar *description);
+GList *gdata_picasaweb_album_get_contents (GDataPicasaWebAlbum *self);
+GList *gdata_picasaweb_album_get_thumbnails (GDataPicasaWebAlbum *self);
+
+G_END_DECLS
+
+#endif /* !GDATA_PICASAWEB_ALBUM_H */
diff --git a/gdata/services/picasaweb/gdata-picasaweb-file.c b/gdata/services/picasaweb/gdata-picasaweb-file.c
new file mode 100644
index 00000000..9a89f744
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-file.c
@@ -0,0 +1,1303 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-picasaweb-file
+ * @short_description: GData PicasaWeb file object
+ * @stability: Unstable
+ * @include: gdata/services/picasaweb/gdata-picasaweb-file.h
+ *
+ * #GDataPicasaWebFile is a subclass of #GDataEntry to represent a file in an album on Google PicasaWeb.
+ *
+ * For more details of Google PicasaWeb's GData API, see the
+ * <ulink type="http" url="http://code.google.com/apis/picasaweb/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-picasaweb-file.h"
+#include "gdata-private.h"
+#include "gdata-service.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "media/gdata-media-group.h"
+
+static void gdata_picasaweb_file_dispose (GObject *object);
+static void gdata_picasaweb_file_finalize (GObject *object);
+static void gdata_picasaweb_file_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_picasaweb_file_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+
+struct _GDataPicasaWebFilePrivate {
+ GTimeVal edited;
+ gchar *version;
+ gdouble position;
+ gchar *album_id;
+ guint width;
+ guint height;
+ gsize size;
+ gchar *client;
+ gchar *checksum;
+ GTimeVal timestamp;
+ gboolean is_commenting_enabled;
+ guint comment_count;
+ guint rotation;
+ gchar *video_status;
+
+ /* media:group */
+ GDataMediaGroup *media_group;
+
+ /* georss:where properties */
+ /* TODO these specify a location, like the following example.
+ * Perhaps investigate whether we can do anything with geoclue/libchamplain
+<georss:where>
+ <gml:Envelope>
+ <gml:lowerCorner>45.2404179 11.7382049</gml:lowerCorner>
+ <gml:upperCorner>45.6278166 12.5196074</gml:upperCorner>
+ </gml:Envelope>
+ <gml:Point>
+ <gml:pos>45.4341173 12.1289062</gml:pos>
+ </gml:Point>
+</georss:where>
+ */
+
+ /* exif:tags */
+ /* TODO yay, we want these :)
+<exif:tags>
+ <exif:fstop>2.8</exif:fstop>
+ <exif:make>EASTMAN KODAK COMPANY</exif:make>
+ <exif:model>KODAK Z740 ZOOM DIGITAL CAMERA</exif:model>
+ <exif:exposure>0.016666668</exif:exposure>
+ <exif:flash>true</exif:flash>
+ <exif:focallength>6.3</exif:focallength>
+ <exif:iso>80</exif:iso>
+ <exif:time>1228588330000</exif:time>
+ <exif:imageUniqueID>1c179e0ac4f6741c8c1cdda3516e69e5</exif:imageUniqueID>
+</exif:tags>
+ */
+};
+
+enum {
+ PROP_EDITED = 1,
+ PROP_VERSION,
+ PROP_POSITION,
+ PROP_ALBUM_ID,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_SIZE,
+ PROP_CLIENT,
+ PROP_CHECKSUM,
+ PROP_TIMESTAMP,
+ PROP_IS_COMMENTING_ENABLED,
+ PROP_COMMENT_COUNT, /* TODO support comments */
+ PROP_ROTATION,
+ PROP_VIDEO_STATUS,
+ PROP_CREDIT,
+ PROP_CAPTION,
+ PROP_TAGS
+};
+
+G_DEFINE_TYPE (GDataPicasaWebFile, gdata_picasaweb_file, GDATA_TYPE_ENTRY)
+#define GDATA_PICASAWEB_FILE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_PICASAWEB_FILE, GDataPicasaWebFilePrivate))
+
+static void
+gdata_picasaweb_file_class_init (GDataPicasaWebFileClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataPicasaWebFilePrivate));
+
+ gobject_class->get_property = gdata_picasaweb_file_get_property;
+ gobject_class->set_property = gdata_picasaweb_file_set_property;
+ gobject_class->dispose = gdata_picasaweb_file_dispose;
+ gobject_class->finalize = gdata_picasaweb_file_finalize;
+
+ parsable_class->get_xml = get_xml;
+ parsable_class->parse_xml = parse_xml;
+ parsable_class->get_namespaces = get_namespaces;
+
+ /**
+ * GDataPicasaWebFile:version:
+ *
+ * The version number of the file. Version numbers are based on modification time, so they don't increment linearly.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_version">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_VERSION,
+ g_param_spec_string ("version",
+ "Version", "The version number of the file.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:album-id:
+ *
+ * The ID for the file's album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_albumid">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_ALBUM_ID,
+ g_param_spec_string ("album-id",
+ "Album ID", "The ID for the file's album.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:client:
+ *
+ * The name of the software which created or last modified the file.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_client">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_CLIENT,
+ g_param_spec_string ("client",
+ "Client", "The name of the software which created or last modified the file.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:checksum:
+ *
+ * A checksum of the file, useful for duplicate detection.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_checksum">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_CHECKSUM,
+ g_param_spec_string ("checksum",
+ "Checksum", "A checksum of the file, useful for duplicate detection.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:video-status:
+ *
+ * The status of the file, if it is a video.
+ *
+ * Possible values include "pending", "ready", "final", and "failed".
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_videostatus">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_VIDEO_STATUS,
+ g_param_spec_string ("video-status",
+ "Video Status", "The status of the file, if it is a video.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:position:
+ *
+ * The ordinal position of the file within the album. Lower values mean the file will be closer to the start of the album.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_position">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_POSITION,
+ g_param_spec_double ("position",
+ "Position", "The ordinal position of the file within the album.",
+ 0.0, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:width:
+ *
+ * The width of the photo or video, in pixels.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_width">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_WIDTH,
+ g_param_spec_uint ("width",
+ "Width", "The width of the photo or video, in pixels.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:height:
+ *
+ * The height of the photo or video, in pixels.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_height">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_HEIGHT,
+ g_param_spec_uint ("height",
+ "Height", "The height of the photo or video, in pixels.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:size:
+ *
+ * The size of the file, in bytes.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_size">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SIZE,
+ g_param_spec_ulong ("size",
+ "Size", "The size of the file, in bytes.",
+ 0, G_MAXULONG, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:edited:
+ *
+ * The time this file was last edited. If the file 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 time this file was last edited.",
+ GDATA_TYPE_G_TIME_VAL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:timestamp:
+ *
+ * The time the file was purportedly taken.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_timestamp">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ /* TODO: This should be the same as exif:timestamp. */
+ g_object_class_install_property (gobject_class, PROP_TIMESTAMP,
+ g_param_spec_boxed ("timestamp",
+ "Timestamp", "The time the file was purportedly taken.",
+ GDATA_TYPE_G_TIME_VAL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:comment-count:
+ *
+ * The number of comments on the file.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_commentCount">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_COMMENT_COUNT,
+ g_param_spec_uint ("comment-count",
+ "Comment Count", "The number of comments on the file.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:rotation:
+ *
+ * The rotation of the photo, in degrees. This will only be non-zero for files which are pending rotation, and haven't yet been
+ * permanently modified. For files which have already been rotated, this will be %0.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#gphoto_rotation">
+ * gphoto specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_ROTATION,
+ g_param_spec_uint ("rotation",
+ "Rotation", "The rotation of the photo, in degrees.",
+ 0, 359, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GDataPicasaWebFile:is-commenting-enabled:
+ *
+ * Whether commenting is enabled for this file.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_IS_COMMENTING_ENABLED,
+ g_param_spec_boolean ("is-commenting-enabled",
+ "Commenting enabled?", "Indicates whether comments are enabled.",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:credit:
+ *
+ * The nickname of the user credited with this file.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#media_credit">Media RSS
+ * specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_CREDIT,
+ g_param_spec_string ("credit",
+ "Credit", "The nickname of the user credited with this file.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:caption:
+ *
+ * The file's descriptive caption.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_CAPTION,
+ g_param_spec_string ("caption",
+ "Caption", "The file's descriptive caption.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebFile:tags:
+ *
+ * A comma-separated list of tags associated with the file.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#media_keywords">
+ * Media RSS specification</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_TAGS,
+ g_param_spec_string ("tags",
+ "Tags", "A comma-separated list of tags associated with the file.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+notify_title_cb (GDataPicasaWebFile *self, GParamSpec *pspec, gpointer user_data)
+{
+ /* Keep the atom:title and media:group/media:title in sync */
+ if (self->priv->media_group != NULL)
+ gdata_media_group_set_title (self->priv->media_group, gdata_entry_get_title (GDATA_ENTRY (self)));
+}
+
+static void
+notify_summary_cb (GDataPicasaWebFile *self, GParamSpec *pspec, gpointer user_data)
+{
+ /* Keep the atom:summary and media:group/media:description in sync */
+ if (self->priv->media_group != NULL)
+ gdata_media_group_set_description (self->priv->media_group, gdata_entry_get_summary (GDATA_ENTRY (self)));
+}
+
+static void
+gdata_picasaweb_file_init (GDataPicasaWebFile *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_PICASAWEB_FILE, GDataPicasaWebFilePrivate);
+ self->priv->media_group = g_object_new (GDATA_TYPE_MEDIA_GROUP, NULL);
+ self->priv->is_commenting_enabled = TRUE;
+
+ /* We need to keep atom:title (the canonical title for the file) in sync with media:group/media:title */
+ g_signal_connect (self, "notify::title", G_CALLBACK (notify_title_cb), NULL);
+ /* atom:summary (the canonical summary/caption for the file) in sync with media:group/media:description */
+ g_signal_connect (self, "notify::summary", G_CALLBACK (notify_summary_cb), NULL);
+}
+
+static void
+gdata_picasaweb_file_dispose (GObject *object)
+{
+ GDataPicasaWebFilePrivate *priv = GDATA_PICASAWEB_FILE_GET_PRIVATE (object);
+
+ if (priv->media_group != NULL)
+ g_object_unref (priv->media_group);
+ priv->media_group = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_picasaweb_file_parent_class)->dispose (object);
+}
+
+static void
+gdata_picasaweb_file_finalize (GObject *object)
+{
+ GDataPicasaWebFilePrivate *priv = GDATA_PICASAWEB_FILE_GET_PRIVATE (object);
+
+ g_free (priv->version);
+ g_free (priv->album_id);
+ g_free (priv->client);
+ g_free (priv->checksum);
+ xmlFree (priv->video_status);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_picasaweb_file_parent_class)->finalize (object);
+}
+
+static void
+gdata_picasaweb_file_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataPicasaWebFilePrivate *priv = GDATA_PICASAWEB_FILE_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_EDITED:
+ g_value_set_boxed (value, &(priv->edited));
+ break;
+ case PROP_VERSION:
+ g_value_set_string (value, priv->version);
+ break;
+ case PROP_POSITION:
+ g_value_set_double (value, priv->position);
+ break;
+ case PROP_ALBUM_ID:
+ g_value_set_string (value, priv->album_id);
+ break;
+ case PROP_WIDTH:
+ g_value_set_uint (value, priv->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_uint (value, priv->height);
+ break;
+ case PROP_SIZE:
+ g_value_set_ulong (value, priv->size);
+ break;
+ case PROP_CLIENT:
+ g_value_set_string (value, priv->client);
+ break;
+ case PROP_CHECKSUM:
+ g_value_set_string (value, priv->checksum);
+ break;
+ case PROP_TIMESTAMP:
+ g_value_set_boxed (value, &(priv->timestamp));
+ break;
+ case PROP_IS_COMMENTING_ENABLED:
+ g_value_set_boolean (value, priv->is_commenting_enabled);
+ break;
+ case PROP_COMMENT_COUNT:
+ g_value_set_uint (value, priv->comment_count);
+ break;
+ case PROP_ROTATION:
+ g_value_set_uint (value, priv->rotation);
+ break;
+ case PROP_VIDEO_STATUS:
+ g_value_set_string (value, priv->video_status);
+ break;
+ case PROP_CREDIT: {
+ GDataMediaCredit *credit = gdata_media_group_get_credit (priv->media_group);
+ g_value_set_string (value, gdata_media_credit_get_credit (credit));
+ break; }
+ case PROP_CAPTION:
+ g_value_set_string (value, gdata_entry_get_summary (GDATA_ENTRY (object)));
+ break;
+ case PROP_TAGS:
+ g_value_set_string (value, gdata_media_group_get_keywords (priv->media_group));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_picasaweb_file_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataPicasaWebFile *self = GDATA_PICASAWEB_FILE (object);
+
+ switch (property_id) {
+ case PROP_VERSION:
+ /* Construct only */
+ g_free (self->priv->version);
+ self->priv->version = g_value_dup_string (value);
+ break;
+ case PROP_POSITION:
+ gdata_picasaweb_file_set_position (self, g_value_get_double (value));
+ break;
+ case PROP_ALBUM_ID:
+ /* TODO: do we allow this to change albums? I think that's how pictures are moved. */
+ gdata_picasaweb_file_set_album_id (self, g_value_get_string (value));
+ break;
+ case PROP_CLIENT:
+ gdata_picasaweb_file_set_client (self, g_value_get_string (value));
+ break;
+ case PROP_CHECKSUM:
+ gdata_picasaweb_file_set_checksum (self, g_value_get_string (value));
+ break;
+ case PROP_TIMESTAMP:
+ gdata_picasaweb_file_set_timestamp (self, g_value_get_boxed (value));
+ break;
+ case PROP_IS_COMMENTING_ENABLED: /* TODO I don't think we can change this on a per file basis */
+ gdata_picasaweb_file_set_is_commenting_enabled (self, g_value_get_boolean (value));
+ break;
+ case PROP_ROTATION:
+ gdata_picasaweb_file_set_rotation (self, g_value_get_uint (value));
+ break;
+ case PROP_CAPTION:
+ gdata_picasaweb_file_set_caption (self, g_value_get_string (value));
+ break;
+ case PROP_TAGS:
+ gdata_picasaweb_file_set_tags (self, g_value_get_string (value));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+ GDataPicasaWebFile *self = GDATA_PICASAWEB_FILE (parsable);
+
+ if (xmlStrcmp (node->name, (xmlChar*) "group") == 0) {
+ /* media:group */
+ GDataMediaGroup *group = GDATA_MEDIA_GROUP (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_GROUP, "group", doc,
+ node, NULL, error));
+ if (group == NULL)
+ return FALSE;
+
+ if (self->priv->media_group != NULL)
+ /* We should really error here, but we can't, as priv->media_group has to be pre-populated
+ * in order for things like gdata_picasaweb_file_set_description() to work. */
+ g_object_unref (self->priv->media_group);
+
+ self->priv->media_group = group;
+ } else if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
+ /* app:edited */
+ xmlChar *edited = xmlNodeListGetString (doc, node->children, TRUE);
+ if (g_time_val_from_iso8601 ((gchar*) edited, &(self->priv->edited)) == FALSE) {
+ /* Error */
+ gdata_parser_error_not_iso8601_format (node, (gchar*) edited, error);
+ xmlFree (edited);
+ return FALSE;
+ }
+ xmlFree (edited);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "imageVersion") == 0) {
+ /* gphoto:imageVersion */
+ xmlChar *version = xmlNodeListGetString (doc, node->children, TRUE);
+ g_free (self->priv->version);
+ self->priv->version = g_strdup ((gchar*) version);
+ xmlFree (version);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "position") == 0) {
+ /* gphoto:position */
+ xmlChar *position_str = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_file_set_position (self, g_ascii_strtod ((gchar*) position_str, NULL));
+ xmlFree (position_str);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "albumid") == 0) {
+ /* gphoto:album_id */
+ xmlChar *album_id = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_file_set_album_id (self, (gchar*) album_id);
+ xmlFree (album_id);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "width") == 0) {
+ /* gphoto:width */
+ xmlChar *width = xmlNodeListGetString (doc, node->children, TRUE);
+ self->priv->width = strtoul ((gchar*) width, NULL, 10);
+ xmlFree (width);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "height") == 0) {
+ /* gphoto:height */
+ xmlChar *height = xmlNodeListGetString (doc, node->children, TRUE);
+ self->priv->height = strtoul ((gchar*) height, NULL, 10);
+ xmlFree (height);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "size") == 0) {
+ /* gphoto:size */
+ xmlChar *size = xmlNodeListGetString (doc, node->children, TRUE);
+ self->priv->size = strtoul ((gchar*) size, NULL, 10);
+ xmlFree (size);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "client") == 0) {
+ /* gphoto:client */
+ xmlChar *client = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_file_set_client (self, (gchar*) client);
+ xmlFree (client);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "checksum") == 0) {
+ /* gphoto:checksum */
+ xmlChar *checksum = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_file_set_checksum (self, (gchar*) checksum);
+ xmlFree (checksum);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "timestamp") == 0) {
+ /* gphoto:timestamp */
+ xmlChar *timestamp_str;
+ long long int milliseconds;
+ GTimeVal timestamp;
+
+ timestamp_str = xmlNodeListGetString (doc, node->children, TRUE);
+ milliseconds = strtoull ((gchar*) timestamp_str, NULL, 10);
+ xmlFree (timestamp_str);
+
+ timestamp.tv_sec = (glong) (milliseconds / 1000);
+ timestamp.tv_usec = (glong) ((milliseconds % 1000) * 1000);
+
+ gdata_picasaweb_file_set_timestamp (self, &timestamp);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "commentingEnabled") == 0) {
+ /* gphoto:commentingEnabled */
+ xmlChar *is_commenting_enabled = xmlNodeListGetString (doc, node->children, TRUE);
+ self->priv->is_commenting_enabled = (strncasecmp ("true", (gchar*) is_commenting_enabled, 5) == 0 ? TRUE : FALSE);
+ xmlFree (is_commenting_enabled);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "commentCount") == 0) {
+ /* gphoto:commentCount */
+ xmlChar *comment_count = xmlNodeListGetString (doc, node->children, TRUE);
+ self->priv->comment_count = strtoul ((gchar*) comment_count, NULL, 10);
+ xmlFree (comment_count);
+ } else if (xmlStrcmp (node->name, (xmlChar*) "videostatus") == 0) {
+ /* gphoto:videostatus */
+ xmlChar *video_status = xmlNodeListGetString (doc, node->children, TRUE);
+ if (self->priv->video_status != NULL) {
+ xmlFree (video_status);
+ return gdata_parser_error_duplicate_element (node, error);
+ }
+ self->priv->video_status = (gchar*) video_status;
+ } else if (xmlStrcmp (node->name, (xmlChar*) "rotation") == 0) {
+ /* gphoto:rotation */
+ xmlChar *rotation = xmlNodeListGetString (doc, node->children, TRUE);
+ gdata_picasaweb_file_set_rotation (self, strtoul ((gchar*) rotation, NULL, 10));
+ xmlFree (rotation);
+ } else if (GDATA_PARSABLE_CLASS (gdata_picasaweb_file_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+ /* Error! */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+ gchar *xml;
+ GDataPicasaWebFilePrivate *priv = GDATA_PICASAWEB_FILE (parsable)->priv;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_picasaweb_file_parent_class)->get_xml (parsable, xml_string);
+
+ /* Add all the PicasaWeb-specific XML */
+ if (priv->version != NULL)
+ g_string_append_printf (xml_string, "<gphoto:version>%s</gphoto:version>", priv->version);
+
+ g_string_append_printf (xml_string, "<gphoto:position>%f</gphoto:position>", priv->position);
+
+ if (priv->album_id != NULL)
+ g_string_append_printf (xml_string, "<gphoto:albumid>%s</gphoto:albumid>", priv->album_id);
+
+ if (priv->client != NULL) {
+ gchar *client = g_markup_escape_text (priv->client, -1);
+ g_string_append_printf (xml_string, "<gphoto:client>%s</gphoto:client>", client);
+ g_free (client);
+ }
+
+ if (priv->checksum != NULL) {
+ gchar *checksum = g_markup_escape_text (priv->checksum, -1);
+ g_string_append_printf (xml_string, "<gphoto:checksum>%s</gphoto:checksum>", checksum);
+ g_free (checksum);
+ }
+
+ if (priv->timestamp.tv_sec != 0 || priv->timestamp.tv_usec != 0) {
+ /* timestamp is in milliseconds */
+ g_string_append_printf (xml_string, "<gphoto:timestamp>%lu</gphoto:timestamp>",
+ priv->timestamp.tv_sec * 1000 + priv->timestamp.tv_usec);
+ }
+
+ if (priv->is_commenting_enabled == TRUE)
+ g_string_append (xml_string, "<gphoto:commentingEnabled>true</gphoto:commentingEnabled>");
+ else
+ g_string_append (xml_string, "<gphoto:commentingEnabled>false</gphoto:commentingEnabled>");
+
+ if (priv->rotation > 0)
+ g_string_append_printf (xml_string, "<gphoto:rotation>%u</gphoto:rotation>", priv->rotation);
+
+ /* media:group */
+ xml = _gdata_parsable_get_xml (GDATA_PARSABLE (priv->media_group), "media:group", FALSE);
+ g_string_append (xml_string, xml);
+ g_free (xml);
+
+ /* TODO:
+ * - Finish supporting all tags
+ * - Check all tags here are valid for insertions and updates
+ * - Check things are escaped (or not) as appropriate
+ * - Write a function to encapsulate g_markup_escape_text and
+ * g_string_append_printf to reduce the number of allocations
+ */
+}
+
+static void
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
+{
+ GDataPicasaWebFilePrivate *priv = GDATA_PICASAWEB_FILE (parsable)->priv;
+
+ /* Chain up to the parent class */
+ GDATA_PARSABLE_CLASS (gdata_picasaweb_file_parent_class)->get_namespaces (parsable, namespaces);
+
+ g_hash_table_insert (namespaces, (gchar*) "gphoto", (gchar*) "http://schemas.google.com/photos/2007");
+ g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app");
+
+ /* Add the media:group namespaces */
+ GDATA_PARSABLE_GET_CLASS (priv->media_group)->get_namespaces (GDATA_PARSABLE (priv->media_group), namespaces);
+}
+
+/**
+ * gdata_picasaweb_file_new:
+ * @id: the file's ID, or %NULL
+ *
+ * Creates a new #GDataPicasaWebFile with the given ID and default properties.
+ *
+ * Return value: a new #GDataPicasaWebFile; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebFile *
+gdata_picasaweb_file_new (const gchar *id)
+{
+ return g_object_new (GDATA_TYPE_PICASAWEB_FILE, "id", id, NULL);
+}
+
+/**
+ * gdata_picasaweb_file_new_from_xml:
+ * @xml: an XML string
+ * @length: the length in characters of @xml, or %-1
+ * @error: a #GError, or %NULL
+ *
+ * Creates a new #GDataPicasaWebFile from an XML string. If @length is %-1, the length of
+ * the string will be calculated.
+ *
+ * Errors from #GDataParserError can be returned if problems are found in the XML.
+ *
+ * Return value: a new #GDataPicasaWebFile, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebFile *
+gdata_picasaweb_file_new_from_xml (const gchar *xml, gint length, GError **error)
+{
+ return GDATA_PICASAWEB_FILE (_gdata_entry_new_from_xml (GDATA_TYPE_PICASAWEB_FILE, xml, length, error));
+}
+
+/**
+ * gdata_picasaweb_file_get_edited:
+ * @self: a #GDataPicasaWebFile
+ * @edited: a #GTimeVal
+ *
+ * Gets the #GDataPicasaWebFile: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_picasaweb_file_get_edited (GDataPicasaWebFile *self, GTimeVal *edited)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ g_return_if_fail (edited != NULL);
+ *edited = self->priv->edited;
+}
+
+/**
+ * gdata_picasaweb_file_get_version:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:version property.
+ *
+ * Return value: the file's version number, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_version (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return self->priv->version;
+}
+
+/**
+ * gdata_picasaweb_file_get_position:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:position property.
+ *
+ * Return value: the file's ordinal position in the album
+ *
+ * Since: 0.4.0
+ **/
+gdouble
+gdata_picasaweb_file_get_position (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), 0.0);
+ return self->priv->position;
+}
+
+/**
+ * gdata_picasaweb_file_set_position:
+ * @self: a #GDataPicasaWebFile
+ * @position: the file's new position in the album
+ *
+ * Sets the #GDataPicasaWebFile:position property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_position (GDataPicasaWebFile *self, gdouble position)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ self->priv->position = position;
+ g_object_notify (G_OBJECT (self), "position");
+}
+
+/**
+ * gdata_picasaweb_file_get_album_id:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:album-id property.
+ *
+ * Return value: the ID of the album containing the #GDataPicasaWebFile
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_album_id (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return self->priv->album_id;
+}
+
+/**
+ * gdata_picasaweb_file_set_album_id:
+ * @self: a #GDataPicasaWebFile
+ * @album_id: the ID of the new album for this file
+ *
+ * Sets the #GDataPicasaWebFile:album-id property, effectively moving the file to the album.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_album_id (GDataPicasaWebFile *self, const gchar *album_id)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ g_return_if_fail (album_id != NULL && *album_id != '\0');
+
+ g_free (self->priv->album_id);
+ self->priv->album_id = g_strdup (album_id);
+ g_object_notify (G_OBJECT (self), "album-id");
+}
+
+/**
+ * gdata_picasaweb_file_get_width:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:width property.
+ *
+ * Return value: the width of the image or video, in pixels
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_file_get_width (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), 0);
+ return self->priv->width;
+}
+
+/**
+ * gdata_picasaweb_file_get_height:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:height property.
+ *
+ * Return value: the height of the image or video, in pixels
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_file_get_height (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), 0);
+ return self->priv->height;
+}
+
+/**
+ * gdata_picasaweb_file_get_size:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:size property.
+ *
+ * Return value: the size of the file, in bytes
+ *
+ * Since: 0.4.0
+ **/
+gsize
+gdata_picasaweb_file_get_size (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), 0);
+ return self->priv->size;
+}
+
+/**
+ * gdata_picasaweb_file_get_client:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:client property.
+ *
+ * Return value: the name of the software which created the photo, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_client (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return self->priv->client;
+}
+
+/**
+ * gdata_picasaweb_file_set_client:
+ * @self: a #GDataPicasaWebFile
+ * @client: the name of the software which created or modified the photo, or %NULL
+ *
+ * Sets the #GDataPicasaWebFile:client property to @client.
+ *
+ * Set @client to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_client (GDataPicasaWebFile *self, const gchar *client)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+
+ g_free (self->priv->client);
+ self->priv->client = g_strdup (client);
+ g_object_notify (G_OBJECT (self), "client");
+}
+
+/**
+ * gdata_picasaweb_file_get_checksum:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:checksum property.
+ *
+ * Return value: the checksum assigned to this file, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_checksum (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return self->priv->checksum;
+}
+
+/**
+ * gdata_picasaweb_file_set_checksum:
+ * @self: a #GDataPicasaWebFile
+ * @checksum: the new checksum for this file, or %NULL
+ *
+ * Sets the #GDataPicasaWebFile:checksum property to @checksum.
+ *
+ * Set @checksum to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_checksum (GDataPicasaWebFile *self, const gchar *checksum)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+
+ g_free (self->priv->checksum);
+ self->priv->checksum = g_strdup (checksum);
+ g_object_notify (G_OBJECT (self), "checksum");
+}
+
+/**
+ * gdata_picasaweb_file_get_timestamp:
+ * @self: a #GDataPicasaWebFile
+ * @timestamp: a #GTimeVal
+ *
+ * Gets the #GDataPicasaWebFile:timestamp property and puts it in @timestamp. If the property is unset,
+ * both fields in the #GTimeVal will be set to %0.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_get_timestamp (GDataPicasaWebFile *self, GTimeVal *timestamp)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ g_return_if_fail (timestamp != NULL);
+ *timestamp = self->priv->timestamp;
+}
+
+/**
+ * gdata_picasaweb_file_set_timestamp:
+ * @self: a #GDataPicasaWebFile
+ * @timestamp: a #GTimeVal, or %NULL
+ *
+ * Sets the #GDataPicasaWebFile:timestamp property from values supplied by @timestamp. If @timestamp is %NULL,
+ * the property will be unset.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_timestamp (GDataPicasaWebFile *self, GTimeVal *timestamp)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ if (timestamp == NULL)
+ self->priv->timestamp.tv_sec = self->priv->timestamp.tv_usec = 0;
+ else
+ self->priv->timestamp = *timestamp;
+ g_object_notify (G_OBJECT (self), "timestamp");
+}
+
+/**
+ * gdata_picasaweb_file_is_commenting_enabled:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:is-commenting-enabled property.
+ *
+ * Return value: %TRUE if commenting is enabled, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_picasaweb_file_is_commenting_enabled (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), FALSE);
+ return self->priv->is_commenting_enabled;
+}
+
+/**
+ * gdata_picasaweb_file_set_is_commenting_enabled:
+ * @self: a #GDataPicasaWebFile
+ * @is_commenting_enabled: %TRUE if commenting should be enabled for the file, %FALSE otherwise
+ *
+ * Sets the #GDataPicasaWebFile:is-commenting-enabled property to @is_commenting_enabled.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_is_commenting_enabled (GDataPicasaWebFile *self, gboolean is_commenting_enabled)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ self->priv->is_commenting_enabled = is_commenting_enabled;
+ g_object_notify (G_OBJECT (self), "is-commenting-enabled");
+}
+
+/**
+ * gdata_picasaweb_file_get_comment_count:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:comment-count property.
+ *
+ * Return value: the number of comments on the file
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_file_get_comment_count (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), 0);
+ return self->priv->comment_count;
+}
+
+/**
+ * gdata_picasaweb_file_get_rotation:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:rotation property.
+ *
+ * Return value: the image's rotation, in degrees
+ *
+ * Since: 0.4.0
+ **/
+guint
+gdata_picasaweb_file_get_rotation (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), 0);
+ return self->priv->rotation;
+}
+
+/**
+ * gdata_picasaweb_file_set_rotation:
+ * @self: a #GDataPicasaWebFile
+ * @rotation: the new rotation for the image, in degrees
+ *
+ * Sets the #GDataPicasaWebFile:rotation property to @rotation.
+ *
+ * The rotation is absolute, rather than cumulative, through successive calls to gdata_picasaweb_file_set_rotation(),
+ * so calling it with 90° then 20° will result in a final rotation of 20°.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_rotation (GDataPicasaWebFile *self, guint rotation)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+ self->priv->rotation = rotation % 360;
+ g_object_notify (G_OBJECT (self), "rotation");
+}
+
+
+/**
+ * gdata_picasaweb_file_get_video_status:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:video-status property.
+ *
+ * Return value: the status of this video ("pending", "ready", "final" or "failed"), or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_video_status (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return self->priv->video_status;
+}
+
+/**
+ * gdata_picasaweb_file_get_tags:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:tags property.
+ *
+ * Return value: a comma-separated list of tags associated with the file, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_tags (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return gdata_media_group_get_keywords (self->priv->media_group);
+}
+
+/**
+ * gdata_picasaweb_file_set_tags:
+ * @self: a #GDataPicasaWebFile
+ * @tags: a new comma-separated list of tags, or %NULL
+ *
+ * Sets the #GDataPicasaWebFile:tags property to @tags.
+ *
+ * Set @tags to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_tags (GDataPicasaWebFile *self, const gchar *tags)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+
+ gdata_media_group_set_keywords (self->priv->media_group, tags);
+ g_object_notify (G_OBJECT (self), "tags");
+}
+
+/**
+ * gdata_picasaweb_file_get_credit:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:credit property.
+ *
+ * Return value: the nickname of the user credited with this file
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_credit (GDataPicasaWebFile *self)
+{
+ GDataMediaCredit *credit;
+
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+
+ credit = gdata_media_group_get_credit (self->priv->media_group);
+ return (credit == NULL) ? NULL : gdata_media_credit_get_credit (credit);
+}
+
+/**
+ * gdata_picasaweb_file_get_caption:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Gets the #GDataPicasaWebFile:caption property.
+ *
+ * Return value: the file's descriptive caption, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_file_get_caption (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return gdata_entry_get_summary (GDATA_ENTRY (self));
+}
+
+/**
+ * gdata_picasaweb_file_set_caption:
+ * @self: a #GDataPicasaWebFile
+ * @caption: the file's new caption, or %NULL
+ *
+ * Sets the #GDataPicasaWebFile:caption property to @caption.
+ *
+ * Set @caption to %NULL to unset the file's caption.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_file_set_caption (GDataPicasaWebFile *self, const gchar *caption)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (self));
+
+ gdata_entry_set_summary (GDATA_ENTRY (self), caption);
+ gdata_media_group_set_description (self->priv->media_group, caption);
+ g_object_notify (G_OBJECT (self), "caption");
+}
+
+/**
+ * gdata_picasaweb_file_get_contents:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Returns a list of media content, e.g. the actual photo or video.
+ *
+ * Return value: a #GList of #GDataMediaContent items
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_picasaweb_file_get_contents (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return gdata_media_group_get_contents (self->priv->media_group);
+}
+
+/**
+ * gdata_picasaweb_file_get_thumbnails:
+ * @self: a #GDataPicasaWebFile
+ *
+ * Returns a list of thumbnails, often at different sizes, for this file.
+ *
+ * Return value: a #GList of #GDataMediaThumbnail<!-- -->s, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+GList *
+gdata_picasaweb_file_get_thumbnails (GDataPicasaWebFile *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (self), NULL);
+ return gdata_media_group_get_thumbnails (self->priv->media_group);
+}
diff --git a/gdata/services/picasaweb/gdata-picasaweb-file.h b/gdata/services/picasaweb/gdata-picasaweb-file.h
new file mode 100644
index 00000000..a7f17638
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-file.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_PICASAWEB_FILE_H
+#define GDATA_PICASAWEB_FILE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-entry.h>
+#include <gdata/gdata-types.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_PICASAWEB_FILE (gdata_picasaweb_file_get_type ())
+#define GDATA_PICASAWEB_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_PICASAWEB_FILE, GDataPicasaWebFile))
+#define GDATA_PICASAWEB_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_PICASAWEB_FILE, GDataPicasaWebFileClass))
+#define GDATA_IS_PICASAWEB_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_PICASAWEB_FILE))
+#define GDATA_IS_PICASAWEB_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_PICASAWEB_FILE))
+#define GDATA_PICASAWEB_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_PICASAWEB_FILE, GDataPicasaWebFileClass))
+
+typedef struct _GDataPicasaWebFilePrivate GDataPicasaWebFilePrivate;
+
+/**
+ * GDataPicasaWebFile:
+ *
+ * All the fields in the #GDataPicasaWebFile structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataEntry parent;
+ GDataPicasaWebFilePrivate *priv;
+} GDataPicasaWebFile;
+
+/**
+ * GDataPicasaWebFileClass:
+ *
+ * All the fields in the #GDataPicasaWebFileClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataEntryClass parent;
+} GDataPicasaWebFileClass;
+
+GType gdata_picasaweb_file_get_type (void) G_GNUC_CONST;
+
+GDataPicasaWebFile *gdata_picasaweb_file_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+GDataPicasaWebFile *gdata_picasaweb_file_new_from_xml (const gchar *xml, gint length, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+void gdata_picasaweb_file_get_edited (GDataPicasaWebFile *self, GTimeVal *edited);
+const gchar *gdata_picasaweb_file_get_version (GDataPicasaWebFile *self);
+gdouble gdata_picasaweb_file_get_position (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_position (GDataPicasaWebFile *self, gdouble position);
+const gchar *gdata_picasaweb_file_get_album_id (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_album_id (GDataPicasaWebFile *self, const gchar *album_id); /* TODO should we have a more obvious _move() API too? nah */
+guint gdata_picasaweb_file_get_width (GDataPicasaWebFile *self);
+guint gdata_picasaweb_file_get_height (GDataPicasaWebFile *self);
+gulong gdata_picasaweb_file_get_size (GDataPicasaWebFile *self);
+const gchar *gdata_picasaweb_file_get_client (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_client (GDataPicasaWebFile *self, const gchar *client);
+const gchar *gdata_picasaweb_file_get_checksum (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_checksum (GDataPicasaWebFile *self, const gchar *checksum);
+void gdata_picasaweb_file_get_timestamp (GDataPicasaWebFile *self, GTimeVal *timestamp);
+void gdata_picasaweb_file_set_timestamp (GDataPicasaWebFile *self, GTimeVal *timestamp);
+gboolean gdata_picasaweb_file_is_commenting_enabled (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_is_commenting_enabled (GDataPicasaWebFile *self, gboolean is_commenting_enabled);
+guint gdata_picasaweb_file_get_comment_count (GDataPicasaWebFile *self);
+guint gdata_picasaweb_file_get_rotation (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_rotation (GDataPicasaWebFile *self, guint rotation);
+const gchar *gdata_picasaweb_file_get_video_status (GDataPicasaWebFile *self);
+const gchar *gdata_picasaweb_file_get_tags (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_tags (GDataPicasaWebFile *self, const gchar *tags);
+const gchar *gdata_picasaweb_file_get_credit (GDataPicasaWebFile *self);
+const gchar *gdata_picasaweb_file_get_caption (GDataPicasaWebFile *self);
+void gdata_picasaweb_file_set_caption (GDataPicasaWebFile *self, const gchar *caption);
+GList *gdata_picasaweb_file_get_contents (GDataPicasaWebFile *self);
+GList *gdata_picasaweb_file_get_thumbnails (GDataPicasaWebFile *self);
+
+/* TODO implement get exif */
+/* TODO implement get thumbnail (from media?) */
+/* TODO implement get link */
+/* TODO implement is video */
+/* TODO implement get content; in what form? */
+/* TODO implement get tags */
+/* TODO implement get comments, get num comments */
+/* TODO implement get location */
+
+G_END_DECLS
+
+#endif /* !GDATA_PICASAWEB_FILE_H */
diff --git a/gdata/services/picasaweb/gdata-picasaweb-query.c b/gdata/services/picasaweb/gdata-picasaweb-query.c
new file mode 100644
index 00000000..7001fad0
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-query.c
@@ -0,0 +1,547 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-picasaweb-query
+ * @short_description: GData PicasaWeb query object
+ * @stability: Unstable
+ * @include: gdata/services/picasaweb/gdata-picasaweb-query.h
+ *
+ * #GDataPicasaWebQuery represents a collection of query parameters specific to the Google PicasaWeb service, which go above and beyond
+ * those catered for by #GDataQuery.
+ *
+ * For more information on the custom GData query parameters supported by #GDataPicasaWebQuery, see the <ulink type="http"
+ * url="http://code.google.com/apis/picasaweb/reference.html#Parameters">online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+#include "gdata-picasaweb-query.h"
+#include "gdata-query.h"
+#include "gdata-picasaweb-enums.h"
+
+static void gdata_picasaweb_query_finalize (GObject *object);
+static void gdata_picasaweb_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_picasaweb_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 _GDataPicasaWebQueryPrivate {
+ GDataPicasaWebVisibility visibility;
+ gchar *thumbnail_size;
+ gchar *image_size;
+ gchar *tag;
+ gchar *location;
+
+ struct {
+ gdouble north;
+ gdouble east;
+ gdouble south;
+ gdouble west;
+ } bounding_box;
+};
+
+enum {
+ PROP_VISIBILITY = 1,
+ PROP_THUMBNAIL_SIZE,
+ PROP_IMAGE_SIZE,
+ PROP_TAG,
+ PROP_LOCATION
+};
+
+G_DEFINE_TYPE (GDataPicasaWebQuery, gdata_picasaweb_query, GDATA_TYPE_QUERY)
+#define GDATA_PICASAWEB_QUERY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_PICASAWEB_QUERY, GDataPicasaWebQueryPrivate))
+
+static void
+gdata_picasaweb_query_class_init (GDataPicasaWebQueryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataQueryClass *query_class = GDATA_QUERY_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataPicasaWebQueryPrivate));
+
+ gobject_class->get_property = gdata_picasaweb_query_get_property;
+ gobject_class->set_property = gdata_picasaweb_query_set_property;
+ gobject_class->finalize = gdata_picasaweb_query_finalize;
+
+ query_class->get_query_uri = get_query_uri;
+
+ /**
+ * GDataPicasaWebQuery:visibility:
+ *
+ * Specifies which albums should be listed, in terms of their visibility (#GDataPicasaWebAlbum:visibility).
+ *
+ * Set the property to %0 to list all albums, regardless of their visibility. Otherwise, use values from #GDataPicasaWebVisibility.
+ *
+ * For more information, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#Visibility">
+ * online documentation</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_VISIBILITY,
+ g_param_spec_int ("visibility",
+ "Visibility", "Specifies which albums should be listed, in terms of their visibility.",
+ 0, GDATA_PICASAWEB_PRIVATE, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebQuery:thumbnail-size:
+ *
+ * A comma-separated list of thumbnail sizes (width in pixels) to return. Only certain sizes are allowed, and whether the thumbnail should be
+ * cropped or scaled can be specified; for more information, see the
+ * <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#Parameters">online documentation</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_THUMBNAIL_SIZE,
+ g_param_spec_string ("thumbnail-size",
+ "Thumbnail size", "A comma-separated list of thumbnail sizes (width in pixels) to return.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebQuery:image-size:
+ *
+ * A comma-separated list of image sizes (width in pixels) to return. Only certain sizes are allowed, and whether the image should be
+ * cropped or scaled can be specified; for more information, see the
+ * <ulink type="http" url="http://code.google.com/apis/picasaweb/reference.html#Parameters">online documentation</ulink>.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_IMAGE_SIZE,
+ g_param_spec_string ("image-size",
+ "Image size", "A comma-separated list of image sizes (width in pixels) to return.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebQuery:tag:
+ *
+ * A tag which returned results must contain.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_TAG,
+ g_param_spec_string ("tag",
+ "Tag", "A tag which returned results must contain.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataPicasaWebQuery:location:
+ *
+ * A location to search for photos, e.g. "London".
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_LOCATION,
+ g_param_spec_string ("location",
+ "Location", "A location to search for photos, e.g. \"London\".",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_picasaweb_query_init (GDataPicasaWebQuery *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_PICASAWEB_QUERY, GDataPicasaWebQueryPrivate);
+}
+
+static void
+gdata_picasaweb_query_finalize (GObject *object)
+{
+ GDataPicasaWebQueryPrivate *priv = GDATA_PICASAWEB_QUERY_GET_PRIVATE (object);
+
+ g_free (priv->thumbnail_size);
+ g_free (priv->image_size);
+ g_free (priv->tag);
+ g_free (priv->location);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_picasaweb_query_parent_class)->finalize (object);
+}
+
+static void
+gdata_picasaweb_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataPicasaWebQueryPrivate *priv = GDATA_PICASAWEB_QUERY_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_VISIBILITY:
+ g_value_set_int (value, priv->visibility);
+ break;
+ case PROP_THUMBNAIL_SIZE:
+ g_value_set_string (value, priv->thumbnail_size);
+ break;
+ case PROP_IMAGE_SIZE:
+ g_value_set_string (value, priv->image_size);
+ break;
+ case PROP_TAG:
+ g_value_set_string (value, priv->tag);
+ break;
+ case PROP_LOCATION:
+ g_value_set_string (value, priv->location);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_picasaweb_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataPicasaWebQuery *self = GDATA_PICASAWEB_QUERY (object);
+
+ switch (property_id) {
+ case PROP_VISIBILITY:
+ gdata_picasaweb_query_set_visibility (self, g_value_get_int (value));
+ break;
+ case PROP_THUMBNAIL_SIZE:
+ gdata_picasaweb_query_set_thumbnail_size (self, g_value_get_string (value));
+ break;
+ case PROP_IMAGE_SIZE:
+ gdata_picasaweb_query_set_image_size (self, g_value_get_string (value));
+ break;
+ case PROP_TAG:
+ gdata_picasaweb_query_set_tag (self, g_value_get_string (value));
+ break;
+ case PROP_LOCATION:
+ gdata_picasaweb_query_set_location (self, g_value_get_string (value));
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started)
+{
+ GDataPicasaWebQueryPrivate *priv = GDATA_PICASAWEB_QUERY (self)->priv;
+
+ #define APPEND_SEP g_string_append_c (query_uri, (*params_started == FALSE) ? '?' : '&'); *params_started = TRUE;
+
+ /* Chain up to the parent class */
+ GDATA_QUERY_CLASS (gdata_picasaweb_query_parent_class)->get_query_uri (self, feed_uri, query_uri, params_started);
+
+ APPEND_SEP
+ if (priv->visibility == 0)
+ g_string_append (query_uri, "access=all");
+ else if (priv->visibility == GDATA_PICASAWEB_PUBLIC)
+ g_string_append (query_uri, "access=public");
+ else if (priv->visibility == GDATA_PICASAWEB_PRIVATE)
+ g_string_append (query_uri, "access=private");
+ else
+ g_assert_not_reached ();
+
+ if (priv->thumbnail_size != NULL) {
+ APPEND_SEP
+ g_string_append (query_uri, "thumbsize=");
+ g_string_append_uri_escaped (query_uri, priv->thumbnail_size, NULL, FALSE);
+ }
+
+ if (priv->image_size != NULL) {
+ APPEND_SEP
+ g_string_append (query_uri, "imgmax=");
+ g_string_append_uri_escaped (query_uri, priv->image_size, NULL, FALSE);
+ }
+
+ if (priv->tag != NULL) {
+ APPEND_SEP
+ g_string_append (query_uri, "tag=");
+ g_string_append_uri_escaped (query_uri, priv->tag, NULL, TRUE);
+ }
+
+ if (priv->bounding_box.north != priv->bounding_box.south && priv->bounding_box.east != priv->bounding_box.west) {
+ APPEND_SEP
+ g_string_append_printf (query_uri, "bbox=%f,%f,%f,%f",
+ priv->bounding_box.west, priv->bounding_box.south,
+ priv->bounding_box.east, priv->bounding_box.north);
+ }
+
+ if (priv->location != NULL) {
+ APPEND_SEP
+ g_string_append (query_uri, "l=");
+ g_string_append_uri_escaped (query_uri, priv->location, NULL, TRUE);
+ }
+}
+
+/**
+ * gdata_picasaweb_query_new:
+ * @q: a query string
+ *
+ * Creates a new #GDataPicasaWebQuery with its #GDataQuery:q property set to @q.
+ *
+ * Return value: a new #GDataPicasaWebQuery
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebQuery *
+gdata_picasaweb_query_new (const gchar *q)
+{
+ return g_object_new (GDATA_TYPE_PICASAWEB_QUERY, "q", q, NULL);
+}
+
+/**
+ * gdata_picasaweb_query_get_visibility:
+ * @self: a #GDataPicasaWebQuery
+ *
+ * Gets the #GDataPicasaWebQuery:visibility property.
+ *
+ * Return value: the visibility of the objects to retrieve, or %0 to retrieve all objects
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebVisibility
+gdata_picasaweb_query_get_visibility (GDataPicasaWebQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_QUERY (self), 0);
+ return self->priv->visibility;
+}
+
+/**
+ * gdata_picasaweb_query_set_visibility:
+ * @self: a #GDataPicasaWebQuery
+ * @visibility: the visibility of the objects to retrieve, or %0 to retrieve all objects
+ *
+ * Sets the #GDataPicasaWebQuery:visibility property to @visibility.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_set_visibility (GDataPicasaWebQuery *self, GDataPicasaWebVisibility visibility)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+ self->priv->visibility = visibility;
+ g_object_notify (G_OBJECT (self), "visibility");
+}
+
+/**
+ * gdata_picasaweb_query_get_thumbnail_size:
+ * @self: a #GDataPicasaWebQuery
+ *
+ * Gets the #GDataPicasaWebQuery:thumbnail-size property.
+ *
+ * Return value: a comma-separated list of thumbnail sizes to retrieve, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_query_get_thumbnail_size (GDataPicasaWebQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_QUERY (self), NULL);
+ return self->priv->thumbnail_size;
+}
+
+/**
+ * gdata_picasaweb_query_set_thumbnail_size:
+ * @self: a #GDataPicasaWebQuery
+ * @thumbnail_size: a comma-separated list of thumbnail sizes to retrieve, or %NULL
+ *
+ * Sets the #GDataPicasaWebQuery:thumbnail-size property to @thumbnail_size.
+ *
+ * Set @thumbnail_size to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_set_thumbnail_size (GDataPicasaWebQuery *self, const gchar *thumbnail_size)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+
+ g_free (self->priv->thumbnail_size);
+ self->priv->thumbnail_size = g_strdup (thumbnail_size);
+ g_object_notify (G_OBJECT (self), "thumbnail-size");
+}
+
+/**
+ * gdata_picasaweb_query_get_image_size:
+ * @self: a #GDataPicasaWebQuery
+ *
+ * Gets the #GDataPicasaWebQuery:image-size property.
+ *
+ * Return value: a comma-separated list of image sizes to retrieve, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_query_get_image_size (GDataPicasaWebQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_QUERY (self), NULL);
+ return self->priv->image_size;
+}
+
+/**
+ * gdata_picasaweb_query_set_image_size:
+ * @self: a #GDataPicasaWebQuery
+ * @image_size: a comma-separated list of image sizes to retrieve, or %NULL
+ *
+ * Sets the #GDataPicasaWebQuery:image-size property to @image_size.
+ *
+ * Set @image_size to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_set_image_size (GDataPicasaWebQuery *self, const gchar *image_size)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+
+ g_free (self->priv->image_size);
+ self->priv->image_size = g_strdup (image_size);
+ g_object_notify (G_OBJECT (self), "image-size");
+}
+
+/**
+ * gdata_picasaweb_query_get_tag:
+ * @self: a #GDataPicasaWebQuery
+ *
+ * Gets the #GDataPicasaWebQuery:tag property.
+ *
+ * Return value: a tag which retrieved objects must have, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_query_get_tag (GDataPicasaWebQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_QUERY (self), NULL);
+ return self->priv->tag;
+}
+
+/**
+ * gdata_picasaweb_query_set_tag:
+ * @self: a #GDataPicasaWebQuery
+ * @tag: a tag which retrieved objects must have, or %NULL
+ *
+ * Sets the #GDataPicasaWebQuery:tag property to @tag.
+ *
+ * Set @tag to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_set_tag (GDataPicasaWebQuery *self, const gchar *tag)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+
+ g_free (self->priv->tag);
+ self->priv->tag = g_strdup (tag);
+ g_object_notify (G_OBJECT (self), "tag");
+}
+
+/**
+ * gdata_picasaweb_query_get_bounding_box:
+ * @self: a #GDataPicasaWebQuery
+ * @north: return location for the latitude of the top of the box, or %NULL
+ * @east: return location for the longitude of the right of the box, or %NULL
+ * @south: return location for the latitude of the south of the box, or %NULL
+ * @west: return location for the longitude of the left of the box, or %NULL
+ *
+ * Gets the latitudes and longitudes of a bounding box, inside which all the results must lie.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_get_bounding_box (GDataPicasaWebQuery *self, gdouble *north, gdouble *east, gdouble *south, gdouble *west)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+
+ if (north != NULL)
+ *north = self->priv->bounding_box.north;
+ if (east != NULL)
+ *east = self->priv->bounding_box.east;
+ if (south != NULL)
+ *south = self->priv->bounding_box.south;
+ if (west != NULL)
+ *west = self->priv->bounding_box.west;
+}
+
+/**
+ * gdata_picasaweb_query_set_bounding_box:
+ * @self: a #GDataPicasaWebQuery
+ * @north: latitude of the top of the box
+ * @east: longitude of the right of the box
+ * @south: latitude of the bottom of the box
+ * @west: longitude of the left of the box
+ *
+ * Sets a bounding box, inside which all the returned results must lie.
+ *
+ * Set @north, @east, @south and @west to %0 to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_set_bounding_box (GDataPicasaWebQuery *self, gdouble north, gdouble east, gdouble south, gdouble west)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+ g_return_if_fail (north >= -90.0 && north <= 90.0);
+ g_return_if_fail (south >= -90.0 && south <= 90.0);
+ g_return_if_fail (east >= -180.0 && east <= 180.0);
+ g_return_if_fail (west >= -180.0 && west <= 180.0);
+
+ self->priv->bounding_box.north = north;
+ self->priv->bounding_box.east = east;
+ self->priv->bounding_box.south = south;
+ self->priv->bounding_box.west = west;
+}
+
+/**
+ * gdata_picasaweb_query_get_location:
+ * @self: a #GDataPicasaWebQuery
+ *
+ * Gets the #GDataPicasaWebQuery:location property.
+ *
+ * Return value: a location which returned objects must be near, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_picasaweb_query_get_location (GDataPicasaWebQuery *self)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_QUERY (self), NULL);
+ return self->priv->location;
+}
+
+/**
+ * gdata_picasaweb_query_set_location:
+ * @self: a #GDataPicasaWebQuery
+ * @location: a location which returned objects must be near, or %NULL
+ *
+ * Sets the #GDataPicasaWebQuery:location property to @location.
+ *
+ * Set @location to %NULL to unset the property.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_picasaweb_query_set_location (GDataPicasaWebQuery *self, const gchar *location)
+{
+ g_return_if_fail (GDATA_IS_PICASAWEB_QUERY (self));
+
+ g_free (self->priv->location);
+ self->priv->location = g_strdup (location);
+ g_object_notify (G_OBJECT (self), "location");
+}
diff --git a/gdata/services/picasaweb/gdata-picasaweb-query.h b/gdata/services/picasaweb/gdata-picasaweb-query.h
new file mode 100644
index 00000000..9e557044
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-query.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_PICASAWEB_QUERY_H
+#define GDATA_PICASAWEB_QUERY_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-query.h>
+#include <gdata/services/picasaweb/gdata-picasaweb-album.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_PICASAWEB_QUERY (gdata_picasaweb_query_get_type ())
+#define GDATA_PICASAWEB_QUERY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_PICASAWEB_QUERY, GDataPicasaWebQuery))
+#define GDATA_PICASAWEB_QUERY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_PICASAWEB_QUERY, GDataPicasawebQueryClass))
+#define GDATA_IS_PICASAWEB_QUERY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_PICASAWEB_QUERY))
+#define GDATA_IS_PICASAWEB_QUERY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_PICASAWEB_QUERY))
+#define GDATA_PICASAWEB_QUERY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_PICASAWEB_QUERY, GDataPicasawebQueryClass))
+
+typedef struct _GDataPicasaWebQueryPrivate GDataPicasaWebQueryPrivate;
+
+/**
+ * GDataPicasaWebQuery:
+ *
+ * All the fields in the #GDataPicasaWebQuery structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataQuery parent;
+ GDataPicasaWebQueryPrivate *priv;
+} GDataPicasaWebQuery;
+
+/**
+ * GDataPicasaWebQueryClass:
+ *
+ * All the fields in the #GDataPicasaWebQueryClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataQueryClass parent;
+} GDataPicasaWebQueryClass;
+
+GType gdata_picasaweb_query_get_type (void) G_GNUC_CONST;
+
+GDataPicasaWebQuery *gdata_picasaweb_query_new (const gchar *q) G_GNUC_WARN_UNUSED_RESULT;
+
+GDataPicasaWebVisibility gdata_picasaweb_query_get_visibility (GDataPicasaWebQuery *self);
+void gdata_picasaweb_query_set_visibility (GDataPicasaWebQuery *self, GDataPicasaWebVisibility visibility);
+const gchar *gdata_picasaweb_query_get_thumbnail_size (GDataPicasaWebQuery *self);
+void gdata_picasaweb_query_set_thumbnail_size (GDataPicasaWebQuery *self, const gchar *thumbnail_size);
+const gchar *gdata_picasaweb_query_get_image_size (GDataPicasaWebQuery *self);
+void gdata_picasaweb_query_set_image_size (GDataPicasaWebQuery *self, const gchar *image_size);
+const gchar *gdata_picasaweb_query_get_tag (GDataPicasaWebQuery *self);
+void gdata_picasaweb_query_set_tag (GDataPicasaWebQuery *self, const gchar *tag);
+void gdata_picasaweb_query_get_bounding_box (GDataPicasaWebQuery *self, gdouble *north, gdouble *east, gdouble *south, gdouble *west);
+void gdata_picasaweb_query_set_bounding_box (GDataPicasaWebQuery *self, gdouble north, gdouble east, gdouble south, gdouble west);
+const gchar *gdata_picasaweb_query_get_location (GDataPicasaWebQuery *self);
+void gdata_picasaweb_query_set_location (GDataPicasaWebQuery *self, const gchar *location);
+
+G_END_DECLS
+
+#endif /* !GDATA_PICASAWEB_QUERY_H */
diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c
new file mode 100644
index 00000000..c343a4a1
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-service.c
@@ -0,0 +1,397 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-picasaweb-service
+ * @short_description: GData PicasaWeb service object
+ * @stability: Unstable
+ * @include: gdata/services/picasaweb/gdata-picasaweb-service.h
+ *
+ * #GDataPicasaWebService is a subclass of #GDataService for communicating with the GData API of Google PicasaWeb. It supports querying for files
+ * and albums, and uploading files.
+ *
+ * For more details of PicasaWeb's GData API, see the <ulink type="http" url="http://code.google.com/apis/picasaweb/developers_guide_protocol.html">
+ * online documentation</ulink>.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdata-service.h"
+#include "gdata-picasaweb-service.h"
+#include "gdata-private.h"
+#include "gdata-parser.h"
+#include "atom/gdata-link.h"
+
+G_DEFINE_TYPE (GDataPicasaWebService, gdata_picasaweb_service, GDATA_TYPE_SERVICE)
+
+static void
+gdata_picasaweb_service_class_init (GDataPicasaWebServiceClass *klass)
+{
+ GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
+ service_class->service_name = "lh2";
+}
+
+static void
+gdata_picasaweb_service_init (GDataPicasaWebService *self)
+{
+ /* Nothing to see here */
+}
+
+/*
+ * This constructs the URI we want to access for querying albums.
+ *
+ * Remember to free the URI in the caller.
+*/
+static gchar *
+create_uri (GDataPicasaWebService *self, const gchar *username)
+{
+ if (username == NULL) {
+ /* Ensure we're authenticated first */
+ if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE)
+ return NULL;
+
+ /* Querying Picasa albums for the "default" user when logged in returns the albums for the authenticated user */
+ username = "default";
+ }
+
+ return g_strdup_printf ("http://picasaweb.google.com/data/feed/api/user/%s", username);
+}
+
+/**
+ * gdata_picasaweb_service_new:
+ * @client_id: your application's client ID
+ *
+ * Creates a new #GDataPicasaWebService. The @client_id must be unique for your application, and as registered with Google.
+ *
+ * Return value: a new #GDataPicasaWebService, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebService *
+gdata_picasaweb_service_new (const gchar *client_id)
+{
+ g_return_val_if_fail (client_id != NULL, NULL);
+
+ return g_object_new (GDATA_TYPE_PICASAWEB_SERVICE,
+ "client-id", client_id,
+ NULL);
+}
+
+/**
+ * gdata_picasaweb_service_query_all_albums:
+ * @self: a #GDataPicasaWebService
+ * @query: a #GDataQuery with the query parameters, or %NULL
+ * @username: the username of the user whose albums you wish to retrieve, 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 all albums belonging to the specified @username which match the given
+ * @query. If a user is authenticated with the service, @username can be set as %NULL to return a list of albums belonging
+ * to the currently-authenticated user.
+ *
+ * For more details, see gdata_service_query().
+ *
+ * Return value: a #GDataFeed of query results; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataFeed *
+gdata_picasaweb_service_query_all_albums (GDataPicasaWebService *self, GDataQuery *query, const gchar *username, GCancellable *cancellable,
+ GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
+{
+ gchar *uri;
+ GDataFeed *album_feed;
+
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
+ g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
+
+ uri = create_uri (self, username);
+ if (uri == NULL) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must specify a username or be authenticated to query all albums."));
+ return NULL;
+ }
+
+ /* Execute the query */
+ album_feed = gdata_service_query (GDATA_SERVICE (self), uri, query, GDATA_TYPE_PICASAWEB_ALBUM,
+ cancellable, progress_callback, progress_user_data, error);
+ g_free (uri);
+
+ return album_feed;
+}
+
+/**
+ * gdata_picasaweb_service_query_all_albums_async:
+ * @self: a #GDataPicasaWebService
+ * @query: a #GDataQuery with the query parameters, or %NULL
+ * @username: the username of the user whose albums you wish to retrieve, 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 all albums belonging to the specified @username which match the given
+ * @query. @self, @query and @username are all reffed/copied when this function is called, so can safely be unreffed/freed after
+ * this function returns.
+ *
+ * For more details, see gdata_picasaweb_service_query_all_albums(), 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_picasaweb_service_query_all_albums_async (GDataPicasaWebService *self, GDataQuery *query, const gchar *username,
+ GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GAsyncReadyCallback callback, gpointer user_data)
+{
+ gchar *uri;
+
+ g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self));
+ g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
+ g_return_if_fail (callback != NULL);
+
+ uri = create_uri (self, username);
+ if (uri == NULL) {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must specify a username or be authenticated to query all albums."));
+ return;
+ }
+
+ /* Schedule the async query */
+ gdata_service_query_async (GDATA_SERVICE (self), uri, query, GDATA_TYPE_PICASAWEB_ALBUM, cancellable, progress_callback, progress_user_data,
+ callback, user_data);
+ g_free (uri);
+}
+
+/**
+ * gdata_picasaweb_service_query_files:
+ * @self: a #GDataPicasaWebService
+ * @album: a #GDataPicasaWebAlbum from which to retrieve the files, or %NULL
+ * @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 specified @album for a list of the files which match the given @query. If @album is %NULL and a user is
+ * authenticated with the service, the user's default album will be queried.
+ *
+ * For more details, see gdata_service_query().
+ *
+ * Return value: a #GDataFeed of query results; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataFeed *
+gdata_picasaweb_service_query_files (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataQuery *query, GCancellable *cancellable,
+ GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
+{
+ /* TODO: Async variant */
+ const gchar *uri;
+
+ if (album != NULL) {
+ GDataLink *link = gdata_entry_look_up_link (GDATA_ENTRY (album), "http://schemas.google.com/g/2005#feed");
+ if (link == NULL) {
+ /* Error */
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+ _("The album did not have a feed link."));
+ return NULL;
+ }
+ uri = gdata_link_get_uri (link);
+ } else {
+ /* Default URI */
+ uri = "http://picasaweb.google.com/data/feed/api/user/default/albumid/default";
+ }
+
+ /* Execute the query */
+ return gdata_service_query (GDATA_SERVICE (self), uri, GDATA_QUERY (query), GDATA_TYPE_PICASAWEB_FILE, cancellable,
+ progress_callback, progress_user_data, error);
+}
+
+/**
+ * gdata_picasaweb_service_upload_file:
+ * @self: a #GDataPicasaWebService
+ * @album: a #GDataPicasaWebAlbum into which to insert the file, or %NULL
+ * @file: a #GDataPicasaWebFile to insert
+ * @actual_file: the actual file to upload
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Uploads a file (photo or video) to the given PicasaWeb @album, using the @actual_file from disk and the metadata from @file. If @album is
+ * %NULL, the file will be uploaded to the currently-authenticated user's "Drop Box" album. A user must be authenticated to use this function.
+ *
+ * If @file has already been inserted, a %GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED error will be returned. If no user is authenticated
+ * with the service, %GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED will be returned.
+ *
+ * If there is a problem reading @actual_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: the inserted #GDataPicasaWebFile with updated properties from @file; unref with g_object_unref()
+ *
+ * Since: 0.4.0
+ **/
+GDataPicasaWebFile *
+gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file, GFile *actual_file,
+ GCancellable *cancellable, GError **error)
+{
+ /* TODO: Async variant */
+ #define BOUNDARY_STRING "0xdeadbeef6e0808d5e6ed8bc168390bcc"
+
+ GDataServiceClass *klass;
+ SoupMessage *message;
+ gchar *entry_xml, *upload_uri, *second_chunk_header, *upload_data, *file_contents, *i;
+ const gchar *first_chunk_header, *footer, *album_id, *user_id;
+ guint status;
+ GFileInfo *actual_file_info;
+ gsize content_length, first_chunk_header_length, second_chunk_header_length, entry_xml_length, file_length, footer_length;
+
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_FILE (actual_file), NULL);
+
+ if (gdata_entry_is_inserted (GDATA_ENTRY (file)) == TRUE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
+ _("The entry has already been inserted."));
+ return 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 a file."));
+ return NULL;
+ }
+
+ /* PicasaWeb allows you to post to a default Dropbox */
+ if (album == NULL)
+ album_id = "default";
+ else
+ album_id = gdata_entry_get_id (GDATA_ENTRY (album));
+ user_id = gdata_service_get_username (GDATA_SERVICE (self));
+
+ upload_uri = g_strdup_printf ("http://picasaweb.google.com/data/feed/api/user/%s/albumid/%s", user_id, album_id);
+ message = soup_message_new (SOUP_METHOD_POST, upload_uri);
+ g_free (upload_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 data early so we can calculate the content length */
+ if (g_file_load_contents (actual_file, NULL, &file_contents, &file_length, NULL, error) == FALSE) {
+ g_object_unref (message);
+ return NULL;
+ }
+
+ entry_xml = gdata_entry_get_xml (GDATA_ENTRY (file));
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ g_free (entry_xml);
+ return NULL;
+ }
+
+ actual_file_info = g_file_query_info (actual_file, "standard::display-name,standard::content-type", G_FILE_QUERY_INFO_NONE, NULL, error);
+ if (actual_file_info == NULL) {
+ g_object_unref (message);
+ g_free (entry_xml);
+ return NULL;
+ }
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ g_free (entry_xml);
+ g_object_unref (actual_file_info);
+ return NULL;
+ }
+
+ /* Add file-upload--specific headers */
+ soup_message_headers_append (message->request_headers, "Slug", g_file_info_get_display_name (actual_file_info));
+
+ first_chunk_header = "--" BOUNDARY_STRING "\nContent-Type: application/atom+xml; charset=UTF-8\n\n<?xml version='1.0'?>";
+ second_chunk_header = g_strdup_printf ("\n--" BOUNDARY_STRING "\nContent-Type: %s\nContent-Transfer-Encoding: binary\n\n",
+ g_file_info_get_content_type (actual_file_info));
+ footer = "\n--" BOUNDARY_STRING "--";
+
+ g_object_unref (actual_file_info);
+
+ first_chunk_header_length = strlen (first_chunk_header);
+ second_chunk_header_length = strlen (second_chunk_header);
+ footer_length = strlen (footer);
+ entry_xml_length = strlen (entry_xml);
+
+ content_length = first_chunk_header_length + entry_xml_length + second_chunk_header_length + file_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, file_contents, file_length);
+ g_free (file_contents);
+ i += file_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);
+
+ /* 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 */
+ 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;
+ }
+
+ g_assert (message->response_body->data != NULL);
+
+ return gdata_picasaweb_file_new_from_xml (message->response_body->data, (gint) message->response_body->length, error);
+}
diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.h b/gdata/services/picasaweb/gdata-picasaweb-service.h
new file mode 100644
index 00000000..a4c28a67
--- /dev/null
+++ b/gdata/services/picasaweb/gdata-picasaweb-service.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_PICASAWEB_SERVICE_H
+#define GDATA_PICASAWEB_SERVICE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-service.h>
+#include <gdata/services/picasaweb/gdata-picasaweb-album.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_PICASAWEB_SERVICE (gdata_picasaweb_service_get_type ())
+#define GDATA_PICASAWEB_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_PICASAWEB_SERVICE, GDataPicasaWebService))
+#define GDATA_PICASAWEB_SERVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_PICASAWEB_SERVICE, GDataPicasaWebServiceClass))
+#define GDATA_IS_PICASAWEB_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_PICASAWEB_SERVICE))
+#define GDATA_IS_PICASAWEB_SERVICE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_PICASAWEB_SERVICE))
+#define GDATA_PICASAWEB_SERVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_PICASAWEB_SERVICE, GDataPicasaWebServiceClass))
+
+/**
+ * GDataPicasaWebService:
+ *
+ * All the fields in the #GDataPicasaWebService structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ GDataService parent;
+} GDataPicasaWebService;
+
+/**
+ * GDataPicasaWebServiceClass:
+ *
+ * All the fields in the #GDataPicasaWebServiceClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+ /*< private >*/
+ GDataServiceClass parent;
+} GDataPicasaWebServiceClass;
+
+GType gdata_picasaweb_service_get_type (void) G_GNUC_CONST;
+
+GDataPicasaWebService *gdata_picasaweb_service_new (const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT;
+
+#include <gdata/services/picasaweb/gdata-picasaweb-query.h>
+
+GDataFeed *gdata_picasaweb_service_query_all_albums (GDataPicasaWebService *self, GDataQuery *query, const gchar *username,
+ GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+void gdata_picasaweb_service_query_all_albums_async (GDataPicasaWebService *self, GDataQuery *query, const gchar *username,
+ GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GAsyncReadyCallback callback, gpointer user_data);
+
+GDataFeed *gdata_picasaweb_service_query_files (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataQuery *query,
+ GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+#include <gdata/services/picasaweb/gdata-picasaweb-file.h>
+
+GDataPicasaWebFile *gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file,
+ GFile *actual_file, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+/* TODO: async version */
+
+G_END_DECLS
+
+#endif /* !GDATA_PICASAWEB_SERVICE_H */
diff --git a/gdata/tests/Makefile.am b/gdata/tests/Makefile.am
index b6e647f0..7db39922 100644
--- a/gdata/tests/Makefile.am
+++ b/gdata/tests/Makefile.am
@@ -29,6 +29,9 @@ calendar_SOURCES = calendar.c $(TEST_SRCS)
TEST_PROGS += contacts
contacts_SOURCES = contacts.c $(TEST_SRCS)
+TEST_PROGS += picasaweb
+picasaweb_SOURCES = picasaweb.c $(TEST_SRCS)
+
TEST_PROGS += memory
memory_SOURCES = memory.c $(TEST_SRCS)
diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c
new file mode 100644
index 00000000..d31edc9b
--- /dev/null
+++ b/gdata/tests/picasaweb.c
@@ -0,0 +1,554 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Richard Schwarting 2009 <aquarichy@gmail.com>
+ * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdata.h"
+#include "common.h"
+
+#define PW_USERNAME "libgdata.picasaweb@gmail.com"
+/* the following two properties will change if a new album is added */
+#define NUM_ALBUMS 3
+#define TEST_ALBUM_INDEX 2
+
+/* TODO: probably a better way to do this; some kind of data associated with the test suite? */
+static GDataService *service = NULL;
+static GMainLoop *main_loop = NULL;
+
+static void
+test_authentication (void)
+{
+ gboolean retval;
+ GError *error = NULL;
+
+ /* Create a service */
+ service = GDATA_SERVICE (gdata_picasaweb_service_new (CLIENT_ID));
+
+ g_assert (service != NULL);
+ g_assert (GDATA_IS_SERVICE (service));
+ g_assert_cmpstr (gdata_service_get_client_id (service), ==, CLIENT_ID);
+
+ /* Log in */
+ retval = gdata_service_authenticate (service, PW_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 (service) == TRUE);
+ g_assert_cmpstr (gdata_service_get_username (service), ==, PW_USERNAME);
+ g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+}
+
+static void
+test_authentication_async_cb (GDataService *service, GAsyncResult *async_result, gpointer user_data)
+{
+ gboolean retval;
+ GError *error = NULL;
+
+ retval = gdata_service_authenticate_finish (service, async_result, &error);
+ g_assert_no_error (error);
+ g_assert (retval == TRUE);
+ g_clear_error (&error);
+
+ g_main_loop_quit (main_loop);
+
+ /* Check all is as it should be */
+ g_assert (gdata_service_is_authenticated (service) == TRUE);
+ g_assert_cmpstr (gdata_service_get_username (service), ==, PW_USERNAME);
+ g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+}
+
+
+static void
+test_authentication_async (void)
+{
+ /* Create a service */
+ service = GDATA_SERVICE (gdata_picasaweb_service_new (CLIENT_ID));
+
+ g_assert (service != NULL);
+ g_assert (GDATA_IS_SERVICE (service));
+
+ gdata_service_authenticate_async (service, PW_USERNAME, PASSWORD, NULL, (GAsyncReadyCallback) test_authentication_async_cb, NULL);
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (main_loop);
+ g_main_loop_unref (main_loop);
+}
+
+static void
+test_upload_simple (void)
+{
+ GDataCategory *category;
+ GDataPicasaWebFile *photo;
+ GDataPicasaWebFile *photo_new;
+ GFile *photo_file;
+ gchar *xml;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ photo = gdata_picasaweb_file_new (NULL);
+
+ gdata_entry_set_title (GDATA_ENTRY (photo), "Photo Entry Title");
+ gdata_picasaweb_file_set_caption (photo, "Photo Summary");
+ /* TODO: Have it add this category automatically? Same for GDataCalendarEvent */
+ category = gdata_category_new ("http://schemas.google.com/photos/2007#photo", "http://schemas.google.com/g/2005#kind", NULL);
+ gdata_entry_add_category (GDATA_ENTRY (photo), category);
+
+ /* Check the XML */
+ xml = gdata_entry_get_xml (GDATA_ENTRY (photo));
+ g_assert_cmpstr (xml, ==,
+ "<entry "
+ "xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gphoto='http://schemas.google.com/photos/2007' "
+ "xmlns:media='http://video.search.yahoo.com/mrss' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:app='http://www.w3.org/2007/app'>"
+ "<title type='text'>Photo Entry Title</title>"
+ "<summary type='text'>Photo Summary</summary>"
+ "<category term='http://schemas.google.com/photos/2007#photo' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gphoto:position>0.000000</gphoto:position>"
+ "<gphoto:commentingEnabled>true</gphoto:commentingEnabled>"
+ "<media:group>"
+ "<media:title type='plain'>Photo Entry Title</media:title>"
+ "<media:description type='plain'>Photo Summary</media:description>"
+ "</media:group>"
+ "</entry>");
+ g_free(xml);
+
+ /* File is public domain: http://en.wikipedia.org/wiki/File:German_garden_gnome_cropped.jpg */
+ photo_file = g_file_new_for_path ("photo.jpg"); // TODO make sure file is loaded from tests/ directory
+
+ /* Upload the photo */
+ /* TODO right now, it will just go to the default album, we want an uploading one :| */
+ photo_new = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), NULL, photo, photo_file, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_PICASAWEB_FILE (photo_new));
+ g_clear_error (&error);
+
+ /* TODO: check entries and feed properties */
+
+ g_object_unref (photo);
+ g_object_unref (photo_new);
+ g_object_unref (photo_file);
+}
+
+
+static void
+test_photo (void)
+{
+ GError *error = NULL;
+ GDataFeed *album_feed;
+ GDataFeed *photo_feed;
+ GList *albums;
+ GList *files;
+ GDataEntry *album_entry;
+ GDataEntry *photo_entry;
+ GDataPicasaWebAlbum *album;
+ GDataPicasaWebFile *photo;
+ GList *list;
+ GDataMediaContent *content;
+ GDataMediaThumbnail *thumbnail;
+ GTimeVal _time;
+ gchar *str;
+
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ albums = gdata_feed_get_entries (album_feed);
+ album_entry = GDATA_ENTRY (g_list_nth_data (albums, TEST_ALBUM_INDEX));
+ album = GDATA_PICASAWEB_ALBUM (album_entry);
+
+ photo_feed = gdata_picasaweb_service_query_files (GDATA_PICASAWEB_SERVICE (service), GDATA_PICASAWEB_ALBUM (album), NULL, NULL, NULL,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (photo_feed));
+ g_clear_error (&error);
+
+ files = gdata_feed_get_entries (photo_feed);
+ photo_entry = GDATA_ENTRY (g_list_nth_data (files, 0));
+ photo = GDATA_PICASAWEB_FILE (photo_entry);
+
+ gdata_picasaweb_file_get_edited (photo, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T06:57:03.474000Z");
+ g_free (str);
+
+ /* tests */
+
+ g_assert_cmpstr (gdata_picasaweb_file_get_caption (photo), ==, "Ginger cookie caption");
+ g_assert_cmpstr (gdata_picasaweb_file_get_version (photo), ==, "29"); /* 1240729023474000"); */ /* TODO check how constant this even is */
+ g_assert_cmpfloat (gdata_picasaweb_file_get_position (photo), ==, 0.0);
+ g_assert_cmpstr (gdata_picasaweb_file_get_album_id (photo), ==, "5328889949261497249");
+ g_assert_cmpuint (gdata_picasaweb_file_get_width (photo), ==, 2576);
+ g_assert_cmpuint (gdata_picasaweb_file_get_height (photo), ==, 1932);
+ g_assert_cmpuint (gdata_picasaweb_file_get_size (photo), ==, 1124730);
+ // TODO: file wasn't uploaded with client assigned; g_assert_cmpstr (gdata_picasaweb_file_get_client (photo), ==, ??);
+ // TODO: file wasn't uploaded with checksum assigned; g_assert_cmpstr (gdata_picasaweb_file_get_checksum (photo), ==, ??);
+
+ gdata_picasaweb_file_get_timestamp (photo, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2008-12-06T18:32:10Z");
+ g_free (str);
+
+ g_assert_cmpstr (gdata_picasaweb_file_get_video_status (photo), ==, NULL);
+ /* todo: not a good test of video status; want to upload a video for it */
+ g_assert_cmpuint (gdata_picasaweb_file_is_commenting_enabled (photo), ==, TRUE);
+ g_assert_cmpuint (gdata_picasaweb_file_get_comment_count (photo), ==, 1);
+ g_assert_cmpuint (gdata_picasaweb_file_get_rotation (photo), ==, 0);
+
+ g_assert_cmpstr (gdata_picasaweb_file_get_caption (photo), ==, "Ginger cookie caption");
+ g_assert_cmpstr (gdata_picasaweb_file_get_tags (photo), ==, "cookies");
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (photo)), ==, "100_0269.jpg");
+
+ g_assert_cmpstr (gdata_picasaweb_file_get_credit (photo), ==, "libgdata.picasaweb");
+
+ /* TODO put up warning not to free this, if we are going to be passing them our own internal copy! */
+ list = gdata_picasaweb_file_get_contents (photo);
+
+ g_assert_cmpuint (g_list_length (list), ==, 1);
+ content = GDATA_MEDIA_CONTENT (list->data);
+ g_assert_cmpstr (gdata_media_content_get_uri (content), ==,
+ "http://lh3.ggpht.com/_1kdcGyvOb8c/SfQFWPnuovI/AAAAAAAAAB0/MI0L4Sd11Eg/100_0269.jpg");
+ g_assert_cmpstr (gdata_media_content_get_content_type (content), ==, "image/jpeg");
+ g_assert_cmpuint (gdata_media_content_is_default (content), ==, FALSE);
+ g_assert_cmpint (gdata_media_content_get_duration (content), ==, 0); /* doesn't apply to photos, but let's sanity-check it */
+
+ list = gdata_picasaweb_file_get_thumbnails (photo);
+
+ g_assert_cmpuint (g_list_length (list), ==, 3);
+ thumbnail = GDATA_MEDIA_THUMBNAIL (list->data);
+ g_assert_cmpstr (gdata_media_thumbnail_get_uri (thumbnail), ==,
+ "http://lh3.ggpht.com/_1kdcGyvOb8c/SfQFWPnuovI/AAAAAAAAAB0/MI0L4Sd11Eg/s288/100_0269.jpg");
+ g_assert_cmpuint (gdata_media_thumbnail_get_width (thumbnail), ==, 288);
+ g_assert_cmpuint (gdata_media_thumbnail_get_height (thumbnail), ==, 216);
+ /* TODO consider testing time, gint64 */
+}
+
+static void
+test_photo_feed_entry (void)
+{
+ GDataFeed *album_feed;
+ GDataFeed *photo_feed;
+ GError *error = NULL;
+ GDataEntry *entry;
+ GDataPicasaWebAlbum *album;
+ GList *albums;
+ GList *files;
+ GDataEntry *photo_entry;
+ gchar *str;
+ GTimeVal _time;
+
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ albums = gdata_feed_get_entries (album_feed);
+ entry = GDATA_ENTRY (g_list_nth_data (albums, TEST_ALBUM_INDEX));
+ album = GDATA_PICASAWEB_ALBUM (entry);
+
+ photo_feed = gdata_picasaweb_service_query_files (GDATA_PICASAWEB_SERVICE (service), GDATA_PICASAWEB_ALBUM (album), NULL, NULL, NULL,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (photo_feed));
+ g_clear_error (&error);
+
+ files = gdata_feed_get_entries (photo_feed);
+ photo_entry = GDATA_ENTRY (g_list_nth_data (files, 0));
+
+ /* tests */
+
+ g_assert_cmpuint (g_list_length (files), ==, 1);
+
+ g_assert_cmpstr (gdata_entry_get_title (photo_entry), ==, "100_0269.jpg");
+ g_assert_cmpstr (gdata_entry_get_id (photo_entry), ==, "5328890138794566386");
+ g_assert_cmpstr (gdata_entry_get_etag (photo_entry), !=, NULL);
+
+ gdata_entry_get_updated (photo_entry, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T06:57:03.474000Z");
+ g_free (str);
+
+ gdata_entry_get_published (photo_entry, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T06:55:20Z");
+ g_free (str);
+
+ g_assert_cmpstr (gdata_entry_get_content (photo_entry), ==,
+ "http://lh3.ggpht.com/_1kdcGyvOb8c/SfQFWPnuovI/AAAAAAAAAB0/MI0L4Sd11Eg/100_0269.jpg");
+ g_assert_cmpstr (gdata_entry_get_xml (photo_entry), !=, NULL);
+ g_assert_cmpuint (strlen (gdata_entry_get_xml (photo_entry)), >, 0);
+}
+
+static void
+test_photo_feed (void)
+{
+ GError *error = NULL;
+ GDataFeed *album_feed;
+ GDataFeed *photo_feed;
+ GDataEntry *entry;
+ GDataPicasaWebAlbum *album;
+ GList *albums;
+
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ albums = gdata_feed_get_entries (album_feed);
+ entry = GDATA_ENTRY (g_list_nth_data (albums, TEST_ALBUM_INDEX));
+ album = GDATA_PICASAWEB_ALBUM (entry);
+
+ /* tests */
+
+ photo_feed = gdata_picasaweb_service_query_files (GDATA_PICASAWEB_SERVICE (service), GDATA_PICASAWEB_ALBUM (album), NULL, NULL, NULL,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (photo_feed));
+ g_clear_error (&error);
+
+ g_assert_cmpstr (gdata_feed_get_title (photo_feed), ==, "Test Album 1 - Venice - Public");
+ g_assert_cmpstr (gdata_feed_get_id (photo_feed), ==,
+ "http://picasaweb.google.com/data/feed/user/libgdata.picasaweb/albumid/5328889949261497249");
+ g_assert_cmpstr (gdata_feed_get_etag (photo_feed), !=, NULL);
+ g_assert_cmpuint (gdata_feed_get_items_per_page (photo_feed), ==, 1000);
+ g_assert_cmpuint (gdata_feed_get_start_index (photo_feed), ==, 1);
+ g_assert_cmpuint (gdata_feed_get_total_results (photo_feed), ==, 1);
+}
+
+static void
+test_album (void)
+{
+ GDataFeed *album_feed;
+ GError *error = NULL;
+ GDataPicasaWebAlbum *album;
+ GList *albums;
+ GTimeVal _time;
+ gchar *str;
+
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ albums = gdata_feed_get_entries (album_feed);
+ album = GDATA_PICASAWEB_ALBUM (g_list_nth_data (albums, TEST_ALBUM_INDEX));
+
+ /* Tests */
+ g_assert_cmpstr (gdata_picasaweb_album_get_user (album), ==, "libgdata.picasaweb");
+ g_assert_cmpstr (gdata_picasaweb_album_get_nickname (album), ==, "libgdata.picasaweb");
+
+ gdata_picasaweb_album_get_edited (album, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T06:57:03.474000Z");
+ g_free (str);
+
+ g_assert_cmpstr (gdata_picasaweb_album_get_description (album), ==, "This is the test description. This album should be in Venice.");
+ g_assert_cmpint (gdata_picasaweb_album_get_visibility (album), ==, GDATA_PICASAWEB_PUBLIC);
+ /* Google doesn't seem to be returning this one any more */
+ /*g_assert_cmpstr (gdata_picasaweb_album_get_name (album), ==, "TestAlbum1VenicePublic");*/
+ g_assert_cmpstr (gdata_picasaweb_album_get_location (album), ==, "Venice");
+
+ gdata_picasaweb_album_get_timestamp (album, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T07:00:00Z");
+ g_free (str);
+
+ g_assert_cmpuint (gdata_picasaweb_album_get_num_photos (album), ==, 1);
+ g_assert_cmpuint (gdata_picasaweb_album_get_num_photos_remaining (album), ==, 499);
+ g_assert_cmpuint (gdata_picasaweb_album_get_bytes_used (album), ==, 1124730);
+}
+
+static void
+test_album_feed_entry (void)
+{
+ GDataFeed *album_feed;
+ GError *error = NULL;
+ GDataEntry *entry;
+ GList *albums;
+ gchar *str, *xml;
+ GTimeVal _time;
+
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ albums = gdata_feed_get_entries (album_feed);
+ g_assert_cmpuint (g_list_length (albums), ==, NUM_ALBUMS);
+
+ entry = GDATA_ENTRY (g_list_nth_data (albums, TEST_ALBUM_INDEX));
+ g_assert (entry != NULL);
+
+ g_object_ref (entry);
+ g_object_unref (album_feed);
+
+ /* Tests */
+ g_assert_cmpstr (gdata_entry_get_title (entry), ==, "Test Album 1 - Venice - Public");
+ g_assert_cmpstr (gdata_entry_get_id (entry), ==, "5328889949261497249");
+ g_assert_cmpstr (gdata_entry_get_etag (entry), !=, NULL);
+
+ gdata_entry_get_updated (entry, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T06:57:03.474000Z");
+ g_free (str);
+
+ gdata_entry_get_published (entry, &_time);
+ str = g_time_val_to_iso8601 (&_time);
+ g_assert_cmpstr (str, ==, "2009-04-26T07:00:00Z");
+ g_free (str);
+
+ // g_assert_cmpstr (gdata_entry_get_content (entry), !=, NULL);
+ /* TODO */
+ printf("** WARNING:%s:%d: gdata_entry_get_content(entry) returns null; valid?\n", __FILE__, __LINE__);
+ xml = gdata_entry_get_xml (entry);
+ g_assert_cmpstr (xml, !=, NULL);
+ g_assert_cmpuint (strlen (xml), >, 0);
+ g_free (xml);
+
+ g_object_unref (entry);
+}
+
+static void
+test_album_feed (void)
+{
+ GDataFeed *album_feed;
+ GError *error = NULL;
+
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ /* tests */
+
+ g_assert_cmpstr (gdata_feed_get_title (album_feed), ==, "libgdata.picasaweb");
+ // TODO find out why subtitle == null when returned: no subtitle for feed? // printf("feed subtitle: %s\n", gdata_feed_get_subtitle(feed));
+ g_assert_cmpstr (gdata_feed_get_id (album_feed), ==, "http://picasaweb.google.com/data/feed/user/libgdata.picasaweb");
+ g_assert_cmpstr (gdata_feed_get_etag (album_feed), !=, NULL); /* this varies as albums change, like when a new image is uploaded in our test! */
+ g_assert_cmpuint (gdata_feed_get_items_per_page (album_feed), ==, 1000);
+ g_assert_cmpuint (gdata_feed_get_start_index (album_feed), ==, 1);
+ g_assert_cmpuint (gdata_feed_get_total_results (album_feed), ==, NUM_ALBUMS);
+}
+
+static void
+test_query_all_albums (void)
+{
+ GDataFeed *album_feed;
+ GDataFeed *photo_feed;
+ GError *error = NULL;
+ GList *albums;
+ GDataEntry *entry;
+ GDataPicasaWebAlbum *album;
+
+ g_assert (service != NULL);
+
+ /* TODO: find out whether I need to free this; probably */
+ album_feed = gdata_picasaweb_service_query_all_albums (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (album_feed));
+ g_clear_error (&error);
+
+ albums = gdata_feed_get_entries (album_feed);
+ /* g_object_unref (feed); TODO find out why this complains about not being an object */
+ entry = GDATA_ENTRY (g_list_nth_data (albums, TEST_ALBUM_INDEX));
+ album = GDATA_PICASAWEB_ALBUM (entry);
+
+ photo_feed = gdata_picasaweb_service_query_files (GDATA_PICASAWEB_SERVICE (service), GDATA_PICASAWEB_ALBUM (album), NULL, NULL, NULL,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (photo_feed));
+ g_clear_error (&error);
+
+ //g_object_unref(photo_feed);
+ //g_object_unref(album_feed);
+
+ g_list_free(albums);
+}
+
+static void
+test_query_all_albums_async_cb (GDataService *service, GAsyncResult *async_result, gpointer user_data)
+{
+ GDataFeed *feed;
+ GError *error;
+
+ feed = gdata_service_query_finish (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_albums_async (void)
+{
+ g_assert (service != NULL);
+
+ gdata_picasaweb_service_query_all_albums_async (GDATA_PICASAWEB_SERVICE (service), NULL, NULL, NULL, NULL,
+ NULL, (GAsyncReadyCallback) test_query_all_albums_async_cb, NULL);
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (main_loop);
+ g_main_loop_unref (main_loop);
+}
+
+/* TODO: test private, public albums, test uploading */
+
+int
+main (int argc, char *argv[])
+{
+ 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=");
+
+ g_test_add_func ("/picasaweb/authentication", test_authentication);
+ if (g_test_thorough () == TRUE)
+ g_test_add_func ("/picasaweb/authentication_async", test_authentication_async);
+ g_test_add_func ("/picasaweb/query/all_albums", test_query_all_albums);
+ if (g_test_thorough () == TRUE)
+ g_test_add_func ("/picasaweb/query/all_albums_async", test_query_all_albums_async);
+ g_test_add_func ("/picasaweb/query/album_feed", test_album_feed);
+ g_test_add_func ("/picasaweb/query/album_feed_entry", test_album_feed_entry);
+ g_test_add_func ("/picasaweb/query/album", test_album);
+ g_test_add_func ("/picasaweb/query/photo_feed", test_photo_feed);
+ g_test_add_func ("/picasaweb/query/photo_feed_entry", test_photo_feed_entry);
+ g_test_add_func ("/picasaweb/query/photo", test_photo);
+ g_test_add_func ("/picasaweb/upload/photo", test_upload_simple);
+
+ retval = g_test_run ();
+ if (service != NULL)
+ g_object_unref (service);
+
+ return retval;
+}
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 56a24700..5a29e9e5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,5 +11,6 @@ gdata/services/calendar/gdata-calendar-calendar.c
gdata/services/calendar/gdata-calendar-event.c
gdata/services/calendar/gdata-calendar-service.c
gdata/services/contacts/gdata-contacts-service.c
+gdata/services/picasaweb/gdata-picasaweb-service.c
gdata/services/youtube/gdata-youtube-service.c
gdata/services/youtube/gdata-youtube-video.c