13 import util.coroutines as coroutines
14 import util.misc as misc_utils
15 import util.go_utils as gobject_utils
18 _moduleLogger = logging.getLogger(__name__)
21 class Addressbook(object):
26 OLDEST_COMPATIBLE_FORMAT_VERSION = misc_utils.parse_version("0.8.0")
28 def __init__(self, backend, asyncPool):
29 self._backend = backend
31 self._asyncPool = asyncPool
33 self.updateSignalHandler = coroutines.CoTee()
36 _moduleLogger.debug("Loading cache")
37 assert not self._numbers
39 with open(path, "rb") as f:
40 fileVersion, fileBuild, contacts = pickle.load(f)
41 except (pickle.PickleError, IOError, EOFError, ValueError):
42 _moduleLogger.exception("While loading")
45 if contacts and misc_utils.compare_versions(
46 self.OLDEST_COMPATIBLE_FORMAT_VERSION,
47 misc_utils.parse_version(fileVersion),
49 _moduleLogger.info("Loaded cache")
50 self._numbers = contacts
51 self._loadedFromCache = True
54 "Skipping cache due to version mismatch (%s-%s)" % (
55 fileVersion, fileBuild
60 _moduleLogger.info("Saving cache")
62 _moduleLogger.info("Odd, no conversations to cache. Did we never load the cache?")
66 dataToDump = (constants.__version__, constants.__build__, self._numbers)
67 with open(path, "wb") as f:
68 pickle.dump(dataToDump, f, pickle.HIGHEST_PROTOCOL)
69 except (pickle.PickleError, IOError):
70 _moduleLogger.exception("While saving for %s" % self._name)
71 _moduleLogger.info("Cache saved")
73 def update(self, force=False):
74 if not force and self._numbers:
77 le = gobject_utils.AsyncLinearExecution(self._asyncPool, self._update)
80 @misc_utils.log_exception(_moduleLogger)
84 self._backend.get_contacts,
89 _moduleLogger.exception("While updating the addressbook")
92 oldContacts = self._numbers
93 oldContactNumbers = set(self.get_numbers())
95 self._numbers = self._populate_contacts(contacts)
96 newContactNumbers = set(self.get_numbers())
98 addedContacts = newContactNumbers - oldContactNumbers
99 removedContacts = oldContactNumbers - newContactNumbers
100 changedContacts = set(
102 for contactNumber in newContactNumbers.intersection(oldContactNumbers)
103 if self._numbers[contactNumber] != oldContacts[contactNumber]
106 if addedContacts or removedContacts or changedContacts:
107 message = self, addedContacts, removedContacts, changedContacts
108 self.updateSignalHandler.stage.send(message)
110 def get_numbers(self):
111 return self._numbers.iterkeys()
113 def get_contact_name(self, strippedNumber):
115 @throws KeyError if contact not in list (so client can choose what to display)
117 return self._numbers[strippedNumber][0]
119 def get_phone_type(self, strippedNumber):
121 return self._numbers[strippedNumber][1]
125 def is_blocked(self, strippedNumber):
127 return self._numbers[strippedNumber][2]["response"] == self._RESPONSE_BLOCKED
131 def _populate_contacts(self, contacts):
133 for contactId, contactDetails in contacts:
134 contactName = contactDetails["name"]
137 misc_utils.normalize_number(numberDetails["phoneNumber"]),
138 numberDetails.get("phoneType", "Mobile"),
140 for numberDetails in contactDetails["numbers"]
143 (number, (contactName, phoneType, contactDetails))
144 for (number, phoneType) in contactNumbers