Rounding out the contacts implementation
authorEd Page <eopage@byu.net>
Sat, 16 Oct 2010 17:19:11 +0000 (12:19 -0500)
committerEd Page <eopage@byu.net>
Sat, 16 Oct 2010 17:19:11 +0000 (12:19 -0500)
src/backends/file_backend.py
src/backends/merge_backend.py [deleted file]
src/backends/null_backend.py
src/dialcentral_qt.py

index b373561..fff66ea 100644 (file)
@@ -40,14 +40,33 @@ class CsvAddressBook(object):
        _phoneRe = re.compile("phone", re.IGNORECASE)
        _mobileRe = re.compile("mobile", re.IGNORECASE)
 
-       def __init__(self, csvPath):
-               self.__csvPath = csvPath
-               self.__contacts = list(
-                       self.read_csv(csvPath)
+       def __init__(self, name, csvPath):
+               self._name = name
+               self._csvPath = csvPath
+               self._contacts = {}
+
+       @property
+       def name(self):
+               return self._name
+
+       def update_contacts(self, force = True):
+               if not force or not self._contacts:
+                       return
+               self._contacts = dict(
+                       self._read_csv(self._csvPath)
                )
 
-       @classmethod
-       def read_csv(cls, csvPath):
+       def get_contacts(self):
+               """
+               @returns Iterable of (contact id, contact name)
+               """
+               if not self._contacts:
+                       self._contacts = dict(
+                               self._read_csv(self._csvPath)
+                       )
+               return self._contacts
+
+       def _read_csv(self, csvPath):
                try:
                        csvReader = iter(csv.reader(open(csvPath, "rU")))
                except IOError, e:
@@ -56,7 +75,7 @@ class CsvAddressBook(object):
                        return
 
                header = csvReader.next()
-               nameColumn, phoneColumns = cls._guess_columns(header)
+               nameColumn, phoneColumns = self._guess_columns(header)
 
                yieldCount = 0
                for row in csvReader:
@@ -65,11 +84,18 @@ class CsvAddressBook(object):
                                try:
                                        if len(row[phoneColumn]) == 0:
                                                continue
-                                       contactDetails.append((phoneType, row[phoneColumn]))
+                                       contactDetails.append({
+                                               "phoneType": phoneType,
+                                               "phoneNumber": row[phoneColumn],
+                                       })
                                except IndexError:
                                        pass
-                       if len(contactDetails) != 0:
-                               yield str(yieldCount), row[nameColumn], contactDetails
+                       if 0 < len(contactDetails):
+                               yield str(yieldCount), {
+                                       "contactId": "%s-%d" % (self._name, yieldCount),
+                                       "name": row[nameColumn],
+                                       "numbers": contactDetails,
+                               }
                                yieldCount += 1
 
        @classmethod
@@ -90,31 +116,6 @@ class CsvAddressBook(object):
 
                return names[0][1], phones
 
-       def clear_caches(self):
-               pass
-
-       @staticmethod
-       def factory_name():
-               return "csv"
-
-       @staticmethod
-       def contact_source_short_name(contactId):
-               return "csv"
-
-       def get_contacts(self):
-               """
-               @returns Iterable of (contact id, contact name)
-               """
-               for contact in self.__contacts:
-                       yield contact[0:2]
-
-       def get_contact_details(self, contactId):
-               """
-               @returns Iterable of (Phone Type, Phone Number)
-               """
-               contactId = int(contactId)
-               return iter(self.__contacts[contactId][2])
-
 
 class FilesystemAddressBookFactory(object):
 
@@ -123,49 +124,18 @@ class FilesystemAddressBookFactory(object):
        }
 
        def __init__(self, path):
-               self.__path = path
-
-       def clear_caches(self):
-               pass
+               self._path = path
 
        def get_addressbooks(self):
-               """
-               @returns Iterable of (Address Book Factory, Book Id, Book Name)
-               """
-               for root, dirs, filenames in os.walk(self.__path):
+               for root, dirs, filenames in os.walk(self._path):
                        for filename in filenames:
                                try:
                                        name, ext = filename.rsplit(".", 1)
                                except ValueError:
                                        continue
 
-                               if ext in self.FILETYPE_SUPPORT:
-                                       yield self, os.path.join(root, filename), name
-
-       def open_addressbook(self, bookId):
-               name, ext = bookId.rsplit(".", 1)
-               assert ext in self.FILETYPE_SUPPORT, "Unsupported file extension %s" % ext
-               return self.FILETYPE_SUPPORT[ext](bookId)
-
-       @staticmethod
-       def factory_name():
-               return "File"
-
-
-def print_filebooks(contactPath = None):
-       """
-       Included here for debugging.
-
-       Either insert it into the code or launch python with the "-i" flag
-       """
-       if contactPath is None:
-               contactPath = os.path.join(os.path.expanduser("~"), ".dialcentral", "contacts")
-
-       abf = FilesystemAddressBookFactory(contactPath)
-       for book in abf.get_addressbooks():
-               ab = abf.open_addressbook(book[1])
-               print book
-               for contact in ab.get_contacts():
-                       print "\t", contact
-                       for details in ab.get_contact_details(contact[0]):
-                               print "\t\t", details
+                               try:
+                                       cls = self.FILETYPE_SUPPORT[ext]
+                               except KeyError:
+                                       continue
+                               yield cls(name, os.path.join(root, filename))
diff --git a/src/backends/merge_backend.py b/src/backends/merge_backend.py
deleted file mode 100644 (file)
index 7cdd2e4..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-import logging
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-class MergedAddressBook(object):
-       """
-       Merger of all addressbooks
-       """
-
-       def __init__(self, addressbookFactories, sorter = None):
-               self.__addressbookFactories = addressbookFactories
-               self.__addressbooks = None
-               self.__sort_contacts = sorter if sorter is not None else self.null_sorter
-
-       def clear_caches(self):
-               self.__addressbooks = None
-               for factory in self.__addressbookFactories:
-                       factory.clear_caches()
-
-       def get_addressbooks(self):
-               """
-               @returns Iterable of (Address Book Factory, Book Id, Book Name)
-               """
-               yield self, "", ""
-
-       def open_addressbook(self, bookId):
-               return self
-
-       def contact_source_short_name(self, contactId):
-               if self.__addressbooks is None:
-                       return ""
-               bookIndex, originalId = contactId.split("-", 1)
-               return self.__addressbooks[int(bookIndex)].contact_source_short_name(originalId)
-
-       @staticmethod
-       def factory_name():
-               return "All Contacts"
-
-       def get_contacts(self):
-               """
-               @returns Iterable of (contact id, contact name)
-               """
-               if self.__addressbooks is None:
-                       self.__addressbooks = list(
-                               factory.open_addressbook(id)
-                               for factory in self.__addressbookFactories
-                               for (f, id, name) in factory.get_addressbooks()
-                       )
-               contacts = (
-                       ("-".join([str(bookIndex), contactId]), contactName)
-                               for (bookIndex, addressbook) in enumerate(self.__addressbooks)
-                                       for (contactId, contactName) in addressbook.get_contacts()
-               )
-               sortedContacts = self.__sort_contacts(contacts)
-               return sortedContacts
-
-       def get_contact_details(self, contactId):
-               """
-               @returns Iterable of (Phone Type, Phone Number)
-               """
-               if self.__addressbooks is None:
-                       return []
-               bookIndex, originalId = contactId.split("-", 1)
-               return self.__addressbooks[int(bookIndex)].get_contact_details(originalId)
-
-       @staticmethod
-       def null_sorter(contacts):
-               """
-               Good for speed/low memory
-               """
-               return contacts
-
-       @staticmethod
-       def basic_firtname_sorter(contacts):
-               """
-               Expects names in "First Last" format
-               """
-               contactsWithKey = [
-                       (contactName.rsplit(" ", 1)[0], (contactId, contactName))
-                               for (contactId, contactName) in contacts
-               ]
-               contactsWithKey.sort()
-               return (contactData for (lastName, contactData) in contactsWithKey)
-
-       @staticmethod
-       def basic_lastname_sorter(contacts):
-               """
-               Expects names in "First Last" format
-               """
-               contactsWithKey = [
-                       (contactName.rsplit(" ", 1)[-1], (contactId, contactName))
-                               for (contactId, contactName) in contacts
-               ]
-               contactsWithKey.sort()
-               return (contactData for (lastName, contactData) in contactsWithKey)
-
-       @staticmethod
-       def reversed_firtname_sorter(contacts):
-               """
-               Expects names in "Last, First" format
-               """
-               contactsWithKey = [
-                       (contactName.split(", ", 1)[-1], (contactId, contactName))
-                               for (contactId, contactName) in contacts
-               ]
-               contactsWithKey.sort()
-               return (contactData for (lastName, contactData) in contactsWithKey)
-
-       @staticmethod
-       def reversed_lastname_sorter(contacts):
-               """
-               Expects names in "Last, First" format
-               """
-               contactsWithKey = [
-                       (contactName.split(", ", 1)[0], (contactId, contactName))
-                               for (contactId, contactName) in contacts
-               ]
-               contactsWithKey.sort()
-               return (contactData for (lastName, contactData) in contactsWithKey)
-
-       @staticmethod
-       def guess_firstname(name):
-               if ", " in name:
-                       return name.split(", ", 1)[-1]
-               else:
-                       return name.rsplit(" ", 1)[0]
-
-       @staticmethod
-       def guess_lastname(name):
-               if ", " in name:
-                       return name.split(", ", 1)[0]
-               else:
-                       return name.rsplit(" ", 1)[-1]
-
-       @classmethod
-       def advanced_firstname_sorter(cls, contacts):
-               contactsWithKey = [
-                       (cls.guess_firstname(contactName), (contactId, contactName))
-                               for (contactId, contactName) in contacts
-               ]
-               contactsWithKey.sort()
-               return (contactData for (lastName, contactData) in contactsWithKey)
-
-       @classmethod
-       def advanced_lastname_sorter(cls, contacts):
-               contactsWithKey = [
-                       (cls.guess_lastname(contactName), (contactId, contactName))
-                               for (contactId, contactName) in contacts
-               ]
-               contactsWithKey.sort()
-               return (contactData for (lastName, contactData) in contactsWithKey)
index 6909c7c..fcd3a1d 100644 (file)
@@ -20,127 +20,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 """
 
 
-class NullDialer(object):
-
-       def __init__(self):
-               pass
-
-       def is_quick_login_possible(self):
-               return False
-
-       def is_authed(self, force = False):
-               return False
-
-       def login(self, username, password):
-               return self.is_authed()
-
-       def logout(self):
-               self.clear_caches()
-
-       def call(self, number):
-               return True
-
-       def cancel(self, outgoingNumber=None):
-               pass
-
-       def send_sms(self, number, message):
-               raise NotImplementedError("SMS Is Not Supported")
-
-       def search(self, query):
-               return []
-
-       def get_feed(self, feed):
-               return {}
+class NullAddressBook(object):
 
-       def download(self, messageId, adir):
-               return ""
+       @property
+       def name(self):
+               return "None"
 
-       def clear_caches(self):
+       def update_contacts(self, force = True):
                pass
 
-       def is_valid_syntax(self, number):
-               """
-               @returns If This number be called ( syntax validation only )
-               """
-               return False
-
-       def get_account_number(self):
-               """
-               @returns The grand central phone number
-               """
-               return ""
-
-       def get_callback_numbers(self):
-               return {}
-
-       def set_callback_number(self, callbacknumber):
-               return True
-
-       def get_callback_number(self):
-               return ""
-
-       def get_recent(self):
-               return ()
-
-       def get_addressbooks(self):
-               return ()
-
-       def open_addressbook(self, bookId):
-               return self
-
-       @staticmethod
-       def contact_source_short_name(contactId):
-               return "ERROR"
-
-       @staticmethod
-       def factory_name():
-               return "ERROR"
-
        def get_contacts(self):
-               return ()
-
-       def get_contact_details(self, contactId):
-               return ()
-
-       def get_messages(self):
-               return ()
+               return {}
 
 
-class NullAddressBook(object):
-       """
-       Minimal example of both an addressbook factory and an addressbook
-       """
-
-       def clear_caches(self):
-               pass
+class NullAddressBookFactory(object):
 
        def get_addressbooks(self):
-               """
-               @returns Iterable of (Address Book Factory, Book Id, Book Name)
-               """
-               yield self, "", "None"
-
-       def open_addressbook(self, bookId):
-               return self
-
-       @staticmethod
-       def contact_source_short_name(contactId):
-               return ""
-
-       @staticmethod
-       def factory_name():
-               return ""
-
-       @staticmethod
-       def get_contacts():
-               """
-               @returns Iterable of (contact id, contact name)
-               """
-               return []
-
-       @staticmethod
-       def get_contact_details(contactId):
-               """
-               @returns Iterable of (Phone Type, Phone Number)
-               """
-               return []
+               yield NullAddressBook()
index 44024f0..febb097 100755 (executable)
@@ -19,6 +19,8 @@ from util import qui_utils
 from util import qtpie
 from util import misc as misc_utils
 
+import backends.null_backend as null_backend
+import backends.file_backend as file_backend
 import session
 
 
@@ -91,6 +93,10 @@ class Dialcentral(object):
                self._mainWindow.destroy()
 
        @property
+       def fsContactsPath(self):
+               return os.path.join(constants._data_path_, "contacts")
+
+       @property
        def fullscreenAction(self):
                return self._fullscreenAction
 
@@ -968,16 +974,18 @@ class Messages(object):
 class Contacts(object):
 
        def __init__(self, app, session, errorLog):
-               self._selectedFilter = ""
                self._app = app
                self._session = session
                self._session.contactsUpdated.connect(self._on_contacts_updated)
                self._errorLog = errorLog
+               self._addressBookFactories = [
+                       null_backend.NullAddressBookFactory(),
+                       file_backend.FilesystemAddressBookFactory(app.fsContactsPath),
+               ]
+               self._addressBooks = []
 
                self._listSelection = QtGui.QComboBox()
                self._listSelection.addItems([])
-               # @todo Implement more contact lists
-               #self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter))
                self._listSelection.currentIndexChanged[str].connect(self._on_filter_changed)
 
                self._itemStore = QtGui.QStandardItemModel()
@@ -998,6 +1006,7 @@ class Contacts(object):
                self._widget = QtGui.QWidget()
                self._widget.setLayout(self._layout)
 
+               self.update_addressbooks()
                self._populate_items()
 
        @property
@@ -1014,12 +1023,46 @@ class Contacts(object):
                self._itemView.clear()
 
        def refresh(self, force=True):
-               self._session.update_contacts(force)
+               self._backend.update_contacts(force)
+
+       @property
+       def _backend(self):
+               return self._addressBooks[self._listSelection.currentIndex()]["book"]
+
+       def update_addressbooks(self):
+               self._addressBooks = [
+                       {"book": book, "name": book.name}
+                       for factory in self._addressBookFactories
+                       for book in factory.get_addressbooks()
+               ]
+               self._addressBooks.append(
+                       {
+                               "book": self._session,
+                               "name": "Google Voice",
+                       }
+               )
+
+               currentItem = str(self._listSelection.currentText())
+               if currentItem == "":
+                       # Not loaded yet
+                       currentItem = "None"
+               while 0 < self._listSelection.count():
+                       self._listSelection.removeItem(0)
+               bookNames = [book["name"] for book in self._addressBooks]
+               try:
+                       newIndex = bookNames.index(currentItem)
+               except ValueError:
+                       # Switch over to None for the user
+                       newIndex = 0
+                       self._itemStore.clear()
+                       _moduleLogger.info("Addressbook %r doesn't exist anymore, switching to None" % currentItem)
+               self._listSelection.addItems(bookNames)
+               self._listSelection.setCurrentIndex(newIndex)
 
        def _populate_items(self):
                self._itemStore.clear()
 
-               contacts = list(self._session.get_contacts().itervalues())
+               contacts = list(self._backend.get_contacts().itervalues())
                contacts.sort(key=lambda contact: contact["name"].lower())
                for item in contacts:
                        name = item["name"]
@@ -1034,7 +1077,7 @@ class Contacts(object):
        @QtCore.pyqtSlot(str)
        @misc_utils.log_exception(_moduleLogger)
        def _on_filter_changed(self, newItem):
-               self._selectedFilter = str(newItem)
+               self._populate_items()
 
        @QtCore.pyqtSlot()
        @misc_utils.log_exception(_moduleLogger)
@@ -1109,7 +1152,6 @@ class MainWindow(object):
        assert len(_TAB_CLASS) == MAX_TABS
 
        def __init__(self, parent, app):
-               self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
                self._app = app
                self._session = session.Session(constants._data_path_)
                self._session.error.connect(self._on_session_error)
@@ -1225,7 +1267,8 @@ class MainWindow(object):
                self._window.close()
 
        def destroy(self):
-               self._session.logout()
+               if self._session.state != self._session.LOGGEDOUT_STATE:
+                       self._session.logout()
 
        def set_fullscreen(self, isFullscreen):
                if isFullscreen:
@@ -1297,7 +1340,8 @@ class MainWindow(object):
                csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)")
                if not csvName:
                        return
-               shutil.copy2(csvName, self._fsContactsPath)
+               shutil.copy2(csvName, self._app.fsContactsPath)
+               self._tabsContents[self.CONTACTS_TAB].update_addressbooks()
 
        @QtCore.pyqtSlot()
        @QtCore.pyqtSlot(bool)