Add v0.0.6 of Hermes from source tarball 0.0.6
authorAndrew Flegg <andrew@bleb.org>
Tue, 13 Oct 2009 19:35:41 +0000 (20:35 +0100)
committerAndrew Flegg <andrew@bleb.org>
Tue, 13 Oct 2009 19:35:41 +0000 (20:35 +0100)
12 files changed:
package/Makefile
package/debian/changelog
package/debian/control
package/share/hermes-256.png
package/share/hermes-48.png
package/share/hermes-64.png
package/src/contact-update.c [deleted file]
package/src/contacts.py
package/src/contactview.py
package/src/gui.py
package/src/hermes.py
package/src/pygobject.py [new file with mode: 0644]

index 78f40e8..6501e47 100644 (file)
@@ -3,12 +3,10 @@ compile:
        uuencode -m share/hermes-48.png - | perl -ne 'print " $$_" unless $$. == 1 or /^====$$/' >>debian/control
        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/hildon/hermes.png
        install -D -m 0644 -o root -g root share/hermes-48.png ${DESTDIR}/usr/share/icons/hicolor/48x48/hildon/hermes.png
index 1dc476b..a3cef84 100644 (file)
@@ -1,3 +1,16 @@
+hermes (0.0.6) unstable; urgency=low
+
+  * New icons from Tim Samoff.
+  * Set Facebook and Twitter URLs into profiles (increases data
+    enrichment, and will be used for resyncing).
+  * Manipulate contact photos directly from Python, rather than
+    using external C program.
+  * Remove "Back" buttons from contact views - not consistent with
+    Maemo 5 HIG.
+  * Implement birthday adding.
+
+ -- Andrew Flegg <andrew@bleb.org>  Tue,  6 Oct 2009 22:49:22 +0100
+
 hermes (0.0.5) unstable; urgency=low
 
   * Fix gnome-python dependency (re-reported by Andrew Smith &
index 352db09..1861bab 100644 (file)
@@ -2,12 +2,11 @@ Source: hermes
 Section: user/utilities
 Priority: extra
 Maintainer: Andrew Flegg <andrew@bleb.org>
-Build-Depends: debhelper (>= 5), python-runtime | python2.5-runtime, libebook-dev,
- sharutils, libosso-abook-dev
+Build-Depends: debhelper (>= 5), python-runtime | python2.5-runtime, sharutils
 Standards-Version: 3.7.2
 
 Package: hermes
-Architecture: any
+Architecture: all
 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, gnome-python
@@ -19,112 +18,118 @@ Description: Enrich contacts' information from social networks.
 XB-Maemo-Display-Name: Hermes
 XB-Maemo-Icon-26:
  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
-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
- EevKlKQAAAAASUVORK5CYII=
+ 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
index 524cd5d..e3b774f 100644 (file)
Binary files a/package/share/hermes-256.png and b/package/share/hermes-256.png differ
index d6240ca..b8dd00f 100644 (file)
Binary files a/package/share/hermes-48.png and b/package/share/hermes-48.png differ
index 9f4d645..0cec6ae 100644 (file)
Binary files a/package/share/hermes-64.png and b/package/share/hermes-64.png differ
diff --git a/package/src/contact-update.c b/package/src/contact-update.c
deleted file mode 100644 (file)
index 47b6a74..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * 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 <name> --photo <mime-type> <file>
- *         contact-update <name> --birthday <year> <month> <day>
- */
-#include <libebook/e-book.h>
-#include <libebook/e-contact.h>
-#include <glib.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <string.h>
-
-// 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, <var>message</var> will be shown
- *               and the process terminated.
- * @param message Message to display if <var>result</var> 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: <name> --photo <mime-type> <file>\n        <name> --birthday <year> <month> <day>");
-
-        // -- 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: <name> --photo <mime-type> <file>");
-          photo = load_photo(argv[3], argv[4]);
-          is_photo = 1;
-          
-        } else if (strcmp(argv[2], "--birthday") == 0) {
-          try(argc == 6, "syntax: <name> --birthday <year> <month> <day>");
-          birthday = read_date(argv[3], argv[4], argv[5]);
-          is_birthday = 1;
-          
-        } else {
-          try(1 == 0, "\nsyntax: <name> --photo <mime-type> <file>\n        <name> --birthday <year> <month> <day>");
-        }
-        
-        // -- 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;
-}
-
index 0b7a1e9..4963191 100644 (file)
@@ -3,6 +3,17 @@ import os.path
 import urllib
 import Image
 import ImageOps
+import StringIO
+import datetime
+from pygobject import *
+from ctypes import *
+
+# Constants from http://library.gnome.org/devel/libebook/stable/EContact.html#EContactField
+ebook = CDLL('libebook-1.2.so.5')
+E_CONTACT_HOMEPAGE_URL = 42
+E_CONTACT_PHOTO = 94
+E_CONTACT_BIRTHDAY_DATE = 107
+
 
 class ContactStore:
   """Provide an API for changing contact data. Abstracts limitations
@@ -17,7 +28,6 @@ class ContactStore:
     """Create a new contact store for modifying contacts in the given
        EBook."""
 
-    self.temp_file = os.tmpnam()
     self.book = book
 
  
@@ -25,8 +35,7 @@ class ContactStore:
   def close(self):
     """Close the store and tidy-up any resources."""
 
-    if (os.path.isfile(self.temp_file)):
-      os.unlink(self.temp_file)
+    pass
 
 
   # -----------------------------------------------------------------------
@@ -35,13 +44,91 @@ class ContactStore:
        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)
+    f = urllib.urlopen(url)
+    data = ''
+    while True:
+      read_data = f.read()
+      data += read_data
+      if not read_data:
+        break
+
+    im = Image.open(StringIO.StringIO(data))
     (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)
+    f = StringIO.StringIO()
+    im.save(f, "JPEG")
+    image_data = f.getvalue()
+    photo = EContactPhoto()
+    photo.type = 0
+    photo.data = EContactPhoto_data()
+    photo.data.inlined = EContactPhoto_inlined()
+    photo.data.inlined.mime_type = cast(create_string_buffer("image/jpeg"), c_char_p)
+    photo.data.inlined.length = len(image_data)
+    photo.data.inlined.data = cast(create_string_buffer(image_data), c_void_p)
+    ebook.e_contact_set(hash(contact), E_CONTACT_PHOTO, addressof(photo))
+    return True
+    
+    
+  # -----------------------------------------------------------------------
+  def set_birthday(self, contact, day, month, year = 0):
+    if year == 0:
+      year = datetime.date.today().year
+      
+    birthday = EContactDate()
+    birthday.year = year
+    birthday.month = month
+    birthday.day = day
+    print "Setting birthday for [%s] to %d-%d-%d" % (contact.get_name(), year, month, day)
+    ebook.e_contact_set(hash(contact), E_CONTACT_BIRTHDAY_DATE, addressof(birthday))
+    return True
+    
+    
+  # -----------------------------------------------------------------------
+  def get_urls(self, contact):
+    """Return a list of URLs which are associated with this contact."""
+
+    urls = []
+    ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL))
+    while ai.has_next():
+      attr = ai.next(as_a = EVCardAttribute)
+      if not attr:
+        raise Exception("Unexpected null attribute for [" + contact.get_name() + "] with URLs " + urls)
+      urls.append(string_at(attr.value().next()))
+      
+    return urls
+
+    
+  # -----------------------------------------------------------------------
+  def add_url(self, contact, url, unique = ''):
+    """Add a new URL to the set of URLs for the given contact."""
+
+    unique = unique or url
+    url_attr = None
+    ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL))
+    while ai.has_next():
+      attr = ai.next(as_a = EVCardAttribute)
+      existing = string_at(attr.value().next())
+      if existing == unique or existing == url:
+        return False
+      elif existing.find(unique) > -1:
+        url_attr = attr
+        break
+    
+    if not url_attr:
+      ai.add()
+      url_attr = EVCardAttribute()
+      url_attr.group = ''
+      url_attr.name = 'URL'
+      
+    val = GList()
+    print "Setting URL for [%s] to [%s]" % (contact.get_name(), url)
+    val.set(create_string_buffer(url))
+    ai.set(addressof(url_attr))
+    url_attr.values = cast(addressof(val), POINTER(GList))
+    ebook.e_contact_set_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL, addressof(ai))
+    return True
+
index 6488cc5..e6db658 100644 (file)
@@ -1,6 +1,7 @@
 import gtk
 import hildon
 from ctypes import *
+from pygobject import *
 
 class ContactView(hildon.PannableArea):
   """Widget which shows a list of contacts in a pannable area.
@@ -50,18 +51,3 @@ class ContactView(hildon.PannableArea):
     self.add(self.treeview)
     self.set_size_request(600, 380)
 
-
-class EContactPhoto_inlined(Structure):
-  _fields_ = [('mime_type', c_char_p),
-              ('length', c_uint),
-              ('data', c_void_p)]
-
-class EContactPhoto_data(Union):
-  _fields_ = [('inlined', EContactPhoto_inlined),
-              ('uri', c_char_p)]
-
-class EContactPhoto(Structure):
-  _fields_ = [('type', c_int),
-              ('data', EContactPhoto_data)]
-
-
index 37e843b..64fa675 100755 (executable)
@@ -83,7 +83,6 @@ class HermesGUI:
     view = contactview.ContactView(contacts)
 
     dialog = gtk.Dialog('Contacts', self.window)
-    dialog.add_button('Back', gtk.RESPONSE_OK)
     #view.connect('contact-activated', self.map_contact)
     dialog.vbox.add(view)
     dialog.show_all()
index 45bb05f..81faeac 100644 (file)
@@ -116,7 +116,7 @@ class Hermes:
         self.do_fb_login()
 
       # Get the list of friends...
-      attrs = ['uid', 'name', 'pic_big', 'birthday_date']
+      attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url']
       for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
         friend['pic'] = friend[attrs[2]]
         self.friends[friend['name']] = friend
@@ -130,8 +130,10 @@ class Hermes:
       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}
+        self.friends[friend.name] = {'name': friend.name, 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': 'http://twitter.com/%s' % (friend.screen_name), 'homepage' : friend.url}
   
+    # TODO What if the user has *no* contacts?
+
   
   # -----------------------------------------------------------------------
   def sync_contacts(self, resync = False):
@@ -160,15 +162,32 @@ class Hermes:
         if name in self.friends:
           friend = self.friends[name]
           found = True
+          updated = False
       
           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)
+            updated = store.set_photo(contact, friend['pic']) or updated
         
           if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
-            print "Birthday for %s is [%s]" % (name, friend['birthday_date'])
+            date_str = friend['birthday_date'].split('/')
+            date_str.append('0')
+            updated = store.set_birthday(contact, int(date_str[1]),
+                                                  int(date_str[0]),
+                                                  int(date_str[2])) or updated
+
+          if 'profile_url' in friend and friend['profile_url']:
+            updated = store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
+            
+          if 'twitter_url' in friend and friend['twitter_url']:
+            updated = store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
+            
+          if 'homepage' in friend and friend['homepage']:
+            updated = store.add_url(contact, friend['homepage']) or updated
     
+          if updated:
+            self.updated.append(contact)
+            addresses.commit_contact(contact)
+            print "Saved changes to [%s]" % (contact.get_name())
+            
           break
       
       if found:
diff --git a/package/src/pygobject.py b/package/src/pygobject.py
new file mode 100644 (file)
index 0000000..d095b6f
--- /dev/null
@@ -0,0 +1,156 @@
+from ctypes import *
+import sys
+import ctypes
+
+import gobject
+
+# -------------------------------------------------------------------------
+class _PyGObject_Functions(ctypes.Structure):
+    """GObject <-> Python mapping from http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp"""
+    _fields_ = [
+        ('register_class',
+         ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p,
+                           ctypes.c_int, ctypes.py_object,
+                           ctypes.py_object)),
+        ('register_wrapper',
+         ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.py_object)),
+        ('register_sinkfunc',
+         ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
+        ('lookupclass',
+         ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)),
+        ('newgobj',
+         ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
+        ]
+
+
+# -------------------------------------------------------------------------
+class PyGObjectCAPI(object):
+    """GObject <-> Python mapping from http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp"""
+    
+    def __init__(self):
+        addr = ctypes.pythonapi.PyCObject_AsVoidPtr(
+            ctypes.py_object(gobject._PyGObject_API))
+        self._api = _PyGObject_Functions.from_address(addr)
+
+    def pygobject_new(self, addr):
+        return self._api.newgobj(addr)
+
+
+# -------------------------------------------------------------------------
+class GList(Structure):
+  """GList representation and convenience functions, based on Java's Iterable.
+  
+     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+     Released under the Artistic Licence."""
+
+
+  # -----------------------------------------------------------------------
+  @classmethod
+  def new(clazz, ptr = None):
+    """Return a reference to an empty, or valid, GList at the
+       given pointer address."""
+       
+    if ptr:
+      return cast(c_void_p(ptr), POINTER(GList)).contents
+    else:
+      return GList()
+
+
+  # -----------------------------------------------------------------------
+  _fields_ = [('_data', c_void_p),
+              ('_next', c_void_p),
+              ('_prev', c_void_p)]
+              
+  _ptr = None # Initialises to before the list for `while(has_next)...'
+
+  
+  # -----------------------------------------------------------------------
+  def reset(self):
+    """Rewind the iterable to the start of the list."""
+    
+    self._ptr = None
+    
+  # -----------------------------------------------------------------------
+  def has_next(self):
+    """Return True if the list has an item on which next can be called."""
+    
+    return (not self._ptr and self._data) or (self._ptr and self._ptr._next)
+
+
+  # -----------------------------------------------------------------------
+  def next(self, as_a = None):
+    """Move the pointer on to the next item in the list and return its value, or
+       raise an exception if already on the last."""
+       
+    if self._ptr and not self._ptr._next:
+      raise Exception("IndexOutOfBounds")
+
+    self._ptr = self._ptr and cast(self._ptr._next, POINTER(GList)).contents or self
+    if not self._ptr._data:
+      return None
+    elif as_a:
+      return cast(self._ptr._data, POINTER(as_a)).contents
+    else:
+      return self._ptr._data
+      
+      
+  # -----------------------------------------------------------------------
+  def set(self, value):
+    """Set the data in the current position in the list."""
+    
+    if not self._ptr:
+      self._ptr = self
+      
+    self._ptr._data = cast(value, c_void_p);
+    
+    
+    
+  # -----------------------------------------------------------------------
+  def add(self):
+    """Add a new entry on to the end of the list, ready to be "set"."""
+    
+    self.reset()
+    while self.has_next():
+      self.next()
+      
+    if not self._ptr:
+      self._ptr = self
+    else:      
+      new = GList()
+      new._prev = addressof(self._ptr)
+      self._ptr._next = addressof(new)
+      self._ptr = new
+
+
+# -------------------------------------------------------------------------
+class EContactPhoto_inlined(Structure):
+  _fields_ = [('mime_type', c_char_p),
+              ('length', c_uint),
+              ('data', c_void_p)]
+
+class EContactPhoto_data(Union):
+  _fields_ = [('inlined', EContactPhoto_inlined),
+              ('uri', c_char_p)]
+
+class EContactPhoto(Structure):
+  _fields_ = [('type', c_int),
+              ('data', EContactPhoto_data)]
+
+class EContactDate(Structure):
+  _fields_ = [('year', c_uint),
+              ('month', c_uint),
+              ('day', c_uint)]
+
+# -------------------------------------------------------------------------
+class EVCardAttribute(Structure):
+ _fields_ = [('group', c_char_p),
+            ('name', c_char_p),
+            ('params', POINTER(GList)),
+            ('values', POINTER(GList)),]
+            
+ def value(self):
+   if not self.values:
+     return None
+     
+   return self.values.contents
+