Bump to 1.3.6
[gc-dialer] / src / backends / file_backend.py
1 #!/usr/bin/python
2
3 """
4 DialCentral - Front end for Google's Grand Central service.
5 Copyright (C) 2008  Eric Warnke ericew AT gmail DOT com
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
21 Filesystem backend for contact support
22 """
23
24 from __future__ import with_statement
25
26 import os
27 import csv
28
29
30 def try_unicode(s):
31         try:
32                 return s.decode("UTF-8")
33         except UnicodeDecodeError:
34                 return s
35
36
37 class CsvAddressBook(object):
38         """
39         Currently supported file format
40         @li Has the first line as a header
41         @li Escapes with quotes
42         @li Comma as delimiter
43         @li Column 0 is name, column 1 is number
44         """
45
46         def __init__(self, name, csvPath):
47                 self._name = name
48                 self._csvPath = csvPath
49                 self._contacts = {}
50
51         @property
52         def name(self):
53                 return self._name
54
55         def update_account(self, force = True):
56                 if not force or not self._contacts:
57                         return
58                 self._contacts = dict(
59                         self._read_csv(self._csvPath)
60                 )
61
62         def get_contacts(self):
63                 """
64                 @returns Iterable of (contact id, contact name)
65                 """
66                 if not self._contacts:
67                         self._contacts = dict(
68                                 self._read_csv(self._csvPath)
69                         )
70                 return self._contacts
71
72         def _read_csv(self, csvPath):
73                 try:
74                         f = open(csvPath, "rU")
75                         csvReader = iter(csv.reader(f))
76                 except IOError, e:
77                         if e.errno == 2:
78                                 return
79                         raise
80
81                 header = csvReader.next()
82                 nameColumns, nameFallbacks, phoneColumns = self._guess_columns(header)
83
84                 yieldCount = 0
85                 for row in csvReader:
86                         contactDetails = []
87                         for (phoneType, phoneColumn) in phoneColumns:
88                                 try:
89                                         if len(row[phoneColumn]) == 0:
90                                                 continue
91                                         contactDetails.append({
92                                                 "phoneType": try_unicode(phoneType),
93                                                 "phoneNumber": row[phoneColumn],
94                                         })
95                                 except IndexError:
96                                         pass
97                         if 0 < len(contactDetails):
98                                 nameParts = (row[i].strip() for i in nameColumns)
99                                 nameParts = (part for part in nameParts if part)
100                                 fullName = " ".join(nameParts).strip()
101                                 if not fullName:
102                                         for fallbackColumn in nameFallbacks:
103                                                 if row[fallbackColumn].strip():
104                                                         fullName = row[fallbackColumn].strip()
105                                                         break
106                                         else:
107                                                 fullName = "Unknown"
108                                 fullName = try_unicode(fullName)
109                                 yield str(yieldCount), {
110                                         "contactId": "%s-%d" % (self._name, yieldCount),
111                                         "name": fullName,
112                                         "numbers": contactDetails,
113                                 }
114                                 yieldCount += 1
115
116         @classmethod
117         def _guess_columns(cls, row):
118                 firstMiddleLast = [-1, -1, -1]
119                 names = []
120                 nameFallbacks = []
121                 phones = []
122                 for i, item in enumerate(row):
123                         lowerItem = item.lower()
124                         if 0 <= lowerItem.find("name"):
125                                 names.append((item, i))
126
127                                 if 0 <= lowerItem.find("couple"):
128                                         names.insert(0, (item, i))
129
130                                 if 0 <= lowerItem.find("first") or 0 <= lowerItem.find("given"):
131                                         firstMiddleLast[0] = i
132                                 elif 0 <= lowerItem.find("middle"):
133                                         firstMiddleLast[1] = i
134                                 elif 0 <= lowerItem.find("last") or 0 <= lowerItem.find("family"):
135                                         firstMiddleLast[2] = i
136                         elif 0 <= lowerItem.find("phone"):
137                                 phones.append((item, i))
138                         elif 0 <= lowerItem.find("mobile"):
139                                 phones.append((item, i))
140                         elif 0 <= lowerItem.find("email") or 0 <= lowerItem.find("e-mail"):
141                                 nameFallbacks.append(i)
142                 if len(names) == 0:
143                         names.append(("Name", 0))
144                 if len(phones) == 0:
145                         phones.append(("Phone", 1))
146
147                 nameColumns = [i for i in firstMiddleLast if 0 <= i]
148                 if len(nameColumns) < 2:
149                         del nameColumns[:]
150                         nameColumns.append(names[0][1])
151
152                 return nameColumns, nameFallbacks, phones
153
154
155 class FilesystemAddressBookFactory(object):
156
157         FILETYPE_SUPPORT = {
158                 "csv": CsvAddressBook,
159         }
160
161         def __init__(self, path):
162                 self._path = path
163
164         def get_addressbooks(self):
165                 for root, dirs, filenames in os.walk(self._path):
166                         for filename in filenames:
167                                 try:
168                                         name, ext = filename.rsplit(".", 1)
169                                 except ValueError:
170                                         continue
171
172                                 try:
173                                         cls = self.FILETYPE_SUPPORT[ext]
174                                 except KeyError:
175                                         continue
176                                 yield cls(name, os.path.join(root, filename))