[libsocialweb] photobucket: Added service.
- From: Eitan Isaacson <eitani src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsocialweb] photobucket: Added service.
- Date: Mon, 7 Mar 2011 22:10:36 +0000 (UTC)
commit ec31519d7c6c385c2e42348e5338263443b81c23
Author: Eitan Isaacson <eitan isaacson collabora co uk>
Date: Thu Feb 24 17:28:14 2011 -0800
photobucket: Added service.
configure.ac | 2 +
libsocialweb/sw-debug.c | 1 +
libsocialweb/sw-debug.h | 5 +-
services/Makefile.am | 4 +
services/photobucket/Makefile.am | 20 +
services/photobucket/module.c | 32 +
services/photobucket/photobucket.c | 916 ++++++++++++++++++++++++++++++
services/photobucket/photobucket.h | 62 ++
services/photobucket/photobucket.keys.in | 11 +
9 files changed, 1051 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0d07b24..6d1900f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,7 @@ AC_ARG_ENABLE([all-services],[AS_HELP_STRING([--enable-all-services], [enable ev
SOCIALWEB_ENABLE_SERVICE(Facebook, facebook, FACEBOOK)
SOCIALWEB_ENABLE_SERVICE(Flickr, flickr, FLICKR)
SOCIALWEB_ENABLE_SERVICE(Last.fm, lastfm, LASTFM)
+SOCIALWEB_ENABLE_SERVICE(Photobucket, photobucket, PHOTOBUCKET)
SOCIALWEB_ENABLE_SERVICE(Plurk, plurk, PLURK)
SOCIALWEB_ENABLE_SERVICE(SmugMug, smugmug, SMUGMUG)
SOCIALWEB_ENABLE_SERVICE(Twitter, twitter, TWITTER)
@@ -188,6 +189,7 @@ AC_OUTPUT([
services/facebook/Makefile
services/flickr/Makefile
services/lastfm/Makefile
+ services/photobucket/Makefile
services/plurk/Makefile
services/smugmug/Makefile
services/twitter/Makefile
diff --git a/libsocialweb/sw-debug.c b/libsocialweb/sw-debug.c
index 6480bd7..365ac46 100644
--- a/libsocialweb/sw-debug.c
+++ b/libsocialweb/sw-debug.c
@@ -38,6 +38,7 @@ sw_debug_init (const char *string)
{ "vimeo", SW_DEBUG_VIMEO },
{ "flickr", SW_DEBUG_FLICKR },
{ "smugmug", SW_DEBUG_SMUGMUG },
+ { "photobucket", SW_DEBUG_PHOTOBUCKET },
{ "client-monitor", SW_DEBUG_CLIENT_MONITOR }
};
diff --git a/libsocialweb/sw-debug.h b/libsocialweb/sw-debug.h
index a9f8b4f..79b5e4d 100644
--- a/libsocialweb/sw-debug.h
+++ b/libsocialweb/sw-debug.h
@@ -28,8 +28,9 @@ typedef enum {
SW_DEBUG_CORE = 1 << 6,
SW_DEBUG_VIMEO = 1 << 7,
SW_DEBUG_FLICKR = 1 << 8,
- SW_DEBUG_SMUGMUG = 1 << 7,
- SW_DEBUG_CLIENT_MONITOR = 1 << 9
+ SW_DEBUG_SMUGMUG = 1 << 9,
+ SW_DEBUG_PHOTOBUCKET = 1 << 10,
+ SW_DEBUG_CLIENT_MONITOR = 1 << 11
} SwDebugFlags;
extern guint sw_debug_flags;
diff --git a/services/Makefile.am b/services/Makefile.am
index acfd04b..e8377ae 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -12,6 +12,10 @@ if WITH_LASTFM
SUBDIRS += lastfm
endif
+if WITH_PHOTOBUCKET
+SUBDIRS += photobucket
+endif
+
if WITH_PLURK
SUBDIRS += plurk
endif
diff --git a/services/photobucket/Makefile.am b/services/photobucket/Makefile.am
new file mode 100644
index 0000000..55743a4
--- /dev/null
+++ b/services/photobucket/Makefile.am
@@ -0,0 +1,20 @@
+services_LTLIBRARIES = libphotobucket.la
+libphotobucket_la_SOURCES = module.c \
+ photobucket.c photobucket.h
+
+libphotobucket_la_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/interfaces \
+ $(SOUP_CFLAGS) $(REST_CFLAGS) $(KEYRING_CFLAGS) \
+ $(DBUS_GLIB_CFLAGS) -DG_LOG_DOMAIN=\"Photobucket\" \
+ $(GCOV_CFLAGS)
+libphotobucket_la_LIBADD = $(top_builddir)/libsocialweb/libsocialweb.la \
+ $(top_builddir)/interfaces/libsocialweb-ginterfaces.la \
+ $(top_builddir)/libsocialweb-keystore/libsocialweb-keystore.la \
+ $(top_builddir)/libsocialweb-keyfob/libsocialweb-keyfob.la \
+ $(SOUP_LIBS) $(REST_LIBS) $(KEYRING_LIBS) $(DBUS_GLIB_LIBS) \
+ $(GCOV_LDFLAGS)
+libphotobucket_la_LDFLAGS = -module -avoid-version
+
+servicesdata_DATA = photobucket.keys
+CLEANFILES = photobucket.keys
+EXTRA_DIST = photobucket.keys.in
+ INTLTOOL_SOCIALWEB_KEYS@
diff --git a/services/photobucket/module.c b/services/photobucket/module.c
new file mode 100644
index 0000000..f64148e
--- /dev/null
+++ b/services/photobucket/module.c
@@ -0,0 +1,32 @@
+/*
+ * libsocialweb - social data store
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <libsocialweb/sw-module.h>
+#include "photobucket.h"
+
+const gchar *
+sw_module_get_name (void)
+{
+ return "photobucket";
+}
+
+const GType
+sw_module_get_type (void)
+{
+ return SW_TYPE_SERVICE_PHOTOBUCKET;
+}
diff --git a/services/photobucket/photobucket.c b/services/photobucket/photobucket.c
new file mode 100644
index 0000000..1eee466
--- /dev/null
+++ b/services/photobucket/photobucket.c
@@ -0,0 +1,916 @@
+/*
+ * libsocialweb Photobucket service support
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * Authors: Eitan Isaacson <eitan isaacson collabora co uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <gnome-keyring.h>
+
+#include <libsocialweb/sw-service.h>
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-web.h>
+#include <libsocialweb/sw-call-list.h>
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb/sw-client-monitor.h>
+#include <libsocialweb/sw-online.h>
+#include <libsocialweb-keyfob/sw-keyfob.h>
+#include <libsocialweb-keystore/sw-keystore.h>
+
+#include <rest/oauth-proxy.h>
+#include <rest/rest-xml-parser.h>
+
+#include <interfaces/sw-collections-ginterface.h>
+#include <interfaces/sw-photo-upload-ginterface.h>
+#include <interfaces/sw-video-upload-ginterface.h>
+
+#include "photobucket.h"
+
+static void collections_iface_init (gpointer g_iface, gpointer iface_data);
+static void photo_upload_iface_init (gpointer g_iface, gpointer iface_data);
+static void video_upload_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SwServicePhotobucket, sw_service_photobucket, SW_TYPE_SERVICE,
+ G_IMPLEMENT_INTERFACE (SW_TYPE_COLLECTIONS_IFACE,
+ collections_iface_init)
+ G_IMPLEMENT_INTERFACE (SW_TYPE_PHOTO_UPLOAD_IFACE,
+ photo_upload_iface_init)
+ G_IMPLEMENT_INTERFACE (SW_TYPE_VIDEO_UPLOAD_IFACE,
+ video_upload_iface_init));
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_SERVICE_PHOTOBUCKET, SwServicePhotobucketPrivate))
+
+#define ALBUM_PREFIX "photobucket-"
+#define ENTRYPOINT_URL "http://api.photobucket.com/"
+
+static const ParameterNameMapping upload_params[] = {
+ { "title", "title" },
+ { "description", "description" },
+ { "x-photobucket-scramble", "scramble" },
+ { "x-photobucket-degrees", "degrees" },
+ { "x-photobucket-size", "size" },
+ { "x-photobucket-read-geo-exif", "read_geo_exif" },
+ { "x-photobucket-latitude", "latitude" },
+ { "x-photobucket-longitude", "longitude" },
+ { "x-photobucket-altitude", "altitude" },
+ { "x-photobucket-compass", "compass" },
+ { "x-photobucket-gps-timestamp", "gps_timestamp" },
+ { NULL, NULL }
+};
+
+
+struct _SwServicePhotobucketPrivate {
+ RestProxy *proxy;
+ RestProxy *silo_proxy;
+ gchar *uid;
+ gboolean configured;
+};
+
+enum {
+ COLLECTION = 1,
+ PHOTO = 2,
+ VIDEO = 4
+} typedef MediaType;
+
+static const char *
+get_name (SwService *service)
+{
+ return "photobucket";
+}
+
+static const char **
+get_static_caps (SwService *service)
+{
+ static const char * caps[] = {
+ HAS_PHOTO_UPLOAD_IFACE,
+ HAS_VIDEO_UPLOAD_IFACE,
+ HAS_BANISHABLE_IFACE,
+
+ NULL
+ };
+
+ return caps;
+}
+
+static const char **
+get_dynamic_caps (SwService *service)
+{
+ SwServicePhotobucketPrivate *priv = GET_PRIVATE (service);
+ static const char *configured_caps[] = {
+ IS_CONFIGURED,
+ NULL
+ };
+
+ static const char *authorized_caps[] = {
+ IS_CONFIGURED,
+ CREDENTIALS_VALID,
+ NULL
+ };
+
+ static const char *no_caps[] = { NULL };
+
+ if (priv->uid != NULL)
+ return authorized_caps;
+ else if (priv->configured)
+ return configured_caps;
+ else
+ return no_caps;
+}
+
+static RestXmlNode *
+node_from_call (RestProxyCall *call, GError **error)
+{
+ static RestXmlParser *parser = NULL;
+ RestXmlNode *node;
+ RestXmlNode *status_node;
+ const gchar *status_str = g_intern_string ("status");
+
+ if (call == NULL)
+ return NULL;
+
+ if (parser == NULL)
+ parser = rest_xml_parser_new ();
+
+ if (!SOUP_STATUS_IS_SUCCESSFUL (rest_proxy_call_get_status_code (call))) {
+ g_set_error (error, SW_SERVICE_ERROR, SW_SERVICE_ERROR_REMOTE_ERROR,
+ "HTTP error: %s (%d)",
+ rest_proxy_call_get_status_message (call),
+ rest_proxy_call_get_status_code (call));
+ return NULL;
+ }
+
+ node = rest_xml_parser_parse_from_data (parser,
+ rest_proxy_call_get_payload (call),
+ rest_proxy_call_get_payload_length (call));
+
+ /* Invalid XML, or incorrect root */
+ if (node == NULL || !g_str_equal (node->name, "response") ||
+ g_hash_table_lookup (node->children, status_str) == NULL) {
+ g_set_error (error, SW_SERVICE_ERROR, SW_SERVICE_ERROR_REMOTE_ERROR,
+ "malformed remote response: %s",
+ rest_proxy_call_get_payload (call));
+ if (node)
+ rest_xml_node_unref (node);
+ return NULL;
+ }
+
+ status_node = g_hash_table_lookup (node->children, status_str);
+
+ if (g_strcmp0 (status_node->content, "OK") != 0) {
+ RestXmlNode *msg = rest_xml_node_find (node, "message");
+ g_set_error (error, SW_SERVICE_ERROR, SW_SERVICE_ERROR_REMOTE_ERROR,
+ "remote Photobucket error: %s", msg->content);
+ rest_xml_node_unref (node);
+ return NULL;
+ }
+
+ return node;
+}
+
+static void
+_check_access_token_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ RestXmlNode *root;
+ GError *err = NULL;
+ SwServicePhotobucket *self = SW_SERVICE_PHOTOBUCKET (weak_object);
+ SwService *service = SW_SERVICE (self);
+ SwServicePhotobucketPrivate *priv = self->priv;
+
+ g_free (priv->uid);
+ priv->uid = NULL;
+
+ root = node_from_call (call, &err);
+
+ if (err != NULL) {
+ SW_DEBUG (PHOTOBUCKET, "Invalid access token: %s", err->message);
+ g_error_free (err);
+ return;
+ }
+
+ if (root != NULL) {
+ RestXmlNode *api = rest_xml_node_find(root, "api");
+ RestXmlNode *username = rest_xml_node_find(root, "username");
+
+ if (api != NULL) {
+ SW_DEBUG (PHOTOBUCKET, "silo subdomain: %s", api->content);
+ rest_proxy_bind (priv->silo_proxy, api->content);
+ } else {
+ g_warning ("no silo subdomain");
+ }
+
+ if (username != NULL)
+ priv->uid = g_strdup (username->content);
+ else
+ g_warning ("no username");
+ }
+
+ sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+}
+
+static void
+got_tokens_cb (RestProxy *proxy, gboolean got_tokens, gpointer user_data)
+{
+ SwServicePhotobucket *self = (SwServicePhotobucket *) user_data;
+ SwService *service = SW_SERVICE (self);
+ SwServicePhotobucketPrivate *priv = self->priv;
+
+ priv->configured = got_tokens;
+
+ SW_DEBUG (PHOTOBUCKET, "Got tokens: %s", got_tokens ? "yes" : "no");
+
+ if (got_tokens) {
+ RestProxyCall *call;
+ OAuthProxy *oauth_proxy = OAUTH_PROXY (priv->proxy);
+
+ oauth_proxy_set_token (OAUTH_PROXY (priv->silo_proxy),
+ oauth_proxy_get_token (oauth_proxy));
+ oauth_proxy_set_token_secret (OAUTH_PROXY (priv->silo_proxy),
+ oauth_proxy_get_token_secret (oauth_proxy));
+
+ call = rest_proxy_new_call (priv->proxy);
+ rest_proxy_call_set_function (call, "user/-/url");
+
+
+ rest_proxy_call_async (call,
+ _check_access_token_cb,
+ G_OBJECT (self),
+ NULL,
+ NULL);
+
+ g_object_unref (call);
+ }
+
+ sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+}
+
+static void
+online_notify (gboolean online, gpointer user_data)
+{
+ SwServicePhotobucket *self = (SwServicePhotobucket *) user_data;
+ SwService *service = SW_SERVICE (self);
+ SwServicePhotobucketPrivate *priv = self->priv;
+
+ if (online) {
+ sw_keyfob_oauth ((OAuthProxy *) priv->proxy, got_tokens_cb, self);
+ } else {
+ g_free (priv->uid);
+ priv->uid = NULL;
+ sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+ }
+}
+
+/*
+ * The credentials have been updated (or we're starting up), so fetch them from
+ * the keyring.
+ */
+static void
+refresh_credentials (SwServicePhotobucket *self)
+{
+ SwService *service = SW_SERVICE (self);
+ SwServicePhotobucketPrivate *priv = self->priv;
+
+ SW_DEBUG (PHOTOBUCKET, "Credentials updated");
+
+ priv->configured = FALSE;
+
+ sw_service_emit_user_changed (service);
+
+ online_notify (FALSE, service);
+ online_notify (TRUE, service);
+}
+
+/*
+ * SwService:credentials_updated vfunc implementation
+ */
+static void
+credentials_updated (SwService *service)
+{
+ refresh_credentials (SW_SERVICE_PHOTOBUCKET (service));
+}
+
+static void
+sw_service_photobucket_dispose (GObject *object)
+{
+ SwServicePhotobucketPrivate *priv = ((SwServicePhotobucket*)object)->priv;
+
+ if (priv->proxy) {
+ g_object_unref (priv->proxy);
+ priv->proxy = NULL;
+ }
+
+ if (priv->silo_proxy) {
+ g_object_unref (priv->silo_proxy);
+ priv->silo_proxy = NULL;
+ }
+
+ G_OBJECT_CLASS (sw_service_photobucket_parent_class)->dispose (object);
+}
+
+static gint
+_upload_file (SwServicePhotobucket *self,
+ MediaType upload_type,
+ const gchar *filename,
+ GHashTable *extra_fields,
+ RestProxyCallAsyncCallback upload_cb,
+ GError **error)
+{
+ SwServicePhotobucketPrivate *priv = self->priv;
+ RestProxyCall *call;
+ RestParam *param;
+ gchar *basename;
+ gchar *content_type;
+ GMappedFile *map;
+ gint opid = -1;
+ const gchar *collection_id;
+ const gchar *id;
+
+ g_return_val_if_fail (priv->silo_proxy != NULL, -1);
+
+ /* Open the file */
+ map = g_mapped_file_new (filename, FALSE, error);
+ if (*error != NULL) {
+ g_warning ("Error opening file %s: %s", filename, (*error)->message);
+ return -1;
+ }
+
+ /* Get the file information */
+ basename = g_path_get_basename (filename);
+ content_type = g_content_type_guess (
+ filename,
+ (const guchar*) g_mapped_file_get_contents (map),
+ g_mapped_file_get_length (map),
+ NULL);
+
+ call = rest_proxy_new_call (priv->silo_proxy);
+ rest_proxy_call_set_function (call, "album/!/upload");
+
+ collection_id = g_hash_table_lookup (extra_fields, "collection");
+
+ if (collection_id == NULL) {
+ id = priv->uid;
+ } else if (!g_str_has_prefix (collection_id, ALBUM_PREFIX)) {
+ g_set_error (error, SW_SERVICE_ERROR, SW_SERVICE_ERROR_NOT_SUPPORTED,
+ "collection (%s) must be in the format: %salbumid",
+ collection_id, ALBUM_PREFIX);
+ goto OUT;
+ } else {
+ id = collection_id + strlen (ALBUM_PREFIX);
+ }
+
+ rest_proxy_call_add_param (call, "id", id);
+ rest_proxy_call_add_param (call, "type",
+ (upload_type == VIDEO) ? "video" : "image");
+
+ sw_service_map_params (upload_params, extra_fields,
+ (SwServiceSetParamFunc) rest_proxy_call_add_param,
+ call);
+
+ param = rest_param_new_with_owner ("uploadfile",
+ g_mapped_file_get_contents (map),
+ g_mapped_file_get_length (map),
+ content_type,
+ basename,
+ map,
+ (GDestroyNotify) g_mapped_file_unref);
+
+ rest_proxy_call_add_param_full (call, param);
+
+ rest_proxy_call_set_method (call, "POST");
+
+ opid = sw_next_opid ();
+
+ SW_DEBUG (PHOTOBUCKET, "Uploading %s", basename);
+
+ rest_proxy_call_async (call,
+ upload_cb,
+ G_OBJECT (self),
+ GINT_TO_POINTER (opid),
+ NULL);
+
+OUT:
+ g_free (basename);
+ g_free (content_type);
+ g_object_unref (call);
+
+ return opid;
+}
+
+/* Collections Interface */
+
+static GValueArray *
+_extract_collection_details_from_xml (RestXmlNode *album)
+{
+ GValueArray *value_array;
+ GHashTable *attribs = g_hash_table_new (g_str_hash, g_str_equal);
+ GValue *value = NULL;
+ gint64 count = 0;
+ const gchar *id = rest_xml_node_get_attr (album, "name");
+ const gchar *title = rest_xml_node_get_attr (album, "title");
+ const gchar *photo_count = rest_xml_node_get_attr (album, "photo_count");
+ const gchar *video_count = rest_xml_node_get_attr (album, "video_count");
+ const gchar *thumb = rest_xml_node_get_attr (album, "thumb");
+ const gchar *privacy = rest_xml_node_get_attr (album, "privacy");
+
+ const gchar *name_sep = g_strrstr (id, "/");
+
+ value_array = g_value_array_new (5);
+
+ value_array = g_value_array_append (value_array, NULL);
+ value = g_value_array_get_nth (value_array, 0);
+ g_value_init (value, G_TYPE_STRING);
+ g_value_take_string (value, g_strdup_printf ("%s%s", ALBUM_PREFIX, id));
+
+ value_array = g_value_array_append (value_array, NULL);
+ value = g_value_array_get_nth (value_array, 1);
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_static_string (value, title);
+
+ value_array = g_value_array_append (value_array, NULL);
+ value = g_value_array_get_nth (value_array, 2);
+ g_value_init (value, G_TYPE_STRING);
+
+ if (g_strstr_len (id, name_sep - id, "/") == NULL) {
+ /* a "top-level" album */
+ g_value_set_static_string (value, "");
+ } else {
+ gchar *parent_id = g_strndup (id, name_sep - id);
+ g_value_take_string (value, g_strdup_printf ("%s%s", ALBUM_PREFIX,
+ parent_id));
+ g_free (parent_id);
+ }
+
+ value_array = g_value_array_append (value_array, NULL);
+ value = g_value_array_get_nth (value_array, 3);
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, PHOTO | VIDEO | COLLECTION);
+
+ if (photo_count != NULL) {
+ g_hash_table_insert (attribs, "x-photobucket-photo-count",
+ (gpointer) photo_count);
+ count = g_ascii_strtoll (photo_count, NULL, 10);
+ }
+
+ if (video_count != NULL) {
+ g_hash_table_insert (attribs, "x-photobucket-video-count",
+ (gpointer) video_count);
+ count = count + g_ascii_strtoll (video_count, NULL, 10);
+ }
+
+ value_array = g_value_array_append (value_array, NULL);
+ value = g_value_array_get_nth (value_array, 4);
+ g_value_init (value, G_TYPE_INT);
+ g_value_set_int (value, count);
+
+ if (thumb != NULL)
+ g_hash_table_insert (attribs, "x-photobucket-thumb", (gpointer) thumb);
+
+ if (privacy != NULL)
+ g_hash_table_insert (attribs, "x-photobucket-privacy", (gpointer) privacy);
+
+ value_array = g_value_array_append (value_array, NULL);
+ value = g_value_array_get_nth (value_array, 5);
+ g_value_init (value, dbus_g_type_get_map ("GHashTable",
+ G_TYPE_STRING,
+ G_TYPE_STRING));
+ g_value_take_boxed (value, attribs);
+
+ return value_array;
+}
+
+static void
+_list_albums_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+ RestXmlNode *root = NULL;
+ RestXmlNode *album;
+ GPtrArray *rv;
+ GError *err = NULL;
+
+ if (error != NULL)
+ err = g_error_new (SW_SERVICE_ERROR, SW_SERVICE_ERROR_REMOTE_ERROR,
+ "rest call failed: %s", error->message);
+
+ if (err == NULL)
+ root = node_from_call (call, &err);
+
+ if (err != NULL) {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ if (root != NULL)
+ rest_xml_node_unref (root);
+ return;
+ }
+
+ rv = g_ptr_array_new_with_free_func ((GDestroyNotify )g_value_array_free);
+
+ album = rest_xml_node_find (root, "album");
+
+ /* The albums are nested 1 in the top level album (user album) */
+ album = rest_xml_node_find (album, "album");
+
+ while (album != NULL) {
+ SW_DEBUG (PHOTOBUCKET, " name: %s",
+ rest_xml_node_get_attr (album, "name"));
+ GValueArray *collection_details =
+ _extract_collection_details_from_xml (album);
+ g_ptr_array_add (rv, collection_details);
+ album = album->next;
+ }
+
+ sw_collections_iface_return_from_get_list (context, rv);
+
+ g_ptr_array_free (rv, TRUE);
+ rest_xml_node_unref (root);
+}
+
+static void
+_photobucket_collections_get_list (SwCollectionsIface *self,
+ DBusGMethodInvocation *context)
+{
+ SwServicePhotobucket *photobucket = SW_SERVICE_PHOTOBUCKET (self);
+ SwServicePhotobucketPrivate *priv = photobucket->priv;
+ RestProxyCall *call;
+
+ g_return_if_fail (priv->silo_proxy != NULL);
+
+ call = rest_proxy_new_call (priv->silo_proxy);
+ rest_proxy_call_set_function (call, "album/!");
+
+ rest_proxy_call_add_param (call, "id", priv->uid);
+ rest_proxy_call_add_param (call, "recurse", "true");
+ rest_proxy_call_add_param (call, "view", "flat");
+ rest_proxy_call_add_param (call, "media", "none");
+
+
+ rest_proxy_call_async (call,
+ (RestProxyCallAsyncCallback) _list_albums_cb,
+ (GObject *) photobucket,
+ context,
+ NULL);
+
+ g_object_unref (call);
+}
+
+typedef struct {
+ DBusGMethodInvocation *dbus_context;
+ gchar *album_id;
+} PhotobucketCreateAlbumCtx;
+
+void
+_create_album_ctx_free (PhotobucketCreateAlbumCtx *ctx)
+{
+ g_free (ctx->album_id);
+ g_slice_free (PhotobucketCreateAlbumCtx, ctx);
+}
+
+static void
+_create_album_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ PhotobucketCreateAlbumCtx *ctx = (PhotobucketCreateAlbumCtx *) user_data;
+ RestXmlNode *root = NULL;
+ GError *err = NULL;
+
+ if (error != NULL)
+ err = g_error_new (SW_SERVICE_ERROR, SW_SERVICE_ERROR_REMOTE_ERROR,
+ "rest call failed: %s", error->message);
+
+ if (err == NULL)
+ root = node_from_call (call, &err);
+
+ if (err != NULL) {
+ dbus_g_method_return_error (ctx->dbus_context, err);
+ g_error_free (err);
+ if (root != NULL)
+ rest_xml_node_unref (root);
+ return;
+ }
+
+ sw_collections_iface_return_from_create (ctx->dbus_context, ctx->album_id);
+
+ _create_album_ctx_free (ctx);
+ rest_xml_node_unref (root);
+}
+
+static void
+_photobucket_collections_create (SwCollectionsIface *self,
+ const gchar *collection_name,
+ MediaType supported_types,
+ const gchar *collection_parent,
+ GHashTable *extra_parameters,
+ DBusGMethodInvocation *context)
+{
+ SwServicePhotobucket *photobucket = SW_SERVICE_PHOTOBUCKET (self);
+ SwServicePhotobucketPrivate *priv = photobucket->priv;
+ RestProxyCall *call;
+ PhotobucketCreateAlbumCtx *ctx;
+ const gchar *id;
+
+ g_return_if_fail (priv->silo_proxy != NULL);
+
+ if (g_strcmp0 (collection_parent, "") == 0) {
+ id = priv->uid;
+ } else if (!g_str_has_prefix (collection_parent, ALBUM_PREFIX)) {
+ GError *error = g_error_new (SW_SERVICE_ERROR,
+ SW_SERVICE_ERROR_NOT_SUPPORTED,
+ "Photobucket collection ID %s must start"
+ " with '%s'", collection_parent,
+ ALBUM_PREFIX);
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ } else {
+ id = collection_parent + strlen (ALBUM_PREFIX);
+ }
+
+ call = rest_proxy_new_call (priv->silo_proxy);
+ rest_proxy_call_set_function (call, "album/!");
+
+ rest_proxy_call_add_param (call, "id", id);
+ rest_proxy_call_add_param (call, "name", collection_name);
+ rest_proxy_call_set_method (call, "POST");
+
+ ctx = g_slice_new0 (PhotobucketCreateAlbumCtx);
+ ctx->dbus_context = context;
+ ctx->album_id = g_strdup_printf ("%s%s/%s", ALBUM_PREFIX, id,
+ collection_name);
+
+ rest_proxy_call_async (call,
+ (RestProxyCallAsyncCallback) _create_album_cb,
+ (GObject *) photobucket,
+ ctx,
+ NULL);
+
+ g_object_unref (call);
+}
+
+static void
+_get_album_details_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+ GValueArray *collection_details;
+ RestXmlNode *root = NULL;
+ RestXmlNode *album;
+ GError *err = NULL;
+
+ if (error != NULL)
+ err = g_error_new (SW_SERVICE_ERROR, SW_SERVICE_ERROR_REMOTE_ERROR,
+ "rest call failed: %s", error->message);
+
+ if (err == NULL)
+ root = node_from_call (call, &err);
+
+ if (err != NULL) {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ if (root != NULL)
+ rest_xml_node_unref (root);
+ return;
+ }
+
+ album = rest_xml_node_find (root, "album");
+
+ collection_details = _extract_collection_details_from_xml (album);
+
+ sw_collections_iface_return_from_get_details (context,
+ collection_details);
+
+ g_value_array_free (collection_details);
+
+ rest_xml_node_unref (root);
+}
+
+static void
+_photobucket_collections_get_details (SwCollectionsIface *self,
+ const gchar *collection_id,
+ DBusGMethodInvocation *context)
+{
+ SwServicePhotobucket *photobucket = SW_SERVICE_PHOTOBUCKET (self);
+ SwServicePhotobucketPrivate *priv = photobucket->priv;
+ RestProxyCall *call;
+
+ if (!g_str_has_prefix (collection_id, ALBUM_PREFIX))
+ {
+ GError *error = g_error_new (SW_SERVICE_ERROR,
+ SW_SERVICE_ERROR_NOT_SUPPORTED,
+ "Photobucket collection ID %s must start"
+ " with '%s'", collection_id, ALBUM_PREFIX);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (priv->silo_proxy != NULL);
+
+ call = rest_proxy_new_call (priv->silo_proxy);
+ rest_proxy_call_set_function (call, "album/!");
+
+ rest_proxy_call_add_param (call, "id",
+ collection_id + strlen (ALBUM_PREFIX));
+ rest_proxy_call_add_param (call, "media", "none");
+
+ rest_proxy_call_async (call,
+ (RestProxyCallAsyncCallback) _get_album_details_cb,
+ (GObject *) photobucket,
+ context,
+ NULL);
+
+ g_object_unref (call);
+}
+
+static void
+collections_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SwCollectionsIfaceClass *klass = (SwCollectionsIfaceClass *) g_iface;
+
+ sw_collections_iface_implement_get_list (klass,
+ _photobucket_collections_get_list);
+
+ sw_collections_iface_implement_create (klass,
+ _photobucket_collections_create);
+
+ sw_collections_iface_implement_get_details (klass,
+ _photobucket_collections_get_details);
+}
+
+/* Photo Upload Interface */
+
+static void
+_upload_photo_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ SwServicePhotobucket *self = SW_SERVICE_PHOTOBUCKET (weak_object);
+ int opid = GPOINTER_TO_INT (user_data);
+
+ if (error) {
+ sw_photo_upload_iface_emit_photo_upload_progress (self, opid, -1,
+ error->message);
+ } else {
+ sw_photo_upload_iface_emit_photo_upload_progress (self, opid, 100, "");
+ }
+}
+
+static void
+_photobucket_upload_photo (SwPhotoUploadIface *self,
+ const gchar *filename,
+ GHashTable *fields,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+ gint opid;
+
+ opid = _upload_file (SW_SERVICE_PHOTOBUCKET (self), PHOTO, filename,
+ fields, (RestProxyCallAsyncCallback) _upload_photo_cb,
+ &error);
+
+ if (error) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ sw_photo_upload_iface_return_from_upload_photo (context, opid);
+}
+
+static void
+photo_upload_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SwPhotoUploadIfaceClass *klass = (SwPhotoUploadIfaceClass *) g_iface;
+
+ sw_photo_upload_iface_implement_upload_photo (klass,
+ _photobucket_upload_photo);
+
+}
+
+/* Video Upload Interface */
+
+static void
+_upload_video_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ SwServicePhotobucket *self = SW_SERVICE_PHOTOBUCKET (weak_object);
+ int opid = GPOINTER_TO_INT (user_data);
+
+ if (error) {
+ sw_video_upload_iface_emit_video_upload_progress (self, opid, -1,
+ error->message);
+ } else {
+ sw_video_upload_iface_emit_video_upload_progress (self, opid, 100, "");
+ }
+}
+
+static void
+_photobucket_upload_video (SwVideoUploadIface *self,
+ const gchar *filename,
+ GHashTable *fields,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+ gint opid;
+
+ opid = _upload_file (SW_SERVICE_PHOTOBUCKET (self), VIDEO, filename, fields,
+ (RestProxyCallAsyncCallback) _upload_video_cb, &error);
+
+ if (error) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ sw_video_upload_iface_return_from_upload_video (context, opid);
+}
+
+static void
+video_upload_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SwVideoUploadIfaceClass *klass = (SwVideoUploadIfaceClass *) g_iface;
+
+ sw_video_upload_iface_implement_upload_video (klass,
+ _photobucket_upload_video);
+
+}
+
+static void
+sw_service_photobucket_class_init (SwServicePhotobucketClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ SwServiceClass *service_class = SW_SERVICE_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (SwServicePhotobucketPrivate));
+
+ object_class->dispose = sw_service_photobucket_dispose;
+
+ service_class->get_name = get_name;
+ service_class->get_static_caps = get_static_caps;
+ service_class->get_dynamic_caps = get_dynamic_caps;
+ service_class->credentials_updated = credentials_updated;
+}
+
+static void
+sw_service_photobucket_init (SwServicePhotobucket *self)
+{
+ SwServicePhotobucketPrivate *priv;
+ const gchar *api_key;
+ const gchar *api_secret;
+ SoupURI *url;
+
+ priv = self->priv = GET_PRIVATE (self);
+
+ sw_keystore_get_key_secret ("photobucket", &api_key, &api_secret);
+
+ priv->proxy = oauth_proxy_new (api_key, api_secret, ENTRYPOINT_URL, FALSE);
+
+ priv->silo_proxy = oauth_proxy_new (api_key, api_secret,
+ "http://%s.photobucket.com/", TRUE);
+
+ url = soup_uri_new (ENTRYPOINT_URL);
+
+ oauth_proxy_set_signature_host (OAUTH_PROXY (priv->silo_proxy), url->host);
+
+ sw_online_add_notify (online_notify, self);
+
+ refresh_credentials (self);
+
+ soup_uri_free (url);
+}
diff --git a/services/photobucket/photobucket.h b/services/photobucket/photobucket.h
new file mode 100644
index 0000000..7caf821
--- /dev/null
+++ b/services/photobucket/photobucket.h
@@ -0,0 +1,62 @@
+/*
+ * libsocialweb Photobucket service support
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * Authors: Eitan Isaacson <eitan isaacson collabora co uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _SW_SERVICE_PHOTOBUCKET
+#define _SW_SERVICE_PHOTOBUCKET
+
+#include <glib-object.h>
+#include <libsocialweb/sw-service.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_SERVICE_PHOTOBUCKET sw_service_photobucket_get_type()
+
+#define SW_SERVICE_PHOTOBUCKET(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_SERVICE_PHOTOBUCKET, SwServicePhotobucket))
+
+#define SW_SERVICE_PHOTOBUCKET_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_SERVICE_PHOTOBUCKET, SwServicePhotobucketClass))
+
+#define SW_IS_SERVICE_PHOTOBUCKET(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_SERVICE_PHOTOBUCKET))
+
+#define SW_IS_SERVICE_PHOTOBUCKET_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_SERVICE_PHOTOBUCKET))
+
+#define SW_SERVICE_PHOTOBUCKET_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_SERVICE_PHOTOBUCKET, SwServicePhotobucketClass))
+
+typedef struct _SwServicePhotobucketPrivate SwServicePhotobucketPrivate;
+
+typedef struct {
+ SwService parent;
+ SwServicePhotobucketPrivate *priv;
+} SwServicePhotobucket;
+
+typedef struct {
+ SwServiceClass parent_class;
+} SwServicePhotobucketClass;
+
+GType sw_service_photobucket_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SW_SERVICE_PHOTOBUCKET */
diff --git a/services/photobucket/photobucket.keys.in b/services/photobucket/photobucket.keys.in
new file mode 100644
index 0000000..0d85a6f
--- /dev/null
+++ b/services/photobucket/photobucket.keys.in
@@ -0,0 +1,11 @@
+[LibSocialWebService]
+_Name=Photobucket
+_Description=Snap and show it off! Photobucket makes it easy to share your photos and videos anywhere.
+Link=http://www.photobucket.com/
+AuthType=oauth
+
+[OAuth]
+BaseURL=http://api.photobucket.com/
+RequestTokenFunction=login/request
+AuthoriseFunction=http://photobucket.com/apilogin/login
+AccessTokenFunction=login/access
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]