-rw-r--r-- | libopie/pim/ocontactaccessbackend_xml.cpp | 739 | ||||
-rw-r--r-- | libopie2/opiepim/backend/ocontactaccessbackend_xml.cpp | 739 |
2 files changed, 1478 insertions, 0 deletions
diff --git a/libopie/pim/ocontactaccessbackend_xml.cpp b/libopie/pim/ocontactaccessbackend_xml.cpp new file mode 100644 index 0000000..2df6757 --- a/dev/null +++ b/libopie/pim/ocontactaccessbackend_xml.cpp | |||
@@ -0,0 +1,739 @@ | |||
1 | /* | ||
2 | * XML Backend for the OPIE-Contact Database. | ||
3 | * | ||
4 | * Copyright (c) 2002 by Stefan Eilers (Eilers.Stefan@epost.de) | ||
5 | * | ||
6 | * ===================================================================== | ||
7 | *This program is free software; you can redistribute it and/or | ||
8 | *modify it under the terms of the GNU Library General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2 of the License, or (at your option) any later version. | ||
11 | * ===================================================================== | ||
12 | * ToDo: XML-Backend: Automatic reload if something was changed... | ||
13 | * | ||
14 | * | ||
15 | * ===================================================================== | ||
16 | * Version: $Id$ | ||
17 | * ===================================================================== | ||
18 | * History: | ||
19 | * $Log$ | ||
20 | * Revision 1.1 2003/02/09 15:05:01 eilers | ||
21 | * Nothing happened.. Just some cleanup before I will start.. | ||
22 | * | ||
23 | * Revision 1.12 2003/01/03 16:58:03 eilers | ||
24 | * Reenable debug output | ||
25 | * | ||
26 | * Revision 1.11 2003/01/03 12:31:28 eilers | ||
27 | * Bugfix for calculating data diffs.. | ||
28 | * | ||
29 | * Revision 1.10 2003/01/02 14:27:12 eilers | ||
30 | * Improved query by example: Search by date is possible.. First step | ||
31 | * for a today plugin for birthdays.. | ||
32 | * | ||
33 | * Revision 1.9 2002/12/08 12:48:57 eilers | ||
34 | * Moved journal-enum from ocontact into i the xml-backend.. | ||
35 | * | ||
36 | * Revision 1.8 2002/11/14 17:04:24 eilers | ||
37 | * Sorting will now work if fullname is identical on some entries | ||
38 | * | ||
39 | * Revision 1.7 2002/11/13 15:02:46 eilers | ||
40 | * Small Bug in sorted fixed | ||
41 | * | ||
42 | * Revision 1.6 2002/11/13 14:14:51 eilers | ||
43 | * Added sorted for Contacts.. | ||
44 | * | ||
45 | * Revision 1.5 2002/11/01 15:10:42 eilers | ||
46 | * Added regExp-search in database for all fields in a contact. | ||
47 | * | ||
48 | * Revision 1.4 2002/10/16 10:52:40 eilers | ||
49 | * Added some docu to the interface and now using the cache infrastucture by zecke.. :) | ||
50 | * | ||
51 | * Revision 1.3 2002/10/14 16:21:54 eilers | ||
52 | * Some minor interface updates | ||
53 | * | ||
54 | * Revision 1.2 2002/10/07 17:34:24 eilers | ||
55 | * added OBackendFactory for advanced backend access | ||
56 | * | ||
57 | * Revision 1.1 2002/09/27 17:11:44 eilers | ||
58 | * Added API for accessing the Contact-Database ! It is compiling, but | ||
59 | * please do not expect that anything is working ! | ||
60 | * I will debug that stuff in the next time .. | ||
61 | * Please read README_COMPILE for compiling ! | ||
62 | * | ||
63 | * | ||
64 | */ | ||
65 | |||
66 | #include "ocontactaccessbackend_xml.h" | ||
67 | |||
68 | #include <qasciidict.h> | ||
69 | #include <qdatetime.h> | ||
70 | #include <qfile.h> | ||
71 | #include <qfileinfo.h> | ||
72 | #include <qregexp.h> | ||
73 | #include <qarray.h> | ||
74 | #include <qmap.h> | ||
75 | #include <qdatetime.h> | ||
76 | |||
77 | #include <qpe/global.h> | ||
78 | |||
79 | #include <opie/xmltree.h> | ||
80 | #include "ocontactaccessbackend.h" | ||
81 | #include "ocontactaccess.h" | ||
82 | |||
83 | #include <stdlib.h> | ||
84 | #include <errno.h> | ||
85 | |||
86 | using namespace Opie; | ||
87 | |||
88 | |||
89 | OContactAccessBackend_XML::OContactAccessBackend_XML ( QString appname, QString filename = 0l ): | ||
90 | m_changed( false ) | ||
91 | { | ||
92 | m_appName = appname; | ||
93 | |||
94 | /* Set journalfile name ... */ | ||
95 | m_journalName = getenv("HOME"); | ||
96 | m_journalName +="/.abjournal" + appname; | ||
97 | |||
98 | /* Expecting to access the default filename if nothing else is set */ | ||
99 | if ( filename.isEmpty() ){ | ||
100 | m_fileName = Global::applicationFileName( "addressbook","addressbook.xml" ); | ||
101 | } else | ||
102 | m_fileName = filename; | ||
103 | |||
104 | /* Load Database now */ | ||
105 | load (); | ||
106 | } | ||
107 | |||
108 | bool OContactAccessBackend_XML::save() | ||
109 | { | ||
110 | |||
111 | if ( !m_changed ) | ||
112 | return true; | ||
113 | |||
114 | QString strNewFile = m_fileName + ".new"; | ||
115 | QFile f( strNewFile ); | ||
116 | if ( !f.open( IO_WriteOnly|IO_Raw ) ) | ||
117 | return false; | ||
118 | |||
119 | int total_written; | ||
120 | QString out; | ||
121 | out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE Addressbook ><AddressBook>\n" | ||
122 | " <Groups>\n" | ||
123 | " </Groups>\n" | ||
124 | " <Contacts>\n"; | ||
125 | //QValueList<Contact>::iterator it; | ||
126 | QValueListConstIterator<OContact> it; | ||
127 | for ( it = m_contactList.begin(); it != m_contactList.end(); ++it ) { | ||
128 | out += "<Contact "; | ||
129 | (*it).save( out ); | ||
130 | out += "/>\n"; | ||
131 | QCString cstr = out.utf8(); | ||
132 | total_written = f.writeBlock( cstr.data(), cstr.length() ); | ||
133 | if ( total_written != int(cstr.length()) ) { | ||
134 | f.close(); | ||
135 | QFile::remove( strNewFile ); | ||
136 | return false; | ||
137 | } | ||
138 | out = ""; | ||
139 | } | ||
140 | out += " </Contacts>\n</AddressBook>\n"; | ||
141 | |||
142 | QCString cstr = out.utf8(); | ||
143 | total_written = f.writeBlock( cstr.data(), cstr.length() ); | ||
144 | if ( total_written != int( cstr.length() ) ) { | ||
145 | f.close(); | ||
146 | QFile::remove( strNewFile ); | ||
147 | return false; | ||
148 | } | ||
149 | f.close(); | ||
150 | |||
151 | // move the file over, I'm just going to use the system call | ||
152 | // because, I don't feel like using QDir. | ||
153 | if ( ::rename( strNewFile.latin1(), m_fileName.latin1() ) < 0 ) { | ||
154 | qWarning( "problem renaming file %s to %s, errno: %d", | ||
155 | strNewFile.latin1(), m_journalName.latin1(), errno ); | ||
156 | // remove the tmp file... | ||
157 | QFile::remove( strNewFile ); | ||
158 | } | ||
159 | |||
160 | /* The journalfile should be removed now... */ | ||
161 | removeJournal(); | ||
162 | |||
163 | m_changed = false; | ||
164 | return true; | ||
165 | } | ||
166 | |||
167 | bool OContactAccessBackend_XML::load () | ||
168 | { | ||
169 | m_contactList.clear(); | ||
170 | |||
171 | /* Load XML-File and journal if it exists */ | ||
172 | if ( !load ( m_fileName, false ) ) | ||
173 | return false; | ||
174 | /* The returncode of the journalfile is ignored due to the | ||
175 | * fact that it does not exist when this class is instantiated ! | ||
176 | * But there may such a file exist, if the application crashed. | ||
177 | * Therefore we try to load it to get the changes before the # | ||
178 | * crash happened... | ||
179 | */ | ||
180 | load (m_journalName, true); | ||
181 | |||
182 | return true; | ||
183 | } | ||
184 | |||
185 | void OContactAccessBackend_XML::clear () | ||
186 | { | ||
187 | m_contactList.clear(); | ||
188 | m_changed = false; | ||
189 | |||
190 | } | ||
191 | |||
192 | bool OContactAccessBackend_XML::wasChangedExternally() | ||
193 | { | ||
194 | QFileInfo fi( m_fileName ); | ||
195 | |||
196 | QDateTime lastmod = fi.lastModified (); | ||
197 | |||
198 | return (lastmod != m_readtime); | ||
199 | } | ||
200 | |||
201 | QArray<int> OContactAccessBackend_XML::allRecords() const | ||
202 | { | ||
203 | QArray<int> uid_list( m_contactList.count() ); | ||
204 | |||
205 | uint counter = 0; | ||
206 | QValueListConstIterator<OContact> it; | ||
207 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
208 | uid_list[counter++] = (*it).uid(); | ||
209 | } | ||
210 | |||
211 | return ( uid_list ); | ||
212 | } | ||
213 | |||
214 | OContact OContactAccessBackend_XML::find ( int uid ) const | ||
215 | { | ||
216 | bool found = false; | ||
217 | OContact foundContact; //Create empty contact | ||
218 | |||
219 | QValueListConstIterator<OContact> it; | ||
220 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
221 | if ((*it).uid() == uid){ | ||
222 | found = true; | ||
223 | break; | ||
224 | } | ||
225 | } | ||
226 | if ( found ){ | ||
227 | foundContact = *it; | ||
228 | } | ||
229 | |||
230 | return ( foundContact ); | ||
231 | } | ||
232 | |||
233 | QArray<int> OContactAccessBackend_XML::queryByExample ( const OContact &query, int settings ) | ||
234 | { | ||
235 | |||
236 | QArray<int> m_currentQuery( m_contactList.count() ); | ||
237 | QValueListConstIterator<OContact> it; | ||
238 | uint arraycounter = 0; | ||
239 | |||
240 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
241 | /* Search all fields and compare them with query object. Store them into list | ||
242 | * if all fields matches. | ||
243 | */ | ||
244 | QDate* queryDate = 0l; | ||
245 | QDate* checkDate = 0l; | ||
246 | bool allcorrect = true; | ||
247 | for ( int i = 0; i < Qtopia::Groups; i++ ) { | ||
248 | // Birthday and anniversary are special nonstring fields and should | ||
249 | // be handled specially | ||
250 | switch ( i ){ | ||
251 | case Qtopia::Birthday: | ||
252 | queryDate = new QDate( query.birthday() ); | ||
253 | checkDate = new QDate( (*it).birthday() ); | ||
254 | case Qtopia::Anniversary: | ||
255 | if ( queryDate == 0l ){ | ||
256 | queryDate = new QDate( query.anniversary() ); | ||
257 | checkDate = new QDate( (*it).anniversary() ); | ||
258 | } | ||
259 | |||
260 | if ( queryDate->isValid() ){ | ||
261 | if( checkDate->isValid() ){ | ||
262 | if ( settings & OContactAccess::DateYear ){ | ||
263 | if ( queryDate->year() != checkDate->year() ) | ||
264 | allcorrect = false; | ||
265 | } | ||
266 | if ( settings & OContactAccess::DateMonth ){ | ||
267 | if ( queryDate->month() != checkDate->month() ) | ||
268 | allcorrect = false; | ||
269 | } | ||
270 | if ( settings & OContactAccess::DateDay ){ | ||
271 | if ( queryDate->day() != checkDate->day() ) | ||
272 | allcorrect = false; | ||
273 | } | ||
274 | if ( settings & OContactAccess::DateDiff ) { | ||
275 | QDate current = QDate::currentDate(); | ||
276 | // We have to equalize the year, otherwise | ||
277 | // the search will fail.. | ||
278 | checkDate->setYMD( current.year(), | ||
279 | checkDate->month(), | ||
280 | checkDate->day() ); | ||
281 | if ( *checkDate < current ) | ||
282 | checkDate->setYMD( current.year()+1, | ||
283 | checkDate->month(), | ||
284 | checkDate->day() ); | ||
285 | qWarning("Checking if %s is between %s and %s ! ", | ||
286 | checkDate->toString().latin1(), | ||
287 | current.toString().latin1(), | ||
288 | queryDate->toString().latin1() ); | ||
289 | if ( current.daysTo( *queryDate ) > 0 ){ | ||
290 | if ( !( ( *checkDate >= current ) && | ||
291 | ( *checkDate <= *queryDate ) ) ){ | ||
292 | allcorrect = false; | ||
293 | qWarning (" Nope!.."); | ||
294 | } | ||
295 | } | ||
296 | } | ||
297 | } else{ | ||
298 | // checkDate is invalid. Therfore this entry is always rejected | ||
299 | allcorrect = false; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | delete queryDate; | ||
304 | queryDate = 0l; | ||
305 | delete checkDate; | ||
306 | checkDate = 0l; | ||
307 | break; | ||
308 | default: | ||
309 | /* Just compare fields which are not empty in the query object */ | ||
310 | if ( !query.field(i).isEmpty() ){ | ||
311 | switch ( settings & ~( OContactAccess::IgnoreCase | ||
312 | | OContactAccess::DateDiff | ||
313 | | OContactAccess::DateYear | ||
314 | | OContactAccess::DateMonth | ||
315 | | OContactAccess::DateDay | ||
316 | | OContactAccess::MatchOne | ||
317 | ) ){ | ||
318 | |||
319 | case OContactAccess::RegExp:{ | ||
320 | QRegExp expr ( query.field(i), | ||
321 | !(settings & OContactAccess::IgnoreCase), | ||
322 | false ); | ||
323 | if ( expr.find ( (*it).field(i), 0 ) == -1 ) | ||
324 | allcorrect = false; | ||
325 | } | ||
326 | break; | ||
327 | case OContactAccess::WildCards:{ | ||
328 | QRegExp expr ( query.field(i), | ||
329 | !(settings & OContactAccess::IgnoreCase), | ||
330 | true ); | ||
331 | if ( expr.find ( (*it).field(i), 0 ) == -1 ) | ||
332 | allcorrect = false; | ||
333 | } | ||
334 | break; | ||
335 | case OContactAccess::ExactMatch:{ | ||
336 | if (settings & OContactAccess::IgnoreCase){ | ||
337 | if ( query.field(i).upper() != | ||
338 | (*it).field(i).upper() ) | ||
339 | allcorrect = false; | ||
340 | }else{ | ||
341 | if ( query.field(i) != (*it).field(i) ) | ||
342 | allcorrect = false; | ||
343 | } | ||
344 | } | ||
345 | break; | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | if ( allcorrect ){ | ||
351 | m_currentQuery[arraycounter++] = (*it).uid(); | ||
352 | } | ||
353 | } | ||
354 | |||
355 | // Shrink to fit.. | ||
356 | m_currentQuery.resize(arraycounter); | ||
357 | |||
358 | return m_currentQuery; | ||
359 | } | ||
360 | |||
361 | QArray<int> OContactAccessBackend_XML::matchRegexp( const QRegExp &r ) const | ||
362 | { | ||
363 | QArray<int> m_currentQuery( m_contactList.count() ); | ||
364 | QValueListConstIterator<OContact> it; | ||
365 | uint arraycounter = 0; | ||
366 | |||
367 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
368 | if ( (*it).match( r ) ){ | ||
369 | m_currentQuery[arraycounter++] = (*it).uid(); | ||
370 | } | ||
371 | |||
372 | } | ||
373 | // Shrink to fit.. | ||
374 | m_currentQuery.resize(arraycounter); | ||
375 | |||
376 | return m_currentQuery; | ||
377 | } | ||
378 | |||
379 | const uint OContactAccessBackend_XML::querySettings() | ||
380 | { | ||
381 | return ( OContactAccess::WildCards | ||
382 | | OContactAccess::IgnoreCase | ||
383 | | OContactAccess::RegExp | ||
384 | | OContactAccess::ExactMatch | ||
385 | | OContactAccess::DateDiff | ||
386 | | OContactAccess::DateYear | ||
387 | | OContactAccess::DateMonth | ||
388 | | OContactAccess::DateDay | ||
389 | ); | ||
390 | } | ||
391 | |||
392 | bool OContactAccessBackend_XML::hasQuerySettings (uint querySettings) const | ||
393 | { | ||
394 | /* OContactAccess::IgnoreCase, DateDiff, DateYear, DateMonth, DateDay | ||
395 | * may be added with any of the other settings. IgnoreCase should never used alone. | ||
396 | * Wildcards, RegExp, ExactMatch should never used at the same time... | ||
397 | */ | ||
398 | |||
399 | if ( querySettings == OContactAccess::IgnoreCase ) | ||
400 | return false; | ||
401 | |||
402 | switch ( querySettings & ~( OContactAccess::IgnoreCase | ||
403 | | OContactAccess::DateDiff | ||
404 | | OContactAccess::DateYear | ||
405 | | OContactAccess::DateMonth | ||
406 | | OContactAccess::DateDay | ||
407 | ) | ||
408 | ){ | ||
409 | case OContactAccess::RegExp: | ||
410 | return ( true ); | ||
411 | case OContactAccess::WildCards: | ||
412 | return ( true ); | ||
413 | case OContactAccess::ExactMatch: | ||
414 | return ( true ); | ||
415 | default: | ||
416 | return ( false ); | ||
417 | } | ||
418 | } | ||
419 | |||
420 | // Currently only asc implemented.. | ||
421 | QArray<int> OContactAccessBackend_XML::sorted( bool asc, int , int , int ) | ||
422 | { | ||
423 | QMap<QString, int> nameToUid; | ||
424 | QStringList names; | ||
425 | QArray<int> m_currentQuery( m_contactList.count() ); | ||
426 | |||
427 | // First fill map and StringList with all Names | ||
428 | // Afterwards sort namelist and use map to fill array to return.. | ||
429 | QValueListConstIterator<OContact> it; | ||
430 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
431 | names.append( (*it).fileAs() + QString::number( (*it).uid() ) ); | ||
432 | nameToUid.insert( (*it).fileAs() + QString::number( (*it).uid() ), (*it).uid() ); | ||
433 | } | ||
434 | names.sort(); | ||
435 | |||
436 | int i = 0; | ||
437 | if ( asc ){ | ||
438 | for ( QStringList::Iterator it = names.begin(); it != names.end(); ++it ) | ||
439 | m_currentQuery[i++] = nameToUid[ (*it) ]; | ||
440 | }else{ | ||
441 | for ( QStringList::Iterator it = names.end(); it != names.begin(); --it ) | ||
442 | m_currentQuery[i++] = nameToUid[ (*it) ]; | ||
443 | } | ||
444 | |||
445 | return m_currentQuery; | ||
446 | |||
447 | } | ||
448 | |||
449 | bool OContactAccessBackend_XML::add ( const OContact &newcontact ) | ||
450 | { | ||
451 | //qWarning("odefaultbackend: ACTION::ADD"); | ||
452 | updateJournal (newcontact, ACTION_ADD); | ||
453 | addContact_p( newcontact ); | ||
454 | |||
455 | m_changed = true; | ||
456 | |||
457 | return true; | ||
458 | } | ||
459 | |||
460 | bool OContactAccessBackend_XML::replace ( const OContact &contact ) | ||
461 | { | ||
462 | m_changed = true; | ||
463 | |||
464 | bool found = false; | ||
465 | |||
466 | QValueListIterator<OContact> it; | ||
467 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
468 | if ( (*it).uid() == contact.uid() ){ | ||
469 | found = true; | ||
470 | break; | ||
471 | } | ||
472 | } | ||
473 | if (found) { | ||
474 | updateJournal (contact, ACTION_REPLACE); | ||
475 | m_contactList.remove (it); | ||
476 | m_contactList.append (contact); | ||
477 | return true; | ||
478 | } else | ||
479 | return false; | ||
480 | } | ||
481 | |||
482 | bool OContactAccessBackend_XML::remove ( int uid ) | ||
483 | { | ||
484 | m_changed = true; | ||
485 | |||
486 | bool found = false; | ||
487 | QValueListIterator<OContact> it; | ||
488 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
489 | if ((*it).uid() == uid){ | ||
490 | found = true; | ||
491 | break; | ||
492 | } | ||
493 | } | ||
494 | if (found) { | ||
495 | updateJournal ( *it, ACTION_REMOVE); | ||
496 | m_contactList.remove (it); | ||
497 | return true; | ||
498 | } else | ||
499 | return false; | ||
500 | } | ||
501 | |||
502 | bool OContactAccessBackend_XML::reload(){ | ||
503 | /* Reload is the same as load in this implementation */ | ||
504 | return ( load() ); | ||
505 | } | ||
506 | |||
507 | void OContactAccessBackend_XML::addContact_p( const OContact &newcontact ) | ||
508 | { | ||
509 | m_contactList.append (newcontact); | ||
510 | } | ||
511 | |||
512 | /* This function loads the xml-database and the journalfile */ | ||
513 | bool OContactAccessBackend_XML::load( const QString filename, bool isJournal ) | ||
514 | { | ||
515 | |||
516 | /* We use the time of the last read to check if the file was | ||
517 | * changed externally. | ||
518 | */ | ||
519 | if ( !isJournal ){ | ||
520 | QFileInfo fi( filename ); | ||
521 | m_readtime = fi.lastModified (); | ||
522 | } | ||
523 | |||
524 | const int JOURNALACTION = Qtopia::Notes + 1; | ||
525 | const int JOURNALROW = JOURNALACTION + 1; | ||
526 | |||
527 | bool foundAction = false; | ||
528 | journal_action action = ACTION_ADD; | ||
529 | int journalKey = 0; | ||
530 | QMap<int, QString> contactMap; | ||
531 | QMap<QString, QString> customMap; | ||
532 | QMap<QString, QString>::Iterator customIt; | ||
533 | QAsciiDict<int> dict( 47 ); | ||
534 | |||
535 | dict.setAutoDelete( TRUE ); | ||
536 | dict.insert( "Uid", new int(Qtopia::AddressUid) ); | ||
537 | dict.insert( "Title", new int(Qtopia::Title) ); | ||
538 | dict.insert( "FirstName", new int(Qtopia::FirstName) ); | ||
539 | dict.insert( "MiddleName", new int(Qtopia::MiddleName) ); | ||
540 | dict.insert( "LastName", new int(Qtopia::LastName) ); | ||
541 | dict.insert( "Suffix", new int(Qtopia::Suffix) ); | ||
542 | dict.insert( "FileAs", new int(Qtopia::FileAs) ); | ||
543 | dict.insert( "Categories", new int(Qtopia::AddressCategory) ); | ||
544 | dict.insert( "DefaultEmail", new int(Qtopia::DefaultEmail) ); | ||
545 | dict.insert( "Emails", new int(Qtopia::Emails) ); | ||
546 | dict.insert( "HomeStreet", new int(Qtopia::HomeStreet) ); | ||
547 | dict.insert( "HomeCity", new int(Qtopia::HomeCity) ); | ||
548 | dict.insert( "HomeState", new int(Qtopia::HomeState) ); | ||
549 | dict.insert( "HomeZip", new int(Qtopia::HomeZip) ); | ||
550 | dict.insert( "HomeCountry", new int(Qtopia::HomeCountry) ); | ||
551 | dict.insert( "HomePhone", new int(Qtopia::HomePhone) ); | ||
552 | dict.insert( "HomeFax", new int(Qtopia::HomeFax) ); | ||
553 | dict.insert( "HomeMobile", new int(Qtopia::HomeMobile) ); | ||
554 | dict.insert( "HomeWebPage", new int(Qtopia::HomeWebPage) ); | ||
555 | dict.insert( "Company", new int(Qtopia::Company) ); | ||
556 | dict.insert( "BusinessStreet", new int(Qtopia::BusinessStreet) ); | ||
557 | dict.insert( "BusinessCity", new int(Qtopia::BusinessCity) ); | ||
558 | dict.insert( "BusinessState", new int(Qtopia::BusinessState) ); | ||
559 | dict.insert( "BusinessZip", new int(Qtopia::BusinessZip) ); | ||
560 | dict.insert( "BusinessCountry", new int(Qtopia::BusinessCountry) ); | ||
561 | dict.insert( "BusinessWebPage", new int(Qtopia::BusinessWebPage) ); | ||
562 | dict.insert( "JobTitle", new int(Qtopia::JobTitle) ); | ||
563 | dict.insert( "Department", new int(Qtopia::Department) ); | ||
564 | dict.insert( "Office", new int(Qtopia::Office) ); | ||
565 | dict.insert( "BusinessPhone", new int(Qtopia::BusinessPhone) ); | ||
566 | dict.insert( "BusinessFax", new int(Qtopia::BusinessFax) ); | ||
567 | dict.insert( "BusinessMobile", new int(Qtopia::BusinessMobile) ); | ||
568 | dict.insert( "BusinessPager", new int(Qtopia::BusinessPager) ); | ||
569 | dict.insert( "Profession", new int(Qtopia::Profession) ); | ||
570 | dict.insert( "Assistant", new int(Qtopia::Assistant) ); | ||
571 | dict.insert( "Manager", new int(Qtopia::Manager) ); | ||
572 | dict.insert( "Spouse", new int(Qtopia::Spouse) ); | ||
573 | dict.insert( "Children", new int(Qtopia::Children) ); | ||
574 | dict.insert( "Gender", new int(Qtopia::Gender) ); | ||
575 | dict.insert( "Birthday", new int(Qtopia::Birthday) ); | ||
576 | dict.insert( "Anniversary", new int(Qtopia::Anniversary) ); | ||
577 | dict.insert( "Nickname", new int(Qtopia::Nickname) ); | ||
578 | dict.insert( "Notes", new int(Qtopia::Notes) ); | ||
579 | dict.insert( "action", new int(JOURNALACTION) ); | ||
580 | dict.insert( "actionrow", new int(JOURNALROW) ); | ||
581 | |||
582 | //qWarning( "OContactDefaultBackEnd::loading %s", filename.latin1() ); | ||
583 | |||
584 | XMLElement *root = XMLElement::load( filename ); | ||
585 | if(root != 0l ){ // start parsing | ||
586 | /* Parse all XML-Elements and put the data into the | ||
587 | * Contact-Class | ||
588 | */ | ||
589 | XMLElement *element = root->firstChild(); | ||
590 | //qWarning("OContactAccess::load tagName(): %s", root->tagName().latin1() ); | ||
591 | element = element->firstChild(); | ||
592 | |||
593 | /* Search Tag "Contacts" which is the parent of all Contacts */ | ||
594 | while( element && !isJournal ){ | ||
595 | if( element->tagName() != QString::fromLatin1("Contacts") ){ | ||
596 | //qWarning ("OContactDefBack::Searching for Tag \"Contacts\"! Found: %s", | ||
597 | // element->tagName().latin1()); | ||
598 | element = element->nextChild(); | ||
599 | } else { | ||
600 | element = element->firstChild(); | ||
601 | break; | ||
602 | } | ||
603 | } | ||
604 | /* Parse all Contacts and ignore unknown tags */ | ||
605 | while( element ){ | ||
606 | if( element->tagName() != QString::fromLatin1("Contact") ){ | ||
607 | //qWarning ("OContactDefBack::Searching for Tag \"Contact\"! Found: %s", | ||
608 | // element->tagName().latin1()); | ||
609 | element = element->nextChild(); | ||
610 | continue; | ||
611 | } | ||
612 | /* Found alement with tagname "contact", now parse and store all | ||
613 | * attributes contained | ||
614 | */ | ||
615 | //qWarning("OContactDefBack::load element tagName() : %s", | ||
616 | // element->tagName().latin1() ); | ||
617 | QString dummy; | ||
618 | foundAction = false; | ||
619 | |||
620 | XMLElement::AttributeMap aMap = element->attributes(); | ||
621 | XMLElement::AttributeMap::Iterator it; | ||
622 | contactMap.clear(); | ||
623 | customMap.clear(); | ||
624 | for( it = aMap.begin(); it != aMap.end(); ++it ){ | ||
625 | // qWarning ("Read Attribute: %s=%s", it.key().latin1(),it.data().latin1()); | ||
626 | |||
627 | int *find = dict[ it.key() ]; | ||
628 | /* Unknown attributes will be stored as "Custom" elements */ | ||
629 | if ( !find ) { | ||
630 | qWarning("Attribute %s not known.", it.key().latin1()); | ||
631 | //contact.setCustomField(it.key(), it.data()); | ||
632 | customMap.insert( it.key(), it.data() ); | ||
633 | continue; | ||
634 | } | ||
635 | |||
636 | /* Check if special conversion is needed and add attribute | ||
637 | * into Contact class | ||
638 | */ | ||
639 | switch( *find ) { | ||
640 | /* | ||
641 | case Qtopia::AddressUid: | ||
642 | contact.setUid( it.data().toInt() ); | ||
643 | break; | ||
644 | case Qtopia::AddressCategory: | ||
645 | contact.setCategories( Qtopia::Record::idsFromString( it.data( ))); | ||
646 | break; | ||
647 | */ | ||
648 | case JOURNALACTION: | ||
649 | action = journal_action(it.data().toInt()); | ||
650 | foundAction = true; | ||
651 | qWarning ("ODefBack(journal)::ACTION found: %d", action); | ||
652 | break; | ||
653 | case JOURNALROW: | ||
654 | journalKey = it.data().toInt(); | ||
655 | break; | ||
656 | default: // no conversion needed add them to the map | ||
657 | contactMap.insert( *find, it.data() ); | ||
658 | break; | ||
659 | } | ||
660 | } | ||
661 | /* now generate the Contact contact */ | ||
662 | OContact contact( contactMap ); | ||
663 | |||
664 | for (customIt = customMap.begin(); customIt != customMap.end(); ++customIt ) { | ||
665 | contact.setCustomField( customIt.key(), customIt.data() ); | ||
666 | } | ||
667 | |||
668 | if (foundAction){ | ||
669 | foundAction = false; | ||
670 | switch ( action ) { | ||
671 | case ACTION_ADD: | ||
672 | addContact_p (contact); | ||
673 | break; | ||
674 | case ACTION_REMOVE: | ||
675 | if ( !remove (contact.uid()) ) | ||
676 | qWarning ("ODefBack(journal)::Unable to remove uid: %d", | ||
677 | contact.uid() ); | ||
678 | break; | ||
679 | case ACTION_REPLACE: | ||
680 | if ( !replace ( contact ) ) | ||
681 | qWarning ("ODefBack(journal)::Unable to replace uid: %d", | ||
682 | contact.uid() ); | ||
683 | break; | ||
684 | default: | ||
685 | qWarning ("Unknown action: ignored !"); | ||
686 | break; | ||
687 | } | ||
688 | }else{ | ||
689 | /* Add contact to list */ | ||
690 | addContact_p (contact); | ||
691 | } | ||
692 | |||
693 | /* Move to next element */ | ||
694 | element = element->nextChild(); | ||
695 | } | ||
696 | }else { | ||
697 | qWarning("ODefBack::could not load"); | ||
698 | } | ||
699 | delete root; | ||
700 | qWarning("returning from loading" ); | ||
701 | return true; | ||
702 | } | ||
703 | |||
704 | |||
705 | void OContactAccessBackend_XML::updateJournal( const OContact& cnt, | ||
706 | journal_action action ) | ||
707 | { | ||
708 | QFile f( m_journalName ); | ||
709 | bool created = !f.exists(); | ||
710 | if ( !f.open(IO_WriteOnly|IO_Append) ) | ||
711 | return; | ||
712 | |||
713 | QString buf; | ||
714 | QCString str; | ||
715 | |||
716 | // if the file was created, we have to set the Tag "<CONTACTS>" to | ||
717 | // get a XML-File which is readable by our parser. | ||
718 | // This is just a cheat, but better than rewrite the parser. | ||
719 | if ( created ){ | ||
720 | buf = "<Contacts>"; | ||
721 | QCString cstr = buf.utf8(); | ||
722 | f.writeBlock( cstr.data(), cstr.length() ); | ||
723 | } | ||
724 | |||
725 | buf = "<Contact "; | ||
726 | cnt.save( buf ); | ||
727 | buf += " action=\"" + QString::number( (int)action ) + "\" "; | ||
728 | buf += "/>\n"; | ||
729 | QCString cstr = buf.utf8(); | ||
730 | f.writeBlock( cstr.data(), cstr.length() ); | ||
731 | } | ||
732 | |||
733 | void OContactAccessBackend_XML::removeJournal() | ||
734 | { | ||
735 | QFile f ( m_journalName ); | ||
736 | if ( f.exists() ) | ||
737 | f.remove(); | ||
738 | } | ||
739 | |||
diff --git a/libopie2/opiepim/backend/ocontactaccessbackend_xml.cpp b/libopie2/opiepim/backend/ocontactaccessbackend_xml.cpp new file mode 100644 index 0000000..2df6757 --- a/dev/null +++ b/libopie2/opiepim/backend/ocontactaccessbackend_xml.cpp | |||
@@ -0,0 +1,739 @@ | |||
1 | /* | ||
2 | * XML Backend for the OPIE-Contact Database. | ||
3 | * | ||
4 | * Copyright (c) 2002 by Stefan Eilers (Eilers.Stefan@epost.de) | ||
5 | * | ||
6 | * ===================================================================== | ||
7 | *This program is free software; you can redistribute it and/or | ||
8 | *modify it under the terms of the GNU Library General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2 of the License, or (at your option) any later version. | ||
11 | * ===================================================================== | ||
12 | * ToDo: XML-Backend: Automatic reload if something was changed... | ||
13 | * | ||
14 | * | ||
15 | * ===================================================================== | ||
16 | * Version: $Id$ | ||
17 | * ===================================================================== | ||
18 | * History: | ||
19 | * $Log$ | ||
20 | * Revision 1.1 2003/02/09 15:05:01 eilers | ||
21 | * Nothing happened.. Just some cleanup before I will start.. | ||
22 | * | ||
23 | * Revision 1.12 2003/01/03 16:58:03 eilers | ||
24 | * Reenable debug output | ||
25 | * | ||
26 | * Revision 1.11 2003/01/03 12:31:28 eilers | ||
27 | * Bugfix for calculating data diffs.. | ||
28 | * | ||
29 | * Revision 1.10 2003/01/02 14:27:12 eilers | ||
30 | * Improved query by example: Search by date is possible.. First step | ||
31 | * for a today plugin for birthdays.. | ||
32 | * | ||
33 | * Revision 1.9 2002/12/08 12:48:57 eilers | ||
34 | * Moved journal-enum from ocontact into i the xml-backend.. | ||
35 | * | ||
36 | * Revision 1.8 2002/11/14 17:04:24 eilers | ||
37 | * Sorting will now work if fullname is identical on some entries | ||
38 | * | ||
39 | * Revision 1.7 2002/11/13 15:02:46 eilers | ||
40 | * Small Bug in sorted fixed | ||
41 | * | ||
42 | * Revision 1.6 2002/11/13 14:14:51 eilers | ||
43 | * Added sorted for Contacts.. | ||
44 | * | ||
45 | * Revision 1.5 2002/11/01 15:10:42 eilers | ||
46 | * Added regExp-search in database for all fields in a contact. | ||
47 | * | ||
48 | * Revision 1.4 2002/10/16 10:52:40 eilers | ||
49 | * Added some docu to the interface and now using the cache infrastucture by zecke.. :) | ||
50 | * | ||
51 | * Revision 1.3 2002/10/14 16:21:54 eilers | ||
52 | * Some minor interface updates | ||
53 | * | ||
54 | * Revision 1.2 2002/10/07 17:34:24 eilers | ||
55 | * added OBackendFactory for advanced backend access | ||
56 | * | ||
57 | * Revision 1.1 2002/09/27 17:11:44 eilers | ||
58 | * Added API for accessing the Contact-Database ! It is compiling, but | ||
59 | * please do not expect that anything is working ! | ||
60 | * I will debug that stuff in the next time .. | ||
61 | * Please read README_COMPILE for compiling ! | ||
62 | * | ||
63 | * | ||
64 | */ | ||
65 | |||
66 | #include "ocontactaccessbackend_xml.h" | ||
67 | |||
68 | #include <qasciidict.h> | ||
69 | #include <qdatetime.h> | ||
70 | #include <qfile.h> | ||
71 | #include <qfileinfo.h> | ||
72 | #include <qregexp.h> | ||
73 | #include <qarray.h> | ||
74 | #include <qmap.h> | ||
75 | #include <qdatetime.h> | ||
76 | |||
77 | #include <qpe/global.h> | ||
78 | |||
79 | #include <opie/xmltree.h> | ||
80 | #include "ocontactaccessbackend.h" | ||
81 | #include "ocontactaccess.h" | ||
82 | |||
83 | #include <stdlib.h> | ||
84 | #include <errno.h> | ||
85 | |||
86 | using namespace Opie; | ||
87 | |||
88 | |||
89 | OContactAccessBackend_XML::OContactAccessBackend_XML ( QString appname, QString filename = 0l ): | ||
90 | m_changed( false ) | ||
91 | { | ||
92 | m_appName = appname; | ||
93 | |||
94 | /* Set journalfile name ... */ | ||
95 | m_journalName = getenv("HOME"); | ||
96 | m_journalName +="/.abjournal" + appname; | ||
97 | |||
98 | /* Expecting to access the default filename if nothing else is set */ | ||
99 | if ( filename.isEmpty() ){ | ||
100 | m_fileName = Global::applicationFileName( "addressbook","addressbook.xml" ); | ||
101 | } else | ||
102 | m_fileName = filename; | ||
103 | |||
104 | /* Load Database now */ | ||
105 | load (); | ||
106 | } | ||
107 | |||
108 | bool OContactAccessBackend_XML::save() | ||
109 | { | ||
110 | |||
111 | if ( !m_changed ) | ||
112 | return true; | ||
113 | |||
114 | QString strNewFile = m_fileName + ".new"; | ||
115 | QFile f( strNewFile ); | ||
116 | if ( !f.open( IO_WriteOnly|IO_Raw ) ) | ||
117 | return false; | ||
118 | |||
119 | int total_written; | ||
120 | QString out; | ||
121 | out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE Addressbook ><AddressBook>\n" | ||
122 | " <Groups>\n" | ||
123 | " </Groups>\n" | ||
124 | " <Contacts>\n"; | ||
125 | //QValueList<Contact>::iterator it; | ||
126 | QValueListConstIterator<OContact> it; | ||
127 | for ( it = m_contactList.begin(); it != m_contactList.end(); ++it ) { | ||
128 | out += "<Contact "; | ||
129 | (*it).save( out ); | ||
130 | out += "/>\n"; | ||
131 | QCString cstr = out.utf8(); | ||
132 | total_written = f.writeBlock( cstr.data(), cstr.length() ); | ||
133 | if ( total_written != int(cstr.length()) ) { | ||
134 | f.close(); | ||
135 | QFile::remove( strNewFile ); | ||
136 | return false; | ||
137 | } | ||
138 | out = ""; | ||
139 | } | ||
140 | out += " </Contacts>\n</AddressBook>\n"; | ||
141 | |||
142 | QCString cstr = out.utf8(); | ||
143 | total_written = f.writeBlock( cstr.data(), cstr.length() ); | ||
144 | if ( total_written != int( cstr.length() ) ) { | ||
145 | f.close(); | ||
146 | QFile::remove( strNewFile ); | ||
147 | return false; | ||
148 | } | ||
149 | f.close(); | ||
150 | |||
151 | // move the file over, I'm just going to use the system call | ||
152 | // because, I don't feel like using QDir. | ||
153 | if ( ::rename( strNewFile.latin1(), m_fileName.latin1() ) < 0 ) { | ||
154 | qWarning( "problem renaming file %s to %s, errno: %d", | ||
155 | strNewFile.latin1(), m_journalName.latin1(), errno ); | ||
156 | // remove the tmp file... | ||
157 | QFile::remove( strNewFile ); | ||
158 | } | ||
159 | |||
160 | /* The journalfile should be removed now... */ | ||
161 | removeJournal(); | ||
162 | |||
163 | m_changed = false; | ||
164 | return true; | ||
165 | } | ||
166 | |||
167 | bool OContactAccessBackend_XML::load () | ||
168 | { | ||
169 | m_contactList.clear(); | ||
170 | |||
171 | /* Load XML-File and journal if it exists */ | ||
172 | if ( !load ( m_fileName, false ) ) | ||
173 | return false; | ||
174 | /* The returncode of the journalfile is ignored due to the | ||
175 | * fact that it does not exist when this class is instantiated ! | ||
176 | * But there may such a file exist, if the application crashed. | ||
177 | * Therefore we try to load it to get the changes before the # | ||
178 | * crash happened... | ||
179 | */ | ||
180 | load (m_journalName, true); | ||
181 | |||
182 | return true; | ||
183 | } | ||
184 | |||
185 | void OContactAccessBackend_XML::clear () | ||
186 | { | ||
187 | m_contactList.clear(); | ||
188 | m_changed = false; | ||
189 | |||
190 | } | ||
191 | |||
192 | bool OContactAccessBackend_XML::wasChangedExternally() | ||
193 | { | ||
194 | QFileInfo fi( m_fileName ); | ||
195 | |||
196 | QDateTime lastmod = fi.lastModified (); | ||
197 | |||
198 | return (lastmod != m_readtime); | ||
199 | } | ||
200 | |||
201 | QArray<int> OContactAccessBackend_XML::allRecords() const | ||
202 | { | ||
203 | QArray<int> uid_list( m_contactList.count() ); | ||
204 | |||
205 | uint counter = 0; | ||
206 | QValueListConstIterator<OContact> it; | ||
207 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
208 | uid_list[counter++] = (*it).uid(); | ||
209 | } | ||
210 | |||
211 | return ( uid_list ); | ||
212 | } | ||
213 | |||
214 | OContact OContactAccessBackend_XML::find ( int uid ) const | ||
215 | { | ||
216 | bool found = false; | ||
217 | OContact foundContact; //Create empty contact | ||
218 | |||
219 | QValueListConstIterator<OContact> it; | ||
220 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
221 | if ((*it).uid() == uid){ | ||
222 | found = true; | ||
223 | break; | ||
224 | } | ||
225 | } | ||
226 | if ( found ){ | ||
227 | foundContact = *it; | ||
228 | } | ||
229 | |||
230 | return ( foundContact ); | ||
231 | } | ||
232 | |||
233 | QArray<int> OContactAccessBackend_XML::queryByExample ( const OContact &query, int settings ) | ||
234 | { | ||
235 | |||
236 | QArray<int> m_currentQuery( m_contactList.count() ); | ||
237 | QValueListConstIterator<OContact> it; | ||
238 | uint arraycounter = 0; | ||
239 | |||
240 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
241 | /* Search all fields and compare them with query object. Store them into list | ||
242 | * if all fields matches. | ||
243 | */ | ||
244 | QDate* queryDate = 0l; | ||
245 | QDate* checkDate = 0l; | ||
246 | bool allcorrect = true; | ||
247 | for ( int i = 0; i < Qtopia::Groups; i++ ) { | ||
248 | // Birthday and anniversary are special nonstring fields and should | ||
249 | // be handled specially | ||
250 | switch ( i ){ | ||
251 | case Qtopia::Birthday: | ||
252 | queryDate = new QDate( query.birthday() ); | ||
253 | checkDate = new QDate( (*it).birthday() ); | ||
254 | case Qtopia::Anniversary: | ||
255 | if ( queryDate == 0l ){ | ||
256 | queryDate = new QDate( query.anniversary() ); | ||
257 | checkDate = new QDate( (*it).anniversary() ); | ||
258 | } | ||
259 | |||
260 | if ( queryDate->isValid() ){ | ||
261 | if( checkDate->isValid() ){ | ||
262 | if ( settings & OContactAccess::DateYear ){ | ||
263 | if ( queryDate->year() != checkDate->year() ) | ||
264 | allcorrect = false; | ||
265 | } | ||
266 | if ( settings & OContactAccess::DateMonth ){ | ||
267 | if ( queryDate->month() != checkDate->month() ) | ||
268 | allcorrect = false; | ||
269 | } | ||
270 | if ( settings & OContactAccess::DateDay ){ | ||
271 | if ( queryDate->day() != checkDate->day() ) | ||
272 | allcorrect = false; | ||
273 | } | ||
274 | if ( settings & OContactAccess::DateDiff ) { | ||
275 | QDate current = QDate::currentDate(); | ||
276 | // We have to equalize the year, otherwise | ||
277 | // the search will fail.. | ||
278 | checkDate->setYMD( current.year(), | ||
279 | checkDate->month(), | ||
280 | checkDate->day() ); | ||
281 | if ( *checkDate < current ) | ||
282 | checkDate->setYMD( current.year()+1, | ||
283 | checkDate->month(), | ||
284 | checkDate->day() ); | ||
285 | qWarning("Checking if %s is between %s and %s ! ", | ||
286 | checkDate->toString().latin1(), | ||
287 | current.toString().latin1(), | ||
288 | queryDate->toString().latin1() ); | ||
289 | if ( current.daysTo( *queryDate ) > 0 ){ | ||
290 | if ( !( ( *checkDate >= current ) && | ||
291 | ( *checkDate <= *queryDate ) ) ){ | ||
292 | allcorrect = false; | ||
293 | qWarning (" Nope!.."); | ||
294 | } | ||
295 | } | ||
296 | } | ||
297 | } else{ | ||
298 | // checkDate is invalid. Therfore this entry is always rejected | ||
299 | allcorrect = false; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | delete queryDate; | ||
304 | queryDate = 0l; | ||
305 | delete checkDate; | ||
306 | checkDate = 0l; | ||
307 | break; | ||
308 | default: | ||
309 | /* Just compare fields which are not empty in the query object */ | ||
310 | if ( !query.field(i).isEmpty() ){ | ||
311 | switch ( settings & ~( OContactAccess::IgnoreCase | ||
312 | | OContactAccess::DateDiff | ||
313 | | OContactAccess::DateYear | ||
314 | | OContactAccess::DateMonth | ||
315 | | OContactAccess::DateDay | ||
316 | | OContactAccess::MatchOne | ||
317 | ) ){ | ||
318 | |||
319 | case OContactAccess::RegExp:{ | ||
320 | QRegExp expr ( query.field(i), | ||
321 | !(settings & OContactAccess::IgnoreCase), | ||
322 | false ); | ||
323 | if ( expr.find ( (*it).field(i), 0 ) == -1 ) | ||
324 | allcorrect = false; | ||
325 | } | ||
326 | break; | ||
327 | case OContactAccess::WildCards:{ | ||
328 | QRegExp expr ( query.field(i), | ||
329 | !(settings & OContactAccess::IgnoreCase), | ||
330 | true ); | ||
331 | if ( expr.find ( (*it).field(i), 0 ) == -1 ) | ||
332 | allcorrect = false; | ||
333 | } | ||
334 | break; | ||
335 | case OContactAccess::ExactMatch:{ | ||
336 | if (settings & OContactAccess::IgnoreCase){ | ||
337 | if ( query.field(i).upper() != | ||
338 | (*it).field(i).upper() ) | ||
339 | allcorrect = false; | ||
340 | }else{ | ||
341 | if ( query.field(i) != (*it).field(i) ) | ||
342 | allcorrect = false; | ||
343 | } | ||
344 | } | ||
345 | break; | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | if ( allcorrect ){ | ||
351 | m_currentQuery[arraycounter++] = (*it).uid(); | ||
352 | } | ||
353 | } | ||
354 | |||
355 | // Shrink to fit.. | ||
356 | m_currentQuery.resize(arraycounter); | ||
357 | |||
358 | return m_currentQuery; | ||
359 | } | ||
360 | |||
361 | QArray<int> OContactAccessBackend_XML::matchRegexp( const QRegExp &r ) const | ||
362 | { | ||
363 | QArray<int> m_currentQuery( m_contactList.count() ); | ||
364 | QValueListConstIterator<OContact> it; | ||
365 | uint arraycounter = 0; | ||
366 | |||
367 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
368 | if ( (*it).match( r ) ){ | ||
369 | m_currentQuery[arraycounter++] = (*it).uid(); | ||
370 | } | ||
371 | |||
372 | } | ||
373 | // Shrink to fit.. | ||
374 | m_currentQuery.resize(arraycounter); | ||
375 | |||
376 | return m_currentQuery; | ||
377 | } | ||
378 | |||
379 | const uint OContactAccessBackend_XML::querySettings() | ||
380 | { | ||
381 | return ( OContactAccess::WildCards | ||
382 | | OContactAccess::IgnoreCase | ||
383 | | OContactAccess::RegExp | ||
384 | | OContactAccess::ExactMatch | ||
385 | | OContactAccess::DateDiff | ||
386 | | OContactAccess::DateYear | ||
387 | | OContactAccess::DateMonth | ||
388 | | OContactAccess::DateDay | ||
389 | ); | ||
390 | } | ||
391 | |||
392 | bool OContactAccessBackend_XML::hasQuerySettings (uint querySettings) const | ||
393 | { | ||
394 | /* OContactAccess::IgnoreCase, DateDiff, DateYear, DateMonth, DateDay | ||
395 | * may be added with any of the other settings. IgnoreCase should never used alone. | ||
396 | * Wildcards, RegExp, ExactMatch should never used at the same time... | ||
397 | */ | ||
398 | |||
399 | if ( querySettings == OContactAccess::IgnoreCase ) | ||
400 | return false; | ||
401 | |||
402 | switch ( querySettings & ~( OContactAccess::IgnoreCase | ||
403 | | OContactAccess::DateDiff | ||
404 | | OContactAccess::DateYear | ||
405 | | OContactAccess::DateMonth | ||
406 | | OContactAccess::DateDay | ||
407 | ) | ||
408 | ){ | ||
409 | case OContactAccess::RegExp: | ||
410 | return ( true ); | ||
411 | case OContactAccess::WildCards: | ||
412 | return ( true ); | ||
413 | case OContactAccess::ExactMatch: | ||
414 | return ( true ); | ||
415 | default: | ||
416 | return ( false ); | ||
417 | } | ||
418 | } | ||
419 | |||
420 | // Currently only asc implemented.. | ||
421 | QArray<int> OContactAccessBackend_XML::sorted( bool asc, int , int , int ) | ||
422 | { | ||
423 | QMap<QString, int> nameToUid; | ||
424 | QStringList names; | ||
425 | QArray<int> m_currentQuery( m_contactList.count() ); | ||
426 | |||
427 | // First fill map and StringList with all Names | ||
428 | // Afterwards sort namelist and use map to fill array to return.. | ||
429 | QValueListConstIterator<OContact> it; | ||
430 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
431 | names.append( (*it).fileAs() + QString::number( (*it).uid() ) ); | ||
432 | nameToUid.insert( (*it).fileAs() + QString::number( (*it).uid() ), (*it).uid() ); | ||
433 | } | ||
434 | names.sort(); | ||
435 | |||
436 | int i = 0; | ||
437 | if ( asc ){ | ||
438 | for ( QStringList::Iterator it = names.begin(); it != names.end(); ++it ) | ||
439 | m_currentQuery[i++] = nameToUid[ (*it) ]; | ||
440 | }else{ | ||
441 | for ( QStringList::Iterator it = names.end(); it != names.begin(); --it ) | ||
442 | m_currentQuery[i++] = nameToUid[ (*it) ]; | ||
443 | } | ||
444 | |||
445 | return m_currentQuery; | ||
446 | |||
447 | } | ||
448 | |||
449 | bool OContactAccessBackend_XML::add ( const OContact &newcontact ) | ||
450 | { | ||
451 | //qWarning("odefaultbackend: ACTION::ADD"); | ||
452 | updateJournal (newcontact, ACTION_ADD); | ||
453 | addContact_p( newcontact ); | ||
454 | |||
455 | m_changed = true; | ||
456 | |||
457 | return true; | ||
458 | } | ||
459 | |||
460 | bool OContactAccessBackend_XML::replace ( const OContact &contact ) | ||
461 | { | ||
462 | m_changed = true; | ||
463 | |||
464 | bool found = false; | ||
465 | |||
466 | QValueListIterator<OContact> it; | ||
467 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
468 | if ( (*it).uid() == contact.uid() ){ | ||
469 | found = true; | ||
470 | break; | ||
471 | } | ||
472 | } | ||
473 | if (found) { | ||
474 | updateJournal (contact, ACTION_REPLACE); | ||
475 | m_contactList.remove (it); | ||
476 | m_contactList.append (contact); | ||
477 | return true; | ||
478 | } else | ||
479 | return false; | ||
480 | } | ||
481 | |||
482 | bool OContactAccessBackend_XML::remove ( int uid ) | ||
483 | { | ||
484 | m_changed = true; | ||
485 | |||
486 | bool found = false; | ||
487 | QValueListIterator<OContact> it; | ||
488 | for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ | ||
489 | if ((*it).uid() == uid){ | ||
490 | found = true; | ||
491 | break; | ||
492 | } | ||
493 | } | ||
494 | if (found) { | ||
495 | updateJournal ( *it, ACTION_REMOVE); | ||
496 | m_contactList.remove (it); | ||
497 | return true; | ||
498 | } else | ||
499 | return false; | ||
500 | } | ||
501 | |||
502 | bool OContactAccessBackend_XML::reload(){ | ||
503 | /* Reload is the same as load in this implementation */ | ||
504 | return ( load() ); | ||
505 | } | ||
506 | |||
507 | void OContactAccessBackend_XML::addContact_p( const OContact &newcontact ) | ||
508 | { | ||
509 | m_contactList.append (newcontact); | ||
510 | } | ||
511 | |||
512 | /* This function loads the xml-database and the journalfile */ | ||
513 | bool OContactAccessBackend_XML::load( const QString filename, bool isJournal ) | ||
514 | { | ||
515 | |||
516 | /* We use the time of the last read to check if the file was | ||
517 | * changed externally. | ||
518 | */ | ||
519 | if ( !isJournal ){ | ||
520 | QFileInfo fi( filename ); | ||
521 | m_readtime = fi.lastModified (); | ||
522 | } | ||
523 | |||
524 | const int JOURNALACTION = Qtopia::Notes + 1; | ||
525 | const int JOURNALROW = JOURNALACTION + 1; | ||
526 | |||
527 | bool foundAction = false; | ||
528 | journal_action action = ACTION_ADD; | ||
529 | int journalKey = 0; | ||
530 | QMap<int, QString> contactMap; | ||
531 | QMap<QString, QString> customMap; | ||
532 | QMap<QString, QString>::Iterator customIt; | ||
533 | QAsciiDict<int> dict( 47 ); | ||
534 | |||
535 | dict.setAutoDelete( TRUE ); | ||
536 | dict.insert( "Uid", new int(Qtopia::AddressUid) ); | ||
537 | dict.insert( "Title", new int(Qtopia::Title) ); | ||
538 | dict.insert( "FirstName", new int(Qtopia::FirstName) ); | ||
539 | dict.insert( "MiddleName", new int(Qtopia::MiddleName) ); | ||
540 | dict.insert( "LastName", new int(Qtopia::LastName) ); | ||
541 | dict.insert( "Suffix", new int(Qtopia::Suffix) ); | ||
542 | dict.insert( "FileAs", new int(Qtopia::FileAs) ); | ||
543 | dict.insert( "Categories", new int(Qtopia::AddressCategory) ); | ||
544 | dict.insert( "DefaultEmail", new int(Qtopia::DefaultEmail) ); | ||
545 | dict.insert( "Emails", new int(Qtopia::Emails) ); | ||
546 | dict.insert( "HomeStreet", new int(Qtopia::HomeStreet) ); | ||
547 | dict.insert( "HomeCity", new int(Qtopia::HomeCity) ); | ||
548 | dict.insert( "HomeState", new int(Qtopia::HomeState) ); | ||
549 | dict.insert( "HomeZip", new int(Qtopia::HomeZip) ); | ||
550 | dict.insert( "HomeCountry", new int(Qtopia::HomeCountry) ); | ||
551 | dict.insert( "HomePhone", new int(Qtopia::HomePhone) ); | ||
552 | dict.insert( "HomeFax", new int(Qtopia::HomeFax) ); | ||
553 | dict.insert( "HomeMobile", new int(Qtopia::HomeMobile) ); | ||
554 | dict.insert( "HomeWebPage", new int(Qtopia::HomeWebPage) ); | ||
555 | dict.insert( "Company", new int(Qtopia::Company) ); | ||
556 | dict.insert( "BusinessStreet", new int(Qtopia::BusinessStreet) ); | ||
557 | dict.insert( "BusinessCity", new int(Qtopia::BusinessCity) ); | ||
558 | dict.insert( "BusinessState", new int(Qtopia::BusinessState) ); | ||
559 | dict.insert( "BusinessZip", new int(Qtopia::BusinessZip) ); | ||
560 | dict.insert( "BusinessCountry", new int(Qtopia::BusinessCountry) ); | ||
561 | dict.insert( "BusinessWebPage", new int(Qtopia::BusinessWebPage) ); | ||
562 | dict.insert( "JobTitle", new int(Qtopia::JobTitle) ); | ||
563 | dict.insert( "Department", new int(Qtopia::Department) ); | ||
564 | dict.insert( "Office", new int(Qtopia::Office) ); | ||
565 | dict.insert( "BusinessPhone", new int(Qtopia::BusinessPhone) ); | ||
566 | dict.insert( "BusinessFax", new int(Qtopia::BusinessFax) ); | ||
567 | dict.insert( "BusinessMobile", new int(Qtopia::BusinessMobile) ); | ||
568 | dict.insert( "BusinessPager", new int(Qtopia::BusinessPager) ); | ||
569 | dict.insert( "Profession", new int(Qtopia::Profession) ); | ||
570 | dict.insert( "Assistant", new int(Qtopia::Assistant) ); | ||
571 | dict.insert( "Manager", new int(Qtopia::Manager) ); | ||
572 | dict.insert( "Spouse", new int(Qtopia::Spouse) ); | ||
573 | dict.insert( "Children", new int(Qtopia::Children) ); | ||
574 | dict.insert( "Gender", new int(Qtopia::Gender) ); | ||
575 | dict.insert( "Birthday", new int(Qtopia::Birthday) ); | ||
576 | dict.insert( "Anniversary", new int(Qtopia::Anniversary) ); | ||
577 | dict.insert( "Nickname", new int(Qtopia::Nickname) ); | ||
578 | dict.insert( "Notes", new int(Qtopia::Notes) ); | ||
579 | dict.insert( "action", new int(JOURNALACTION) ); | ||
580 | dict.insert( "actionrow", new int(JOURNALROW) ); | ||
581 | |||
582 | //qWarning( "OContactDefaultBackEnd::loading %s", filename.latin1() ); | ||
583 | |||
584 | XMLElement *root = XMLElement::load( filename ); | ||
585 | if(root != 0l ){ // start parsing | ||
586 | /* Parse all XML-Elements and put the data into the | ||
587 | * Contact-Class | ||
588 | */ | ||
589 | XMLElement *element = root->firstChild(); | ||
590 | //qWarning("OContactAccess::load tagName(): %s", root->tagName().latin1() ); | ||
591 | element = element->firstChild(); | ||
592 | |||
593 | /* Search Tag "Contacts" which is the parent of all Contacts */ | ||
594 | while( element && !isJournal ){ | ||
595 | if( element->tagName() != QString::fromLatin1("Contacts") ){ | ||
596 | //qWarning ("OContactDefBack::Searching for Tag \"Contacts\"! Found: %s", | ||
597 | // element->tagName().latin1()); | ||
598 | element = element->nextChild(); | ||
599 | } else { | ||
600 | element = element->firstChild(); | ||
601 | break; | ||
602 | } | ||
603 | } | ||
604 | /* Parse all Contacts and ignore unknown tags */ | ||
605 | while( element ){ | ||
606 | if( element->tagName() != QString::fromLatin1("Contact") ){ | ||
607 | //qWarning ("OContactDefBack::Searching for Tag \"Contact\"! Found: %s", | ||
608 | // element->tagName().latin1()); | ||
609 | element = element->nextChild(); | ||
610 | continue; | ||
611 | } | ||
612 | /* Found alement with tagname "contact", now parse and store all | ||
613 | * attributes contained | ||
614 | */ | ||
615 | //qWarning("OContactDefBack::load element tagName() : %s", | ||
616 | // element->tagName().latin1() ); | ||
617 | QString dummy; | ||
618 | foundAction = false; | ||
619 | |||
620 | XMLElement::AttributeMap aMap = element->attributes(); | ||
621 | XMLElement::AttributeMap::Iterator it; | ||
622 | contactMap.clear(); | ||
623 | customMap.clear(); | ||
624 | for( it = aMap.begin(); it != aMap.end(); ++it ){ | ||
625 | // qWarning ("Read Attribute: %s=%s", it.key().latin1(),it.data().latin1()); | ||
626 | |||
627 | int *find = dict[ it.key() ]; | ||
628 | /* Unknown attributes will be stored as "Custom" elements */ | ||
629 | if ( !find ) { | ||
630 | qWarning("Attribute %s not known.", it.key().latin1()); | ||
631 | //contact.setCustomField(it.key(), it.data()); | ||
632 | customMap.insert( it.key(), it.data() ); | ||
633 | continue; | ||
634 | } | ||
635 | |||
636 | /* Check if special conversion is needed and add attribute | ||
637 | * into Contact class | ||
638 | */ | ||
639 | switch( *find ) { | ||
640 | /* | ||
641 | case Qtopia::AddressUid: | ||
642 | contact.setUid( it.data().toInt() ); | ||
643 | break; | ||
644 | case Qtopia::AddressCategory: | ||
645 | contact.setCategories( Qtopia::Record::idsFromString( it.data( ))); | ||
646 | break; | ||
647 | */ | ||
648 | case JOURNALACTION: | ||
649 | action = journal_action(it.data().toInt()); | ||
650 | foundAction = true; | ||
651 | qWarning ("ODefBack(journal)::ACTION found: %d", action); | ||
652 | break; | ||
653 | case JOURNALROW: | ||
654 | journalKey = it.data().toInt(); | ||
655 | break; | ||
656 | default: // no conversion needed add them to the map | ||
657 | contactMap.insert( *find, it.data() ); | ||
658 | break; | ||
659 | } | ||
660 | } | ||
661 | /* now generate the Contact contact */ | ||
662 | OContact contact( contactMap ); | ||
663 | |||
664 | for (customIt = customMap.begin(); customIt != customMap.end(); ++customIt ) { | ||
665 | contact.setCustomField( customIt.key(), customIt.data() ); | ||
666 | } | ||
667 | |||
668 | if (foundAction){ | ||
669 | foundAction = false; | ||
670 | switch ( action ) { | ||
671 | case ACTION_ADD: | ||
672 | addContact_p (contact); | ||
673 | break; | ||
674 | case ACTION_REMOVE: | ||
675 | if ( !remove (contact.uid()) ) | ||
676 | qWarning ("ODefBack(journal)::Unable to remove uid: %d", | ||
677 | contact.uid() ); | ||
678 | break; | ||
679 | case ACTION_REPLACE: | ||
680 | if ( !replace ( contact ) ) | ||
681 | qWarning ("ODefBack(journal)::Unable to replace uid: %d", | ||
682 | contact.uid() ); | ||
683 | break; | ||
684 | default: | ||
685 | qWarning ("Unknown action: ignored !"); | ||
686 | break; | ||
687 | } | ||
688 | }else{ | ||
689 | /* Add contact to list */ | ||
690 | addContact_p (contact); | ||
691 | } | ||
692 | |||
693 | /* Move to next element */ | ||
694 | element = element->nextChild(); | ||
695 | } | ||
696 | }else { | ||
697 | qWarning("ODefBack::could not load"); | ||
698 | } | ||
699 | delete root; | ||
700 | qWarning("returning from loading" ); | ||
701 | return true; | ||
702 | } | ||
703 | |||
704 | |||
705 | void OContactAccessBackend_XML::updateJournal( const OContact& cnt, | ||
706 | journal_action action ) | ||
707 | { | ||
708 | QFile f( m_journalName ); | ||
709 | bool created = !f.exists(); | ||
710 | if ( !f.open(IO_WriteOnly|IO_Append) ) | ||
711 | return; | ||
712 | |||
713 | QString buf; | ||
714 | QCString str; | ||
715 | |||
716 | // if the file was created, we have to set the Tag "<CONTACTS>" to | ||
717 | // get a XML-File which is readable by our parser. | ||
718 | // This is just a cheat, but better than rewrite the parser. | ||
719 | if ( created ){ | ||
720 | buf = "<Contacts>"; | ||
721 | QCString cstr = buf.utf8(); | ||
722 | f.writeBlock( cstr.data(), cstr.length() ); | ||
723 | } | ||
724 | |||
725 | buf = "<Contact "; | ||
726 | cnt.save( buf ); | ||
727 | buf += " action=\"" + QString::number( (int)action ) + "\" "; | ||
728 | buf += "/>\n"; | ||
729 | QCString cstr = buf.utf8(); | ||
730 | f.writeBlock( cstr.data(), cstr.length() ); | ||
731 | } | ||
732 | |||
733 | void OContactAccessBackend_XML::removeJournal() | ||
734 | { | ||
735 | QFile f ( m_journalName ); | ||
736 | if ( f.exists() ) | ||
737 | f.remove(); | ||
738 | } | ||
739 | |||