[tracker/miner-flickr-review: 2/2] Add Flickr miner



commit d43fc5443d5bab5bff78357e5370dff23c4b3760
Author: Adrien Bustany <abustany gnome org>
Date:   Wed Mar 10 09:44:56 2010 -0300

    Add Flickr miner
    
    The Flickr miner imports all the photosets, and also does writeback for tags (ie
    tagging a photo locally will propagate the tag on Flickr).

 configure.ac                                       |   32 +
 data/dbus/Makefile.am                              |    5 +
 ...rg.freedesktop.Tracker1.Miner.Flickr.service.in |    3 +
 data/icons/scalable/Makefile.am                    |   11 +-
 data/icons/scalable/tracker-miner-flickr.svg       |  206 +++++
 data/miners/Makefile.am                            |    7 +
 data/miners/tracker-miner-flickr.desktop.in.in     |    8 +
 src/miners/Makefile.am                             |    4 +
 src/miners/flickr/Makefile.am                      |   63 ++
 src/miners/flickr/tracker-miner-flickr.vala        |  853 ++++++++++++++++++++
 10 files changed, 1191 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 212c88c..15bb858 100644
--- a/configure.ac
+++ b/configure.ac
@@ -160,6 +160,7 @@ GEE_REQUIRED=0.3
 ID3LIB_REQUIRED=3.8.3
 GNOME_KEYRING_REQUIRED=2.26
 LIBGRSS_REQUIRED=0.3
+REST_REQUIRED=0.6
 
 # Library Checks
 PKG_CHECK_MODULES(GLIB2, [glib-2.0 >= $GLIB_REQUIRED])
@@ -820,6 +821,32 @@ fi
 
 AM_CONDITIONAL(HAVE_GNOME_KEYRING, test "x$have_gnome_keyring" = "xyes")
 
+##################################################################
+# Flickr miner
+##################################################################
+
+AC_ARG_ENABLE(miner_flickr,
+              AS_HELP_STRING([--miner-flickr],
+                             [enable Flickr miner [[default=auto]]]),,
+                             [enable_miner_flickr=auto])
+
+if test "x$enable_miner_flickr" != "xno"; then
+	PKG_CHECK_MODULES(MINER_FLICKR,
+	                  [ rest-0.6 >= $REST_REQUIRED ],
+	                  [have_miner_flickr_deps=yes],
+	                  [have_miner_flickr_deps=no])
+	AC_SUBST(MINER_FLICKR_LIBS)
+	AC_SUBST(MINER_FLICKR_CFLAGS)
+fi
+
+if test "x$enable_miner_flickr" = "xyes"; then
+	if test "x$have_miner_flickr_deps" != "xyes"; then
+		AC_MSG_ERROR([Couldn't find the required dependencies for the Flickr miner: rest-0.6 >= $REST_REQUIRED.])
+	fi
+fi
+
+AM_CONDITIONAL(HAVE_MINER_FLICKR, test "x$have_miner_flickr_deps" = "xyes")
+
 ####################################################################
 # Mail miners
 ####################################################################
@@ -1682,6 +1709,7 @@ AC_CONFIG_FILES([
 	src/miners/Makefile
 	src/miners/fs/Makefile
 	src/miners/rss/Makefile
+	src/miners/flickr/Makefile
 	src/tracker-status-icon/Makefile
 	src/tracker-status-icon/tracker-status-icon.desktop.in
 	src/tracker-store/Makefile
@@ -1810,6 +1838,10 @@ Plugins:
 
         Nautilus: (tagging widget)              $have_nautilus_extension
 
+Extra miners:
+
+	Flickr miner				$have_miner_flickr_deps
+
 Writeback:
 
 	MP3:                                    $have_id3lib
diff --git a/data/dbus/Makefile.am b/data/dbus/Makefile.am
index 64a6f7c..c7e6d94 100644
--- a/data/dbus/Makefile.am
+++ b/data/dbus/Makefile.am
@@ -23,6 +23,11 @@ service_in_files =						\
 	org.freedesktop.Tracker1.Miner.Applications.service.in	\
 	org.freedesktop.Tracker1.Miner.Files.service.in		\
 	org.freedesktop.Tracker1.Extract.service.in
+
+if HAVE_MINER_FLICKR
+service_in_files += org.freedesktop.Tracker1.Miner.Flickr.service.in
+endif
+
 service_DATA = $(service_in_files:.service.in=.service)
 
 %.service: %.service.in
diff --git a/data/dbus/org.freedesktop.Tracker1.Miner.Flickr.service.in b/data/dbus/org.freedesktop.Tracker1.Miner.Flickr.service.in
new file mode 100644
index 0000000..b06fe93
--- /dev/null
+++ b/data/dbus/org.freedesktop.Tracker1.Miner.Flickr.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Tracker1.Miner.Flickr
+Exec= libexecdir@/tracker-miner-flickr
diff --git a/data/icons/scalable/Makefile.am b/data/icons/scalable/Makefile.am
index 5583008..a8310e5 100644
--- a/data/icons/scalable/Makefile.am
+++ b/data/icons/scalable/Makefile.am
@@ -3,4 +3,13 @@ include $(top_srcdir)/Makefile.decl
 icondir = $(datadir)/icons/hicolor/scalable/apps
 icon_DATA = tracker.svg
 
-EXTRA_DIST = $(icon_DATA)
+minericonsdir = $(datadir)/tracker/icons
+minericons_DATA =
+
+if HAVE_MINER_FLICKR
+minericons_DATA += tracker-miner-flickr.svg
+endif
+
+EXTRA_DIST = 					\
+	$(icon_DATA)				\
+	$(minericons_DATA)
diff --git a/data/icons/scalable/tracker-miner-flickr.svg b/data/icons/scalable/tracker-miner-flickr.svg
new file mode 100644
index 0000000..bf5f830
--- /dev/null
+++ b/data/icons/scalable/tracker-miner-flickr.svg
@@ -0,0 +1,206 @@
+<?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:xlink="http://www.w3.org/1999/xlink";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="48"
+   height="48"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   version="1.0"
+   sodipodi:docname="flickr48.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/cube/Dropbox/Inkscape-Files/flickr/flickr48.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient6275">
+      <stop
+         id="stop6277"
+         offset="0"
+         style="stop-color:#f45d80;stop-opacity:1;" />
+      <stop
+         id="stop6279"
+         offset="1"
+         style="stop-color:#d40f3d;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient6261">
+      <stop
+         id="stop6263"
+         offset="0"
+         style="stop-color:#c5a0df;stop-opacity:1;" />
+      <stop
+         id="stop6265"
+         offset="1"
+         style="stop-color:#3e78b6;stop-opacity:1;" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6261"
+       id="radialGradient6259"
+       cx="7.3078551"
+       cy="18.809063"
+       fx="7.3078551"
+       fy="18.809063"
+       r="12.897959"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6275"
+       id="radialGradient6273"
+       cx="6.4923115"
+       cy="17.852686"
+       fx="6.4923115"
+       fy="17.852686"
+       r="12.897959"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       inkscape:collect="always"
+       id="filter6287"
+       x="-0.13891452"
+       width="1.277829"
+       y="-0.83348718"
+       height="2.6669744">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="1.0753131"
+         id="feGaussianBlur6289" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.1666667"
+     inkscape:cx="24"
+     inkscape:cy="24"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1280"
+     inkscape:window-height="974"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:snap-nodes="false"
+     inkscape:snap-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2383"
+       visible="true"
+       enabled="true" />
+  </sodipodi:namedview>
+  <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:creator>
+          <cc:Agent>
+            <dc:title>Jakub Szypulka</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:publisher>
+          <cc:Agent>
+            <dc:title>Jakub Szypulka</dc:title>
+          </cc:Agent>
+        </dc:publisher>
+        <dc:rights>
+          <cc:Agent>
+            <dc:title>Jakub Szypulka</dc:title>
+          </cc:Agent>
+        </dc:rights>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:url(#radialGradient6259);fill-opacity:1;fill-rule:evenodd;stroke:#204a87;stroke-width:1.17254185999999990px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="path5475"
+       sodipodi:cx="10.877922"
+       sodipodi:cy="23.407793"
+       sodipodi:rx="12.311688"
+       sodipodi:ry="12.311688"
+       d="M 23.18961,23.407793 A 12.311688,12.311688 0 1 1 -1.4337664,23.407793 A 12.311688,12.311688 0 1 1 23.18961,23.407793 z"
+       transform="matrix(0.8528481,0,0,0.8528481,2.7227857,3.0367088)" />
+    <path
+       transform="matrix(0.8528481,0,0,0.8528481,25.722785,3.0367083)"
+       d="M 23.18961,23.407793 A 12.311688,12.311688 0 1 1 -1.4337664,23.407793 A 12.311688,12.311688 0 1 1 23.18961,23.407793 z"
+       sodipodi:ry="12.311688"
+       sodipodi:rx="12.311688"
+       sodipodi:cy="23.407793"
+       sodipodi:cx="10.877922"
+       id="path5477"
+       style="opacity:1;fill:url(#radialGradient6273);fill-opacity:1;fill-rule:evenodd;stroke:#a40026;stroke-width:1.17254185999999994px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       sodipodi:type="arc" />
+    <path
+       transform="matrix(0.7716245,0,0,0.7716245,3.6063293,4.9379737)"
+       d="M 23.18961,23.407793 A 12.311688,12.311688 0 1 1 -1.4337664,23.407793 A 12.311688,12.311688 0 1 1 23.18961,23.407793 z"
+       sodipodi:ry="12.311688"
+       sodipodi:rx="12.311688"
+       sodipodi:cy="23.407793"
+       sodipodi:cx="10.877922"
+       id="path5479"
+       style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.29596722000000010px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       sodipodi:type="arc" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.29596722px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="path6251"
+       sodipodi:cx="10.877922"
+       sodipodi:cy="23.407793"
+       sodipodi:rx="12.311688"
+       sodipodi:ry="12.311688"
+       d="M 23.18961,23.407793 A 12.311688,12.311688 0 1 1 -1.4337664,23.407793 A 12.311688,12.311688 0 1 1 23.18961,23.407793 z"
+       transform="matrix(0.7716245,0,0,0.7716245,26.606329,4.9379737)" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:0.3;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.90345997px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter6287);enable-background:accumulate"
+       id="path6281"
+       sodipodi:cx="14.449541"
+       sodipodi:cy="35.821102"
+       sodipodi:rx="9.288991"
+       sodipodi:ry="1.5481651"
+       d="M 23.738532,35.821102 A 9.288991,1.5481651 0 1 1 5.1605501,35.821102 A 9.288991,1.5481651 0 1 1 23.738532,35.821102 z"
+       transform="matrix(1.1659259,0,0,1.1190295,-4.6773701,-6.3524276)" />
+    <path
+       transform="matrix(1.2735802,0,0,1.1190295,16.427625,-6.3524276)"
+       d="M 23.738532,35.821102 A 9.288991,1.5481651 0 1 1 5.1605501,35.821102 A 9.288991,1.5481651 0 1 1 23.738532,35.821102 z"
+       sodipodi:ry="1.5481651"
+       sodipodi:rx="9.288991"
+       sodipodi:cy="35.821102"
+       sodipodi:cx="14.449541"
+       id="path6291"
+       style="opacity:0.3;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.90345997px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter6287);enable-background:accumulate"
+       sodipodi:type="arc" />
+  </g>
+</svg>
diff --git a/data/miners/Makefile.am b/data/miners/Makefile.am
index e768375..edb274c 100644
--- a/data/miners/Makefile.am
+++ b/data/miners/Makefile.am
@@ -15,7 +15,14 @@ if USING_MINER_RSS
 tracker_miners_DATA += 	tracker-miner-rss.desktop
 endif
 
+if HAVE_MINER_FLICKR
+tracker_miners_DATA += tracker-miner-flickr.desktop
+endif
+
 @INTLTOOL_DESKTOP_RULE@
 
+%.desktop.in: %.desktop.in.in
+	@sed -e "s|@datadir[ ]|$(datadir)|" $< > $@
+
 EXTRA_DIST = $(desktop_in_files)
 CLEANFILES = $(tracker_miners_DATA)
diff --git a/data/miners/tracker-miner-flickr.desktop.in.in b/data/miners/tracker-miner-flickr.desktop.in.in
new file mode 100644
index 0000000..a9f1ae9
--- /dev/null
+++ b/data/miners/tracker-miner-flickr.desktop.in.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Encoding=UTF-8
+_Name=Flickr
+_Comment=Index your Flickr photo albums
+Icon= datadir@/tracker/icons/tracker-miner-flickr.svg
+DBusName=org.freedesktop.Tracker1.Miner.Flickr
+DBusPath=/org/freedesktop/Tracker1/Miner/Flickr
+AuthenticationMethod=Token
diff --git a/src/miners/Makefile.am b/src/miners/Makefile.am
index f6373b6..a6e7d7a 100644
--- a/src/miners/Makefile.am
+++ b/src/miners/Makefile.am
@@ -5,3 +5,7 @@ SUBDIRS = fs
 if USING_MINER_RSS
 SUBDIRS += rss
 endif
+
+if HAVE_MINER_FLICKR
+SUBDIRS += flickr
+endif
diff --git a/src/miners/flickr/Makefile.am b/src/miners/flickr/Makefile.am
new file mode 100644
index 0000000..3d7b550
--- /dev/null
+++ b/src/miners/flickr/Makefile.am
@@ -0,0 +1,63 @@
+include $(top_srcdir)/Makefile.decl
+
+INCLUDES =								\
+	-Wall								\
+	-DSHAREDIR=\""$(datadir)"\"					\
+	-DPKGLIBDIR=\""$(libdir)/tracker"\"				\
+	-DLOCALEDIR=\""$(localedir)"\" 					\
+	-DLIBEXEC_PATH=\""$(libexecdir)"\"				\
+	-DG_LOG_DOMAIN=\"Tracker\"					\
+	-DTRACKER_COMPILATION						\
+	-I$(top_srcdir)/src						\
+	$(WARN_CFLAGS)							\
+	$(GMODULE_CFLAGS)						\
+	$(PANGO_CFLAGS)							\
+	$(DBUS_CFLAGS)							\
+	$(MINER_FLICKR_CFLAGS)						\
+	$(GCOV_CFLAGS)
+
+VALAFLAGS =								\
+	--pkg gio-2.0							\
+	--pkg rest							\
+	--pkg posix							\
+	--thread
+
+libexec_PROGRAMS = tracker-miner-flickr
+
+tracker_miner_flickr_VALASOURCES =					\
+	tracker-miner-flickr.vala					\
+	$(top_srcdir)/src/libtracker-client/tracker-client-0.8.vapi
+
+tracker_miner_flickr_SOURCES =						\
+	$(tracker_miner_flickr_VALASOURCES:.vala=.c)
+
+tracker_miner_flickr_LDADD =						\
+	$(top_builddir)/src/libtracker-miner/libtracker-miner- TRACKER_API_VERSION@.la \
+	$(top_builddir)/src/libtracker-client/libtracker-client- TRACKER_API_VERSION@.la \
+	$(DBUS_LIBS)							\
+	$(GMODULE_LIBS)							\
+	$(GTHREAD_LIBS)							\
+	$(GIO_LIBS)							\
+	$(GCOV_LIBS)							\
+	$(GLIB2_LIBS)							\
+	$(MINER_FLICKR_LIBS)						\
+	-lz								\
+	-lm
+
+vapi_sources =								\
+	$(top_srcdir)/src/libtracker-miner/tracker-miner- TRACKER_API_VERSION@.vapi
+
+tracker-miner-flickr.vala.stamp: $(tracker_miner_flickr_VALASOURCES) $(vapi_sources)
+	$(AM_V_GEN)$(VALAC) $(GCOV_VALAFLAGS) -C $(VALAFLAGS) $^
+	touch $@
+
+
+BUILT_SOURCES = tracker-miner-flickr.vala.stamp
+
+MAINTAINERCLEANFILES =							\
+	tracker-miner-flickr.vala.stamp					\
+	$(tracker_miner_flickr_VALASOURCES:.vala=.c)
+
+EXTRA_DIST =								\
+	$(tracker_miner_flickr_VALASOURCES)				\
+	tracker-miner-flickr.vala.stamp
diff --git a/src/miners/flickr/tracker-miner-flickr.vala b/src/miners/flickr/tracker-miner-flickr.vala
new file mode 100644
index 0000000..9a52322
--- /dev/null
+++ b/src/miners/flickr/tracker-miner-flickr.vala
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) 2010, Adrien Bustany <abustany gnome org>
+ *
+ * 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; either
+ * 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 Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+namespace Tracker {
+
+private errordomain RestCallError {
+	INVALID_RESPONSE, /* Malformed XML */
+	CALL_ERROR        /* Call failed */
+}
+
+public class MinerFlickr : Tracker.MinerWeb {
+	private static const string MINER_NAME = "Flickr";
+	private static const string MINER_DESCRIPTION = "Tracker miner for Flickr";
+	private static const string API_KEY = "7983269709fa3158c752e3e4d6b3b9e5";
+	private static const string SHARED_SECRET = "c0316d1cb4b15e2d";
+	private static const string DATASOURCE_URN = "urn:nepomuk:datasource:2208f9fc-3c5b-4e40-ade4-45a0d7b0cf6f";
+	private static const string FLICKR_AUTH_URL = "http://api.flickr.com/services/auth/";;
+	private static const string FLICKR_REST_URL = "http://api.flickr.com/services/rest/";;
+	private static const string FLICKR_PHOTOSET_URL = "http://www.flickr.com/photos/%s/sets/%s";;
+	private static const string FLICKR_PHOTO_URL = "http://farm%s.static.flickr.com/%s/%s_%s.jpg";;
+	private static const string NMM_PHOTO = "http://www.tracker-project.org/temp/nmm#Photo";;
+
+	/* Values taken from the EXIF spec */
+	private enum ExifTag {
+		CAMERA = 271,
+		FLASH = 37385,
+		FNUMBER = 33437,
+		FOCAL_LENGTH = 37386,
+		ISO_SPEED = 2,
+		METERING_MODE = 37383,
+		WHITE_BALANCE = 5
+	}
+
+	private enum ExifMeteringMode {
+		AVERAGE = 1,
+		CENTER_WEIGHTED_AVERAGE,
+		SPOT,
+		MULTISPOT,
+		PATTERN,
+		PARTIAL
+	}
+
+	private enum ExifWhiteBalance {
+		AUTO = 0,
+		MANUAL
+	}
+
+	private static const uint PULL_INTERVAL = 5*60; /* seconds */
+	private uint pull_timeout_handle;
+
+	private static MainLoop main_loop;
+
+	/* Needed to connect to the writeback signal */
+	private Tracker.Client tracker_client;
+
+	private Rest.Proxy rest;
+
+	/* Only used during association phase */
+	private string frob;
+	/* Used to sign calls */
+	private string auth_token;
+	/* Used to form some urls */
+	private string user_id;
+
+	construct {
+		set ("name", MINER_NAME);
+		set ("associated", false);
+		set ("status", "Idle");
+		set ("progress", 1.0);
+
+		rest = new Rest.Proxy (FLICKR_REST_URL, false);
+
+		tracker_client = new Tracker.Client (0, -1);
+		tracker_client.writeback_connect (writeback);
+
+		this.notify["associated"].connect (association_status_changed);
+	}
+
+	public void shutdown () {
+		set ("associated", false);
+	}
+
+	private void association_status_changed (Object source, ParamSpec pspec) {
+		bool associated;
+
+		get ("associated", out associated);
+
+		if (associated) {
+			if (pull_timeout_handle != 0)
+				return;
+
+			message ("Miner is now associated. Initiating periodic pull.");
+			pull_timeout_handle = Timeout.add_seconds (PULL_INTERVAL, pull_timeout_cb);
+			Idle.add ( () => { pull_timeout_cb (); return false; });
+		} else {
+			if (pull_timeout_handle == 0)
+				return;
+
+			Source.remove (pull_timeout_handle);
+		}
+	}
+
+	private bool pull_timeout_cb () {
+		init_pull ();
+		return true;
+	}
+
+	private async void init_pull () {
+		Rest.ProxyCall albums_call;
+		Rest.XmlNode photosets_node;
+
+		set ("status", "Refreshing photo albums");
+		set ("progress", 0.0);
+
+		Idle.add (init_pull.callback);
+		yield;
+
+
+		/* First get the list of albums */
+		albums_call = rest.new_call ();
+		albums_call.add_param ("method", "flickr.photosets.getList");
+
+		try {
+			photosets_node = run_call (albums_call);
+			insert_photosets (photosets_node);
+		} catch (Error call_error) {
+			warning ("Could not get photosets list: %s", call_error.message);
+		}
+
+		set ("status", "Idle");
+		set ("progress", 1.0);
+		message ("Pull finished");
+	}
+
+	private void insert_photosets (Rest.XmlNode root_node) {
+		Rest.XmlNode photoset_node;
+		Rest.XmlNode title_node;
+		Rest.XmlNode photos_node;
+		string photoset_url;
+		string photoset_identifier;
+		bool resource_created;
+		string photoset_urn;
+		string delete_query;
+		Rest.ProxyCall photos_call;
+		SparqlBuilder builder;
+		uint n_photosets;
+		uint indexed_photosets = 0;
+
+		photoset_node = root_node.find ("photoset");
+		n_photosets = root_node.children.size ();
+
+		while (photoset_node != null) {
+			try {
+				photoset_url = FLICKR_PHOTOSET_URL.printf (user_id, photoset_node.get_attr ("id"));
+				photoset_identifier = "flickr:photoset:%s".printf (photoset_node.get_attr ("id"));
+				photoset_urn = get_resource (photoset_url,
+				                             {"nfo:MediaList", "nfo:RemoteDataObject"},
+				                             photoset_identifier,
+				                             out resource_created);
+
+				message ("Getting photos for album %s", photoset_url);
+
+				builder = new SparqlBuilder.update ();
+				builder.insert_open (photoset_url);
+				builder.subject_iri (photoset_urn);
+
+				if (!resource_created) {
+					delete_query = ("delete { <%1$s> dc:title ?title }"
+					             +  "where  { <%1$s> dc:title ?title }")
+					             .printf (photoset_urn);
+					tracker_client.sparql_update (delete_query);
+				} else {
+					builder.predicate ("nie:dataSource");
+					builder.object_iri (DATASOURCE_URN);
+					builder.predicate ("nie:url");
+					builder.object_string (photoset_url);
+				}
+
+				title_node = photoset_node.find ("title");
+				if (title_node != null) {
+					builder.predicate ("dc:title");
+					builder.object_string (title_node.content);
+				}
+
+				builder.insert_close ();
+
+				tracker_client.sparql_update (builder.get_result ());
+
+				set ("status", "Refresing album \"%s\"".printf (title_node.content));
+
+				photos_call = rest.new_call ();
+				photos_call.add_params ("method", "flickr.photosets.getPhotos",
+				                        "photoset_id", photoset_node.get_attr ("id"),
+				                        "media", "photos",
+				                        "extras", "original_format");
+				photos_node = run_call (photos_call);
+				insert_photos (photos_node);
+			} catch (Error err) {
+				warning ("Could not list photos for photoset %s: %s", photoset_url, err.message);
+			}
+			photoset_node = photoset_node.next;
+
+			indexed_photosets ++;
+			set ("progress", (1.0*indexed_photosets)/n_photosets);
+		}
+	}
+
+	private void insert_photos (Rest.XmlNode root_node) {
+		Rest.XmlNode photoset_node;
+		string photoset_url;
+		Rest.XmlNode photo_node;
+		string photo_url;
+		string photo_urn;
+		bool resource_created;
+		SparqlBuilder builder;
+
+		photoset_node = root_node.find ("photoset");
+		if (photoset_node == null || photoset_node.get_attr ("id") == null) {
+			warning ("Malformed response for flickr.photosets.getPhotos");
+			return;
+		}
+
+		photoset_url = FLICKR_PHOTOSET_URL.printf (user_id, photoset_node.get_attr ("id"));
+		message ("Indexing photoset %s", photoset_url);
+
+		photo_node = root_node.find ("photo");
+
+		while (photo_node != null) {
+			try {
+				photo_url = FLICKR_PHOTO_URL.printf (photo_node.get_attr ("farm"),
+				                                     photo_node.get_attr ("server"),
+				                                     photo_node.get_attr ("id"),
+				                                     photo_node.get_attr ("secret"));
+				photo_urn = get_resource (photo_url,
+				                          {"nmm:Photo", "nfo:RemoteDataObject", "nfo:MediaFileListEntry"},
+				                          "flickr:photo:%s".printf (photo_node.get_attr ("id")),
+				                          out resource_created);
+
+				builder = new SparqlBuilder.update ();
+
+				if (resource_created) {
+					builder.insert_open (photo_url);
+					builder.subject_iri (photo_urn);
+					builder.predicate ("nie:dataSource");
+					builder.object_iri (DATASOURCE_URN);
+					builder.predicate ("nie:url");
+					builder.object_string (photo_url);
+					builder.insert_close ();
+				}
+
+				insert_photo_info (photo_node, builder, photo_url, photo_urn);
+				insert_exif_data (photo_node, builder, photo_url, photo_urn);
+
+
+				tracker_client.sparql_update (builder.get_result ());
+			} catch (Error err) {
+				warning ("Couldn't insert photo %s: %s", photo_url, err.message);
+			}
+
+			photo_node = photo_node.next;
+		}
+	}
+
+	private void insert_photo_info (Rest.XmlNode photo_node, SparqlBuilder builder, string graph, string urn) {
+		var info_call = rest.new_call ();
+		Rest.XmlNode root_node;
+		Rest.XmlNode title_node;
+		Rest.XmlNode description_node;
+		Rest.XmlNode tag_node;
+
+		info_call.add_params ("method", "flickr.photos.getInfo",
+		                      "photo_id", photo_node.get_attr ("id"));
+
+		try {
+			root_node = run_call (info_call);
+		} catch (Error call_error) {
+			warning ("Couldn't get info for photo %s: %s", photo_node.get_attr ("id"), call_error.message);
+			return;
+		}
+
+		title_node = root_node.find ("title");
+		if (title_node != null && title_node.content != null) {
+			update_triple_string (builder, graph, urn, "dc:title", title_node.content);
+		}
+
+		description_node = root_node.find ("description");
+		if (description_node != null && description_node.content != null) {
+			update_triple_string (builder, graph, urn, "rdfs:comment", description_node.content);
+		}
+
+		tag_node = root_node.find ("tags").find ("tag");
+
+		builder.insert_open (graph);
+		builder.subject_iri (urn);
+
+		while (tag_node != null) {
+			builder.predicate ("nao:hasTag");
+
+			builder.object_blank_open ();
+			builder.predicate ("a");
+			builder.object ("nao:Tag");
+			builder.predicate ("nao:prefLabel");
+			builder.object_string (tag_node.get_attr ("raw"));
+			builder.object_blank_close ();
+
+			tag_node = tag_node.next;
+		}
+
+		builder.insert_close ();
+	}
+
+	private void insert_exif_data (Rest.XmlNode photo_node, SparqlBuilder builder, string graph, string urn) {
+		var exif_call = rest.new_call ();
+		Rest.XmlNode root_node;
+		Rest.XmlNode exif_node;
+		string exif_value;
+
+		exif_call.add_params ("method", "flickr.photos.getExif",
+		                      "photo_id", photo_node.get_attr ("id"));
+
+		try {
+			root_node = run_call (exif_call);
+		} catch (Error call_error) {
+			warning ("Couldn't get EXIF data for photo %s: %s", photo_node.get_attr ("id"), call_error.message);
+			return;
+		}
+
+		exif_node = root_node.find ("exif");
+
+		while (exif_node != null) {
+			exif_value = exif_node.find ("raw").content;
+
+			switch (exif_node.get_attr ("tag").to_int ()) {
+				case ExifTag.CAMERA:
+					update_triple_string (builder, graph, urn, "nmm:camera", exif_value);
+					break;
+				case ExifTag.FLASH:
+					update_triple_object (builder, graph, urn, "nmm:flash", exif_value.to_int ()%2 == 1 ? "nmm:flash-on" : "nmm:flash-off");
+					break;
+				case ExifTag.FNUMBER:
+					update_triple_double (builder, graph, urn, "nmm:fnumber", ratioToDouble (exif_value));
+					break;
+				case ExifTag.FOCAL_LENGTH:
+					update_triple_double (builder, graph, urn, "nmm:focalLength", ratioToDouble (exif_value));
+					break;
+				case ExifTag.ISO_SPEED:
+					update_triple_int64 (builder, graph, urn, "nmm:isoSpeed", (int64)exif_value.to_int ());
+					break;
+				case ExifTag.METERING_MODE:
+					switch (exif_value.to_int ()) {
+						case ExifMeteringMode.AVERAGE:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-average");
+							break;
+						case ExifMeteringMode.CENTER_WEIGHTED_AVERAGE:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-center-weighted-average");
+							break;
+						case ExifMeteringMode.SPOT:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-spot");
+							break;
+						case ExifMeteringMode.MULTISPOT:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-multispot");
+							break;
+						case ExifMeteringMode.PATTERN:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-pattern");
+							break;
+						case ExifMeteringMode.PARTIAL:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-partial");
+							break;
+						default:
+							update_triple_object (builder, graph, urn, "nmm:meteringMode", "nmm:meteringMode-other");
+							break;
+					}
+					break;
+				case ExifTag.WHITE_BALANCE:
+					switch (exif_value.to_int ()) {
+						case ExifWhiteBalance.AUTO:
+							update_triple_object (builder, graph, urn, "nmm:whiteBalance", "nmm:whiteBalance-auto");
+							break;
+						case ExifWhiteBalance.MANUAL:
+							update_triple_object (builder, graph, urn, "nmm:whiteBalance", "nmm:whiteBalance-manual");
+							break;
+					}
+					break;
+				default:
+					break;
+			}
+			exif_node = exif_node.next;
+		}
+	}
+
+	private void add_tags (string photo_id, string[] tags) {
+		Rest.ProxyCall tag_call;
+
+		tag_call = rest.new_call ();
+		tag_call.add_params ("method", "flickr.photos.addTags",
+		                     "photo_id", photo_id,
+		                     "tags", string.joinv (",", tags));
+
+		try {
+			run_call (tag_call);
+		} catch (Error call_error) {
+			warning ("Couldn't add tags for photo %s: %s", photo_id, call_error.message);
+			return;
+		}
+	}
+
+	private void remove_tag (string tag_id) {
+		Rest.ProxyCall tag_call;
+
+		tag_call = rest.new_call ();
+		tag_call.add_params ("method", "flickr.photos.removeTag",
+		                     "tag_id", tag_id);
+
+		try {
+			run_call (tag_call);
+		} catch (Error call_error) {
+			warning ("Couldn't remove tag: %s", call_error.message);
+			return;
+		}
+	}
+
+	private async void writeback_photo (string uri) {
+		weak PtrArray results;
+		weak string[][] triples;
+		string photo_id;
+		string[] local_tags = {};
+		HashTable<string, string> flickr_tags = new HashTable<string, string> (str_hash, str_equal);
+		string[] tags_to_add = {};
+		string[] tags_to_remove = {};
+		Rest.ProxyCall tag_call;
+		Rest.XmlNode root_node;
+		Rest.XmlNode tag_node;
+
+		try {
+			results = yield execute_sparql (("select ?photo_id ?tag where { <%s> nie:dataSource <%s> ;"
+			                                                               +    "nao:identifier ?photo_id ;"
+			                                                               +    "nao:hasTag ?t ."
+			                                                               + "?t nao:prefLabel ?tag }").printf (uri, DATASOURCE_URN));
+		} catch (Error tracker_error) {
+			warning ("Tracker error when doing writeback for photo %s: %s", uri, tracker_error.message);
+			return;
+		}
+
+		if (results.len == 0) {
+			return;
+		}
+
+		triples = (string[][])results.pdata;
+		photo_id = triples[0][0];
+
+		for (uint i = 0 ; i < results.len ; ++i) {
+			local_tags += triples[i][1];
+		}
+
+		tag_call = rest.new_call ();
+		tag_call.add_params ("method", "flickr.tags.getListPhoto",
+		                     "photo_id", photo_id);
+		try {
+			root_node = run_call (tag_call);
+		} catch (Error get_tags_error) {
+			warning ("Couldn't get tags for photo %s: %s", uri, get_tags_error.message);
+			return;
+		}
+
+		tag_node = root_node.find ("tags").find ("tag");
+
+		while (tag_node != null) {
+			flickr_tags.insert (tag_node.get_attr ("raw"),
+			                    tag_node.get_attr ("id"));
+			tag_node = tag_node.next;
+		}
+
+		foreach (string local_tag in local_tags) {
+			if (flickr_tags.lookup (local_tag) == null) {
+				tags_to_add += local_tag;
+			}
+		}
+
+		foreach (weak string flickr_tag in flickr_tags.get_keys ()) {
+			if (array_search_str (flickr_tag, local_tags) == -1)
+				tags_to_remove += flickr_tags.lookup (flickr_tag);
+		}
+
+		add_tags (photo_id, tags_to_add);
+
+		foreach (string tag in tags_to_remove)
+			remove_tag (tag);
+	}
+
+	private double ratioToDouble (string ratio) {
+		string[] tokens = ratio.split ("/");
+        if (tokens[1].to_int () == 0) {
+            critical ("fracToDouble : divide by zero while parsing ratio '%s'", ratio);
+            return 0;
+        }
+        return (tokens[0].to_int () * 1.0) / (tokens[1].to_int ());
+	}
+
+	private int array_search_str (string needle, string[] haystack) {
+		for (int i = 0 ; i < haystack.length ; ++i) {
+			if (haystack[i] == needle)
+				return i;
+		}
+
+		return -1;
+	}
+
+	private void sign_call (Rest.ProxyCall call) {
+		StringBuilder signature;
+		HashTable<string, string> parameters;
+		List<weak string> parameter_names;
+
+		call.add_param ("api_key", API_KEY);
+        if (auth_token != null)
+            call.add_param ("auth_token", auth_token);
+
+        signature = new StringBuilder (SHARED_SECRET);
+        parameters = call.get_params ();
+
+        parameter_names = parameters.get_keys ().copy ();
+        parameter_names.sort ((CompareFunc)strcmp);
+
+        foreach (string parameter in parameter_names) {
+            signature.append (parameter);
+			signature.append (parameters.lookup (parameter));
+        }
+
+        call.add_param ("api_sig", Checksum.compute_for_string (ChecksumType.MD5, signature.str));
+	}
+
+	private Rest.XmlNode? run_call (Rest.ProxyCall call) throws GLib.Error {
+		Rest.XmlParser parser;
+		Rest.XmlNode root_node;
+
+		sign_call (call);
+
+		try {
+			call.run (null);
+		} catch (Error call_error) {
+			throw call_error;
+		}
+
+		parser = new Rest.XmlParser ();
+		root_node = parser.parse_from_data (call.get_payload (), call.get_payload_length ());
+		if (root_node == null || root_node.name != "rsp") {
+			throw new RestCallError.INVALID_RESPONSE ("Empty payload or root node not \"rsp\"");
+		}
+
+		return root_node;
+	}
+
+	public override HashTable<string, string> get_association_data () throws Tracker.MinerWebError {
+		var association_data = new HashTable<string, string> (str_hash, str_equal);
+		var frob_call = rest.new_call ();
+		Rest.XmlNode root_node;
+		Rest.XmlNode frob_node;
+		string api_signature;
+		string url;
+
+		frob_call.add_param ("method", "flickr.auth.getFrob");
+
+		try {
+			root_node = run_call (frob_call);
+		} catch (Error call_error) {
+			throw new MinerWebError.SERVICE ("Error while getting association data: %s", call_error.message);
+		}
+
+		frob_node = root_node.find ("frob");
+		if (frob_node == null || frob_node.content == null) {
+			throw new MinerWebError.SERVICE ("Malformed XML response while getting frob");
+		}
+
+		this.frob = frob_node.content;
+
+		api_signature = Checksum.compute_for_string (ChecksumType.MD5,
+		                                             SHARED_SECRET + "api_key" + API_KEY + "frob" + this.frob + "permswrite");
+		url = FLICKR_AUTH_URL + "?api_key=" + API_KEY + "&perms=write&frob=" + this.frob + "&api_sig=" + api_signature;
+
+		association_data.insert ("url", url);
+
+		return association_data;
+	}
+
+	public override void associate (HashTable<string, string> association_data) throws Tracker.MinerWebError {
+		var password_provider = PasswordProvider.get ();
+		var token_call = rest.new_call ();
+		Rest.XmlNode root_node;
+		Rest.XmlNode token_node;
+		Rest.XmlNode user_node;
+
+		token_call.add_params ("method", "flickr.auth.getToken",
+		                       "frob", this.frob);
+
+		try {
+			root_node = run_call (token_call);
+		} catch (Error call_error) {
+			throw new MinerWebError.SERVICE ("Unable to get authentication token: %s", call_error.message);
+		}
+
+		token_node = root_node.find ("token");
+		user_node = root_node.find ("user");
+		if (token_node == null || token_node.content == null
+		 || user_node == null || user_node.get_attr ("username") == null) {
+			throw new MinerWebError.SERVICE ("Malformed XML response while getting token");
+		}
+
+		try {
+			password_provider.store_password (MINER_NAME,
+			                                  MINER_DESCRIPTION,
+			                                  user_node.get_attr ("username"),
+			                                  token_node.content);
+		} catch (Error e) {
+			if (e is PasswordProviderError.SERVICE) {
+				throw new MinerWebError.KEYRING (e.message);
+			}
+
+			critical ("Internal error: %s", e.message);
+			return;
+		}
+	}
+
+	public override void authenticate () throws MinerWebError {
+		PasswordProvider password_provider;
+		Rest.ProxyCall login_call;
+		Rest.XmlNode root_node;
+		Rest.XmlNode user_node;
+
+		password_provider = PasswordProvider.get ();
+
+		set ("associated", false);
+
+		try {
+			auth_token = password_provider.get_password (MINER_NAME, null);
+		} catch (Error e) {
+			if (e is PasswordProviderError.NOTFOUND) {
+				throw new MinerWebError.NO_CREDENTIALS ("Miner is not associated");
+			}
+			throw new MinerWebError.KEYRING (e.message);
+		}
+
+		login_call = rest.new_call ();
+		login_call.add_param ("method", "flickr.auth.checkToken");
+
+		try {
+			root_node = run_call (login_call);
+		} catch (Error call_error) {
+			throw new MinerWebError.SERVICE ("Cannot verify login: %s", call_error.message);
+		}
+
+		user_node = root_node.find ("user");
+		if (user_node == null || user_node.get_attr ("nsid") == null) {
+			throw new MinerWebError.WRONG_CREDENTIALS ("Stored authentication token is not valid");
+		}
+
+		user_id = user_node.get_attr ("nsid");
+
+		message ("Authentication successful");
+		set ("associated", true);
+	}
+
+	public override void dissociate () throws MinerWebError {
+		var password_provider = PasswordProvider.get ();
+
+		try {
+			password_provider.forget_password (MINER_NAME);
+		} catch (Error e) {
+			if (e is PasswordProviderError.SERVICE) {
+				throw new MinerWebError.KEYRING (e.message);
+			}
+
+			critical ("Internal error: %s", e.message);
+			return;
+		}
+
+		set ("associated", false);
+	}
+
+	public void writeback (HashTable<string, void*> properties)
+	{
+		List<weak string> uris = (List<weak string>)properties.get_keys ();
+		weak string[] rdf_classes;
+
+		foreach (string uri in uris) {
+			rdf_classes = (string[])properties.lookup (uri);
+
+			for (uint i = 0; rdf_classes[i] != null; i++) {
+				if (rdf_classes[i] == NMM_PHOTO) {
+					writeback_photo (uri);
+					return;
+				}
+			}
+		}
+	}
+
+	private string get_resource (string graph, string[] types, string identifier, out bool created) throws GLib.Error {
+		string inner_query;
+		string select_query;
+		string insert_query;
+		GLib.PtrArray query_results;
+		unowned string[][] triples;
+		HashTable<string, string> anonymous_nodes;
+
+
+		select_query = "";
+		inner_query = "";
+		created = false;
+
+		foreach (string type in types) {
+			inner_query += " a %s; ".printf (type);
+		}
+		inner_query += "nao:identifier \"%s\"".printf (identifier);
+
+		select_query = "select ?urn where { ?urn %s }".printf (inner_query);
+
+		try {
+			query_results = tracker_client.sparql_query (select_query);
+		} catch (Error tracker_error) {
+			throw tracker_error;
+		}
+
+		if (query_results.len > 0) {
+			triples = (string[][]) query_results.pdata;
+			return triples[0][0];
+		}
+
+		insert_query = "insert into <%s> { _:res %s }".printf (graph, inner_query);
+
+		try {
+			created = true;
+			query_results = tracker_client.sparql_update_blank (insert_query);
+			anonymous_nodes = ((HashTable<string, string>[])(((PtrArray[])query_results.pdata)[0].pdata))[0];
+			return anonymous_nodes.lookup ("res");
+		} catch (Error tracker_error) {
+			throw tracker_error;
+		}
+	}
+
+	public void update_triple_string (SparqlBuilder builder, string graph, string urn, string property, string new_value) {
+		builder.delete_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object_string (new_value);
+		builder.delete_close ();
+
+		builder.insert_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object_string (new_value);
+		builder.insert_close ();
+	}
+
+	public void update_triple_object (SparqlBuilder builder, string graph, string urn, string property, string new_value) {
+		builder.delete_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object (new_value);
+		builder.delete_close ();
+
+		builder.insert_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object (new_value);
+		builder.insert_close ();
+	}
+
+	public void update_triple_double (SparqlBuilder builder, string graph, string urn, string property, double new_value) {
+		builder.delete_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object_double (new_value);
+		builder.delete_close ();
+
+		builder.insert_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object_double (new_value);
+		builder.insert_close ();
+	}
+
+	public void update_triple_int64 (SparqlBuilder builder, string graph, string urn, string property, int64 new_value) {
+		builder.delete_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object_int64 (new_value);
+		builder.delete_close ();
+
+		builder.insert_open (graph);
+		builder.subject_iri (urn);
+		builder.predicate (property);
+		builder.object_int64 (new_value);
+		builder.insert_close ();
+	}
+
+	private static bool in_loop = false;
+	private static void signal_handler (int signo) {
+		if (in_loop) {
+			Posix.exit (Posix.EXIT_FAILURE);
+		}
+
+		switch (signo) {
+			case Posix.SIGINT:
+			case Posix.SIGTERM:
+				in_loop = true;
+				main_loop.quit ();
+				break;
+		}
+	}
+
+	private static void init_signals () {
+#if G_OS_WIN32
+#else
+		Posix.sigaction_t act = Posix.sigaction_t ();
+		Posix.sigset_t    empty_mask = Posix.sigset_t ();
+		Posix.sigemptyset (empty_mask);
+		act.sa_handler = signal_handler;
+		act.sa_mask    = empty_mask;
+		act.sa_flags   = 0;
+
+		Posix.sigaction (Posix.SIGTERM, act, null);
+		Posix.sigaction (Posix.SIGINT, act, null);
+#endif
+	}
+
+	public static void main (string[] args) {
+		Environment.set_application_name ("Flickr tracker miner");
+		MinerFlickr flickr_miner = Object.new (typeof (MinerFlickr)) as MinerFlickr;
+
+		init_signals ();
+
+		main_loop = new MainLoop (null, false);
+		main_loop.run ();
+
+		flickr_miner.shutdown ();
+	}
+}
+
+} // End namespace Tracker



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]