e8816968154208579eb941e476b886fe494ea03d
[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_contacts(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                         if 0 <= item.lower().find("name"):
114                                 names.append((item, i))
115                                 if 0 <= item.lower().find("first") or 0 <= item.lower().find("given"):
116                                         firstMiddleLast[0] = i
117                                 elif 0 <= item.lower().find("middle"):
118                                         firstMiddleLast[1] = i
119                                 elif 0 <= item.lower().find("last") or 0 <= item.lower().find("family"):
120                                         firstMiddleLast[2] = i
121                         elif 0 <= item.lower().find("phone"):
122                                 phones.append((item, i))
123                         elif 0 <= item.lower().find("mobile"):
124                                 phones.append((item, i))
125                         elif 0 <= item.lower().find("email") or 0 <= item.lower().find("e-mail"):
126                                 nameFallbacks.append(i)
127                 if len(names) == 0:
128                         names.append(("Name", 0))
129                 if len(phones) == 0:
130                         phones.append(("Phone", 1))
131
132                 nameColumns = [i for i in firstMiddleLast if 0 <= i]
133                 if not nameColumns:
134                         nameColumns.append(names[0][1])
135
136                 return nameColumns, nameFallbacks, phones
137
138
139 class FilesystemAddressBookFactory(object):
140
141         FILETYPE_SUPPORT = {
142                 "csv": CsvAddressBook,
143         }
144
145         def __init__(self, path):
146                 self._path = path
147
148         def get_addressbooks(self):
149                 for root, dirs, filenames in os.walk(self._path):
150                         for filename in filenames:
151                                 try:
152                                         name, ext = filename.rsplit(".", 1)
153                                 except ValueError:
154                                         continue
155
156                                 try:
157                                         cls = self.FILETYPE_SUPPORT[ext]
158                                 except KeyError:
159                                         continue
160                                 yield cls(name, os.path.join(root, filename))