--- /dev/null
+#!/usr/bin/python
+
+"""
+DialCentral - Front end for Google's Grand Central service.
+Copyright (C) 2008 Eric Warnke ericew AT gmail DOT com
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Filesystem backend for contact support
+"""
+
+from __future__ import with_statement
+
+import os
+import csv
+
+
+def try_unicode(s):
+ try:
+ return s.decode("UTF-8")
+ except UnicodeDecodeError:
+ return s
+
+
+class CsvAddressBook(object):
+ """
+ Currently supported file format
+ @li Has the first line as a header
+ @li Escapes with quotes
+ @li Comma as delimiter
+ @li Column 0 is name, column 1 is number
+ """
+
+ def __init__(self, name, csvPath):
+ self._name = name
+ self._csvPath = csvPath
+ self._contacts = {}
+
+ @property
+ def name(self):
+ return self._name
+
+ def update_account(self, force = True):
+ if not force or not self._contacts:
+ return
+ self._contacts = dict(
+ self._read_csv(self._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:
+ f = open(csvPath, "rU")
+ csvReader = iter(csv.reader(f))
+ except IOError, e:
+ if e.errno == 2:
+ return
+ raise
+
+ header = csvReader.next()
+ nameColumns, nameFallbacks, phoneColumns = self._guess_columns(header)
+
+ yieldCount = 0
+ for row in csvReader:
+ contactDetails = []
+ for (phoneType, phoneColumn) in phoneColumns:
+ try:
+ if len(row[phoneColumn]) == 0:
+ continue
+ contactDetails.append({
+ "phoneType": try_unicode(phoneType),
+ "phoneNumber": row[phoneColumn],
+ })
+ except IndexError:
+ pass
+ if 0 < len(contactDetails):
+ nameParts = (row[i].strip() for i in nameColumns)
+ nameParts = (part for part in nameParts if part)
+ fullName = " ".join(nameParts).strip()
+ if not fullName:
+ for fallbackColumn in nameFallbacks:
+ if row[fallbackColumn].strip():
+ fullName = row[fallbackColumn].strip()
+ break
+ else:
+ fullName = "Unknown"
+ fullName = try_unicode(fullName)
+ yield str(yieldCount), {
+ "contactId": "%s-%d" % (self._name, yieldCount),
+ "name": fullName,
+ "numbers": contactDetails,
+ }
+ yieldCount += 1
+
+ @classmethod
+ def _guess_columns(cls, row):
+ firstMiddleLast = [-1, -1, -1]
+ names = []
+ nameFallbacks = []
+ phones = []
+ for i, item in enumerate(row):
+ lowerItem = item.lower()
+ if 0 <= lowerItem.find("name"):
+ names.append((item, i))
+
+ if 0 <= lowerItem.find("couple"):
+ names.insert(0, (item, i))
+
+ if 0 <= lowerItem.find("first") or 0 <= lowerItem.find("given"):
+ firstMiddleLast[0] = i
+ elif 0 <= lowerItem.find("middle"):
+ firstMiddleLast[1] = i
+ elif 0 <= lowerItem.find("last") or 0 <= lowerItem.find("family"):
+ firstMiddleLast[2] = i
+ elif 0 <= lowerItem.find("phone"):
+ phones.append((item, i))
+ elif 0 <= lowerItem.find("mobile"):
+ phones.append((item, i))
+ elif 0 <= lowerItem.find("email") or 0 <= lowerItem.find("e-mail"):
+ nameFallbacks.append(i)
+ if len(names) == 0:
+ names.append(("Name", 0))
+ if len(phones) == 0:
+ phones.append(("Phone", 1))
+
+ nameColumns = [i for i in firstMiddleLast if 0 <= i]
+ if len(nameColumns) < 2:
+ del nameColumns[:]
+ nameColumns.append(names[0][1])
+
+ return nameColumns, nameFallbacks, phones
+
+
+class FilesystemAddressBookFactory(object):
+
+ FILETYPE_SUPPORT = {
+ "csv": CsvAddressBook,
+ }
+
+ def __init__(self, path):
+ self._path = path
+
+ def get_addressbooks(self):
+ for root, dirs, filenames in os.walk(self._path):
+ for filename in filenames:
+ try:
+ name, ext = filename.rsplit(".", 1)
+ except ValueError:
+ continue
+
+ try:
+ cls = self.FILETYPE_SUPPORT[ext]
+ except KeyError:
+ continue
+ yield cls(name, os.path.join(root, filename))