3 from __future__ import with_statement
15 import util.coroutines as coroutines
16 import util.misc as misc_utils
17 import util.go_utils as gobject_utils
20 _moduleLogger = logging.getLogger(__name__)
23 class Addressbook(object):
28 OLDEST_COMPATIBLE_FORMAT_VERSION = misc_utils.parse_version("0.8.0")
30 def __init__(self, backend, asyncPool):
31 self._backend = backend
33 self._asyncPool = asyncPool
35 self.updateSignalHandler = coroutines.CoTee()
38 _moduleLogger.debug("Loading cache")
39 assert not self._numbers
41 with open(path, "rb") as f:
42 fileVersion, fileBuild, contacts = pickle.load(f)
43 except (pickle.PickleError, IOError, EOFError, ValueError):
44 _moduleLogger.exception("While loading")
47 if contacts and misc_utils.compare_versions(
48 self.OLDEST_COMPATIBLE_FORMAT_VERSION,
49 misc_utils.parse_version(fileVersion),
51 _moduleLogger.info("Loaded cache")
52 self._numbers = contacts
53 self._loadedFromCache = True
56 "Skipping cache due to version mismatch (%s-%s)" % (
57 fileVersion, fileBuild
62 _moduleLogger.info("Saving cache")
64 _moduleLogger.info("Odd, no conversations to cache. Did we never load the cache?")
68 dataToDump = (constants.__version__, constants.__build__, self._numbers)
69 with open(path, "wb") as f:
70 pickle.dump(dataToDump, f, pickle.HIGHEST_PROTOCOL)
71 except (pickle.PickleError, IOError):
72 _moduleLogger.exception("While saving for %s" % self._name)
73 _moduleLogger.info("Cache saved")
75 def update(self, force=False):
76 if not force and self._numbers:
79 le = gobject_utils.AsyncLinearExecution(self._asyncPool, self._update)
82 @misc_utils.log_exception(_moduleLogger)
86 self._backend.get_contacts,
91 _moduleLogger.exception("While updating the addressbook")
94 oldContacts = self._numbers
95 oldContactNumbers = set(self.get_numbers())
97 self._numbers = self._populate_contacts(contacts)
98 newContactNumbers = set(self.get_numbers())
100 addedContacts = newContactNumbers - oldContactNumbers
101 removedContacts = oldContactNumbers - newContactNumbers
102 changedContacts = set(
104 for contactNumber in newContactNumbers.intersection(oldContactNumbers)
105 if self._numbers[contactNumber] != oldContacts[contactNumber]
108 if addedContacts or removedContacts or changedContacts:
109 message = self, addedContacts, removedContacts, changedContacts
110 self.updateSignalHandler.stage.send(message)
112 def get_numbers(self):
113 return self._numbers.iterkeys()
115 def get_contact_name(self, strippedNumber):
117 @throws KeyError if contact not in list (so client can choose what to display)
119 return self._numbers[strippedNumber][0]
121 def get_phone_type(self, strippedNumber):
123 return self._numbers[strippedNumber][1]
127 def is_blocked(self, strippedNumber):
129 return self._numbers[strippedNumber][2]["response"] == self._RESPONSE_BLOCKED
133 def _populate_contacts(self, contacts):
135 for contactId, contactDetails in contacts:
136 contactName = contactDetails["name"]
139 misc_utils.normalize_number(numberDetails["phoneNumber"]),
140 numberDetails.get("phoneType", "Mobile"),
142 for numberDetails in contactDetails["numbers"]
145 (number, (contactName, phoneType, contactDetails))
146 for (number, phoneType) in contactNumbers
151 def print_addressbook(path):
155 with open(path, "rb") as f:
156 fileVersion, fileBuild, contacts = pickle.load(f)
157 except (pickle.PickleError, IOError, EOFError, ValueError):
158 _moduleLogger.exception("")
160 pprint.pprint((fileVersion, fileBuild))
161 pprint.pprint(contacts)