1 """
2 Syncronize with a SQLite database
3
4 General information about the database specs of Qtopia PIM is provided on this site: http://doc.trolltech.com/qtopia4.3/database-specification.html
5 Details may be obtained from a source code release (ftp://ftp.trolltech.com/qtopia/source/) in folder "src/libraries/qtopiapim/resources".
6
7 This file is part of Pisi.
8
9 Pisi is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Pisi is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Pisi. If not, see <http://www.gnu.org/licenses/>
21
22 """
23
24 import os.path
25 import sqlite3
26 import sys,os,re
27
28
29 sys.path.insert(0,os.path.abspath(__file__+"/../.."))
30 from contacts import contacts
31 from pisiconstants import *
32 import pisiprogress
33
34 QTOPIADB_PHONETYPE_HOME = 1
35 """Constant value for phone type used in QTOPIA database - value for type home phone."""
36 QTOPIADB_PHONETYPE_MOBILE = 257
37 """Constant value for phone type used in QTOPIA database - value for type mobile phone."""
38 QTOPIADB_PHONETYPE_OFFICE = 2
39 """Constant value for phone type used in QTOPIA database - value for type office phone."""
40 QTOPIADB_PHONETYPE_FAX = 514
41 """Constant value for phone type used in QTOPIA database - value for type fax."""
42 QTOPIADB_ADDRESSTYPE_PRIVATE = 1
43 """Constant value for address type used in QTOPIA database - value for type home address."""
44 QTOPIADB_ADDRESSTYPE_BUSINESS = 2
45 """Constant value for address type used in QTOPIA database - value for type business address."""
46
48 """
49 The implementation of the interface L{contacts.AbstractContactSynchronizationModule} for SQLite persistence backend
50 """
51 - def __init__( self, modulesString, config, configsection, folder, verbose=False, soft=False):
52 """
53 Constructor
54
55 Super class constructor (L{contacts.AbstractContactSynchronizationModule.__init__}) is called.
56 Local variables are initialized.
57 The settings from the configuration file are loaded.
58 """
59 contacts.AbstractContactSynchronizationModule.__init__(self, verbose, soft, modulesString, config, configsection, "SQLite")
60 self._dbpath = config.get(configsection,'database')
61 pisiprogress.getCallback().verbose('contact sqlite module loaded using file %s' % (self._dbpath))
62 self._idMappingInternalGlobal = {}
63 self._idMappingGlobalInternal = {}
64
66 """
67 Loads all attributes for all contact entries from the SQLite database
68
69 For each entry a new L{contacts.contacts.Contact} instance is created and stored in the instance dictionary L{contacts.AbstractContactSynchronizationModule._allContacts}.
70 Several requests to the database are executed in order to get all the detailled information about phone numbers and addresses, etc. as well.
71 """
72 pisiprogress.getCallback().verbose("SQLite: Loading")
73 database = sqlite3.connect(self._dbpath, isolation_level=None)
74 db_call = "select recid, firstname, middlename, lastname, company, title, department from contacts"
75 contactEntries = database.execute(db_call).fetchall()
76
77 pisiprogress.getCallback().progress.setProgress(20)
78 pisiprogress.getCallback().update('Loading')
79 i=0
80 for contactEntry in contactEntries:
81 atts = {}
82 recid = contactEntry[0]
83 atts['firstname'] = contactEntry[1]
84 atts['middlename'] = contactEntry[2]
85 atts['lastname'] = contactEntry[3]
86 atts['businessOrganisation'] = contactEntry[4]
87 atts['title'] = contactEntry[5]
88 atts['businessDepartment'] = contactEntry[6]
89
90
91 db_call = "select phone_number, phone_type from contactphonenumbers where recid = %s" %(recid)
92 phoneEntries = database.execute(db_call).fetchall()
93 for phoneEntry in phoneEntries:
94 type = int(phoneEntry[1])
95 if type == QTOPIADB_PHONETYPE_MOBILE:
96 atts['mobile'] = phoneEntry[0]
97 elif type == QTOPIADB_PHONETYPE_HOME:
98 atts['phone'] = phoneEntry[0]
99 elif type == QTOPIADB_PHONETYPE_OFFICE:
100 atts['officePhone'] = phoneEntry[0]
101 elif type == QTOPIADB_PHONETYPE_FAX:
102 atts['fax'] = phoneEntry[0]
103
104 db_call = "select addr from emailaddresses where recid = %s" %(recid)
105 emailEntries = database.execute(db_call).fetchall()
106 for emailEntry in emailEntries:
107 atts['email'] = emailEntry[0]
108
109 db_call = "select street, city, state, zip, country, addresstype from contactaddresses where recid = %s" %(recid)
110 addressEntries = database.execute(db_call).fetchall()
111 for addressEntry in addressEntries:
112 type = int(addressEntry[5])
113 if type == QTOPIADB_ADDRESSTYPE_PRIVATE:
114 atts['homeStreet'] = addressEntry[0]
115 atts['homeCity'] = addressEntry[1]
116 atts['homeState'] = addressEntry[2]
117 atts['homePostalCode'] = addressEntry[3]
118 atts['homeCountry'] = addressEntry[4]
119 if type == QTOPIADB_ADDRESSTYPE_BUSINESS:
120 atts['businessStreet'] = addressEntry[0]
121 atts['businessCity'] = addressEntry[1]
122 atts['businessState'] = addressEntry[2]
123 atts['businessPostalCode'] = addressEntry[3]
124 atts['businessCountry'] = addressEntry[4]
125
126 id = contacts.assembleID(atts)
127 c = contacts.Contact(id, atts)
128 self._allContacts[id] = c
129
130 self._idMappingGlobalInternal[id] = recid
131 self._idMappingInternalGlobal[recid] = id
132 i+=1
133 pisiprogress.getCallback().progress.setProgress(20 + ((i*80) / len(contactEntries)))
134 pisiprogress.getCallback().update('Loading')
135 database.close()
136
138 """
139 Supporting function to get around problems with integrating 'None' values and KeyErrors if an attribute is not available
140 """
141 try:
142 val = contact.attributes[st].replace("'", "''")
143 return "'" +val + "'"
144 except TypeError:
145 return "''"
146 except KeyError:
147 return "''"
148
150 """
151 Save all new entries to SQLite database
152
153 The information from all the attributes in the Contact entry are stored 1st in the major table and afterwards in all the related tables (addresses, phone numbers, etc.). Thereby the foreign keys are taken care of.
154 """
155 contact = self.getContact(id)
156
157 st = "insert into contacts (firstname, middlename, lastname, default_email, default_phone, context, company, title, department) values (%s, %s, %s, %s, %s, 1, %s, %s, %s)" %(
158 self._quoteString(contact, 'firstname'),
159 self._quoteString(contact, 'middlename'),
160 self._quoteString(contact, 'lastname'),
161 self._quoteString(contact, 'email'),
162 self._quoteString(contact, 'mobile'),
163 self._quoteString(contact, 'businessOrganisation'),
164 self._quoteString(contact, 'title'),
165 self._quoteString(contact, 'businessDepartment'))
166 database.execute(st)
167 db_call = "select recid from contacts where firstname=%s and lastname=%s" %(
168 self._quoteString(contact, 'firstname'),
169 self._quoteString(contact, 'lastname') )
170 recidEntry = database.execute(db_call).fetchall()[0]
171 recid = recidEntry[0]
172
173 try:
174 if contact.attributes['mobile'] != '' and contact.attributes['mobile'] != None:
175 st = "insert into contactphonenumbers (phone_number, recid, phone_type) values (%s, %d, %d)" %(
176 self._quoteString(contact, 'mobile'),
177 recid,
178 QTOPIADB_PHONETYPE_MOBILE)
179 database.execute(st)
180 except KeyError:
181 pass
182
183 try:
184 if contact.attributes['email'] != '' and contact.attributes['email'] != None:
185 st = "insert into emailaddresses (recid, addr) values (%d, %s)" %(
186 recid,
187 self._quoteString(contact, 'email'))
188 database.execute(st)
189 except KeyError:
190 pass
191
192 try:
193 if contact.attributes['phone'] != '' and contact.attributes['phone'] != None:
194 st = "insert into contactphonenumbers (phone_number, recid, phone_type) values (%s, %d, %d)" %(
195 self._quoteString(contact, 'phone'),
196 recid,
197 QTOPIADB_PHONETYPE_HOME)
198 database.execute(st)
199 except KeyError:
200 pass
201
202 try:
203 if contact.attributes['officePhone'] != '' and contact.attributes['officePhone'] != None:
204 st = "insert into contactphonenumbers (phone_number, recid, phone_type) values (%s, %d, %d)" %(
205 self._quoteString(contact, 'officePhone'),
206 recid,
207 QTOPIADB_PHONETYPE_OFFICE)
208 database.execute(st)
209 except KeyError:
210 pass
211
212 try:
213 if contact.attributes['fax'] != '' and contact.attributes['fax'] != None:
214 st = "insert into contactphonenumbers (phone_number, recid, phone_type) values (%s, %d, %d)" %(
215 self._quoteString(contact, 'fax'),
216 recid,
217 QTOPIADB_PHONETYPE_FAX)
218 database.execute(st)
219 except KeyError:
220 pass
221
222
223
224 try:
225 if contact.attributes['homeCity'] != '' and contact.attributes['homeCity'] != None:
226 st = "insert into contactaddresses (street, city, state, zip, country, addresstype, recid) values (%s, %s, %s, %s, %s, %d, %d)" %(
227 self._quoteString(contact, 'homeStreet'),
228 self._quoteString(contact, 'homeCity'),
229 self._quoteString(contact, 'homeState'),
230 self._quoteString(contact, 'homePostalCode'),
231 self._quoteString(contact, 'homeCountry'),
232 QTOPIADB_ADDRESSTYPE_PRIVATE,
233 recid)
234 database.execute(st)
235 except KeyError:
236 pass
237
238
239 try:
240 if contact.attributes['businessCity'] != '' and contact.attributes['businessCity'] != None:
241 st = "insert into contactaddresses (street, city, state, zip, country, addresstype, recid) values (%s, %s, %s, %s, %s, %d, %d)" %(
242 self._quoteString(contact, 'businessStreet'),
243 self._quoteString(contact, 'businessCity'),
244 self._quoteString(contact, 'businessState'),
245 self._quoteString(contact, 'businessPostalCode'),
246 self._quoteString(contact, 'businessCountry'),
247 QTOPIADB_ADDRESSTYPE_BUSINESS,
248 recid)
249 database.execute(st)
250 except KeyError:
251 pass
252
254 """
255 Finally deletes the contact entry identified by its ID in the database
256
257 Besides the deletion in the major table all traces in related tables (addresses phone numbers, etc.) are erased as well.
258 """
259 recid = self._idMappingGlobalInternal[id]
260 st = "delete from contacts where recid=%s" %(recid)
261 database.execute(st)
262 st = "delete from emailaddresses where recid=%s" %(recid)
263 database.execute(st)
264 st = "delete from contactphonenumbers where recid=%s" %(recid)
265 database.execute(st)
266 st = "delete from contactaddresses where recid=%s" %(recid)
267 database.execute(st)
268
270 """
271 Applies changes from an entry to the SQLite database
272
273 In order to keep things simple the old value is simply erased using the method L{_saveOperationDelete} and a new one is inserted (L{_saveOperationAdd}).
274 """
275 self._saveOperationDelete(id, database)
276 self._saveOperationAdd(id, database)
277
308