7 from org.maemo.hermes.engine.names import canonical, variants
8 from org.maemo.hermes.engine.phonenumber import PhoneNumber
9 from pygobject import *
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
17 E_CONTACT_BIRTHDAY_DATE = 107
18 E_CONTACT_PHONE_OTHER = 30
22 """Provide an abstraction of contact, working around limitations
23 in the evolution-python bindings. Properties are defined at:
25 http://library.gnome.org/devel/libebook/stable/EContact.html#id3066469
27 Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
28 Released under the Artistic Licence."""
31 # -----------------------------------------------------------------------
32 def __init__(self, book, econtact):
33 """Create a new contact store for modifying contacts in the given
37 self._contact = econtact
38 self._identifiers = self._find_ids()
39 self._mapped_to = set([])
42 # -----------------------------------------------------------------------
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
50 for name in variants(self._contact.get_name()):
51 result.add(canonical(name))
53 for name in variants(self._contact.get_property('nickname')):
54 result.add(canonical(name))
56 for email in self.get_emails():
57 user = canonical(email.split('@')[0])
64 # -----------------------------------------------------------------------
66 """Return this contact's name."""
68 return self._contact.get_name() or self._contact.get_property('nickname')
71 # -----------------------------------------------------------------------
72 def get_identifiers(self):
73 """Return the lowercase, Unicode-normalised, all-alphabetic
74 versions of identifiers for this contact."""
76 return self._identifiers
79 # -----------------------------------------------------------------------
80 def get_econtact(self):
81 """Return the EContact which backs this contact."""
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()."""
91 self._mapped_to.add(id)
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."""
99 return self._mapped_to
102 # -----------------------------------------------------------------------
104 """Return the photo property, or None. The result is of type
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 == '':
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."""
123 f = urllib.urlopen(url)
131 im = Image.open(StringIO.StringIO(data))
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))
140 ## print u"Updating photo for %s" % (self._contact.get_name())
141 f = StringIO.StringIO()
143 image_data = f.getvalue()
144 photo = EContactPhoto()
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))
154 print "FAILED to get photo from URL %s" % url
158 # -----------------------------------------------------------------------
159 def set_birthday(self, day, month, year = 0):
160 """Set the birthday for this contact to the given day, month and year."""
163 year = datetime.date.today().year
165 birthday = EContactDate()
167 birthday.month = month
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))
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
180 # -----------------------------------------------------------------------
181 def set_nickname(self, nickname):
182 """Set the nickname for this contact to the given nickname."""
184 # FIXME does this work?
185 self._contact.set_property('nickname', nickname)
186 #ebook.e_contact_set(hash(self._contact), E_NICKNAME, addressof(nickname))
189 # -----------------------------------------------------------------------
190 def get_nickname(self):
191 """Get the nickname for this contact."""
193 return self._contact.get_property('nickname')
196 # -----------------------------------------------------------------------
197 def get_emails(self):
198 """Return the email addresses associated with this contact."""
201 ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_EMAIL))
203 attr = ai.next(as_a = EVCardAttribute)
205 raise Exception(u"Unexpected null attribute for [" + self._contact.get_name() + "] with emails " + emails)
206 emails.append(string_at(attr.value().next()))
211 # -----------------------------------------------------------------------
213 """Return a list of URLs which are associated with this contact."""
216 ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_HOMEPAGE_URL))
218 attr = ai.next(as_a = EVCardAttribute)
220 raise Exception(u"Unexpected null attribute for [" + self._contact.get_name() + "] with URLs " + urls)
221 urls.append(string_at(attr.value().next()))
226 # -----------------------------------------------------------------------
227 def get_phones(self):
228 """Return a list of phone numbers associated with this contact."""
231 ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_PHONE_OTHER))
233 attr = ai.next(as_a = EVCardAttribute)
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()))
240 device = 'VOICE' in types and 'landline' \
241 or 'CELL' in types and 'mobile' \
243 type = 'HOME' in types and 'home' \
244 or 'WORK' in types and 'work' \
246 number = string_at(attr.value().next())
247 nums.append(PhoneNumber(number, type = type, device = device))
252 # -----------------------------------------------------------------------
253 def add_url(self, str, unique = ''):
254 """Add a new URL to the set of URLs for the given contact."""
256 urls = re.findall('(?:(?:ftp|https?):\/\/|\\bwww\.|\\bftp\.)[,\w\.\-\/@:%?&=%+#~_$\*]+[\w=\/&=+#]', str, re.I | re.S)
259 updated = self._add_url(url, unique or re.sub('(?:.*://)?(\w+(?:[\w\.])*).*', '\\1', url)) or updated
264 # -----------------------------------------------------------------------
265 def add_phone(self, phone):
266 """Add a new phone number to the set of numbers for this contact."""
268 compare = phone.get_number()[-6:]
269 for test in self.get_phones():
270 if test.get_number().endswith(compare):
273 ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_PHONE_OTHER))
275 tel_attr = EVCardAttribute()
277 tel_attr.name = 'TEL'
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' \
287 ebook.e_vcard_attribute_param_add_value(param,
288 create_string_buffer(phone.get_type().upper()))
292 tel_attr.params = cast(addressof(params), POINTER(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))
303 # -----------------------------------------------------------------------
304 def _add_url(self, url, unique):
305 """Do the work of adding a unique URL to a contact."""
308 ai = GList.new(ebook.e_contact_get_attributes(hash(self._contact), E_CONTACT_HOMEPAGE_URL))
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:
315 elif existing.find(unique) > -1:
320 url_attr = EVCardAttribute()
322 url_attr.name = 'URL'
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))