making pickled caches block no execution
[theonering] / src / gvoice / addressbook.py
index 3ad875c..4b3793b 100644 (file)
 #!/usr/bin/python
 
+from __future__ import with_statement
+
 
 import logging
 
+try:
+       import cPickle
+       pickle = cPickle
+except ImportError:
+       import pickle
+
+import constants
 import util.coroutines as coroutines
+import util.misc as misc_utils
+import util.go_utils as gobject_utils
 
 
-_moduleLogger = logging.getLogger("gvoice.addressbook")
+_moduleLogger = logging.getLogger(__name__)
 
 
 class Addressbook(object):
 
-       def __init__(self, backend):
+       _RESPONSE_GOOD = 0
+       _RESPONSE_BLOCKED = 3
+
+       OLDEST_COMPATIBLE_FORMAT_VERSION = misc_utils.parse_version("0.8.0")
+
+       def __init__(self, backend, asyncPool):
                self._backend = backend
-               self._contacts = {}
-               self._addedContacts = set()
-               self._removedContacts = set()
-               self._changedContacts = set()
+               self._numbers = {}
+               self._asyncPool = asyncPool
 
                self.updateSignalHandler = coroutines.CoTee()
 
-       def update(self, force=False):
-               if not force and self._contacts:
+       def load(self, path):
+               _moduleLogger.debug("Loading cache")
+               assert not self._numbers
+               try:
+                       with open(path, "rb") as f:
+                               fileVersion, fileBuild, contacts = pickle.load(f)
+               except (pickle.PickleError, IOError, EOFError, ValueError, Exception):
+                       _moduleLogger.exception("While loading")
                        return
-               oldContacts = self._contacts
-               oldContactIds = set(self.get_contacts())
-
-               self._contacts = {}
-               self._populate_contacts()
-               newContactIds = set(self.get_contacts())
-
-               self._addedContacts = newContactIds - oldContactIds
-               self._removedContacts = oldContactIds - newContactIds
-               self._changedContacts = set(
-                       contactId
-                       for contactId in newContactIds.intersection(oldContactIds)
-                       if self._has_contact_changed(contactId, oldContacts)
-               )
-
-               message = self, self._addedContacts, self._removedContacts, self._changedContacts
-               self.updateSignalHandler.stage.send(message)
-
-       def get_contacts(self):
-               return self._contacts.iterkeys()
 
-       def get_contact_name(self, contactId):
-               return self._contacts[contactId][0]
+               if contacts and misc_utils.compare_versions(
+                       self.OLDEST_COMPATIBLE_FORMAT_VERSION,
+                       misc_utils.parse_version(fileVersion),
+               ) <= 0:
+                       _moduleLogger.info("Loaded cache")
+                       self._numbers = contacts
+                       self._loadedFromCache = True
+               else:
+                       _moduleLogger.debug(
+                               "Skipping cache due to version mismatch (%s-%s)" % (
+                                       fileVersion, fileBuild
+                               )
+                       )
+
+       def save(self, path):
+               _moduleLogger.info("Saving cache")
+               if not self._numbers:
+                       _moduleLogger.info("Odd, no conversations to cache.  Did we never load the cache?")
+                       return
 
-       def get_contact_details(self, contactId):
-               self._populate_contact_details(contactId)
-               return self._get_contact_details(contactId)
+               try:
+                       dataToDump = (constants.__version__, constants.__build__, self._numbers)
+                       with open(path, "wb") as f:
+                               pickle.dump(dataToDump, f, pickle.HIGHEST_PROTOCOL)
+               except (pickle.PickleError, IOError):
+                       _moduleLogger.exception("While saving for %s" % self._name)
+               _moduleLogger.info("Cache saved")
 
-       def _populate_contacts(self):
-               if self._contacts:
+       def update(self, force=False):
+               if not force and self._numbers:
                        return
-               contacts = self._backend.get_contacts()
-               for contactId, contactName in contacts:
-                       self._contacts[contactId] = (contactName, {})
 
-       def _populate_contact_details(self, contactId):
-               if self._get_contact_details(contactId):
+               le = gobject_utils.AsyncLinearExecution(self._asyncPool, self._update)
+               le.start()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _update(self):
+               try:
+                       contacts = yield (
+                               self._backend.get_contacts,
+                               (),
+                               {},
+                       )
+               except Exception:
+                       _moduleLogger.exception("While updating the addressbook")
                        return
-               self._get_contact_details(contactId).update(
-                       self._backend.get_contact_details(contactId)
-               )
 
-       def _get_contact_details(self, contactId):
-               return self._contacts[contactId][1]
+               oldContacts = self._numbers
+               oldContactNumbers = set(self.get_numbers())
+
+               self._numbers = self._populate_contacts(contacts)
+               newContactNumbers = set(self.get_numbers())
 
-       def _has_contact_changed(self, contactId, oldContacts):
-               oldContact = oldContacts[contactId]
-               oldContactName = oldContact[0]
-               oldContactDetails = oldContact[1]
-               if oldContactName != self.get_contact_name(contactId):
-                       return True
-               if not oldContactDetails[1]:
+               addedContacts = newContactNumbers - oldContactNumbers
+               removedContacts = oldContactNumbers - newContactNumbers
+               changedContacts = set(
+                       contactNumber
+                       for contactNumber in newContactNumbers.intersection(oldContactNumbers)
+                       if self._numbers[contactNumber] != oldContacts[contactNumber]
+               )
+
+               if addedContacts or removedContacts or changedContacts:
+                       message = self, addedContacts, removedContacts, changedContacts
+                       self.updateSignalHandler.stage.send(message)
+
+       def get_numbers(self):
+               return self._numbers.iterkeys()
+
+       def get_contact_name(self, strippedNumber):
+               """
+               @throws KeyError if contact not in list (so client can choose what to display)
+               """
+               return self._numbers[strippedNumber][0]
+
+       def get_phone_type(self, strippedNumber):
+               try:
+                       return self._numbers[strippedNumber][1]
+               except KeyError:
+                       return "unknown"
+
+       def is_blocked(self, strippedNumber):
+               try:
+                       return self._numbers[strippedNumber][2]["response"] == self._RESPONSE_BLOCKED
+               except KeyError:
                        return False
-               # if its already in the old cache, purposefully add it into the new cache
-               return oldContactDetails != self.get_contact_details(contactId)
+
+       def _populate_contacts(self, contacts):
+               numbers = {}
+               for contactId, contactDetails in contacts:
+                       contactName = contactDetails["name"]
+                       contactNumbers = (
+                               (
+                                       misc_utils.normalize_number(numberDetails["phoneNumber"]),
+                                       numberDetails.get("phoneType", "Mobile"),
+                               )
+                               for numberDetails in contactDetails["numbers"]
+                       )
+                       numbers.update(
+                               (number, (contactName, phoneType, contactDetails))
+                               for (number, phoneType) in contactNumbers
+                       )
+               return numbers
+
+
+def print_addressbook(path):
+       import pprint
+
+       try:
+               with open(path, "rb") as f:
+                       fileVersion, fileBuild, contacts = pickle.load(f)
+       except (pickle.PickleError, IOError, EOFError, ValueError):
+               _moduleLogger.exception("")
+       else:
+               pprint.pprint((fileVersion, fileBuild))
+               pprint.pprint(contacts)