[libsocialweb] Add Plurk service
- From: Ross Burton <rburton src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsocialweb] Add Plurk service
- Date: Wed, 23 Feb 2011 13:17:52 +0000 (UTC)
commit 6db7d4a9789c48302c3e28abc979c5497f09dc33
Author: Gary Ching-Pang Lin <chingpang gmail com>
Date: Thu Jan 13 15:11:29 2011 +0800
Add Plurk service
configure.ac | 4 +-
services/Makefile.am | 12 +-
services/plurk/Makefile.am | 27 ++
services/plurk/module.c | 35 +++
services/plurk/plurk-item-view.c | 625 ++++++++++++++++++++++++++++++++++++++
services/plurk/plurk-item-view.h | 60 ++++
services/plurk/plurk.c | 614 +++++++++++++++++++++++++++++++++++++
services/plurk/plurk.h | 61 ++++
services/plurk/plurk.keys.in | 6 +
services/plurk/plurk.png | Bin 0 -> 3429 bytes
10 files changed, 1439 insertions(+), 5 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 746e570..d2c87c7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -131,6 +131,7 @@ SOCIALWEB_ENABLE_SERVICE(Last.fm, lastfm, LASTFM)
SOCIALWEB_ENABLE_SERVICE(Twitter, twitter, TWITTER)
SOCIALWEB_ENABLE_SERVICE(Vimeo, vimeo, VIMEO)
SOCIALWEB_ENABLE_SERVICE(SumMug, smugmug, SMUGMUG)
+SOCIALWEB_ENABLE_SERVICE(Plurk, plurk, PLURK)
servicesdir='${libdir}'/libsocialweb/services
AC_SUBST(servicesdir)
@@ -185,9 +186,10 @@ AC_OUTPUT([
services/facebook/Makefile
services/flickr/Makefile
services/lastfm/Makefile
+ services/plurk/Makefile
+ services/smugmug/Makefile
services/twitter/Makefile
services/vimeo/Makefile
- services/smugmug/Makefile
tests/Makefile
tools/Makefile
examples/Makefile
diff --git a/services/Makefile.am b/services/Makefile.am
index 1633760..2a30d7b 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -12,6 +12,14 @@ if WITH_LASTFM
SUBDIRS += lastfm
endif
+if WITH_PLURK
+SUBDIRS += plurk
+endif
+
+if WITH_SMUGMUG
+SUBDIRS += smugmug
+endif
+
if WITH_TWITTER
SUBDIRS += twitter
endif
@@ -19,7 +27,3 @@ endif
if WITH_VIMEO
SUBDIRS += vimeo
endif
-
-if WITH_SMUGMUG
-SUBDIRS += smugmug
-endif
diff --git a/services/plurk/Makefile.am b/services/plurk/Makefile.am
new file mode 100644
index 0000000..f27f5f4
--- /dev/null
+++ b/services/plurk/Makefile.am
@@ -0,0 +1,27 @@
+services_LTLIBRARIES = libplurk.la
+libplurk_la_SOURCES = module.c \
+ plurk.c \
+ plurk.h \
+ plurk-item-view.h \
+ plurk-item-view.c
+libplurk_la_CFLAGS = -I$(top_srcdir) \
+ $(REST_CFLAGS) \
+ $(KEYRING_CFLAGS) \
+ $(DBUS_GLIB_CFLAGS) \
+ $(JSON_GLIB_CFLAGS) \
+ -DG_LOG_DOMAIN=\"Plurk\"
+libplurk_la_LIBADD = $(top_builddir)/libsocialweb/libsocialweb.la \
+ $(top_builddir)/libsocialweb-keyfob/libsocialweb-keyfob.la \
+ $(top_builddir)/libsocialweb-keystore/libsocialweb-keystore.la \
+ $(REST_LIBS) \
+ $(KEYRING_LIBS) \
+ $(DBUS_GLIB_LIBS) \
+ $(JSON_GLIB_LIBS)
+libplurk_la_LDFLAGS = -module -avoid-version
+
+dist_servicesdata_DATA = plurk.png
+
+servicesdata_DATA = plurk.keys
+CLEANFILES = plurk.keys
+EXTRA_DIST = plurk.keys.in
+ INTLTOOL_SOCIALWEB_KEYS@
diff --git a/services/plurk/module.c b/services/plurk/module.c
new file mode 100644
index 0000000..6824ef6
--- /dev/null
+++ b/services/plurk/module.c
@@ -0,0 +1,35 @@
+/*
+ * libsocialweb Plurk service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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 "plurk.h"
+
+const gchar *
+sw_module_get_name (void)
+{
+ return "plurk";
+}
+
+const GType
+sw_module_get_type (void)
+{
+ return SW_TYPE_SERVICE_PLURK;
+}
diff --git a/services/plurk/plurk-item-view.c b/services/plurk/plurk-item-view.c
new file mode 100644
index 0000000..36b95bf
--- /dev/null
+++ b/services/plurk/plurk-item-view.c
@@ -0,0 +1,625 @@
+/*
+ * libsocialweb Plurk service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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 <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libsocialweb/sw-utils.h>
+
+#include <rest/rest-proxy.h>
+#include <rest/rest-xml-parser.h>
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
+
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-cache.h>
+
+#include "plurk-item-view.h"
+
+G_DEFINE_TYPE (SwPlurkItemView,
+ sw_plurk_item_view,
+ SW_TYPE_ITEM_VIEW)
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_PLURK_ITEM_VIEW, SwPlurkItemViewPrivate))
+
+typedef struct _SwPlurkItemViewPrivate SwPlurkItemViewPrivate;
+
+struct _SwPlurkItemViewPrivate {
+ RestProxy *proxy;
+ gchar *api_key;
+ guint timeout_id;
+ GHashTable *params;
+ gchar *query;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PROXY,
+ PROP_APIKEY,
+ PROP_PARAMS,
+ PROP_QUERY
+};
+
+#define UPDATE_TIMEOUT 5 * 60
+
+static void _service_item_hidden_cb (SwService *service,
+ const gchar *uid,
+ SwItemView *item_view);
+
+static void _service_user_changed_cb (SwService *service,
+ SwItemView *item_view);
+static void _service_capabilities_changed_cb (SwService *service,
+ const gchar **caps,
+ SwItemView *item_view);
+
+static void
+sw_plurk_item_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_PROXY:
+ g_value_set_object (value, priv->proxy);
+ break;
+ case PROP_APIKEY:
+ g_value_set_string (value, priv->api_key);
+ break;
+ case PROP_PARAMS:
+ g_value_set_boxed (value, priv->params);
+ break;
+ case PROP_QUERY:
+ g_value_set_string (value, priv->query);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+sw_plurk_item_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_PROXY:
+ if (priv->proxy)
+ {
+ g_object_unref (priv->proxy);
+ }
+ priv->proxy = g_value_dup_object (value);
+ break;
+ case PROP_APIKEY:
+ if (priv->api_key)
+ {
+ g_object_unref (priv->api_key);
+ }
+ priv->api_key = g_value_dup_string (value);
+ break;
+ case PROP_PARAMS:
+ priv->params = g_value_dup_boxed (value);
+ break;
+ case PROP_QUERY:
+ priv->query = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+sw_plurk_item_view_dispose (GObject *object)
+{
+ SwItemView *item_view = SW_ITEM_VIEW (object);
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (object);
+
+ if (priv->proxy)
+ {
+ g_object_unref (priv->proxy);
+ priv->proxy = NULL;
+ }
+
+ if (priv->timeout_id)
+ {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ g_signal_handlers_disconnect_by_func (sw_item_view_get_service (item_view),
+ _service_item_hidden_cb,
+ item_view);
+ g_signal_handlers_disconnect_by_func (sw_item_view_get_service (item_view),
+ _service_user_changed_cb,
+ item_view);
+ g_signal_handlers_disconnect_by_func (sw_item_view_get_service (item_view),
+ _service_capabilities_changed_cb,
+ item_view);
+
+ G_OBJECT_CLASS (sw_plurk_item_view_parent_class)->dispose (object);
+}
+
+static void
+sw_plurk_item_view_finalize (GObject *object)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (object);
+
+ g_free (priv->api_key);
+ g_free (priv->query);
+ g_hash_table_unref (priv->params);
+
+ G_OBJECT_CLASS (sw_plurk_item_view_parent_class)->finalize (object);
+}
+
+static JsonNode *
+json_node_from_call (RestProxyCall *call,
+ const char *name)
+{
+ JsonParser *parser;
+ JsonNode *root = NULL;
+ GError *error;
+ gboolean ret = FALSE;
+
+ parser = json_parser_new ();
+
+ if (call == NULL)
+ goto out;
+
+ if (!SOUP_STATUS_IS_SUCCESSFUL (rest_proxy_call_get_status_code (call))) {
+ g_message ("Error from %s: %s (%d)",
+ name,
+ rest_proxy_call_get_status_message (call),
+ rest_proxy_call_get_status_code (call));
+ goto out;
+ }
+
+ ret = json_parser_load_from_data (parser,
+ rest_proxy_call_get_payload (call),
+ rest_proxy_call_get_payload_length (call),
+ &error);
+ root = json_parser_get_root (parser);
+
+ if (root == NULL) {
+ g_message ("Error from %s: %s",
+ name,
+ rest_proxy_call_get_payload (call));
+ goto out;
+ }
+
+ root = json_node_copy (root);
+
+out:
+ g_object_unref (parser);
+
+ return root;
+}
+
+static char *
+construct_image_url (const char *uid,
+ const gint64 avatar,
+ const gint64 has_profile)
+{
+ char *url = NULL;
+
+ if (has_profile == 1 && avatar <= 0)
+ url = g_strdup_printf ("http://avatars.plurk.com/%s-medium.gif", uid);
+ else if (has_profile == 1 && avatar > 0)
+ url = g_strdup_printf ("http://avatars.plurk.com/%s-medium%lld.gif", uid, avatar);
+ else
+ url = g_strdup_printf ("http://www.plurk.com/static/default_medium.gif");
+
+ return url;
+}
+
+static gchar *
+base36_encode (const gchar *source)
+{
+ gchar *encoded = NULL, *tmp, c;
+ gint64 dividend, quotient;
+ const gint64 divisor = 36;
+
+ dividend = g_ascii_strtoll (source, NULL, 10);
+
+ while (dividend > 0) {
+ quotient = dividend % divisor;
+ dividend = dividend / divisor;
+
+ if (quotient < 10)
+ c = '0' + quotient;
+ else
+ c = 'a' + quotient - 10;
+
+ if (encoded != NULL) {
+ tmp = g_strdup_printf ("%c%s", c, encoded);
+ g_free (encoded);
+ encoded = tmp;
+ } else {
+ encoded = g_strdup_printf ("%c", c);
+ }
+ }
+ return encoded;
+}
+
+static char *
+make_date (const char *s)
+{
+ struct tm tm;
+ strptime (s, "%A, %d %h %Y %H:%M:%S GMT", &tm);
+ return sw_time_t_to_string (timegm (&tm));
+}
+
+static SwItem *
+make_item (SwService *service, JsonNode *plurk_node, JsonNode *plurk_users)
+{
+ JsonNode *node;
+ JsonObject *plurk, *user, *object;
+ char *uid, *pid, *url, *date, *base36, *content;
+ const char *name, *qualifier;
+ gint64 id, avatar, has_profile;
+ SwItem *item;
+
+ item = sw_item_new ();
+ sw_item_set_service (item, service);
+
+ /* Get the plurk object */
+ plurk = json_node_get_object (plurk_node);
+
+ if (!json_object_has_member (plurk, "owner_id"))
+ return NULL;
+
+ /* Get the user object */
+ id = json_object_get_int_member (plurk, "owner_id");
+ uid = g_strdup_printf ("%lld", id);
+ object = json_node_get_object (plurk_users);
+ node = json_object_get_member (object, uid);
+ user = json_node_get_object (node);
+
+ if (!user)
+ return NULL;
+
+ /* authorid */
+ sw_item_take (item, "authorid", uid);
+
+ /* Construct the id of sw_item */
+ id = json_object_get_int_member (plurk, "plurk_id");
+ pid = g_strdup_printf ("%lld", id);
+ sw_item_take (item, "id", g_strconcat ("plurk-", pid, NULL));
+
+ /* Get the display name of the user */
+ name = json_object_get_string_member (user, "full_name");
+ sw_item_put (item, "author", name);
+
+ /* Construct the avatar url */
+ avatar = json_object_get_int_member (user, "avatar");
+ has_profile = json_object_get_int_member (user, "has_profile_image");
+ url = construct_image_url (uid, avatar, has_profile);
+ sw_item_request_image_fetch (item, FALSE, "authoricon", url);
+ g_free (url);
+
+ /* Construct the content of the plurk*/
+ if (json_object_has_member (plurk, "qualifier_translated"))
+ qualifier = json_object_get_string_member (plurk, "qualifier_translated");
+ else
+ qualifier = json_object_get_string_member (plurk, "qualifier");
+ content = g_strdup_printf ("%s %s",
+ qualifier,
+ json_object_get_string_member (plurk, "content_raw"));
+ sw_item_take (item, "content", content);
+
+ /* Get the post date of this plurk*/
+ date = make_date (json_object_get_string_member (plurk, "posted"));
+ sw_item_take (item, "date", date);
+
+ /* Construt the link of the user */
+ base36 = base36_encode (pid);
+ url = g_strconcat ("http://www.plurk.com/p/", base36, NULL);
+ g_free (base36);
+ sw_item_take (item, "url", url);
+
+ return item;
+}
+
+static void
+_got_status_updates_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer userdata)
+{
+ SwPlurkItemView *item_view = SW_PLURK_ITEM_VIEW (weak_object);
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (item_view);
+ SwService *service;
+ JsonNode *root, *plurks, *plurk_users;
+ JsonArray *plurks_array;
+ JsonObject *object;
+ SwSet *set;
+ guint i, length;
+
+ if (error) {
+ g_message ("Error: %s", error->message);
+ g_message ("Error: %s", rest_proxy_call_get_payload(call));
+ return;
+ }
+
+ root = json_node_from_call (call, "Plurk");
+ if (!root)
+ return;
+
+ object = json_node_get_object (root);
+ if (!json_object_has_member (object, "plurks") ||
+ !json_object_has_member (object, "plurk_users"))
+ return;
+
+ service = sw_item_view_get_service (SW_ITEM_VIEW (item_view));
+ set = sw_item_set_new ();
+ plurks = json_object_get_member (object, "plurks");
+ plurk_users = json_object_get_member (object, "plurk_users");
+
+ /* Parser the data and file the set */
+ plurks_array = json_node_get_array (plurks);
+ length = json_array_get_length (plurks_array);
+
+ for (i=0; i<length; i++) {
+ JsonNode *plurk_node = json_array_get_element (plurks_array, i);
+ SwItem *item;
+
+ item = make_item (service, plurk_node, plurk_users);
+ if (!item)
+ continue;
+
+ /* Add the item into the set */
+ if (!sw_service_is_uid_banned (service,
+ sw_item_get (item, "id"))) {
+ sw_set_add (set, G_OBJECT (item));
+ }
+ g_object_unref (item);
+ }
+
+ sw_item_view_set_from_set (SW_ITEM_VIEW (item_view),
+ set);
+
+ /* Save the results of this set to the cache */
+ sw_cache_save (service,
+ priv->query,
+ priv->params,
+ set);
+
+ g_object_unref (call);
+}
+
+static void
+_get_status_updates (SwPlurkItemView *item_view)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (item_view);
+ RestProxyCall *call;
+
+ call = rest_proxy_new_call (priv->proxy);
+
+ /* TODO Request plurks for "own" or "feed" */
+ rest_proxy_call_set_function (call, "Timeline/getPlurks");
+
+ rest_proxy_call_add_params (call,
+ "api_key", priv->api_key,
+ "limit", "20",
+ NULL);
+ rest_proxy_call_async (call, _got_status_updates_cb, (GObject*)item_view, NULL, NULL);
+}
+
+static gboolean
+_update_timeout_cb (gpointer data)
+{
+ SwPlurkItemView *item_view = SW_PLURK_ITEM_VIEW (data);
+
+ _get_status_updates (item_view);
+
+ return TRUE;
+}
+
+static void
+_load_from_cache (SwPlurkItemView *item_view)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (item_view);
+ SwSet *set;
+
+ set = sw_cache_load (sw_item_view_get_service (SW_ITEM_VIEW (item_view)),
+ priv->query,
+ priv->params);
+
+ if (set)
+ {
+ sw_item_view_set_from_set (SW_ITEM_VIEW (item_view),
+ set);
+ sw_set_unref (set);
+ }
+}
+
+static void
+plurk_item_view_start (SwItemView *item_view)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (item_view);
+
+ if (priv->timeout_id)
+ {
+ g_warning (G_STRLOC ": View already started.");
+ } else {
+ priv->timeout_id = g_timeout_add_seconds (UPDATE_TIMEOUT,
+ (GSourceFunc)_update_timeout_cb,
+ item_view);
+ _load_from_cache ((SwPlurkItemView *)item_view);
+ _get_status_updates ((SwPlurkItemView *)item_view);
+ }
+}
+
+static void
+plurk_item_view_stop (SwItemView *item_view)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (item_view);
+
+ if (!priv->timeout_id)
+ {
+ g_warning (G_STRLOC ": View not running");
+ } else {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+}
+
+static void
+plurk_item_view_refresh (SwItemView *item_view)
+{
+ _get_status_updates ((SwPlurkItemView *)item_view);
+}
+
+static void
+_service_item_hidden_cb (SwService *service,
+ const gchar *uid,
+ SwItemView *item_view)
+{
+ sw_item_view_remove_by_uid (item_view, uid);
+}
+
+static void
+_service_user_changed_cb (SwService *service,
+ SwItemView *item_view)
+{
+ SwSet *set;
+
+ /* We need to empty the set */
+ set = sw_item_set_new ();
+ sw_item_view_set_from_set (SW_ITEM_VIEW (item_view),
+ set);
+ sw_set_unref (set);
+
+ /* And drop the cache */
+ sw_cache_drop_all (service);
+}
+
+static void
+_service_capabilities_changed_cb (SwService *service,
+ const gchar **caps,
+ SwItemView *item_view)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE ((SwPlurkItemView*) item_view);
+
+ if (sw_service_has_cap (caps, CREDENTIALS_VALID))
+ {
+ plurk_item_view_refresh (item_view);
+
+ if (!priv->timeout_id)
+ {
+ priv->timeout_id = g_timeout_add_seconds (UPDATE_TIMEOUT,
+ (GSourceFunc)_update_timeout_cb,
+ item_view);
+ }
+ } else {
+ if (priv->timeout_id)
+ {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+ }
+}
+
+static void
+sw_plurk_item_view_constructed (GObject *object)
+{
+ SwItemView *item_view = SW_ITEM_VIEW (object);
+
+ g_signal_connect (sw_item_view_get_service (item_view),
+ "item-hidden",
+ (GCallback)_service_item_hidden_cb,
+ item_view);
+
+ g_signal_connect (sw_item_view_get_service (item_view),
+ "user-changed",
+ (GCallback)_service_user_changed_cb,
+ item_view);
+
+ g_signal_connect (sw_item_view_get_service (item_view),
+ "capabilities-changed",
+ (GCallback)_service_capabilities_changed_cb,
+ item_view);
+
+ if (G_OBJECT_CLASS (sw_plurk_item_view_parent_class)->constructed)
+ G_OBJECT_CLASS (sw_plurk_item_view_parent_class)->constructed (object);
+}
+
+static void
+sw_plurk_item_view_class_init (SwPlurkItemViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ SwItemViewClass *item_view_class = SW_ITEM_VIEW_CLASS (klass);
+ GParamSpec *pspec;
+
+ g_type_class_add_private (klass, sizeof (SwPlurkItemViewPrivate));
+
+ object_class->get_property = sw_plurk_item_view_get_property;
+ object_class->set_property = sw_plurk_item_view_set_property;
+ object_class->dispose = sw_plurk_item_view_dispose;
+ object_class->finalize = sw_plurk_item_view_finalize;
+ object_class->constructed = sw_plurk_item_view_constructed;
+
+ item_view_class->start = plurk_item_view_start;
+ item_view_class->stop = plurk_item_view_stop;
+ item_view_class->refresh = plurk_item_view_refresh;
+
+ pspec = g_param_spec_object ("proxy",
+ "proxy",
+ "proxy",
+ REST_TYPE_PROXY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_PROXY, pspec);
+
+ pspec = g_param_spec_string ("api_key",
+ "api_key",
+ "api_key",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_APIKEY, pspec);
+
+ pspec = g_param_spec_string ("query",
+ "query",
+ "query",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_QUERY, pspec);
+
+ pspec = g_param_spec_boxed ("params",
+ "params",
+ "params",
+ G_TYPE_HASH_TABLE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_PARAMS, pspec);
+}
+
+static void
+sw_plurk_item_view_init (SwPlurkItemView *self)
+{
+ SwPlurkItemViewPrivate *priv = GET_PRIVATE (self);
+ priv->api_key = NULL;
+}
diff --git a/services/plurk/plurk-item-view.h b/services/plurk/plurk-item-view.h
new file mode 100644
index 0000000..b1b5ae7
--- /dev/null
+++ b/services/plurk/plurk-item-view.h
@@ -0,0 +1,60 @@
+/*
+ * libsocialweb Plurk service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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_PLURK_ITEM_VIEW
+#define _SW_PLURK_ITEM_VIEW
+
+#include <glib-object.h>
+#include <libsocialweb/sw-item-view.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_PLURK_ITEM_VIEW sw_plurk_item_view_get_type()
+
+#define SW_PLURK_ITEM_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_PLURK_ITEM_VIEW, SwPlurkItemView))
+
+#define SW_PLURK_ITEM_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_PLURK_ITEM_VIEW, SwPlurkItemViewClass))
+
+#define SW_IS_PLURK_ITEM_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_PLURK_ITEM_VIEW))
+
+#define SW_IS_PLURK_ITEM_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_PLURK_ITEM_VIEW))
+
+#define SW_PLURK_ITEM_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_PLURK_ITEM_VIEW, SwPlurkItemViewClass))
+
+typedef struct {
+ SwItemView parent;
+} SwPlurkItemView;
+
+typedef struct {
+ SwItemViewClass parent_class;
+} SwPlurkItemViewClass;
+
+GType sw_plurk_item_view_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SW_PLURK_ITEM_VIEW */
+
diff --git a/services/plurk/plurk.c b/services/plurk/plurk.c
new file mode 100644
index 0000000..c91ef90
--- /dev/null
+++ b/services/plurk/plurk.c
@@ -0,0 +1,614 @@
+/*
+ * libsocialweb Plurk service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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 <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <gnome-keyring.h>
+
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-set.h>
+#include <libsocialweb/sw-online.h>
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-web.h>
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb-keyfob/sw-keyfob.h>
+#include <libsocialweb-keystore/sw-keystore.h>
+#include <libsocialweb/sw-client-monitor.h>
+
+#include <rest/rest-xml-parser.h>
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
+
+#include <interfaces/sw-query-ginterface.h>
+#include <interfaces/sw-avatar-ginterface.h>
+#include <interfaces/sw-status-update-ginterface.h>
+
+#include "plurk.h"
+#include "plurk-item-view.h"
+
+static void initable_iface_init (gpointer g_iface, gpointer iface_data);
+static void query_iface_init (gpointer g_iface, gpointer iface_data);
+static void avatar_iface_init (gpointer g_iface, gpointer iface_data);
+static void status_update_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SwServicePlurk,
+ sw_service_plurk,
+ SW_TYPE_SERVICE,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ initable_iface_init)
+ G_IMPLEMENT_INTERFACE (SW_TYPE_QUERY_IFACE,
+ query_iface_init)
+ G_IMPLEMENT_INTERFACE (SW_TYPE_AVATAR_IFACE,
+ avatar_iface_init)
+ G_IMPLEMENT_INTERFACE (SW_TYPE_STATUS_UPDATE_IFACE,
+ status_update_iface_init));
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_SERVICE_PLURK, SwServicePlurkPrivate))
+
+struct _SwServicePlurkPrivate {
+ gboolean inited;
+ enum {
+ OFFLINE,
+ CREDS_INVALID,
+ CREDS_VALID
+ } credentials;
+ RestProxy *proxy;
+ char *user_id;
+ char *image_url;
+ char *username, *password;
+ char *api_key;
+};
+
+static void online_notify (gboolean online, gpointer user_data);
+static void credentials_updated (SwService *service);
+
+static JsonNode *
+node_from_call (RestProxyCall *call, JsonParser *parser)
+{
+ JsonNode *root;
+ GError *error;
+ gboolean ret = FALSE;
+
+ if (call == NULL)
+ return NULL;
+
+ if (!SOUP_STATUS_IS_SUCCESSFUL (rest_proxy_call_get_status_code (call))) {
+ g_message ("Error from Plurk: %s (%d)",
+ rest_proxy_call_get_status_message (call),
+ rest_proxy_call_get_status_code (call));
+ return NULL;
+ }
+
+ ret = json_parser_load_from_data (parser,
+ rest_proxy_call_get_payload (call),
+ rest_proxy_call_get_payload_length (call),
+ &error);
+ root = json_parser_get_root (parser);
+
+ if (root == NULL) {
+ g_message ("Error from Plurk: %s",
+ rest_proxy_call_get_payload (call));
+ return NULL;
+ }
+
+ return root;
+}
+
+static char *
+construct_image_url (const char *uid,
+ const gint64 avatar,
+ const gint64 has_profile)
+{
+ char *url = NULL;
+
+ if (has_profile == 1 && avatar <= 0)
+ url = g_strdup_printf ("http://avatars.plurk.com/%s-medium.gif", uid);
+ else if (has_profile == 1 && avatar > 0)
+ url = g_strdup_printf ("http://avatars.plurk.com/%s-medium%lld.gif", uid, avatar);
+ else
+ url = g_strdup_printf ("http://www.plurk.com/static/default_medium.gif");
+
+ return url;
+}
+
+static const char **
+get_static_caps (SwService *service)
+{
+ static const char * caps[] = {
+ CAN_VERIFY_CREDENTIALS,
+ HAS_UPDATE_STATUS_IFACE,
+ HAS_AVATAR_IFACE,
+ HAS_BANISHABLE_IFACE,
+ HAS_QUERY_IFACE,
+
+ /* deprecated */
+ CAN_UPDATE_STATUS,
+ CAN_REQUEST_AVATAR,
+ NULL
+ };
+
+ return caps;
+}
+
+static const char **
+get_dynamic_caps (SwService *service)
+{
+ SwServicePlurkPrivate *priv = GET_PRIVATE (service);
+ static const char *no_caps[] = { NULL };
+ static const char *configured_caps[] = {
+ IS_CONFIGURED,
+ NULL
+ };
+ static const char *invalid_caps[] = {
+ IS_CONFIGURED,
+ CREDENTIALS_INVALID,
+ NULL
+ };
+ static const char *full_caps[] = {
+ IS_CONFIGURED,
+ CREDENTIALS_VALID,
+ CAN_UPDATE_STATUS,
+ CAN_REQUEST_AVATAR,
+ NULL
+ };
+
+ /* Check the conditions and determine which caps array to return */
+ switch (priv->credentials) {
+ case CREDS_VALID:
+ return full_caps;
+ case CREDS_INVALID:
+ return invalid_caps;
+ case OFFLINE:
+ if (priv->username && priv->password)
+ return configured_caps;
+ else
+ return no_caps;
+ }
+
+ /* Just in case we fell through that switch */
+ g_warning ("Unhandled credential state %d", priv->credentials);
+ return no_caps;
+}
+
+static void
+construct_user_data (SwServicePlurk* plurk, JsonNode *root)
+{
+ SwServicePlurkPrivate *priv = GET_PRIVATE (plurk);
+ JsonNode *node;
+ JsonObject *object;
+ const gchar *uid;
+ gint64 id, avatar, has_profile;
+
+ object = json_node_get_object (root);
+ node = json_object_get_member (object, "user_info");
+
+ if (!node)
+ return;
+
+ object = json_node_get_object (node);
+ if (json_object_get_null_member (object, "uid"))
+ return;
+
+ id = json_object_get_int_member (object, "uid");
+
+ avatar = json_object_get_int_member (object, "avatar");
+
+ has_profile = json_object_get_int_member (object, "has_profile_image");
+
+ uid = g_strdup_printf ("%lld", id);
+
+ priv->user_id = (char *) uid;
+ priv->image_url = construct_image_url (uid, avatar, has_profile);
+}
+
+static void
+_got_login_data (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer userdata)
+{
+ SwService *service = SW_SERVICE (weak_object);
+ SwServicePlurk *plurk = SW_SERVICE_PLURK (service);
+ JsonParser *parser = NULL;
+ JsonNode *root;
+
+ if (error) {
+ // TODO sanity_check_date (call);
+ g_message ("Error: %s", error->message);
+
+ plurk->priv->credentials = CREDS_INVALID;
+ sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+
+ return;
+ }
+
+ plurk->priv->credentials = CREDS_VALID;
+
+ parser = json_parser_new ();
+ root = node_from_call (call, parser);
+ construct_user_data (plurk, root);
+ g_object_unref (root);
+
+ sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+
+ g_object_unref (call);
+}
+
+static void
+online_notify (gboolean online, gpointer user_data)
+{
+ SwServicePlurk *plurk = (SwServicePlurk *)user_data;
+ SwServicePlurkPrivate *priv = GET_PRIVATE (plurk);
+
+ priv->credentials = OFFLINE;
+
+ if (online) {
+ if (priv->username && priv->password) {
+ RestProxyCall *call;
+
+ call = rest_proxy_new_call (priv->proxy);
+ rest_proxy_call_set_function (call, "Users/login");
+ rest_proxy_call_add_params (call,
+ "api_key", priv->api_key,
+ "username", priv->username,
+ "password", priv->password,
+ NULL);
+ rest_proxy_call_async (call, _got_login_data, (GObject*)plurk, NULL, NULL);
+ }
+ } else {
+ g_free (priv->user_id);
+ priv->user_id = NULL;
+
+ sw_service_emit_capabilities_changed ((SwService *)plurk,
+ get_dynamic_caps ((SwService *)plurk));
+ }
+}
+
+/*
+ * Callback from the keyring lookup in refresh_credentials.
+ */
+static void
+found_password_cb (GnomeKeyringResult result,
+ GList *list,
+ gpointer user_data)
+{
+ SwService *service = SW_SERVICE (user_data);
+ SwServicePlurk *plurk = SW_SERVICE_PLURK (service);
+ SwServicePlurkPrivate *priv = plurk->priv;
+
+ if (result == GNOME_KEYRING_RESULT_OK && list != NULL) {
+ GnomeKeyringNetworkPasswordData *data = list->data;
+
+ g_free (priv->username);
+ g_free (priv->password);
+
+ priv->username = g_strdup (data->user);
+ priv->password = g_strdup (data->password);
+
+ /* If we're online, force a reconnect to fetch new credentials */
+ if (sw_is_online ()) {
+ online_notify (FALSE, service);
+ online_notify (TRUE, service);
+ }
+ } else {
+ g_free (priv->username);
+ g_free (priv->password);
+ priv->username = NULL;
+ priv->password = NULL;
+ priv->credentials = OFFLINE;
+
+ if (result != GNOME_KEYRING_RESULT_NO_MATCH) {
+ g_warning (G_STRLOC ": Error getting password: %s", gnome_keyring_result_to_message (result));
+ }
+ }
+
+ sw_service_emit_user_changed (service);
+ sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+}
+
+static void
+refresh_credentials (SwServicePlurk *plurk)
+{
+ gnome_keyring_find_network_password (NULL, NULL,
+ "www.plurk.com",
+ NULL, NULL, NULL, 0,
+ found_password_cb, plurk, NULL);
+}
+
+static void
+credentials_updated (SwService *service)
+{
+ refresh_credentials (SW_SERVICE_PLURK (service));
+}
+
+static const char *
+sw_service_plurk_get_name (SwService *service)
+{
+ return "plurk";
+}
+
+static void
+sw_service_plurk_dispose (GObject *object)
+{
+ SwServicePlurkPrivate *priv = SW_SERVICE_PLURK (object)->priv;
+
+ sw_online_remove_notify (online_notify, object);
+
+ /* unref private variables */
+ if (priv->proxy) {
+ g_object_unref (priv->proxy);
+ priv->proxy = NULL;
+ }
+
+ G_OBJECT_CLASS (sw_service_plurk_parent_class)->dispose (object);
+}
+
+static void
+sw_service_plurk_finalize (GObject *object)
+{
+ /* Free private variables*/
+ SwServicePlurkPrivate *priv = SW_SERVICE_PLURK (object)->priv;
+
+ g_free (priv->user_id);
+ g_free (priv->image_url);
+ g_free (priv->username);
+ g_free (priv->password);
+
+ G_OBJECT_CLASS (sw_service_plurk_parent_class)->finalize (object);
+}
+
+static void
+sw_service_plurk_class_init (SwServicePlurkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ SwServiceClass *service_class = SW_SERVICE_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (SwServicePlurkPrivate));
+
+ object_class->dispose = sw_service_plurk_dispose;
+ object_class->finalize = sw_service_plurk_finalize;
+
+ service_class->get_name = sw_service_plurk_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_plurk_init (SwServicePlurk *self)
+{
+ self->priv = GET_PRIVATE (self);
+ self->priv->inited = FALSE;
+}
+
+/* Initable interface */
+
+static gboolean
+sw_service_plurk_initable (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Initialize the service and return TRUE if everything is OK.
+ Otherwise, return FALSE */
+ SwServicePlurk *plurk = SW_SERVICE_PLURK (initable);
+ SwServicePlurkPrivate *priv = GET_PRIVATE (plurk);
+ const char *key = NULL;
+
+ if (priv->inited)
+ return TRUE;
+
+ sw_keystore_get_key_secret ("plurk", &key, NULL);
+ if (key == NULL) {
+ g_set_error_literal (error,
+ SW_SERVICE_ERROR,
+ SW_SERVICE_ERROR_NO_KEYS,
+ "No API key configured");
+ return FALSE;
+ }
+
+ priv->api_key = g_strdup (key);
+
+ priv->credentials = OFFLINE;
+
+ priv->proxy = rest_proxy_new ("http://www.plurk.com/API/", FALSE);
+
+ sw_online_add_notify (online_notify, plurk);
+
+ refresh_credentials (plurk);
+
+ priv->inited = TRUE;
+
+ return TRUE;
+
+}
+
+static void
+initable_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ GInitableIface *klass = (GInitableIface *)g_iface;
+
+ klass->init = sw_service_plurk_initable;
+}
+
+/* Query interface */
+
+static const gchar *valid_queries[] = {"feed",
+ "own",
+ "friends-only"};
+static gboolean
+_check_query_validity (const gchar *query)
+{
+ gint i = 0;
+
+ for (i = 0; i < G_N_ELEMENTS(valid_queries); i++)
+ {
+ if (g_str_equal (query, valid_queries[i]))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+_plurk_query_open_view (SwQueryIface *self,
+ const gchar *query,
+ GHashTable *params,
+ DBusGMethodInvocation *context)
+{
+ SwServicePlurkPrivate *priv = GET_PRIVATE (self);
+ SwItemView *item_view;
+ const gchar *object_path;
+
+ if (!_check_query_validity (query))
+ {
+ dbus_g_method_return_error (context,
+ g_error_new (SW_SERVICE_ERROR,
+ SW_SERVICE_ERROR_INVALID_QUERY,
+ "Query '%s' is invalid",
+ query));
+ return;
+ }
+
+ item_view = g_object_new (SW_TYPE_PLURK_ITEM_VIEW,
+ "proxy", priv->proxy,
+ "api_key", priv->api_key,
+ "service", self,
+ "query", query,
+ "params", params,
+ NULL);
+
+ object_path = sw_item_view_get_object_path (item_view);
+ /* Ensure the object gets disposed when the client goes away */
+ sw_client_monitor_add (dbus_g_method_get_sender (context),
+ (GObject *)item_view);
+ sw_query_iface_return_from_open_view (context,
+ object_path);
+}
+
+
+static void
+query_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SwQueryIfaceClass *klass = (SwQueryIfaceClass*)g_iface;
+
+ sw_query_iface_implement_open_view (klass,
+ _plurk_query_open_view);
+}
+
+/* Avatar interface */
+static void
+_requested_avatar_downloaded_cb (const gchar *uri,
+ gchar *local_path,
+ gpointer userdata)
+{
+ SwService *service = SW_SERVICE (userdata);
+
+ sw_avatar_iface_emit_avatar_retrieved (service, local_path);
+ g_free (local_path);
+}
+
+static void
+_plurk_avatar_request_avatar (SwAvatarIface *self,
+ DBusGMethodInvocation *context)
+{
+ SwServicePlurkPrivate *priv = GET_PRIVATE (self);
+
+ if (priv->image_url) {
+ sw_web_download_image_async (priv->image_url,
+ _requested_avatar_downloaded_cb,
+ self);
+ }
+
+ sw_avatar_iface_return_from_request_avatar (context);
+}
+
+
+static void
+avatar_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SwAvatarIfaceClass *klass = (SwAvatarIfaceClass*)g_iface;
+
+ sw_avatar_iface_implement_request_avatar (klass,
+ _plurk_avatar_request_avatar);
+}
+
+/* Status Update interface */
+static void
+_update_status_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer userdata)
+{
+ if (error)
+ {
+ g_critical (G_STRLOC ": Error updating status: %s",
+ error->message);
+ sw_status_update_iface_emit_status_updated (weak_object, FALSE);
+ } else {
+ sw_status_update_iface_emit_status_updated (weak_object, TRUE);
+ }
+}
+
+static void
+_plurk_status_update_update_status (SwStatusUpdateIface *self,
+ const gchar *msg,
+ GHashTable *fields,
+ DBusGMethodInvocation *context)
+{
+ SwServicePlurk *plurk = SW_SERVICE_PLURK (self);
+ SwServicePlurkPrivate *priv = GET_PRIVATE (plurk);
+ RestProxyCall *call;
+
+ if (!priv->user_id)
+ return;
+
+ call = rest_proxy_new_call (priv->proxy);
+ rest_proxy_call_set_method (call, "POST");
+ rest_proxy_call_set_function (call, "Timeline/plurkAdd");
+
+ rest_proxy_call_add_params (call,
+ "api_key", priv->api_key,
+ "content", msg,
+ "qualifier", ":",
+ NULL);
+
+ rest_proxy_call_async (call, _update_status_cb, (GObject *)self, NULL, NULL);
+ sw_status_update_iface_return_from_update_status (context);
+}
+
+static void
+status_update_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SwStatusUpdateIfaceClass *klass = (SwStatusUpdateIfaceClass*)g_iface;
+
+ sw_status_update_iface_implement_update_status (klass,
+ _plurk_status_update_update_status);
+}
+
diff --git a/services/plurk/plurk.h b/services/plurk/plurk.h
new file mode 100644
index 0000000..eb95968
--- /dev/null
+++ b/services/plurk/plurk.h
@@ -0,0 +1,61 @@
+/*
+ * libsocialweb Plurk service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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_PLURK
+#define _SW_SERVICE_PLURK
+
+#include <libsocialweb/sw-service.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_SERVICE_PLURK sw_service_plurk_get_type()
+
+#define SW_SERVICE_PLURK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_SERVICE_PLURK, SwServicePlurk))
+
+#define SW_SERVICE_PLURK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_SERVICE_PLURK, SwServicePlurkClass))
+
+#define SW_IS_SERVICE_PLURK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_SERVICE_PLURK))
+
+#define SW_IS_SERVICE_PLURK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_SERVICE_PLURK))
+
+#define SW_SERVICE_PLURK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_SERVICE_PLURK, SwServicePlurkClass))
+
+typedef struct _SwServicePlurkPrivate SwServicePlurkPrivate;
+
+typedef struct {
+ SwService parent;
+ SwServicePlurkPrivate *priv;
+} SwServicePlurk;
+
+typedef struct {
+ SwServiceClass parent_class;
+} SwServicePlurkClass;
+
+GType sw_service_plurk_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SW_SERVICE_PLURK */
diff --git a/services/plurk/plurk.keys.in b/services/plurk/plurk.keys.in
new file mode 100644
index 0000000..f420667
--- /dev/null
+++ b/services/plurk/plurk.keys.in
@@ -0,0 +1,6 @@
+[LibSocialWebService]
+_Name=Plurk
+_Description=Plurk is a web site that allows people to share ideas.
+Link=http://www.plurk.com
+AuthType=password
+AuthPasswordServer=www.plurk.com
diff --git a/services/plurk/plurk.png b/services/plurk/plurk.png
new file mode 100644
index 0000000..2f36e1a
Binary files /dev/null and b/services/plurk/plurk.png differ
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]