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