_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:
return
header = csvReader.next()
- nameColumn, phoneColumns = cls._guess_columns(header)
+ nameColumn, phoneColumns = self._guess_columns(header)
yieldCount = 0
for row in csvReader:
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
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):
}
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))
+++ /dev/null
-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)
"""
-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()
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
self._mainWindow.destroy()
@property
+ def fsContactsPath(self):
+ return os.path.join(constants._data_path_, "contacts")
+
+ @property
def fullscreenAction(self):
return self._fullscreenAction
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()
self._widget = QtGui.QWidget()
self._widget.setLayout(self._layout)
+ self.update_addressbooks()
self._populate_items()
@property
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"]
@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)
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)
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:
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)