2a61213658b6dcf94d6690e05e066ac60a13c09d
[theonering] / src / gvoice / addressbook.py
1 #!/usr/bin/python
2
3
4 import logging
5
6 try:
7         import cPickle
8         pickle = cPickle
9 except ImportError:
10         import pickle
11
12 import constants
13 import util.coroutines as coroutines
14 import util.misc as misc_utils
15 import util.go_utils as gobject_utils
16
17
18 _moduleLogger = logging.getLogger(__name__)
19
20
21 class Addressbook(object):
22
23         _RESPONSE_GOOD = 0
24         _RESPONSE_BLOCKED = 3
25
26         OLDEST_COMPATIBLE_FORMAT_VERSION = misc_utils.parse_version("0.8.0")
27
28         def __init__(self, backend, asyncPool):
29                 self._backend = backend
30                 self._numbers = {}
31                 self._asyncPool = asyncPool
32
33                 self.updateSignalHandler = coroutines.CoTee()
34
35         def load(self, path):
36                 _moduleLogger.debug("Loading cache")
37                 assert not self._numbers
38                 try:
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")
43                         return
44
45                 if contacts and misc_utils.compare_versions(
46                         self.OLDEST_COMPATIBLE_FORMAT_VERSION,
47                         misc_utils.parse_version(fileVersion),
48                 ) <= 0:
49                         _moduleLogger.info("Loaded cache")
50                         self._numbers = contacts
51                         self._loadedFromCache = True
52                 else:
53                         _moduleLogger.debug(
54                                 "Skipping cache due to version mismatch (%s-%s)" % (
55                                         fileVersion, fileBuild
56                                 )
57                         )
58
59         def save(self, path):
60                 _moduleLogger.info("Saving cache")
61                 if not self._numbers:
62                         _moduleLogger.info("Odd, no conversations to cache.  Did we never load the cache?")
63                         return
64
65                 try:
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")
72
73         def update(self, force=False):
74                 if not force and self._numbers:
75                         return
76
77                 le = gobject_utils.AsyncLinearExecution(self._asyncPool, self._update)
78                 le.start()
79
80         @misc_utils.log_exception(_moduleLogger)
81         def _update(self):
82                 try:
83                         contacts = yield (
84                                 self._backend.get_contacts,
85                                 (),
86                                 {},
87                         )
88                 except Exception:
89                         _moduleLogger.exception("While updating the addressbook")
90                         return
91
92                 oldContacts = self._numbers
93                 oldContactNumbers = set(self.get_numbers())
94
95                 self._numbers = self._populate_contacts(contacts)
96                 newContactNumbers = set(self.get_numbers())
97
98                 addedContacts = newContactNumbers - oldContactNumbers
99                 removedContacts = oldContactNumbers - newContactNumbers
100                 changedContacts = set(
101                         contactNumber
102                         for contactNumber in newContactNumbers.intersection(oldContactNumbers)
103                         if self._numbers[contactNumber] != oldContacts[contactNumber]
104                 )
105
106                 if addedContacts or removedContacts or changedContacts:
107                         message = self, addedContacts, removedContacts, changedContacts
108                         self.updateSignalHandler.stage.send(message)
109
110         def get_numbers(self):
111                 return self._numbers.iterkeys()
112
113         def get_contact_name(self, strippedNumber):
114                 """
115                 @throws KeyError if contact not in list (so client can choose what to display)
116                 """
117                 return self._numbers[strippedNumber][0]
118
119         def get_phone_type(self, strippedNumber):
120                 try:
121                         return self._numbers[strippedNumber][1]
122                 except KeyError:
123                         return "unknown"
124
125         def is_blocked(self, strippedNumber):
126                 try:
127                         return self._numbers[strippedNumber][2]["response"] == self._RESPONSE_BLOCKED
128                 except KeyError:
129                         return False
130
131         def _populate_contacts(self, contacts):
132                 numbers = {}
133                 for contactId, contactDetails in contacts:
134                         contactName = contactDetails["name"]
135                         contactNumbers = (
136                                 (
137                                         misc_utils.normalize_number(numberDetails["phoneNumber"]),
138                                         numberDetails.get("phoneType", "Mobile"),
139                                 )
140                                 for numberDetails in contactDetails["numbers"]
141                         )
142                         numbers.update(
143                                 (number, (contactName, phoneType, contactDetails))
144                                 for (number, phoneType) in contactNumbers
145                         )
146                 return numbers