eeef1fb24f6f0cbe84bb738cdc3247c27a9b4aba
[hermes] / package / src / org / maemo / hermes / engine / contact.py
1 import urllib
2 import Image
3 import ImageOps
4 import StringIO
5 import datetime
6 import re
7 from org.maemo.hermes.engine.names import canonical, variants
8 from pygobject import *
9 from ctypes import *
10
11 # Constants from http://library.gnome.org/devel/libebook/stable/EContact.html#EContactField
12 ebook = CDLL('libebook-1.2.so.5')
13 E_CONTACT_HOMEPAGE_URL = 42
14 E_CONTACT_PHOTO = 94
15 E_CONTACT_EMAIL = 97
16 E_CONTACT_BIRTHDAY_DATE = 107
17
18
19 class Contact:
20     """Provide an abstraction of contact, working around limitations
21        in the evolution-python bindings. Properties are defined at:
22        
23           http://library.gnome.org/devel/libebook/stable/EContact.html#id3066469
24
25        Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
26        Released under the Artistic Licence."""
27
28        
29     # -----------------------------------------------------------------------
30     def __init__(self, book, econtact):
31         """Create a new contact store for modifying contacts in the given
32            EBook."""
33         
34         self._book = book
35         self._contact = econtact
36         self._identifiers = self._find_ids()
37         self._mapped_to = set([])
38         
39         
40     # -----------------------------------------------------------------------
41     def _find_ids(self):
42         """Return a set of the identifiers which should be used to match this
43            contact. Includes variants of first name, last name, nickname and
44            email address. These are all Unicode-normalised into the traditional
45            US-ASCII namespace"""
46            
47         result = set()
48         for name in variants(self._contact.get_name()):
49             result.add(canonical(name))
50             
51         for name in variants(self._contact.get_property('nickname')):
52             result.add(canonical(name))
53             
54         for email in self.get_emails():
55             user = canonical(email.split('@')[0])
56             if len(user) > 4:
57                 result.add(user)
58
59         return result
60     
61     
62     # -----------------------------------------------------------------------
63     def get_name(self):
64         """Return this contact's name."""
65         
66         return self._contact.get_name() or self._contact.get_property('nickname')
67     
68     
69     # -----------------------------------------------------------------------
70     def get_identifiers(self):
71         """Return the lowercase, Unicode-normalised, all-alphabetic
72            versions of identifiers for this contact."""
73            
74         return self._identifiers
75
76     
77     # -----------------------------------------------------------------------
78     def get_econtact(self):
79         """Return the EContact which backs this contact."""
80         
81         return self._contact
82     
83     
84     # -----------------------------------------------------------------------
85     def add_mapping(self, id):
86         """Record the fact that this contact is mapped against a provider.
87            'id' MUST match the string returned by Provider.get_id()."""
88         
89         self._mapped_to.add(id)
90         
91         
92     # ----------------------------------------------------------------------
93     def get_mappings(self):
94         """Return the set of IDs of providers which are mapped to this contact.
95            The data can only be relied upon after services have run."""
96            
97         return self._mapped_to
98
99
100     # -----------------------------------------------------------------------
101     def get_photo(self):
102         """Return the photo property, or None. The result is of type
103         EContactPhoto."""
104         
105         photo = self._contact.get_property('photo')
106         #print "Photo[" + re.sub('[^A-Za-z0-9_-]', '.', string_at(cast(c_void_p(hash(photo)), c_char_p))) + "]"
107         pi = cast(c_void_p(hash(photo)), POINTER(EContactPhoto))
108         if photo is None or pi.contents.data.uri == '':
109             return None
110         else:
111             return pi
112     
113     
114     # -----------------------------------------------------------------------
115     def set_photo(self, url):
116         """Set the given contact's photo to the picture found at the URL. If the
117            photo is wider than it is tall, it will be cropped with a bias towards
118            the top of the photo."""
119         
120         try:
121             f = urllib.urlopen(url)
122             data = ''
123             while True:
124                 read_data = f.read()
125                 data += read_data
126                 if not read_data:
127                     break
128             
129             im = Image.open(StringIO.StringIO(data))
130             if im.mode != 'RGB':
131                 im.convert('RGB')
132                 
133             (w, h) = im.size
134             if (h > w):
135                 ##print u"Shrinking photo for %s as it's %d x %d" % (self._contact.get_name(), w, h)
136                 im = ImageOps.fit(im, (w, w), Image.NEAREST, 0, (0, 0.1))
137               
138             ## print u"Updating photo for %s" % (self._contact.get_name())
139             f = StringIO.StringIO()
140             im.save(f, "JPEG")
141             image_data = f.getvalue()
142             photo = EContactPhoto()
143             photo.type = 0
144             photo.data = EContactPhoto_data()
145             photo.data.inlined = EContactPhoto_inlined()
146             photo.data.inlined.mime_type = cast(create_string_buffer("image/jpeg"), c_char_p)
147             photo.data.inlined.length = len(image_data)
148             photo.data.inlined.data = cast(create_string_buffer(image_data), c_void_p)
149             ebook.e_contact_set(hash(self._contact), E_CONTACT_PHOTO, addressof(photo))
150             return True
151         except:
152             print "FAILED to get photo from URL %s" % url
153             return False
154       
155       
156     # -----------------------------------------------------------------------
157     def set_birthday(self, day, month, year = 0):
158         """Set the birthday for this contact to the given day, month and year."""
159         
160         if year == 0:
161             year = datetime.date.today().year
162           
163         birthday = EContactDate()
164         birthday.year = year
165         birthday.month = month
166         birthday.day = day
167         print u"Setting birthday for [%s] to %d-%d-%d" % (self._contact.get_name(), year, month, day)
168         ebook.e_contact_set(hash(self._contact), E_CONTACT_BIRTHDAY_DATE, addressof(birthday))
169         return True
170     
171     
172     # -----------------------------------------------------------------------
173     def get_birthday(self):
174         photo = self._contact.get_property('birth-date')
175         return cast(c_void_p(hash(photo)), POINTER(EContactDate))
176         
177     
178     # -----------------------------------------------------------------------
179     def set_nickname(self, nickname):
180         """Set the nickname for this contact to the given nickname."""
181         
182         # FIXME does this work?
183         self._contact.set_property('nickname', nickname)
184         #ebook.e_contact_set(hash(self._contact), E_NICKNAME, addressof(nickname))
185     
186     
187     # -----------------------------------------------------------------------
188     def get_nickname(self):
189         """Get the nickname for this contact."""
190         
191         return self._contact.get_property('nickname')
192     
193     
194     # -----------------------------------------------------------------------
195     def get_emails(self):
196         """Return the email addresses associated with this contact."""
197         
198         emails = []
199         ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_EMAIL))
200         while ai.has_next():
201             attr = ai.next(as_a = EVCardAttribute)
202             if not attr:
203                 raise Exception(u"Unexpected null attribute for [" + self._contact.get_name() + "] with emails " + emails)
204             emails.append(string_at(attr.value().next()))
205           
206         return emails
207         
208       
209       
210     # -----------------------------------------------------------------------
211     def get_urls(self):
212         """Return a list of URLs which are associated with this contact."""
213         
214         urls = []
215         ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_HOMEPAGE_URL))
216         while ai.has_next():
217             attr = ai.next(as_a = EVCardAttribute)
218             if not attr:
219                 raise Exception(u"Unexpected null attribute for [" + self._contact.get_name() + "] with URLs " + urls)
220             urls.append(string_at(attr.value().next()))
221           
222         return urls
223     
224       
225     # -----------------------------------------------------------------------
226     def add_url(self, str, unique = ''):
227         """Add a new URL to the set of URLs for the given contact."""
228         
229         urls = re.findall('(?:(?:ftp|https?):\/\/|\\bwww\.|\\bftp\.)[,\w\.\-\/@:%?&=%+#~_$\*]+[\w=\/&=+#]', str, re.I | re.S)
230         updated = False
231         for url in urls:
232             updated = self._add_url(url, unique or re.sub('(?:.*://)?(\w+(?:[\w\.])*).*', '\\1', url)) or updated
233         
234         return updated
235     
236     
237     # -----------------------------------------------------------------------
238     def _add_url(self, url, unique):
239         """Do the work of adding a unique URL to a contact."""
240         
241         url_attr = None
242         ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_HOMEPAGE_URL))
243         while ai.has_next():
244             attr = ai.next(as_a = EVCardAttribute)
245             existing = string_at(attr.value().next())
246             #print "Existing URL [%s] when adding [%s] to [%s] with constraint [%s]" % (existing, url, contact.get_name(), unique)
247             if existing == unique or existing == url:
248                 return False
249             elif existing.find(unique) > -1:
250                 url_attr = attr
251           
252         if not url_attr:
253             ai.add()
254             url_attr = EVCardAttribute()
255             url_attr.group = ''
256             url_attr.name = 'URL'
257         
258         val = GList()
259         ##print u"Setting URL for [%s] to [%s]" % (self._contact.get_name(), url)
260         val.set(create_string_buffer(url))
261         ai.set(addressof(url_attr))
262         url_attr.values = cast(addressof(val), POINTER(GList))
263         ebook.e_contact_set_attributes(hash(self._contact), E_CONTACT_HOMEPAGE_URL, addressof(ai))
264         return True
265