From 7fb6e976098c50aa2ce802bcf8e0331bfd0d2583 Mon Sep 17 00:00:00 2001 From: Andrew Flegg Date: Tue, 13 Oct 2009 20:31:45 +0100 Subject: [PATCH] Add v0.0.1 of Hermes from source tarball --- package/Makefile | 21 ++++ package/debian/README | 6 ++ package/debian/README.Debian | 6 ++ package/debian/changelog | 5 + package/debian/compat | 1 + package/debian/control | 83 ++++++++++++++ package/debian/copyright | 26 +++++ package/debian/dirs | 2 + package/debian/hermes.postinst | 10 ++ package/debian/rules | 98 +++++++++++++++++ package/share/hermes-256.png | Bin 0 -> 35306 bytes package/share/hermes-48.png | Bin 0 -> 2814 bytes package/share/hermes-64.png | Bin 0 -> 4201 bytes package/share/hermes.desktop | 8 ++ package/share/hermes.service | 3 + package/src/console.py | 20 ++++ package/src/contact-update.c | 147 +++++++++++++++++++++++++ package/src/contacts.py | 47 ++++++++ package/src/gui.py | 232 ++++++++++++++++++++++++++++++++++++++++ package/src/hermes.py | 170 +++++++++++++++++++++++++++++ package/src/names.py | 43 ++++++++ 21 files changed, 928 insertions(+) create mode 100644 package/Makefile create mode 100644 package/debian/README create mode 100644 package/debian/README.Debian create mode 100644 package/debian/changelog create mode 100644 package/debian/compat create mode 100644 package/debian/control create mode 100644 package/debian/copyright create mode 100644 package/debian/dirs create mode 100644 package/debian/docs create mode 100644 package/debian/hermes.postinst create mode 100755 package/debian/rules create mode 100644 package/share/hermes-256.png create mode 100644 package/share/hermes-48.png create mode 100644 package/share/hermes-64.png create mode 100644 package/share/hermes.desktop create mode 100644 package/share/hermes.service create mode 100644 package/src/console.py create mode 100644 package/src/contact-update.c create mode 100644 package/src/contacts.py create mode 100755 package/src/gui.py create mode 100644 package/src/hermes.py create mode 100644 package/src/names.py diff --git a/package/Makefile b/package/Makefile new file mode 100644 index 0000000..3a74903 --- /dev/null +++ b/package/Makefile @@ -0,0 +1,21 @@ +compile: + py_compilefiles src/*.py + mkdir bin + gcc -o bin/contact-update -std=c99 `pkg-config --cflags --libs libebook-1.2 glib-2.0` src/contact-update.c + +install: + mkdir -p ${DESTDIR}/opt/hermes/lib ${DESTDIR}/opt/hermes/bin + ln -s ../lib/gui.py ${DESTDIR}/opt/hermes/bin/hermes + install -D -m 0755 -o root -g root bin/contact-update ${DESTDIR}/opt/hermes/bin/contact-update + install -D -m 0644 -o root -g root src/*.py* ${DESTDIR}/opt/hermes/lib/ + install -D -m 0644 -o root -g root share/hermes-64.png ${DESTDIR}/usr/share/icons/hicolor/scalable/hermes.png + install -D -m 0644 -o root -g root share/hermes-48.png ${DESTDIR}/usr/share/icons/hicolor/48x48/hermes.png + install -D -m 0644 -o root -g root share/hermes.desktop ${DESTDIR}/usr/share/applications/hildon/hermes.desktop + install -D -m 0644 -o root -g root share/hermes.service ${DESTDIR}/usr/share/dbus-1/services/hermes.service + chmod 755 ${DESTDIR}/opt/hermes/lib/gui.py + +clean: + rm -f src/*.py[oc] + rm -f build-stamp configure-stamp + rm -rf debian/hermes bin + find . -name *~ -exec rm -f {} \; diff --git a/package/debian/README b/package/debian/README new file mode 100644 index 0000000..08a6271 --- /dev/null +++ b/package/debian/README @@ -0,0 +1,6 @@ +The Debian Package hermes +---------------------------- + +Comments regarding the Package + + -- unknown Wed, 30 Sep 2009 21:21:27 +0100 diff --git a/package/debian/README.Debian b/package/debian/README.Debian new file mode 100644 index 0000000..e8c6a83 --- /dev/null +++ b/package/debian/README.Debian @@ -0,0 +1,6 @@ +hermes for Debian +----------------- + + + + -- unknown Wed, 30 Sep 2009 21:21:27 +0100 diff --git a/package/debian/changelog b/package/debian/changelog new file mode 100644 index 0000000..0d7ee2a --- /dev/null +++ b/package/debian/changelog @@ -0,0 +1,5 @@ +hermes (0.0.1) unstable; urgency=low + + * Initial Release. + + -- unknown Wed, 30 Sep 2009 21:21:27 +0100 diff --git a/package/debian/compat b/package/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/package/debian/compat @@ -0,0 +1 @@ +5 diff --git a/package/debian/control b/package/debian/control new file mode 100644 index 0000000..ca4f355 --- /dev/null +++ b/package/debian/control @@ -0,0 +1,83 @@ +Source: hermes +Section: user/utilities +Priority: extra +Maintainer: Andrew Flegg +Build-Depends: debhelper (>= 5), python-runtime | python2.5-runtime, libebook-dev +Standards-Version: 3.7.2 + +Package: hermes +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, python-imaging | + python2.5-imaging, python-osso | python2.5-osso, python-hildon | + python2.5-hildon, python-twitter, python-facebook, python-evolution +Description: Enrich contacts' information from social networks. + Hermes, the Greek god of communication, will fill in the gaps in your + contacts' address book. Photos and birthdays for your friends on + social networks like Facebook and Twitter will be used to complete + the jigsaw. +XB-Maemo-Display-Name: Hermes +XB-Maemo-Icon-26: + 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 + YWdlUmVhZHlxyWU8AAAAAElFTkSuQmCC + diff --git a/package/debian/copyright b/package/debian/copyright new file mode 100644 index 0000000..a303810 --- /dev/null +++ b/package/debian/copyright @@ -0,0 +1,26 @@ +This is hermes, written and maintained by unknown +on Wed, 30 Sep 2009 21:21:27 +0100. + +The original source can always be found at: + ftp://ftp.debian.org/dists/unstable/main/source/ + +Copyright Holder: unknown + +License: + + 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 later 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 package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. diff --git a/package/debian/dirs b/package/debian/dirs new file mode 100644 index 0000000..ca882bb --- /dev/null +++ b/package/debian/dirs @@ -0,0 +1,2 @@ +usr/bin +usr/sbin diff --git a/package/debian/docs b/package/debian/docs new file mode 100644 index 0000000..e69de29 diff --git a/package/debian/hermes.postinst b/package/debian/hermes.postinst new file mode 100644 index 0000000..411bd18 --- /dev/null +++ b/package/debian/hermes.postinst @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e +gconftool-2 -s /apps/maemo/hermes/key_app 5916f12942feea4b3247d42a84371112 --type string +gconftool-2 -s /apps/maemo/hermes/key_secret 19f7538edd96b6870f2da7e84a6390a4 --type string + +# Hacky fix for NB#136012 +gconftool-2 -s /desktop/gnome/url-handlers/http/command 'dbus-send --system --type=method_call --dest="com.nokia.osso_browser" --print-reply /com/nokia/osso_browser/request com.nokia.osso_browser.load_url string:"%s"' --type string + +gtk-update-icon-cache -f /usr/share/icons/hicolor diff --git a/package/debian/rules b/package/debian/rules new file mode 100755 index 0000000..0cc4446 --- /dev/null +++ b/package/debian/rules @@ -0,0 +1,98 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + #docbook-to-man debian/hermes.sgml > hermes.1 + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/hermes. + $(MAKE) DESTDIR=$(CURDIR)/debian/hermes install + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs +# dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo +# dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/package/share/hermes-256.png b/package/share/hermes-256.png new file mode 100644 index 0000000000000000000000000000000000000000..0f7f74bf948850f14afcf3345c31838d3a37db3e GIT binary patch literal 35306 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 literal 0 HcmV?d00001 diff --git a/package/share/hermes-48.png b/package/share/hermes-48.png new file mode 100644 index 0000000000000000000000000000000000000000..b33674e645f17e1d3a50eecc40acac326eac34d5 GIT binary patch literal 2814 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 X+7SZ0dmWWdHvzC(w>96W+lK!i5S_l* literal 0 HcmV?d00001 diff --git a/package/share/hermes.desktop b/package/share/hermes.desktop new file mode 100644 index 0000000..0852d3a --- /dev/null +++ b/package/share/hermes.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +Name=Hermes +Icon=hermes +Exec=/opt/hermes/bin/hermes +X-Osso-Service=org.maemo.hermes +Type=Application diff --git a/package/share/hermes.service b/package/share/hermes.service new file mode 100644 index 0000000..95bbb30 --- /dev/null +++ b/package/share/hermes.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.maemo.hermes +Exec=/opt/hermes/bin/hermes diff --git a/package/src/console.py b/package/src/console.py new file mode 100644 index 0000000..5f8cd84 --- /dev/null +++ b/package/src/console.py @@ -0,0 +1,20 @@ +class ConsoleUICallback: + """Meets the fb2contacts' authentication callback contract using + the console. + + Copyright (c) Andrew Flegg 2009. + Released under the Artistic Licence.""" + + # ----------------------------------------------------------------------- + def need_auth(self): + print 'Need authentication...' + + # ----------------------------------------------------------------------- + def block_for_auth(self): + print 'Press enter when logged in...' + raw_input() + + # ----------------------------------------------------------------------- + def progress(self, current, maximum): + print current, maximum + diff --git a/package/src/contact-update.c b/package/src/contact-update.c new file mode 100644 index 0000000..47b6a74 --- /dev/null +++ b/package/src/contact-update.c @@ -0,0 +1,147 @@ +/** + * contact-update (c) Andrew Flegg 2009 + * ~~~~~~~~~~~~~~ Released under the Artistic Licence. + * + * A very quick and dirty program to modify an EDS contact's photo and + * birthday. This is necessary because evolution-python only exposes the two + * structs GBoxed, which cannot be created or manipulated in Python (AFAICT). + * + * Error handling: limited + * Syntax: contact-update --photo + * contact-update --birthday + */ +#include +#include +#include +#include +#include +#include +#include +#include + +// gcc -o contact-update -std=c99 `pkg-config --cflags --libs libebook-1.2 glib-2.0` contact-update.c + +GError *error = NULL; + + +/** + * Simple error handling. + * + * @param result Result of an expression. If false, message will be shown + * and the process terminated. + * @param message Message to display if result is false. + */ +void* try(gboolean result, char* message) { + if (!result) { + fprintf(stderr, "Error %s: %s\n", message, error ? error->message : ""); + exit(EXIT_FAILURE); + } +} + + +/** + * Load a photo from disk into an EContactPhoto record. + * + * @param mime_type MIME type to store in the photo record. + * @param filename Filename on disk to load. + */ +EContactPhoto load_photo(char* mime_type, char* filename) { + struct stat stbuf; + stat(filename, &stbuf); + char* photo_data = malloc(stbuf.st_size); + try(photo_data != NULL, "allocating memory for photo"); + FILE *file = fopen(filename, "r"); + try(file != NULL, "opening file"); + int read; + char* current = photo_data; + while ((read = fread(current, 1, stbuf.st_size, file)) > 0) { + current += read; + } + fclose(file); + + EContactPhoto photo; + photo.type = E_CONTACT_PHOTO_TYPE_INLINED; + photo.data.inlined.mime_type = mime_type; + photo.data.inlined.length = stbuf.st_size; + photo.data.inlined.data = photo_data; + + return photo; +} + + +/** + * Create a date record corresponding to the given strings. + * + * @param year Character representation of the year. + * @param month Character representation of the month. + * @param day Character representation of the day. + */ +EContactDate read_date(char* year, char* month, char* day) { + EContactDate date; + date.year = atoi(year); + date.month = atoi(month); + date.day = atoi(day); + return date; +} + + +/** + * Main entry point: do the work of updating matched records. + */ +int main(int argc, char* argv[]) { + g_type_init(); + + try(argc > 2, "\nsyntax: --photo \n --birthday "); + + // -- Data structures we'll use later... + // + gboolean is_photo; + gboolean is_birthday; + EContactPhoto photo; + EContactDate birthday; + if (strcmp(argv[2], "--photo") == 0) { + try(argc == 5, "syntax: --photo "); + photo = load_photo(argv[3], argv[4]); + is_photo = 1; + + } else if (strcmp(argv[2], "--birthday") == 0) { + try(argc == 6, "syntax: --birthday "); + birthday = read_date(argv[3], argv[4], argv[5]); + is_birthday = 1; + + } else { + try(1 == 0, "\nsyntax: --photo \n --birthday "); + } + + // -- Open the address book... + // + EBook *book = e_book_new_system_addressbook(&error); + try(book != NULL, "finding address book"); + try(e_book_open(book, FALSE, &error), "opening address book"); + try(e_book_is_writable(book), "book is writable"); + + // -- Find the list of contacts... + // + GList *contacts = NULL; + EBookQuery *query = e_book_query_field_test(E_CONTACT_FULL_NAME, E_BOOK_QUERY_IS, argv[1]); + try(e_book_get_contacts(book, query, &contacts, &error), "listing contacts"); + e_book_query_unref(query); + + // -- Update the contacts... + // + int i = 0; + for (GList* node = g_list_first(contacts); node != NULL; node = g_list_next(node)) { + EContact *contact = E_CONTACT(node->data); + if (is_photo) e_contact_set(contact, E_CONTACT_PHOTO, &photo); + if (is_birthday) e_contact_set(contact, E_CONTACT_BIRTH_DATE, &birthday); + + if (is_photo || is_birthday) { + try(e_book_commit_contact(book, contact, &error), "committing contact"); + i++; + } + } + printf("Modified %d records\n", i); + + return EXIT_SUCCESS; +} + diff --git a/package/src/contacts.py b/package/src/contacts.py new file mode 100644 index 0000000..0b7a1e9 --- /dev/null +++ b/package/src/contacts.py @@ -0,0 +1,47 @@ +import os +import os.path +import urllib +import Image +import ImageOps + +class ContactStore: + """Provide an API for changing contact data. Abstracts limitations + in the evolution-python bindings. + + Copyright (c) Andrew Flegg 2009. + Released under the Artistic Licence.""" + + + # ----------------------------------------------------------------------- + def __init__(self, book): + """Create a new contact store for modifying contacts in the given + EBook.""" + + self.temp_file = os.tmpnam() + self.book = book + + + # ----------------------------------------------------------------------- + def close(self): + """Close the store and tidy-up any resources.""" + + if (os.path.isfile(self.temp_file)): + os.unlink(self.temp_file) + + + # ----------------------------------------------------------------------- + def set_photo(self, contact, url): + """Set the given contact's photo to the picture found at the URL. If the + photo is wider than it is tall, it will be cropped with a bias towards + the top of the photo.""" + + urllib.urlretrieve(url, self.temp_file) + im = Image.open(self.temp_file) + (w, h) = im.size + if (h > w): + print "Shrinking photo for %s as it's %d x %d" % (contact.get_name(), w, h) + im = ImageOps.fit(im, (w, w), Image.NEAREST, 0, (0, 0.1)) + im.save(self.temp_file, "JPEG") + + print "Updating photo for %s" % (contact.get_name()) + os.spawnl(os.P_WAIT, '/opt/hermes/bin/contact-update', 'contact-update', contact.get_name(), '--photo', 'image/jpeg', self.temp_file) diff --git a/package/src/gui.py b/package/src/gui.py new file mode 100755 index 0000000..ae572da --- /dev/null +++ b/package/src/gui.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python + +import gtk, gobject +import gnome.gconf +import hildon, osso +import time +import thread +from hermes import Hermes + +gobject.threads_init() + +class HermesGUI: + """Provides the GUI for Hermes, allowing the syncing of Facebook and + Twitter friends' information with the Evolution contacts' database. + + Copyright (c) Andrew Flegg 2009. + Released under the Artistic Licence.""" + + + # ----------------------------------------------------------------------- + def __init__(self): + """Constructor. Initialises the gconf connection.""" + self.gc = gnome.gconf.client_get_default() + + + # ----------------------------------------------------------------------- + def doSync(self, widget, force, main = True): + if main and not self.get_use_facebook() and not self.get_use_twitter(): + saved = self.open_prefs() + if saved == gtk.RESPONSE_DELETE_EVENT: + return + + if main: + self.window.set_property('sensitive', False) + thread.start_new_thread(self.doSync, (widget, force, False)) + else: + try: + fb2c = Hermes(self, + twitter = (self.get_use_twitter() and self.get_twitter_credentials()) or None, + facebook = self.get_use_facebook()) + fb2c.load_friends() + fb2c.sync_contacts(resync = force) + gobject.idle_add(hildon.hildon_banner_show_information, self.window, '', "Updated %d contacts" % (len(fb2c.updated))) + gobject.idle_add(self.window.set_property, 'sensitive', True) + except Exception, e: + print e + gobject.idle_add(self.report_error, 'Something went wrong: ' + e.message) + + + # ----------------------------------------------------------------------- + def open_prefs(self, widget = None): + dialog = gtk.Dialog('Accounts', self.window) + dialog.add_button('Save', gtk.RESPONSE_OK) + + use_facebook = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT) + use_facebook.set_label('Use Facebook') + use_facebook.set_active(self.get_use_facebook()) + dialog.vbox.add(use_facebook) + + use_twitter = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT) + use_twitter.set_label('Use Twitter') + use_twitter.set_active(self.get_use_twitter()) + dialog.vbox.add(use_twitter) + + tw_indent = gtk.HBox() + tw_user = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT) + tw_user.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL) + tw_user.set_placeholder("Username") + tw_user.set_property('is-focus', False) + tw_user.set_text(self.get_twitter_credentials()[0]) + self.sync_edit(use_twitter, tw_user) + use_twitter.connect('toggled', self.sync_edit, tw_user) + tw_indent.pack_start(tw_user, padding=48) + dialog.vbox.add(tw_indent) + + tw_indent = gtk.HBox() + tw_pass = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT) + tw_pass.set_placeholder("Password") + tw_pass.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | + gtk.HILDON_GTK_INPUT_MODE_INVISIBLE) + tw_pass.set_text(self.get_twitter_credentials()[1]) + self.sync_edit(use_twitter, tw_pass) + use_twitter.connect('toggled', self.sync_edit, tw_pass) + tw_indent.pack_start(tw_pass, padding=48) + dialog.vbox.add(tw_indent) + + dialog.show_all() + result = dialog.run() + dialog.hide() + if result == gtk.RESPONSE_OK: + self.set_use_facebook(use_facebook.get_active()) + self.set_use_twitter(use_twitter.get_active(), tw_user.get_text(), tw_pass.get_text()) + + return result + + + # ----------------------------------------------------------------------- + def sync_edit(self, use_twitter, edit): + edit.set_property('sensitive', use_twitter.get_active()) + + + # ----------------------------------------------------------------------- + def need_auth(self, main = False): + if main: + hildon.hildon_banner_show_information(self.window, '', "Need to authenticate with Facebook") + else: + gobject.idle_add(self.need_auth, True) + + + # ----------------------------------------------------------------------- + def block_for_auth(self, main = False, lock = None): + if main: + note = gtk.Dialog('Facebook authorisation', self.window) + note.add_button("Validate", gtk.RESPONSE_OK) + note.vbox.add(gtk.Label("\nPress 'Validate' once Facebook has\nbeen authenticated in web browser.\n")) + + note.show_all() + result = note.run() + note.hide() + lock.release() + + else: + time.sleep(2) + lock = thread.allocate_lock() + lock.acquire() + gobject.idle_add(self.block_for_auth, True, lock) + lock.acquire() + lock.release() + + + # ----------------------------------------------------------------------- + def progress(self, i, j, main = False): + if main: + if i == 0: + self.progressbar = gtk.ProgressBar() + self.progressnote = gtk.Dialog("Fetching friends' info", self.window) + self.progressnote.vbox.add(self.progressbar) + hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 1) + + self.progressnote.show_all() + + elif i < j: + if i == 1: + self.progressnote.set_title("Updating contacts") + hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 0) + + self.progressbar.set_fraction(float(i) / float(j)) + + else: + self.progressnote.destroy() + + print i,j + else: + gobject.idle_add(self.progress, i, j, True) + + + # ----------------------------------------------------------------------- + def report_error(self, e): + hildon.hildon_banner_show_information(self.window, '', e) + + + # ----------------------------------------------------------------------- + def main(self): + # -- Window and app... + # + self.app = hildon.Program() + self.window = hildon.Window() + gtk.set_application_name('Hermes') + osso_context = osso.Context('org.maemo.hermes', '0.0.1', False) + self.app.add_window(self.window) + + self.window.connect("delete-event", gtk.main_quit) + + # -- Main window buttons... + # + box = gtk.HButtonBox() + self.window.add(box) + + box.set_property('layout-style', gtk.BUTTONBOX_SPREAD) + button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL, + title = 'Retrieve', value = "Get contacts' missing info") + button.set_property('width-request', 250) + button.connect('clicked', self.doSync, False) + box.add(button) + + button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL, + title = 'Update', value = "Update contacts' info") + button.set_property('width-request', 250) + button.connect('clicked', self.doSync, True) + box.add(button) + + # -- Application menu... + # + menu = hildon.AppMenu() + button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) + button.set_label("Accounts") + button.connect("clicked", self.open_prefs) + menu.append(button) + menu.show_all() + self.window.set_app_menu(menu) + + self.window.show_all() + gtk.main() + + + def get_use_facebook(self): + return self.gc.get_bool("/apps/maemo/hermes/use_facebook") + + + def set_use_facebook(self, value): + self.gc.set_bool("/apps/maemo/hermes/use_facebook", value) + + + def get_use_twitter(self): + return self.gc.get_bool("/apps/maemo/hermes/use_twitter") + + + def set_use_twitter(self, value, user, password): + self.gc.set_bool("/apps/maemo/hermes/use_twitter", value) + self.gc.set_string("/apps/maemo/hermes/twitter_user", user) + self.gc.set_string("/apps/maemo/hermes/twitter_pwd", password) + + + def get_twitter_credentials(self): + return (self.gc.get_string("/apps/maemo/hermes/twitter_user") or '', + self.gc.get_string("/apps/maemo/hermes/twitter_pwd") or '') + + +# ------------------------------------------------------------------------- +if __name__ == '__main__': + HermesGUI().main() + diff --git a/package/src/hermes.py b/package/src/hermes.py new file mode 100644 index 0000000..f2178ee --- /dev/null +++ b/package/src/hermes.py @@ -0,0 +1,170 @@ +import os.path +import evolution +from facebook import Facebook +import twitter +import gnome.gconf +from contacts import ContactStore +import names + +class Hermes: + """Encapsulate the process of syncing Facebook friends' information with the + Evolution contacts' database. This should be used as follows: + + * Initialise, passing in a callback (methods: need_auth(), + block_for_auth(), use_twitter(), use_facebook()). + * Call load_friends(). + * Call sync_contacts(). + * Retrieve information on changes effected. + + This requires two gconf paths to contain Facebook application keys: + /apps/maemo/hermes/key_app + /apps/maemo/hermes/key_secret + + Copyright (c) Andrew Flegg 2009. + Released under the Artistic Licence.""" + + + # ----------------------------------------------------------------------- + def __init__(self, callback, twitter = None, facebook = False): + """Constructor. Passed a callback which must implement three informational + methods: + + need_auth() - called to indicate a login is about to occur. The user + should be informed. + + block_for_auth() - prompt the user to take some action once they have + successfully logged in to Facebook. + + progress(i, j) - the application is currently processing friend 'i' of + 'j'. Should be used to provide the user a progress bar. + + Other parameters: + twitter - a username/password tuple or None if Twitter should not be + used. Defaults to None. + + facebook - boolean indicating if Facebook should be used. Defaults to + False. + """ + + self.gc = gnome.gconf.client_get_default() + self.callback = callback + self.twitter = twitter + self.facebook = facebook + + # -- Check the environment is going to work... + # + if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'): + raise Exception('Browser in gconf invalid (see NB#136012). Installation error.') + + # -- Get private keys for this app... + # + key_app = self.gc.get_string('/apps/maemo/hermes/key_app') + key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret') + if (key_app is None or key_secret is None): + raise Exception('No Facebook application keys found. Installation error.') + + self.fb = Facebook(key_app, key_secret) + self.fb.desktop = True + + + # ----------------------------------------------------------------------- + def do_fb_login(self): + """Perform authentication against Facebook and store the result in gconf + for later use. Uses the 'need_auth' and 'block_for_auth' methods on + the callback class. The former allows a message to warn the user + about what is about to happen to be shown; the second is to wait + for the user to confirm they have logged in.""" + self.fb.session_key = None + self.fb.secret = None + self.fb.uid = None + + self.callback.need_auth() + self.fb.auth.createToken() + self.fb.login() + self.callback.block_for_auth() + session = self.fb.auth.getSession() + + self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key']) + self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret']) + self.gc.set_int('/apps/maemo/hermes/uid', session['uid']) + + + # ----------------------------------------------------------------------- + def load_friends(self): + """Load information on the authenticated user's friends. If no user is + currently authenticated, prompts for a login.""" + + self.friends = {} + self.blocked_pictures = [] + + # -- Get a user session and retrieve Facebook friends... + # + if self.facebook: + if self.fb.session_key is None: + self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key') + self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key') + self.fb.uid = self.gc.get_int('/apps/maemo/hermes/uid') + + # Check the available session is still valid... + while True: + try: + if self.fb.users.getLoggedInUser() and self.fb.session_key: + break + except FacebookError: + pass + self.do_fb_login() + + self.callback.progress(0, 0) + # Get the list of friends... + attrs = ['uid', 'name', 'pic_big', 'birthday_date'] + for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs): + friend['pic'] = friend[attrs[2]] + self.friends[friend['name']] = friend + if not friend['pic']: + self.blocked_pictures.append(friend) + + # -- Retrieve following information from Twitter... + # + if self.twitter is not None: + (user, passwd) = self.twitter + api = twitter.Api(username=user, password=passwd) + users = api.GetFriends() + for friend in api.GetFriends(): + self.friends[friend.name] = {'name': friend.name, 'pic': friend.profile_image_url, 'birthday_date': None} + + + # ----------------------------------------------------------------------- + def sync_contacts(self, resync = False): + """Synchronise Facebook profiles to contact database. If resync is false, + no existing information will be overwritten.""" + + # -- Find addresses... + # + print "+++ Syncing contacts..." + addresses = evolution.ebook.open_addressbook('default') + print "+++ Addressbook opened..." + store = ContactStore(addresses) + print "+++ Contact store created..." + self.updated = [] + contacts = addresses.get_all_contacts() + current = 0 + maximum = len(contacts) + for contact in contacts: + current += 1 + self.callback.progress(current, maximum) + for name in names.variants(contact.get_name()): + if name in self.friends: + friend = self.friends[name] + + if friend['pic'] and (resync or contact.get_property('photo') is None): + print "Picture for %s is [%s]" % (name, friend['pic']) + store.set_photo(contact, friend['pic']) + self.updated.append(contact) + + if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None): + print "Birthday for %s is [%s]" % (name, friend['birthday_date']) + + break + + store.close() + diff --git a/package/src/names.py b/package/src/names.py new file mode 100644 index 0000000..b0b5fd0 --- /dev/null +++ b/package/src/names.py @@ -0,0 +1,43 @@ +"""Utilities for determing name variants. + + Copyright (c) Andrew Flegg 2009. + Released under the Artistic Licence.""" + +__names__ = [ + ['Andrew', 'Andy', 'Andi', 'Drew'], + ['Christian', 'Chris'], + ['Christopher', 'Chris'], + ['David', 'Dave'], + ['Daniel', 'Dan', 'Danny'], + ['Michael', 'Mike', 'Mic', 'Mik', 'Micky'], + ['Peter', 'Pete'], + ['Robert', 'Rob', 'Bob', 'Bobby', 'Robbie'], + ] + +__map__ = {} +for row in __names__: + for name in row: + if (not name in __map__): + __map__[name] = set(row) + else: + __map__[name] = __map__[name].union(row) + +# ----------------------------------------------------------------------- +def variants(name): + """Return a set of names which should be checked for given the input + name. Any word which is has a replacement will be replaced, and an + iterable list of all variants will be returned.""" + + result = set() + if (name is None): + return result + + result.add(name) + bits = name.split(' ') + for bit in bits: + if (bit in __map__): + for replacement in __map__[bit]: + result.add(name.replace(bit, replacement)) + + return result + -- 1.7.9.5