[tracker/miner-flickr: 1/6] Add Flickr miner



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

    Add Flickr miner

 configure.ac                                       |   32 ++
 src/Makefile.am                                    |    4 +
 src/tracker-miner-flickr/Makefile.am               |   64 +++
 src/tracker-miner-flickr/query-queue.vala          |   56 ++
 src/tracker-miner-flickr/tracker-miner-flickr.vala |  583 ++++++++++++++++++++
 5 files changed, 739 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3012e71..e1b3b20 100644
--- a/configure.ac
+++ b/configure.ac
@@ -159,6 +159,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])
@@ -793,6 +794,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
 ####################################################################
@@ -1717,6 +1744,7 @@ AC_CONFIG_FILES([
 	src/tracker-extract/Makefile
 	src/tracker-miner-fs/Makefile
 	src/tracker-miner-rss/Makefile
+	src/tracker-miner-flickr/Makefile
 	src/tracker-preferences/Makefile
 	src/tracker-preferences/tracker-preferences.desktop.in
 	src/tracker-search-bar/Makefile
@@ -1838,6 +1866,10 @@ Plugins/Extensions:
 	KMail plugin        (data-push):	$enable_kmail_miner
 	Miner RSS:          (data-push):	$enable_miner_rss
 
+Extra miners:
+
+	Flickr miner				$have_miner_flickr_deps
+
 Writeback:
 
 	MP3 writeback:                          $have_id3lib
diff --git a/src/Makefile.am b/src/Makefile.am
index 3823389..ff8d5c4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -42,3 +42,7 @@ endif
 if USING_MINER_RSS
 SUBDIRS += tracker-miner-rss
 endif
+
+if HAVE_MINER_FLICKR
+SUBDIRS += tracker-miner-flickr
+endif
diff --git a/src/tracker-miner-flickr/Makefile.am b/src/tracker-miner-flickr/Makefile.am
new file mode 100644
index 0000000..f5593ef
--- /dev/null
+++ b/src/tracker-miner-flickr/Makefile.am
@@ -0,0 +1,64 @@
+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					\
+	query-queue.vala						\
+	$(top_srcdir)/src/libtracker-client/tracker-client-0.7.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/tracker-miner-flickr/query-queue.vala b/src/tracker-miner-flickr/query-queue.vala
new file mode 100644
index 0000000..7afeeb7
--- /dev/null
+++ b/src/tracker-miner-flickr/query-queue.vala
@@ -0,0 +1,56 @@
+public class QueryQueue : GLib.Object {
+	/* Holds the pending sparql updates and monitors them */
+	private HashTable<uint, string> queue;
+	private uint cookie;
+
+	private Mutex flush_mutex;
+
+	private Tracker.Miner miner;
+
+	public QueryQueue (Tracker.Miner parent) {
+		miner = parent;
+
+		queue = new HashTable<uint, string> (direct_hash, direct_equal);
+		cookie = 0;
+
+		flush_mutex = new Mutex ();
+	}
+
+	public async void append (string query) {
+		uint current_cookie = cookie ++;
+		queue.insert (current_cookie, query);
+
+		try {
+			yield miner.execute_batch_update (query);
+		} catch (Error tracker_error) {
+			warning ("BatchUpdate query failed: %s", tracker_error.message);
+		}
+
+		queue.remove (current_cookie);
+	}
+
+	/* BLOCKING flush */
+	public void flush () {
+		if (!flush_mutex.trylock ()) {
+			message ("There's already a flush taking place");
+			return;
+		}
+
+		if (queue.size () > 0) {
+			MainLoop wait_loop;
+			try {
+				wait_loop = new MainLoop (null, false);
+				miner.commit (null, () => { wait_loop.quit (); });
+				wait_loop.run ();
+			} catch (Error tracker_error) {
+				warning ("Commit query failed: %s", tracker_error.message);
+			}
+		}
+
+		flush_mutex.unlock ();
+	}
+
+	public uint size () {
+		return queue.size ();
+	}
+}
diff --git a/src/tracker-miner-flickr/tracker-miner-flickr.vala b/src/tracker-miner-flickr/tracker-miner-flickr.vala
new file mode 100644
index 0000000..65d38d4
--- /dev/null
+++ b/src/tracker-miner-flickr/tracker-miner-flickr.vala
@@ -0,0 +1,583 @@
+/*
+ * 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: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";;
+
+	/* 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 QueryQueue query_queue;
+	private static MainLoop main_loop;
+
+	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 ("association-status", MinerWebAssociationStatus.UNASSOCIATED);
+
+		rest = new Rest.Proxy (FLICKR_REST_URL, false);
+
+		query_queue = new QueryQueue (this);
+
+		this.notify["association-status"].connect (association_status_changed);
+	}
+
+	public void shutdown () {
+		set ("association-status", MinerWebAssociationStatus.UNASSOCIATED);
+	}
+
+	private void association_status_changed (Object source, ParamSpec pspec) {
+		MinerWebAssociationStatus status;
+
+		get ("association-status", out status);
+
+		switch (status) {
+			case MinerWebAssociationStatus.ASSOCIATED:
+				if (pull_timeout_handle != 0)
+					return;
+
+				message ("Miner is now associated. Initiating periodic pull.");
+				Timeout.add_seconds (PULL_INTERVAL, pull_timeout_cb);
+				Idle.add ( () => { pull_timeout_cb (); return false; });
+				break;
+			case MinerWebAssociationStatus.UNASSOCIATED:
+				if (pull_timeout_handle == 0)
+					return;
+
+				Source.remove (pull_timeout_handle);
+				break;
+		}
+	}
+
+	private bool pull_timeout_cb () {
+		init_pull ();
+		return true;
+	}
+
+	private async void init_pull () {
+		Rest.ProxyCall albums_call;
+		Rest.XmlNode photosets_node;
+
+		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);
+			query_queue.flush ();
+		} catch (Error call_error) {
+			warning ("Could not get photosets list: %s", call_error.message);
+		}
+		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;
+		Rest.ProxyCall photos_call;
+		SparqlBuilder builder;
+
+		photoset_node = root_node.find ("photoset");
+
+		while (photoset_node != null) {
+			photoset_url = FLICKR_PHOTOSET_URL.printf (user_id, photoset_node.get_attr ("id"));
+
+			message ("Getting photos for album %s", photoset_url);
+			builder = new SparqlBuilder.update ();
+			builder.insert_open (DATASOURCE_URN);
+
+			builder.subject ("_:album");
+			builder.predicate ("a");
+			builder.object ("nfo:MediaList");
+			builder.predicate ("a");
+			builder.object ("nfo:RemoteDataObject");
+			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 ();
+
+			query_queue.append (builder.get_result ());
+
+			try {
+				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 call_error) {
+				warning ("Could not list photos for photoset %s: %s", photoset_url, call_error.message);
+			}
+			photoset_node = photoset_node.next;
+		}
+	}
+
+	private void insert_photos (Rest.XmlNode root_node) {
+		Rest.XmlNode photoset_node;
+		string photoset_url;
+		Rest.XmlNode photo_node;
+		string photo_url;
+		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) {
+			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"));
+			builder = new SparqlBuilder.update ();
+			builder.insert_open (DATASOURCE_URN);
+
+			builder.subject ("_:photo");
+			builder.predicate ("a");
+			builder.object ("nmm:Photo");
+			builder.predicate ("a");
+			builder.object ("nfo:RemoteDataObject");
+			builder.predicate ("a");
+			builder.object ("nfo:MediaFileListEntry");
+
+			builder.predicate ("nie:url");
+			builder.object_string (photo_url);
+
+			builder.predicate ("dc:title");
+			builder.object_string (photo_node.get_attr ("title"));
+
+			insert_exif_data (photo_node, builder);
+			insert_tags (photo_node, builder);
+
+			builder.insert_close ();
+
+			query_queue.append (builder.get_result ());
+
+			photo_node = photo_node.next;
+		}
+	}
+
+	private void insert_exif_data (Rest.XmlNode photo_node, SparqlBuilder builder) {
+		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:
+					builder.predicate ("nmm:camera");
+					builder.object_string (exif_value);
+					break;
+				case ExifTag.FLASH:
+					builder.predicate ("nmm:flash");
+					builder.object (exif_value.to_int ()%2 == 1 ? "nmm:flash-on" : "nmm:flash-off");
+					break;
+				case ExifTag.FNUMBER:
+					builder.predicate ("nmm:fnumber");
+					builder.object_double (ratioToDouble (exif_value));
+					break;
+				case ExifTag.FOCAL_LENGTH:
+					builder.predicate ("nmm:focalLength");
+					builder.object_double (ratioToDouble (exif_value));
+					break;
+				case ExifTag.ISO_SPEED:
+					builder.predicate ("nmm:isoSpeed");
+					builder.object_int64 ((int64)exif_value.to_int ());
+					break;
+				case ExifTag.METERING_MODE:
+					builder.predicate ("nmm:meteringMode");
+					switch (exif_value.to_int ()) {
+						case ExifMeteringMode.AVERAGE:
+							builder.object ("nmm:meteringMode-average");
+							break;
+						case ExifMeteringMode.CENTER_WEIGHTED_AVERAGE:
+							builder.object ("nmm:meteringMode-center-weighted-average");
+							break;
+						case ExifMeteringMode.SPOT:
+							builder.object ("nmm:meteringMode-spot");
+							break;
+						case ExifMeteringMode.MULTISPOT:
+							builder.object ("nmm:meteringMode-multispot");
+							break;
+						case ExifMeteringMode.PATTERN:
+							builder.object ("nmm:meteringMode-pattern");
+							break;
+						case ExifMeteringMode.PARTIAL:
+							builder.object ("nmm:meteringMode-partial");
+							break;
+						default:
+							builder.object ("nmm:meteringMode-other");
+							break;
+					}
+					break;
+				case ExifTag.WHITE_BALANCE:
+					builder.predicate ("nmm:whiteBalance");
+					switch (exif_value.to_int ()) {
+						case ExifWhiteBalance.AUTO:
+							builder.object ("nmm:whiteBalance-auto");
+							break;
+						case ExifWhiteBalance.MANUAL:
+							builder.object ("nmm:whiteBalance-manual");
+							break;
+					}
+					break;
+				default:
+					break;
+			}
+			exif_node = exif_node.next;
+		}
+	}
+
+	private void insert_tags (Rest.XmlNode photo_node, SparqlBuilder builder) {
+		var tags_call = rest.new_call ();
+		Rest.XmlNode root_node;
+		Rest.XmlNode tag_node;
+
+		tags_call.add_params ("method", "flickr.tags.getListPhoto",
+		                      "photo_id", photo_node.get_attr ("id"));
+
+		try {
+			root_node = run_call (tags_call);
+		} catch (Error call_error) {
+			warning ("Couldn't get tags for photo %s: %s", photo_node.get_attr ("id"), call_error.message);
+			return;
+		}
+
+		tag_node = root_node.find ("tag");
+
+		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;
+		}
+	}
+
+	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 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 + "permsread");
+		url = FLICKR_AUTH_URL + "?api_key=" + API_KEY + "&perms=read&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 ("association-status", MinerWebAssociationStatus.UNASSOCIATED);
+
+		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 ("association-status", MinerWebAssociationStatus.ASSOCIATED);
+	}
+
+	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 ("association-status", MinerWebAssociationStatus.UNASSOCIATED);
+	}
+
+	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]