901122565480778d42d72b1fa894e6fb921bde82
[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
25 import os
26 import csv
27
28
29 class CsvAddressBook(object):
30         """
31         Currently supported file format
32         @li Has the first line as a header
33         @li Escapes with quotes
34         @li Comma as delimiter
35         @li Column 0 is name, column 1 is number
36         """
37
38         def __init__(self, name, csvPath):
39                 self._name = name
40                 self._csvPath = csvPath
41                 self._contacts = {}
42
43         @property
44         def name(self):
45                 return self._name
46
47         def update_account(self, force = True):
48                 if not force or not self._contacts:
49                         return
50                 self._contacts = dict(
51                         self._read_csv(self._csvPath)
52                 )
53
54         def get_contacts(self):
55                 """
56                 @returns Iterable of (contact id, contact name)
57                 """
58                 if not self._contacts:
59                         self._contacts = dict(
60                                 self._read_csv(self._csvPath)
61                         )
62                 return self._contacts
63
64         def _read_csv(self, csvPath):
65                 try:
66                         csvReader = iter(csv.reader(open(csvPath, "rU")))
67                 except IOError, e:
68                         if e.errno != 2:
69                                 raise
70                         return
71
72                 header = csvReader.next()
73                 nameColumns, nameFallbacks, phoneColumns = self._guess_columns(header)
74
75                 yieldCount = 0
76                 for row in csvReader:
77                         contactDetails = []
78                         for (phoneType, phoneColumn) in phoneColumns:
79                                 try:
80                                         if len(row[phoneColumn]) == 0:
81                                                 continue
82                                         contactDetails.append({
83                                                 "phoneType": phoneType,
84                                                 "phoneNumber": row[phoneColumn],
85                                         })
86                                 except IndexError:
87                                         pass
88                         if 0 < len(contactDetails):
89                                 nameParts = (row[i].strip() for i in nameColumns)
90                                 nameParts = (part for part in nameParts if part)
91                                 fullName = " ".join(nameParts).strip()
92                                 if not fullName:
93                                         for fallbackColumn in nameFallbacks:
94                                                 if row[fallbackColumn].strip():
95                                                         fullName = row[fallbackColumn].strip()
96                                                         break
97                                         else:
98                                                 fullName = "Unknown"
99                                 yield str(yieldCount), {
100                                         "contactId": "%s-%d" % (self._name, yieldCount),
101                                         "name": fullName,
102                                         "numbers": contactDetails,
103                                 }
104                                 yieldCount += 1
105
106         @classmethod
107         def _guess_columns(cls, row):
108                 firstMiddleLast = [-1, -1, -1]
109                 names = []
110                 nameFallbacks = []
111                 phones = []
112                 for i, item in enumerate(row):
113                         lowerItem = item.lower()
114                         if 0 <= lowerItem.find("name"):
115                                 names.append((item, i))
116
117                                 if 0 <= lowerItem.find("couple"):
118                                         names.insert(0, (item, i))
119
120                                 if 0 <= lowerItem.find("first") or 0 <= lowerItem.find("given"):
121                                         firstMiddleLast[0] = i
122                                 elif 0 <= lowerItem.find("middle"):
123                                         firstMiddleLast[1] = i
124                                 elif 0 <= lowerItem.find("last") or 0 <= lowerItem.find("family"):
125                                         firstMiddleLast[2] = i
126                         elif 0 <= lowerItem.find("phone"):
127                                 phones.append((item, i))
128                         elif 0 <= lowerItem.find("mobile"):
129                                 phones.append((item, i))
130                         elif 0 <= lowerItem.find("email") or 0 <= lowerItem.find("e-mail"):
131                                 nameFallbacks.append(i)
132                 if len(names) == 0:
133                         names.append(("Name", 0))
134                 if len(phones) == 0:
135                         phones.append(("Phone", 1))
136
137                 nameColumns = [i for i in firstMiddleLast if 0 <= i]
138                 if len(nameColumns) < 2:
139                         del nameColumns[:]
140                         nameColumns.append(names[0][1])
141
142                 return nameColumns, nameFallbacks, phones
143
144
145 class FilesystemAddressBookFactory(object):
146
147         FILETYPE_SUPPORT = {
148                 "csv": CsvAddressBook,
149         }
150
151         def __init__(self, path):
152                 self._path = path
153
154         def get_addressbooks(self):
155                 for root, dirs, filenames in os.walk(self._path):
156                         for filename in filenames:
157                                 try:
158                                         name, ext = filename.rsplit(".", 1)
159                                 except ValueError:
160                                         continue
161
162                                 try:
163                                         cls = self.FILETYPE_SUPPORT[ext]
164                                 except KeyError:
165                                         continue
166                                 yield cls(name, os.path.join(root, filename))