[grilo-plugins] pocket: Add Pocket source
- From: Juan A. Suarez Romero <jasuarez src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [grilo-plugins] pocket: Add Pocket source
- Date: Sun, 9 Feb 2014 23:28:14 +0000 (UTC)
commit ec026789c4a72634ab86f56d87845d01be4fe7a1
Author: Bastien Nocera <hadess hadess net>
Date: Sun Feb 2 18:15:02 2014 +0100
pocket: Add Pocket source
A number of improvements can still be done:
- Respect browse spec (count, skip)
- Add change support
- Find a way to get thumbnails
- Add delete/archive support
https://bugzilla.gnome.org/show_bug.cgi?id=722819
configure.ac | 53 +++
src/Makefile.am | 6 +-
src/pocket/Makefile.am | 52 +++
src/pocket/channel-pocket.svg | 137 ++++++
src/pocket/gnome-pocket.c | 934 +++++++++++++++++++++++++++++++++++++++
src/pocket/gnome-pocket.h | 117 +++++
src/pocket/grl-pocket.c | 405 +++++++++++++++++
src/pocket/grl-pocket.h | 76 ++++
src/pocket/grl-pocket.xml | 10 +
src/pocket/pocket.gresource.xml | 6 +
10 files changed, 1795 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d3dae9f..b9f08ac 100644
--- a/configure.ac
+++ b/configure.ac
@@ -146,6 +146,8 @@ PKG_CHECK_MODULES(GOA, [goa-1.0 >= 3.7.1], HAVE_GOA=yes, HAVE_GOA=no)
PKG_CHECK_MODULES(TOTEM_PL_PARSER, totem-plparser >= 3.4.1, HAVE_TOTEM_PL_PARSER=yes,
HAVE_TOTEM_PL_PARSER=no)
+PKG_CHECK_MODULES(REST, rest-0.7, HAVE_REST=yes, HAVE_REST=no)
+
PKG_CHECK_MODULES([JSON], [json-glib-1.0], HAVE_JSON_GLIB=yes, HAVE_JSON_GLIB=no)
PKG_CHECK_MODULES(GMIME, gmime-2.6,
@@ -545,6 +547,56 @@ fi
AC_SUBST(DEPS_FLICKR_LIBS)
# ----------------------------------------------------------
+# BUILD POCKET PLUGIN
+# ----------------------------------------------------------
+
+AC_ARG_ENABLE(pocket,
+ AC_HELP_STRING([--enable-pocket],
+ [enable Pocket plugin (default: auto)]),
+ [
+ case "$enableval" in
+ yes)
+ if test "x$HAVE_JSON_GLIB" = "xno"; then
+ AC_MSG_ERROR([json-glib not found, install it or use --disable-pocket])
+ fi
+ if test "x$HAVE_GOA" = "xno"; then
+ AC_MSG_ERROR([gnome-online-accounts not found, install it or use
--disable-pocket])
+ fi
+ if test "x$HAVE_REST" = "xno"; then
+ AC_MSG_ERROR([rest not found, install it or use --disable-pocket])
+ fi
+ if test "x$HAVE_TOTEM_PL_PARSER" = "xno"; then
+ AC_MSG_ERROR([totem-pl-parser not found, install it or use --disable-pocket])
+ fi
+ ;;
+ esac
+ ],
+ [
+ if test "x$HAVE_JSON_GLIB" = "xyes" -a "x$HAVE_GOA" = "xyes" -a "x$HAVE_REST" = "xyes" -a
"x$HAVE_TOTEM_PL_PARSER" = "xyes"; then
+ enable_pocket=yes
+ else
+ enable_pocket=no
+ fi
+ ])
+
+AM_CONDITIONAL([POCKET_PLUGIN], [test "x$enable_pocket" = "xyes"])
+GRL_PLUGINS_ALL="$GRL_PLUGINS_ALL pocket"
+if test "x$enable_pocket" = "xyes"
+then
+ GRL_PLUGINS_ENABLED="$GRL_PLUGINS_ENABLED pocket"
+fi
+
+POCKET_PLUGIN_ID="grl-pocket"
+AC_SUBST(POCKET_PLUGIN_ID)
+AC_DEFINE_UNQUOTED([POCKET_PLUGIN_ID], ["$POCKET_PLUGIN_ID"], [Pocket plugin ID])
+
+DEPS_POCKET_CFLAGS="$DEPS_CFLAGS $JSON_CFLAGS $GOA_CFLAGS $REST_CFLAGS $TOTEM_PL_PARSER_CFLAGS"
+AC_SUBST(DEPS_POCKET_CFLAGS)
+
+DEPS_POCKET_LIBS="$DEPS_LIBS $JSON_LIBS $GOA_LIBS $REST_LIBS $TOTEM_PL_PARSER_LIBS"
+AC_SUBST(DEPS_POCKET_LIBS)
+
+# ----------------------------------------------------------
# BUILD PODCASTS PLUGIN
# ----------------------------------------------------------
@@ -1264,6 +1316,7 @@ AC_CONFIG_FILES([
src/magnatune/Makefile
src/metadata-store/Makefile
src/optical-media/Makefile
+ src/pocket/Makefile
src/podcasts/Makefile
src/raitv/Makefile
src/shoutcast/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 4fc5a9d..467b6fd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,6 +62,10 @@ if OPTICAL_MEDIA_PLUGIN
SUBDIRS += optical-media
endif
+if POCKET_PLUGIN
+SUBDIRS += pocket
+endif
+
if PODCASTS_PLUGIN
SUBDIRS += podcasts
endif
@@ -105,7 +109,7 @@ endif
DIST_SUBDIRS = \
apple-trailers bliptv bookmarks dmap filesystem flickr freebox gravatar guardian-videos jamendo \
lastfm-albumart local-metadata magnatune metadata-store optical-media \
- podcasts raitv shoutcast tmdb tracker upnp vimeo youtube
+ pocket podcasts raitv shoutcast tmdb tracker upnp vimeo youtube
MAINTAINERCLEANFILES = \
*.in \
diff --git a/src/pocket/Makefile.am b/src/pocket/Makefile.am
new file mode 100644
index 0000000..38590ea
--- /dev/null
+++ b/src/pocket/Makefile.am
@@ -0,0 +1,52 @@
+#
+# Makefile.am
+#
+# Author: Bastien Nocera <hadess hadess net>
+#
+# Copyright (C) 2013 Bastien Nocera
+
+include $(top_srcdir)/gtester.mk
+
+ext_LTLIBRARIES = libgrlpocket.la
+
+libgrlpocket_la_CFLAGS = \
+ $(DEPS_POCKET_CFLAGS) \
+ -DG_LOG_DOMAIN=\"GrlPocket\" \
+ -DLOCALEDIR=\"$(localedir)\"
+
+libgrlpocket_la_LIBADD = \
+ $(DEPS_POCKET_LIBS)
+
+libgrlpocket_la_LDFLAGS = \
+ -no-undefined \
+ -module \
+ -avoid-version
+
+libgrlpocket_la_SOURCES = \
+ grl-pocket.c \
+ grl-pocket.h \
+ gnome-pocket.h \
+ gnome-pocket.c \
+ pocketresources.h \
+ pocketresources.c
+
+pocketresources.h: pocket.gresource.xml
+ $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $(srcdir)/pocket.gresource.xml \
+ --target=$@ --sourcedir=$(srcdir) --c-name _grl_pocket --generate-header
+pocketresources.c: pocket.gresource.xml pocketresources.h channel-pocket.svg
+ $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $(srcdir)/pocket.gresource.xml \
+ --target=$@ --sourcedir=$(srcdir) --c-name _grl_pocket --generate-source
+
+extdir = $(GRL_PLUGINS_DIR)
+pocketxmldir = $(GRL_PLUGINS_DIR)
+pocketxml_DATA = $(POCKET_PLUGIN_ID).xml
+
+EXTRA_DIST = $(pocketxml_DATA) channel-pocket.svg pocket.gresource.xml
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+CLEANFILES = pocketresources.h pocketresources.c
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/pocket/channel-pocket.svg b/src/pocket/channel-pocket.svg
new file mode 100644
index 0000000..90e834c
--- /dev/null
+++ b/src/pocket/channel-pocket.svg
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="256"
+ height="256"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="channel-youtube.svg">
+ <defs
+ id="defs4">
+ <clipPath
+ id="clipPath6193"
+ clipPathUnits="userSpaceOnUse">
+ <path
+ id="path6195"
+ d="m 1600,2252.8 5020,0 0,3650 -5020,0 0,-3650 z" />
+ </clipPath>
+ <linearGradient
+ id="linearGradient6181"
+ spreadMethod="pad"
+ gradientTransform="matrix(-3.593e-5,822,822,3.593e-5,411,0)"
+ gradientUnits="userSpaceOnUse"
+ y2="0"
+ x2="1"
+ y1="0"
+ x1="0">
+ <stop
+ id="stop6183"
+ offset="0"
+ style="stop-opacity:1;stop-color:#c01e25" />
+ <stop
+ id="stop6185"
+ offset="1"
+ style="stop-opacity:1;stop-color:#e62426" />
+ </linearGradient>
+ <clipPath
+ id="clipPath6177"
+ clipPathUnits="userSpaceOnUse">
+ <path
+ id="path6179"
+ d="M 8220,0 0,0 l 0,8220 8220,0 0,-8220 m -6620,5902.8 0,-3650 5020,0 0,3650 -5020,0" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#505050"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="120.49386"
+ inkscape:cy="175.09957"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ borderlayer="true"
+ inkscape:showpageshadow="false"
+ inkscape:window-width="2560"
+ inkscape:window-height="1374"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:object-nodes="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-796.36218)">
+ <g
+ transform="matrix(630.025,0,0,-458.9,-77.5125,1218.8747)"
+ id="g6197" />
+ <path
+
style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 30.115602,817.66358 197.356268,0 8.00002,11.43214 L 235,1033.6169 l -212.5,0
-0.384417,-204.52118 z"
+ id="rect7653"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccc" />
+ <path
+
style="color:#000000;fill:#ee4055;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 70.934854,870.79411 c -8.818633,0 -15.925195,7.19797 -15.925195,16.13479 l 0,34.39697 c
0,46.31516 24.636814,78.60443 72.990311,78.60443 47.87064,0 72.99037,-32.01415 72.99037,-78.60443 l
0,-34.39697 c 0,-8.93682 -7.1065,-16.13479 -15.92514,-16.13479 z m 90.664866,36.33346 c 2.50474,0
5.01393,0.98537 6.937,2.93364 3.84636,3.89713 3.84636,10.22089 0,14.11802 l -32.84559,33.2774 c
-2.11228,2.13974 -4.94066,4.42546 -7.6911,4.21491 -2.7505,0.21336 -5.57882,-2.07517 -7.69116,-4.21491 l
-32.845583,-33.2774 c -3.846309,-3.89713 -3.846309,-10.22089 0,-14.11802 1.923126,-1.94827 4.457406,-2.90276
6.967205,-2.90276 2.50986,0 4.98384,0.95449 6.906958,2.90276 l 26.66258,27.01316 26.66252,-27.01316 c
1.92324,-1.94827 4.43236,-2.93364 6.93717,-2.93364 z"
+ id="path4636"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssssssssscssssscss" />
+ <g
+ transform="matrix(5.6146396,0,0,5.6146396,-2740.2871,170.98003)"
+ style="display:inline;enable-background:new"
+ id="g10051">
+ <path
+
style="color:#000000;fill:#75d4a5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new"
+ d="m 493.42485,115 7.57515,0 0,2.03613 -9,0 z"
+ id="rect10035"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+
style="color:#000000;fill:#50b6b1;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new"
+ d="m 501,115 10,0 0,2.03613 -10.40074,0 z"
+ id="rect10037"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+
style="color:#000000;fill:#e94055;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new"
+ d="m 511,115 10,0 0.44526,2.03613 -10.44526,0 z"
+ id="rect10039"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+
style="color:#000000;fill:#e8a945;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new"
+ d="m 521,115 7.57515,0 1.42485,2.03613 -8.59926,0 z"
+ id="rect10041"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ </g>
+ </g>
+</svg>
diff --git a/src/pocket/gnome-pocket.c b/src/pocket/gnome-pocket.c
new file mode 100644
index 0000000..97ef8f5
--- /dev/null
+++ b/src/pocket/gnome-pocket.c
@@ -0,0 +1,934 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2013 Bastien Nocera <hadess hadess net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any pocket version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Implementation of:
+ * http://getpocket.com/developer/docs/overview
+ */
+
+//#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#define GOA_API_IS_SUBJECT_TO_CHANGE 1
+#include <goa/goa.h>
+#include <rest/rest-proxy.h>
+#include <rest/rest-proxy-call.h>
+#include <json-glib/json-glib.h>
+
+#include "gnome-pocket.h"
+
+static gpointer
+gnome_pocket_item_copy (gpointer boxed)
+{
+ GnomePocketItem *src = boxed;
+
+ GnomePocketItem *dest = g_new (GnomePocketItem, 1);
+ dest->id = g_strdup (src->id);
+ dest->url = g_strdup (src->url);
+ dest->title = g_strdup (src->title);
+ dest->favorite = src->favorite;
+ dest->status = src->status;
+ dest->is_article = src->is_article;
+ dest->has_image = src->has_image;
+ dest->has_video = src->has_video;
+ dest->time_added = src->time_added;
+ dest->tags = g_strdupv (src->tags);
+
+ return dest;
+}
+
+static void
+gnome_pocket_item_free (gpointer boxed)
+{
+ GnomePocketItem *item = boxed;
+
+ g_free (item->id);
+ g_free (item->url);
+ g_free (item->title);
+ g_strfreev (item->tags);
+ g_free (item);
+}
+
+static char *
+get_string_for_element (JsonReader *reader,
+ const char *element)
+{
+ char *ret;
+
+ if (!json_reader_read_member (reader, element)) {
+ json_reader_end_member (reader);
+ return NULL;
+ }
+ ret = g_strdup (json_reader_get_string_value (reader));
+ if (ret && *ret == '\0')
+ g_clear_pointer (&ret, g_free);
+ json_reader_end_member (reader);
+
+ return ret;
+}
+
+static int
+get_int_for_element (JsonReader *reader,
+ const char *element)
+{
+ int ret;
+
+ if (!json_reader_read_member (reader, element)) {
+ json_reader_end_member (reader);
+ return -1;
+ }
+ ret = atoi (json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
+
+ return ret;
+}
+
+static gint64
+get_time_added (JsonReader *reader)
+{
+ gint64 ret;
+
+ if (!json_reader_read_member (reader, "time_added")) {
+ json_reader_end_member (reader);
+ return -1;
+ }
+ ret = g_ascii_strtoll (json_reader_get_string_value (reader), NULL, 0);
+ json_reader_end_member (reader);
+
+ return ret;
+}
+
+static GnomePocketItem *
+parse_item (JsonReader *reader)
+{
+ GnomePocketItem *item;
+
+ item = g_new0 (GnomePocketItem, 1);
+ item->id = g_strdup (json_reader_get_member_name (reader));
+ if (!item->id)
+ goto bail;
+
+ /* If the item is archived or deleted, we don't need
+ * anything more here */
+ item->status = get_int_for_element (reader, "status");
+ if (item->status != POCKET_STATUS_NORMAL)
+ goto end;
+
+ item->url = get_string_for_element (reader, "resolved_url");
+ if (!item->url)
+ item->url = get_string_for_element (reader, "given_url");
+
+ item->title = get_string_for_element (reader, "resolved_title");
+ if (!item->title)
+ item->title = get_string_for_element (reader, "given_title");
+ if (!item->title)
+ item->title = g_strdup ("PLACEHOLDER"); /* FIXME generate from URL */
+
+ item->favorite = get_int_for_element (reader, "favorite");
+ item->is_article = get_int_for_element (reader, "is_article");
+ if (item->is_article == -1)
+ item->is_article = FALSE;
+ item->has_image = get_int_for_element (reader, "has_image");
+ if (item->has_image == -1)
+ item->has_image = POCKET_HAS_MEDIA_FALSE;
+ item->has_video = get_int_for_element (reader, "has_video");
+ if (item->has_video == -1)
+ item->has_video = POCKET_HAS_MEDIA_FALSE;
+
+ item->time_added = get_time_added (reader);
+
+#if 0
+ if (!json_reader_read_member (reader, "tags")) {
+ json_reader_end_member (reader);
+ goto bail;
+ }
+ item->tags = ;
+ json_reader_end_member (reader);
+#endif
+
+ goto end;
+
+bail:
+ g_clear_pointer (&item, gnome_pocket_item_free);
+
+end:
+ return item;
+}
+
+GnomePocketItem *
+gnome_pocket_item_from_string (const char *str)
+{
+ JsonParser *parser;
+ JsonReader *reader;
+ GnomePocketItem *item = NULL;
+ char **members = NULL;
+
+ parser = json_parser_new ();
+ if (!json_parser_load_from_data (parser, str, -1, NULL))
+ return NULL;
+
+ reader = json_reader_new (json_parser_get_root (parser));
+ members = json_reader_list_members (reader);
+ if (!members)
+ goto bail;
+
+ if (!json_reader_read_member (reader, members[0]))
+ goto bail;
+
+ item = parse_item (reader);
+
+bail:
+ g_clear_pointer (&members, g_strfreev);
+ g_clear_object (&reader);
+ g_clear_object (&parser);
+
+ return item;
+}
+
+static void
+builder_add_int_as_str (JsonBuilder *builder,
+ const char *name,
+ int value)
+{
+ char *tmp;
+
+ json_builder_set_member_name (builder, name);
+ tmp = g_strdup_printf ("%d", value);
+ json_builder_add_string_value (builder, tmp);
+ g_free (tmp);
+}
+
+static void
+builder_add_int64_as_str (JsonBuilder *builder,
+ const char *name,
+ gint64 value)
+{
+ char *tmp;
+
+ json_builder_set_member_name (builder, name);
+ tmp = g_strdup_printf ("%" G_GINT64_FORMAT, value);
+ json_builder_add_string_value (builder, tmp);
+ g_free (tmp);
+}
+
+char *
+gnome_pocket_item_to_string (GnomePocketItem *item)
+{
+ char *ret = NULL;
+ JsonBuilder *builder;
+ JsonGenerator *gen;
+ JsonNode *root;
+
+ builder = json_builder_new ();
+
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, item->id);
+
+ json_builder_begin_object (builder);
+
+ json_builder_set_member_name (builder, "item_id");
+ json_builder_add_string_value (builder, item->id);
+
+ json_builder_set_member_name (builder, "resolved_url");
+ json_builder_add_string_value (builder, item->url);
+
+ json_builder_set_member_name (builder, "resolved_title");
+ json_builder_add_string_value (builder, item->title);
+
+ builder_add_int_as_str (builder, "favorite", item->favorite);
+ builder_add_int_as_str (builder, "status", item->status);
+ builder_add_int_as_str (builder, "is_article", item->is_article);
+ builder_add_int_as_str (builder, "has_image", item->has_image);
+ builder_add_int_as_str (builder, "has_video", item->has_video);
+ builder_add_int64_as_str (builder, "time_added", item->time_added);
+
+ json_builder_end_object (builder);
+ json_builder_end_object (builder);
+
+ gen = json_generator_new ();
+ root = json_builder_get_root (builder);
+ json_generator_set_root (gen, root);
+ ret = json_generator_to_data (gen, NULL);
+
+ json_node_free (root);
+ g_object_unref (gen);
+ g_object_unref (builder);
+
+ return ret;
+}
+
+G_DEFINE_BOXED_TYPE(GnomePocketItem, gnome_pocket_item, gnome_pocket_item_copy, gnome_pocket_item_free)
+
+enum {
+ PROP_0,
+ PROP_AVAILABLE
+};
+
+G_DEFINE_TYPE (GnomePocket, gnome_pocket, G_TYPE_OBJECT);
+
+#define GNOME_POCKET_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_TYPE_POCKET,
GnomePocketPrivate))
+
+struct _GnomePocketPrivate {
+ GCancellable *cancellable;
+ GoaClient *client;
+ GoaOAuth2Based *oauth2;
+ char *access_token;
+ char *consumer_key;
+ RestProxy *proxy;
+
+ /* List data */
+ gboolean cache_loaded;
+ gint64 since;
+ GList *items; /* GnomePocketItem */
+};
+
+gboolean
+gnome_pocket_refresh_finish (GnomePocket *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ gboolean ret = FALSE;
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == gnome_pocket_refresh);
+
+ if (!g_simple_async_result_propagate_error (simple, error))
+ ret = g_simple_async_result_get_op_res_gboolean (simple);
+
+ return ret;
+}
+
+/* FIXME */
+static char *cache_path = NULL;
+
+static char *
+gnome_pocket_item_get_path (GnomePocketItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+ return g_build_filename (cache_path, item->id, NULL);
+}
+
+static void
+gnome_pocket_item_save (GnomePocketItem *item)
+{
+ char *path;
+ char *str;
+
+ g_return_if_fail (item != NULL);
+
+ path = gnome_pocket_item_get_path (item);
+ str = gnome_pocket_item_to_string (item);
+ g_file_set_contents (path, str, -1, NULL);
+ g_free (str);
+ g_free (path);
+}
+
+static void
+gnome_pocket_item_remove (GnomePocketItem *item)
+{
+ char *path;
+
+ g_return_if_fail (item != NULL);
+
+ path = gnome_pocket_item_get_path (item);
+ g_unlink (path);
+ g_free (path);
+}
+
+static void
+update_list (GnomePocket *self,
+ GList *updated_items)
+{
+ GHashTable *removed; /* key=id, value=gboolean */
+ GList *l;
+
+ if (updated_items == NULL)
+ return;
+
+ removed = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ for (l = updated_items; l != NULL; l = l->next) {
+ GnomePocketItem *item = l->data;
+
+ if (item->status != POCKET_STATUS_NORMAL) {
+ g_hash_table_insert (removed,
+ g_strdup (item->id),
+ GINT_TO_POINTER (1));
+ gnome_pocket_item_free (item);
+ } else {
+ self->priv->items = g_list_prepend (self->priv->items, item);
+ gnome_pocket_item_save (item);
+ }
+ }
+
+ /* And remove the old items */
+ for (l = self->priv->items; l != NULL; l = l->next) {
+ GnomePocketItem *item = l->data;
+
+ if (g_hash_table_lookup (removed, item->id)) {
+ /* Item got removed */
+ self->priv->items = g_list_delete_link (self->priv->items, l);
+
+ gnome_pocket_item_remove (item);
+ gnome_pocket_item_free (item);
+ }
+ }
+
+ g_hash_table_destroy (removed);
+}
+
+static gint64
+load_since (GnomePocket *self)
+{
+ char *path;
+ char *contents = NULL;
+ gint64 since = 0;
+
+ path = g_build_filename (cache_path, "since", NULL);
+ g_file_get_contents (path, &contents, NULL, NULL);
+ g_free (path);
+
+ if (contents != NULL) {
+ since = g_ascii_strtoll (contents, NULL, 0);
+ g_free (contents);
+ }
+
+ return since;
+}
+
+static void
+save_since (GnomePocket *self)
+{
+ char *str;
+ char *path;
+
+ if (self->priv->since == 0)
+ return;
+
+ str = g_strdup_printf ("%" G_GINT64_FORMAT, self->priv->since);
+ path = g_build_filename (cache_path, "since", NULL);
+ g_file_set_contents (path, str, -1, NULL);
+ g_free (path);
+ g_free (str);
+}
+
+static GList *
+parse_json (JsonParser *parser,
+ gint64 *since)
+{
+ JsonReader *reader;
+ GList *ret;
+ int num;
+
+ reader = json_reader_new (json_parser_get_root (parser));
+ *since = 0;
+ ret = NULL;
+
+ num = json_reader_count_members (reader);
+ if (num < 0)
+ goto bail;
+
+ /* Grab the since */
+ if (json_reader_read_member (reader, "since"))
+ *since = json_reader_get_int_value (reader);
+ json_reader_end_member (reader);
+
+ /* Grab the list */
+ if (json_reader_read_member (reader, "list")) {
+ char **members;
+ guint i;
+
+ members = json_reader_list_members (reader);
+ if (members != NULL) {
+ for (i = 0; members[i] != NULL; i++) {
+ GnomePocketItem *item;
+
+ if (!json_reader_read_member (reader, members[i])) {
+ json_reader_end_member (reader);
+ continue;
+ }
+ item = parse_item (reader);
+ if (item)
+ ret = g_list_prepend (ret, item);
+ json_reader_end_member (reader);
+ }
+ }
+ g_strfreev (members);
+ }
+ json_reader_end_member (reader);
+
+bail:
+ g_clear_object (&reader);
+ return ret;
+}
+
+static void
+refresh_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GSimpleAsyncResult *simple = user_data;
+ gboolean ret;
+
+ ret = rest_proxy_call_invoke_finish (REST_PROXY_CALL (object), res, &error);
+ if (!ret) {
+ g_simple_async_result_set_from_error (simple, error);
+ } else {
+ JsonParser *parser;
+
+ parser = json_parser_new ();
+ if (json_parser_load_from_data (parser,
+ rest_proxy_call_get_payload (REST_PROXY_CALL (object)),
+ rest_proxy_call_get_payload_length (REST_PROXY_CALL (object)),
+ NULL)) {
+ GList *updated_items;
+ GnomePocket *self;
+
+ self = GNOME_POCKET (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
+ updated_items = parse_json (parser, &self->priv->since);
+ if (self->priv->since != 0)
+ save_since (self);
+ update_list (self, updated_items);
+ }
+ g_object_unref (parser);
+ }
+ g_simple_async_result_set_op_res_gboolean (simple, ret);
+
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ g_clear_error (&error);
+}
+
+void
+gnome_pocket_refresh (GnomePocket *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ RestProxyCall *call;
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (GNOME_IS_POCKET (self));
+ g_return_if_fail (self->priv->consumer_key && self->priv->access_token);
+
+ simple = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ gnome_pocket_refresh);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+ call = rest_proxy_new_call (self->priv->proxy);
+ rest_proxy_call_set_method (call, "POST");
+ rest_proxy_call_set_function (call, "v3/get");
+ rest_proxy_call_add_param (call, "consumer_key", self->priv->consumer_key);
+ rest_proxy_call_add_param (call, "access_token", self->priv->access_token);
+
+ if (self->priv->since > 0) {
+ char *since;
+ since = g_strdup_printf ("%" G_GINT64_FORMAT, self->priv->since);
+ rest_proxy_call_add_param (call, "since", since);
+ g_free (since);
+ }
+
+ rest_proxy_call_invoke_async (call, cancellable, refresh_cb, simple);
+}
+
+gboolean
+gnome_pocket_add_url_finish (GnomePocket *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ gboolean ret = FALSE;
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == gnome_pocket_add_url);
+
+ if (!g_simple_async_result_propagate_error (simple, error))
+ ret = g_simple_async_result_get_op_res_gboolean (simple);
+
+ return ret;
+}
+
+static void
+add_url_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GSimpleAsyncResult *simple = user_data;
+ gboolean ret;
+
+ ret = rest_proxy_call_invoke_finish (REST_PROXY_CALL (object), res, &error);
+ if (!ret)
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_set_op_res_gboolean (simple, ret);
+
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ g_clear_error (&error);
+}
+
+void
+gnome_pocket_add_url (GnomePocket *self,
+ const char *url,
+ const char *tweet_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ RestProxyCall *call;
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (GNOME_IS_POCKET (self));
+ g_return_if_fail (url);
+ g_return_if_fail (self->priv->consumer_key && self->priv->access_token);
+
+ simple = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ gnome_pocket_add_url);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+ call = rest_proxy_new_call (self->priv->proxy);
+ rest_proxy_call_set_method (call, "POST");
+ rest_proxy_call_set_function (call, "v3/add");
+ rest_proxy_call_add_param (call, "consumer_key", self->priv->consumer_key);
+ rest_proxy_call_add_param (call, "access_token", self->priv->access_token);
+ rest_proxy_call_add_param (call, "url", url);
+ if (tweet_id)
+ rest_proxy_call_add_param (call, "tweet_id", tweet_id);
+
+ rest_proxy_call_invoke_async (call, cancellable, add_url_cb, simple);
+}
+
+static void
+parse_cached_data (GnomePocket *self)
+{
+ GDir *dir;
+ const char *name;
+
+ dir = g_dir_open (cache_path, 0, NULL);
+ if (!dir)
+ return;
+
+ self->priv->since = load_since (self);
+
+ name = g_dir_read_name (dir);
+ while (name) {
+ JsonParser *parser;
+ JsonReader *reader;
+ char *full_path = NULL;
+ char **members;
+ GnomePocketItem *item;
+
+ if (g_strcmp0 (name, "since") == 0)
+ goto next;
+
+ full_path = g_build_filename (cache_path, name, NULL);
+ parser = json_parser_new ();
+ if (!json_parser_load_from_file (parser, full_path, NULL))
+ goto next;
+
+ reader = json_reader_new (json_parser_get_root (parser));
+ members = json_reader_list_members (reader);
+ if (!members)
+ goto next;
+
+ if (!json_reader_read_member (reader, members[0]))
+ goto next;
+
+ item = parse_item (reader);
+ if (!item)
+ g_warning ("Could not parse cached file '%s'", full_path);
+ else
+ self->priv->items = g_list_prepend (self->priv->items, item);
+
+next:
+ g_clear_object (&reader);
+ g_clear_object (&parser);
+ g_free (full_path);
+
+ name = g_dir_read_name (dir);
+ }
+ g_dir_close (dir);
+}
+
+static void
+load_cached_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GnomePocket *self = GNOME_POCKET (source_object);
+
+ parse_cached_data (self);
+ self->priv->cache_loaded = TRUE;
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+gnome_pocket_load_cached (GnomePocket *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (GNOME_IS_POCKET (self));
+ g_return_if_fail (!self->priv->cache_loaded);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_run_in_thread (task, load_cached_thread);
+ g_object_unref (task);
+}
+
+gboolean
+gnome_pocket_load_cached_finish (GnomePocket *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GTask *task = G_TASK (res);
+
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+
+ return g_task_propagate_boolean (task, error);
+}
+
+GList *
+gnome_pocket_get_items (GnomePocket *self)
+{
+ g_return_val_if_fail (self->priv->cache_loaded, NULL);
+ return self->priv->items;
+}
+
+static void
+gnome_pocket_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GnomePocket *self = GNOME_POCKET (object);
+
+ switch (property_id) {
+ case PROP_AVAILABLE:
+ g_value_set_boolean (value, self->priv->access_token && self->priv->consumer_key);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gnome_pocket_finalize (GObject *object)
+{
+ GnomePocketPrivate *priv = GNOME_POCKET (object)->priv;
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+
+ g_clear_object (&priv->proxy);
+ g_clear_object (&priv->oauth2);
+ g_clear_object (&priv->client);
+ g_clear_pointer (&priv->access_token, g_free);
+ g_clear_pointer (&priv->consumer_key, g_free);
+
+ G_OBJECT_CLASS (gnome_pocket_parent_class)->finalize (object);
+}
+
+static void
+gnome_pocket_class_init (GnomePocketClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ cache_path = g_build_filename (g_get_user_cache_dir (), "pocket", NULL);
+ g_mkdir_with_parents (cache_path, 0700);
+
+ object_class->get_property = gnome_pocket_get_property;
+ object_class->finalize = gnome_pocket_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_AVAILABLE,
+ g_param_spec_boolean ("available",
+ "Available",
+ "If Read Pocket is available",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private (object_class, sizeof (GnomePocketPrivate));
+}
+
+static void
+got_access_token (GObject *object,
+ GAsyncResult *res,
+ GnomePocket *self)
+{
+ GError *error = NULL;
+ char *access_token;
+
+ if (!goa_oauth2_based_call_get_access_token_finish (GOA_OAUTH2_BASED (object),
+ &access_token,
+ NULL,
+ res,
+ &error)) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free (error);
+ return;
+ }
+ g_warning ("Failed to get access token: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ self->priv->access_token = access_token;
+ self->priv->consumer_key = goa_oauth2_based_dup_client_id (GOA_OAUTH2_BASED (object));
+
+ g_object_notify (G_OBJECT (self), "available");
+}
+
+static void
+handle_accounts (GnomePocket *self)
+{
+ GList *accounts, *l;
+ GoaOAuth2Based *oauth2 = NULL;
+
+ g_clear_object (&self->priv->oauth2);
+ g_clear_pointer (&self->priv->access_token, g_free);
+ g_clear_pointer (&self->priv->consumer_key, g_free);
+
+ accounts = goa_client_get_accounts (self->priv->client);
+
+ for (l = accounts; l != NULL; l = l->next) {
+ GoaObject *object = GOA_OBJECT (l->data);
+ GoaAccount *account;
+
+ account = goa_object_peek_account (object);
+
+ /* Find a Pocket account that doesn't have "Read Pocket" disabled */
+ if (g_strcmp0 (goa_account_get_provider_type (account), "pocket") == 0 &&
+ !goa_account_get_read_later_disabled (account)) {
+ oauth2 = goa_object_get_oauth2_based (object);
+ break;
+ }
+ }
+
+ g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
+
+ if (!oauth2) {
+ g_object_notify (G_OBJECT (self), "available");
+ g_debug ("Could not find a Pocket account");
+ return;
+ }
+
+ self->priv->oauth2 = oauth2;
+
+ goa_oauth2_based_call_get_access_token (oauth2,
+ self->priv->cancellable,
+ (GAsyncReadyCallback) got_access_token,
+ self);
+}
+
+static void
+account_added_cb (GoaClient *client,
+ GoaObject *object,
+ GnomePocket *self)
+{
+ if (self->priv->oauth2 != NULL) {
+ /* Don't care, already have an account */
+ return;
+ }
+
+ handle_accounts (self);
+}
+
+static void
+account_changed_cb (GoaClient *client,
+ GoaObject *object,
+ GnomePocket *self)
+{
+ GoaOAuth2Based *oauth2;
+
+ oauth2 = goa_object_get_oauth2_based (object);
+ if (oauth2 == self->priv->oauth2)
+ handle_accounts (self);
+
+ g_object_unref (oauth2);
+}
+
+static void
+account_removed_cb (GoaClient *client,
+ GoaObject *object,
+ GnomePocket *self)
+{
+ GoaOAuth2Based *oauth2;
+
+ oauth2 = goa_object_get_oauth2_based (object);
+ if (oauth2 == self->priv->oauth2)
+ handle_accounts (self);
+
+ g_object_unref (oauth2);
+}
+
+static void
+client_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ GnomePocket *self)
+{
+ GoaClient *client;
+ GError *error = NULL;
+
+ client = goa_client_new_finish (res, &error);
+ if (client == NULL) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free (error);
+ return;
+ }
+ g_warning ("Failed to get GoaClient: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ self->priv->client = client;
+ g_signal_connect (self->priv->client, "account-added",
+ G_CALLBACK (account_added_cb), self);
+ g_signal_connect (self->priv->client, "account-changed",
+ G_CALLBACK (account_changed_cb), self);
+ g_signal_connect (self->priv->client, "account-removed",
+ G_CALLBACK (account_removed_cb), self);
+
+ handle_accounts (self);
+}
+
+static void
+gnome_pocket_init (GnomePocket *self)
+{
+ self->priv = GNOME_POCKET_GET_PRIVATE (self);
+ self->priv->cancellable = g_cancellable_new ();
+ self->priv->proxy = rest_proxy_new ("https://getpocket.com/", FALSE);
+
+ goa_client_new (self->priv->cancellable,
+ (GAsyncReadyCallback) client_ready_cb, self);
+}
+
+GnomePocket *
+gnome_pocket_new (void)
+{
+ return g_object_new (GNOME_TYPE_POCKET, NULL);
+}
diff --git a/src/pocket/gnome-pocket.h b/src/pocket/gnome-pocket.h
new file mode 100644
index 0000000..1f16025
--- /dev/null
+++ b/src/pocket/gnome-pocket.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2013 Bastien Nocera <hadess hadess net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any pocket version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GNOME_POCKET_H
+#define GNOME_POCKET_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define POCKET_FAVORITE TRUE
+#define POCKET_NOT_FAVORITE FALSE
+
+typedef enum {
+ POCKET_STATUS_NORMAL = 0,
+ POCKET_STATUS_ARCHIVED = 1,
+ POCKET_STATUS_DELETED = 2
+} PocketItemStatus;
+
+#define POCKET_IS_ARTICLE TRUE
+#define POCKET_IS_NOT_ARTICLE FALSE
+
+typedef enum {
+ POCKET_HAS_MEDIA_FALSE = 0,
+ POCKET_HAS_MEDIA_INCLUDED = 1,
+ POCKET_IS_MEDIA = 2
+} PocketMediaInclusion;
+
+typedef struct {
+ char *id;
+ char *url;
+ char *title;
+ gboolean favorite;
+ PocketItemStatus status;
+ gboolean is_article;
+ PocketMediaInclusion has_image;
+ PocketMediaInclusion has_video;
+ gint64 time_added;
+ char **tags;
+} GnomePocketItem;
+
+#define GNOME_TYPE_POCKET_ITEM (gnome_pocket_item_get_type ())
+
+GType gnome_pocket_item_get_type (void) G_GNUC_CONST;
+char *gnome_pocket_item_to_string (GnomePocketItem *item);
+GnomePocketItem *gnome_pocket_item_from_string (const char *str);
+
+#define GNOME_TYPE_POCKET (gnome_pocket_get_type ())
+#define GNOME_POCKET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GNOME_TYPE_POCKET, GnomePocket))
+#define GNOME_POCKET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GNOME_TYPE_POCKET, GnomePocketClass))
+#define GNOME_IS_POCKET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNOME_TYPE_POCKET))
+#define GNOME_IS_POCKET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GNOME_TYPE_POCKET))
+#define GNOME_POCKET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GNOME_TYPE_POCKET, GnomePocketClass))
+
+typedef struct _GnomePocketPrivate GnomePocketPrivate;
+
+typedef struct
+{
+ GObject parent;
+
+ /*< private >*/
+ GnomePocketPrivate *priv;
+} GnomePocket;
+
+typedef struct
+{
+ GObjectClass parent;
+} GnomePocketClass;
+
+GType gnome_pocket_get_type (void);
+
+GnomePocket *gnome_pocket_new (void);
+void gnome_pocket_add_url (GnomePocket *self,
+ const char *url,
+ const char *tweet_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gnome_pocket_add_url_finish (GnomePocket *self,
+ GAsyncResult *res,
+ GError **error);
+void gnome_pocket_refresh (GnomePocket *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gnome_pocket_refresh_finish (GnomePocket *self,
+ GAsyncResult *res,
+ GError **error);
+GList *gnome_pocket_get_items (GnomePocket *self);
+
+void gnome_pocket_load_cached (GnomePocket *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gnome_pocket_load_cached_finish (GnomePocket *self,
+ GAsyncResult *res,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* GNOME_POCKET_H */
diff --git a/src/pocket/grl-pocket.c b/src/pocket/grl-pocket.c
new file mode 100644
index 0000000..99b2578
--- /dev/null
+++ b/src/pocket/grl-pocket.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2013 Bastien Nocera
+ *
+ * Contact: Bastien Nocera <hadess hadess net>
+ *
+ * This library 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; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <grilo.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <totem-pl-parser-mini.h>
+
+#include "grl-pocket.h"
+#include "gnome-pocket.h"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT pocket_log_domain
+GRL_LOG_DOMAIN_STATIC(pocket_log_domain);
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID POCKET_PLUGIN_ID
+
+#define SOURCE_ID "grl-pocket"
+#define SOURCE_NAME _("Pocket")
+#define SOURCE_DESC _("A source for browsing Pocket videos")
+
+/* --- Grilo Pocket Private --- */
+
+#define GRL_POCKET_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_POCKET_SOURCE_TYPE, \
+ GrlPocketSourcePrivate))
+
+struct _GrlPocketSourcePrivate {
+ GnomePocket *pocket;
+ gboolean cache_loaded;
+};
+
+/* --- Data types --- */
+
+typedef struct {
+ GrlSourceBrowseSpec *bs;
+ GCancellable *cancellable;
+ GrlPocketSource *source;
+} OperationData;
+
+static GrlPocketSource *grl_pocket_source_new (GnomePocket *pocket);
+
+static void grl_pocket_source_finalize (GObject *object);
+
+gboolean grl_pocket_plugin_init (GrlRegistry *registry,
+ GrlPlugin *plugin,
+ GList *configs);
+
+static const GList *grl_pocket_source_supported_keys (GrlSource *source);
+
+static void grl_pocket_source_cancel (GrlSource *source,
+ guint operation_id);
+static void grl_pocket_source_browse (GrlSource *source,
+ GrlSourceBrowseSpec *bs);
+
+/* =================== Pocket Plugin =============== */
+
+static void
+is_available (GObject *gobject,
+ GParamSpec *pspec,
+ GrlPlugin *plugin)
+{
+ gboolean avail;
+ GrlPocketSource *source;
+ GnomePocket *pocket;
+
+ source = g_object_get_data (G_OBJECT (plugin), "source");
+ pocket = g_object_get_data (G_OBJECT (plugin), "pocket");
+ g_object_get (pocket, "available", &avail, NULL);
+
+ if (!avail) {
+ GrlRegistry *registry;
+
+ if (source == NULL)
+ return;
+
+ GRL_DEBUG ("Removing Pocket");
+
+ registry = grl_registry_get_default ();
+
+ grl_registry_unregister_source (registry,
+ GRL_SOURCE (source),
+ NULL);
+ } else {
+ GrlRegistry *registry;
+
+ if (source != NULL)
+ return;
+
+ GRL_DEBUG ("Adding Pocket");
+
+ source = grl_pocket_source_new (pocket);
+ registry = grl_registry_get_default ();
+
+ g_object_set_data (G_OBJECT (plugin), "source", source);
+ grl_registry_register_source (registry,
+ plugin,
+ GRL_SOURCE (source),
+ NULL);
+ }
+}
+
+gboolean
+grl_pocket_plugin_init (GrlRegistry *registry,
+ GrlPlugin *plugin,
+ GList *configs)
+{
+ GnomePocket *pocket;
+
+ GRL_LOG_DOMAIN_INIT (pocket_log_domain, "pocket");
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ /* Initialize i18n */
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ pocket = gnome_pocket_new ();
+ g_object_set_data (G_OBJECT (plugin), "pocket", pocket);
+ g_signal_connect (pocket, "notify::available",
+ G_CALLBACK (is_available), plugin);
+
+ return TRUE;
+}
+
+static void
+grl_pocket_plugin_deinit (GrlPlugin *plugin)
+{
+ GnomePocket *pocket;
+
+ GRL_DEBUG ("grl_pocket_plugin_deinit");
+
+ pocket = g_object_get_data (G_OBJECT (plugin), "pocket");
+ g_clear_object (&pocket);
+ g_object_set_data (G_OBJECT (plugin), "pocket", NULL);
+}
+
+GRL_PLUGIN_REGISTER (grl_pocket_plugin_init,
+ grl_pocket_plugin_deinit,
+ PLUGIN_ID);
+
+/* ================== Pocket GObject ================ */
+
+
+G_DEFINE_TYPE (GrlPocketSource,
+ grl_pocket_source,
+ GRL_TYPE_SOURCE);
+
+static GrlPocketSource *
+grl_pocket_source_new (GnomePocket *pocket)
+{
+ GIcon *icon;
+ GFile *file;
+ GrlPocketSource *object;
+
+ g_return_val_if_fail (GNOME_IS_POCKET (pocket), NULL);
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ file = g_file_new_for_uri ("resource:///org/gnome/grilo/plugins/pocket/channel-pocket.svg");
+ icon = g_file_icon_new (file);
+ g_object_unref (file);
+ object = g_object_new (GRL_POCKET_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ "supported-media", GRL_MEDIA_TYPE_VIDEO,
+ "source-icon", icon,
+ NULL);
+ GRL_POCKET_SOURCE (object)->priv->pocket = pocket;
+
+ return object;
+}
+
+static void
+grl_pocket_source_class_init (GrlPocketSourceClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+ object_class->finalize = grl_pocket_source_finalize;
+
+ source_class->supported_keys = grl_pocket_source_supported_keys;
+ source_class->browse = grl_pocket_source_browse;
+ source_class->cancel = grl_pocket_source_cancel;
+
+ g_type_class_add_private (klass, sizeof (GrlPocketSourcePrivate));
+}
+
+static void
+grl_pocket_source_init (GrlPocketSource *source)
+{
+ source->priv = GRL_POCKET_SOURCE_GET_PRIVATE(source);
+}
+
+static void
+grl_pocket_source_finalize (GObject *object)
+{
+ GrlPocketSource *source = GRL_POCKET_SOURCE (object);
+ GrlPocketSourcePrivate *priv = GRL_POCKET_SOURCE (source)->priv;
+
+ G_OBJECT_CLASS (grl_pocket_source_parent_class)->finalize (object);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_pocket_source_supported_keys (GrlSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_FAVOURITE,
+ GRL_METADATA_KEY_CREATION_DATE,
+ NULL);
+ }
+ return keys;
+}
+
+static GrlMedia *
+item_to_media (GnomePocketItem *item)
+{
+ GrlMedia *ret;
+ GDateTime *date;
+ gboolean has_video;
+
+ /* There are a few false positives, but this makes detection
+ * much faster as well */
+ has_video = (item->has_video == POCKET_HAS_MEDIA_INCLUDED ||
+ item->has_video == POCKET_IS_MEDIA);
+ if (!has_video) {
+ GRL_DEBUG ("Ignoring ID %s as it wasn't detected as a video, or as including a video (URL: %s)",
+ item->id, item->url);
+ return NULL;
+ }
+
+ if (!totem_pl_parser_can_parse_from_uri (item->url, FALSE)) {
+ GRL_DEBUG ("Ignoring ID %s as it wasn't detected as from a videosite (URL: %s)",
+ item->id, item->url);
+ return NULL;
+ }
+
+ ret = grl_media_video_new ();
+ grl_media_set_url (ret, item->url);
+ grl_media_set_title (ret, item->title);
+ grl_media_set_favourite (ret, item->favorite);
+
+ date = g_date_time_new_from_unix_utc (item->time_added);
+ grl_media_set_creation_date (ret, date);
+ g_date_time_unref (date);
+
+ return ret;
+}
+
+static void
+refresh_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ OperationData *op_data = user_data;
+ GError *error = NULL;
+ GList *items, *l;
+
+ if (gnome_pocket_refresh_finish (op_data->source->priv->pocket,
+ res, &error) == FALSE) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ goto out;
+ }
+
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->operation_id,
+ NULL,
+ 0,
+ op_data->bs->user_data,
+ error);
+ goto out;
+ }
+
+ items = gnome_pocket_get_items (op_data->source->priv->pocket);
+ for (l = items; l != NULL; l = l->next) {
+ GnomePocketItem *item = l->data;
+ GrlMedia *media;
+
+ media = item_to_media (item);
+ if (media == NULL)
+ continue;
+
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->operation_id,
+ media,
+ GRL_SOURCE_REMAINING_UNKNOWN,
+ op_data->bs->user_data,
+ NULL);
+ }
+
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->operation_id,
+ NULL,
+ 0,
+ op_data->bs->user_data,
+ NULL);
+
+out:
+ g_clear_object (&op_data->cancellable);
+ g_slice_free (OperationData, op_data);
+}
+
+static void
+load_cached_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ OperationData *op_data = user_data;
+ GError *error = NULL;
+
+ if (gnome_pocket_load_cached_finish (op_data->source->priv->pocket,
+ res, &error) == FALSE) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ goto out;
+ }
+ }
+
+ op_data->source->priv->cache_loaded = TRUE;
+ gnome_pocket_refresh (op_data->source->priv->pocket,
+ op_data->cancellable,
+ refresh_cb,
+ op_data);
+ return;
+
+out:
+ g_clear_object (&op_data->cancellable);
+ g_slice_free (OperationData, op_data);
+}
+
+static void
+grl_pocket_source_browse (GrlSource *source,
+ GrlSourceBrowseSpec *bs)
+{
+ GrlPocketSourcePrivate *priv = GRL_POCKET_SOURCE (source)->priv;
+ OperationData *op_data;
+
+ GRL_DEBUG (__FUNCTION__);
+
+ op_data = g_slice_new0 (OperationData);
+ op_data->bs = bs;
+ op_data->cancellable = g_cancellable_new ();
+ op_data->source = GRL_POCKET_SOURCE (source);
+ grl_operation_set_data (bs->operation_id, op_data);
+
+ if (!priv->cache_loaded) {
+ gnome_pocket_load_cached (priv->pocket,
+ op_data->cancellable,
+ load_cached_cb,
+ op_data);
+ } else {
+ gnome_pocket_refresh (priv->pocket,
+ op_data->cancellable,
+ refresh_cb,
+ op_data);
+ }
+}
+
+static void
+grl_pocket_source_cancel (GrlSource *source,
+ guint operation_id)
+{
+ OperationData *op_data;
+
+ GRL_DEBUG ("grl_pocket_source_cancel");
+
+ op_data = (OperationData *) grl_operation_get_data (operation_id);
+ if (op_data)
+ g_cancellable_cancel (op_data->cancellable);
+}
diff --git a/src/pocket/grl-pocket.h b/src/pocket/grl-pocket.h
new file mode 100644
index 0000000..7c51530
--- /dev/null
+++ b/src/pocket/grl-pocket.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 Bastien Nocera
+ *
+ * Contact: Bastien Nocera <hadess hadess net>
+ *
+ * This library 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; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_POCKET_SOURCE_H_
+#define _GRL_POCKET_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_POCKET_SOURCE_TYPE \
+ (grl_pocket_source_get_type ())
+
+#define GRL_POCKET_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_POCKET_SOURCE_TYPE, \
+ GrlPocketSource))
+
+#define GRL_IS_POCKET_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_POCKET_SOURCE_TYPE))
+
+#define GRL_POCKET_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_POCKET_SOURCE_TYPE, \
+ GrlPocketSourceClass))
+
+#define GRL_IS_POCKET_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass) \
+ GRL_POCKET_SOURCE_TYPE))
+
+#define GRL_POCKET_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_POCKET_SOURCE_TYPE, \
+ GrlPocketSourceClass))
+
+
+typedef struct _GrlPocketSource GrlPocketSource;
+typedef struct _GrlPocketSourcePrivate GrlPocketSourcePrivate;
+
+struct _GrlPocketSource {
+
+ GrlSource parent;
+
+ /*< private >*/
+ GrlPocketSourcePrivate *priv;
+};
+
+typedef struct _GrlPocketSourceClass GrlPocketSourceClass;
+
+struct _GrlPocketSourceClass {
+
+ GrlSourceClass parent_class;
+
+};
+
+GType grl_pocket_source_get_type (void);
+
+#endif /* _GRL_POCKET_SOURCE_H_ */
diff --git a/src/pocket/grl-pocket.xml b/src/pocket/grl-pocket.xml
new file mode 100644
index 0000000..b841836
--- /dev/null
+++ b/src/pocket/grl-pocket.xml
@@ -0,0 +1,10 @@
+<plugin>
+ <info>
+ <name>Pocket</name>
+ <module>libgrlpocket</module>
+ <description>A plugin for Pocket videos</description>
+ <author>GNOME</author>
+ <license>LGPL</license>
+ <site>http://www.gnome.org</site>
+ </info>
+</plugin>
diff --git a/src/pocket/pocket.gresource.xml b/src/pocket/pocket.gresource.xml
new file mode 100644
index 0000000..1f7d95c
--- /dev/null
+++ b/src/pocket/pocket.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/grilo/plugins/pocket">
+ <file compressed="false">channel-pocket.svg</file>
+ </gresource>
+</gresources>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]