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