diff options
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, ×tamp); + } 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, ×tamp); + } 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 |