summaryrefslogtreecommitdiff
path: root/libopie/pim
Side-by-side diff
Diffstat (limited to 'libopie/pim') (more/less context) (show whitespace changes)
-rw-r--r--libopie/pim/ocontactaccessbackend_xml.cpp22
1 files changed, 22 insertions, 0 deletions
diff --git a/libopie/pim/ocontactaccessbackend_xml.cpp b/libopie/pim/ocontactaccessbackend_xml.cpp
index 661cd51..097142b 100644
--- a/libopie/pim/ocontactaccessbackend_xml.cpp
+++ b/libopie/pim/ocontactaccessbackend_xml.cpp
@@ -1,721 +1,743 @@
/*
* XML Backend for the OPIE-Contact Database.
*
* Copyright (c) 2002 by Stefan Eilers (Eilers.Stefan@epost.de)
*
* =====================================================================
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
* =====================================================================
* ToDo: XML-Backend: Automatic reload if something was changed...
*
*
* =====================================================================
* Version: $Id$
* =====================================================================
* History:
* $Log$
+ * Revision 1.6 2003/07/07 16:19:47 eilers
+ * Fixing serious bug in hasQuerySettings()
+ *
* Revision 1.5 2003/04/13 18:07:10 zecke
* More API doc
* QString -> const QString&
* QString = 0l -> QString::null
*
* Revision 1.4 2003/03/21 14:32:54 mickeyl
* g++ compliance fix: default arguments belong into the declaration, but not the definition
*
* Revision 1.3 2003/03/21 12:26:28 eilers
* Fixing small bug: If we search a birthday from today to today, it returned
* every contact ..
*
* Revision 1.2 2003/03/21 10:33:09 eilers
* Merged speed optimized xml backend for contacts to main.
* Added QDateTime to querybyexample. For instance, it is now possible to get
* all Birthdays/Anniversaries between two dates. This should be used
* to show all birthdays in the datebook..
* This change is sourcecode backward compatible but you have to upgrade
* the binaries for today-addressbook.
*
* Revision 1.1.2.2 2003/02/11 12:17:28 eilers
* Speed optimization. Removed the sequential search loops.
*
* Revision 1.1.2.1 2003/02/10 15:31:38 eilers
* Writing offsets to debug output..
*
* Revision 1.1 2003/02/09 15:05:01 eilers
* Nothing happened.. Just some cleanup before I will start..
*
* Revision 1.12 2003/01/03 16:58:03 eilers
* Reenable debug output
*
* Revision 1.11 2003/01/03 12:31:28 eilers
* Bugfix for calculating data diffs..
*
* Revision 1.10 2003/01/02 14:27:12 eilers
* Improved query by example: Search by date is possible.. First step
* for a today plugin for birthdays..
*
* Revision 1.9 2002/12/08 12:48:57 eilers
* Moved journal-enum from ocontact into i the xml-backend..
*
* Revision 1.8 2002/11/14 17:04:24 eilers
* Sorting will now work if fullname is identical on some entries
*
* Revision 1.7 2002/11/13 15:02:46 eilers
* Small Bug in sorted fixed
*
* Revision 1.6 2002/11/13 14:14:51 eilers
* Added sorted for Contacts..
*
* Revision 1.5 2002/11/01 15:10:42 eilers
* Added regExp-search in database for all fields in a contact.
*
* Revision 1.4 2002/10/16 10:52:40 eilers
* Added some docu to the interface and now using the cache infrastucture by zecke.. :)
*
* Revision 1.3 2002/10/14 16:21:54 eilers
* Some minor interface updates
*
* Revision 1.2 2002/10/07 17:34:24 eilers
* added OBackendFactory for advanced backend access
*
* Revision 1.1 2002/09/27 17:11:44 eilers
* Added API for accessing the Contact-Database ! It is compiling, but
* please do not expect that anything is working !
* I will debug that stuff in the next time ..
* Please read README_COMPILE for compiling !
*
*
*/
#include "ocontactaccessbackend_xml.h"
#include <qasciidict.h>
#include <qdatetime.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qregexp.h>
#include <qarray.h>
#include <qmap.h>
#include <qdatetime.h>
#include <qpe/global.h>
#include <opie/xmltree.h>
#include "ocontactaccessbackend.h"
#include "ocontactaccess.h"
#include <stdlib.h>
#include <errno.h>
using namespace Opie;
OContactAccessBackend_XML::OContactAccessBackend_XML ( const QString& appname, const QString& filename ):
m_changed( false )
{
// Just m_contactlist should call delete if an entry
// is removed.
m_contactList.setAutoDelete( true );
m_uidToContact.setAutoDelete( false );
m_appName = appname;
/* Set journalfile name ... */
m_journalName = getenv("HOME");
m_journalName +="/.abjournal" + appname;
/* Expecting to access the default filename if nothing else is set */
if ( filename.isEmpty() ){
m_fileName = Global::applicationFileName( "addressbook","addressbook.xml" );
} else
m_fileName = filename;
/* Load Database now */
load ();
}
bool OContactAccessBackend_XML::save()
{
if ( !m_changed )
return true;
QString strNewFile = m_fileName + ".new";
QFile f( strNewFile );
if ( !f.open( IO_WriteOnly|IO_Raw ) )
return false;
int total_written;
int idx_offset = 0;
QString out;
// Write Header
out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE Addressbook ><AddressBook>\n"
" <Groups>\n"
" </Groups>\n"
" <Contacts>\n";
QCString cstr = out.utf8();
f.writeBlock( cstr.data(), cstr.length() );
idx_offset += cstr.length();
out = "";
// Write all contacts
QListIterator<OContact> it( m_contactList );
for ( ; it.current(); ++it ) {
qWarning(" Uid %d at Offset: %x", (*it)->uid(), idx_offset );
out += "<Contact ";
(*it)->save( out );
out += "/>\n";
cstr = out.utf8();
total_written = f.writeBlock( cstr.data(), cstr.length() );
idx_offset += cstr.length();
if ( total_written != int(cstr.length()) ) {
f.close();
QFile::remove( strNewFile );
return false;
}
out = "";
}
out += " </Contacts>\n</AddressBook>\n";
// Write Footer
cstr = out.utf8();
total_written = f.writeBlock( cstr.data(), cstr.length() );
if ( total_written != int( cstr.length() ) ) {
f.close();
QFile::remove( strNewFile );
return false;
}
f.close();
// move the file over, I'm just going to use the system call
// because, I don't feel like using QDir.
if ( ::rename( strNewFile.latin1(), m_fileName.latin1() ) < 0 ) {
qWarning( "problem renaming file %s to %s, errno: %d",
strNewFile.latin1(), m_journalName.latin1(), errno );
// remove the tmp file...
QFile::remove( strNewFile );
}
/* The journalfile should be removed now... */
removeJournal();
m_changed = false;
return true;
}
bool OContactAccessBackend_XML::load ()
{
m_contactList.clear();
m_uidToContact.clear();
/* Load XML-File and journal if it exists */
if ( !load ( m_fileName, false ) )
return false;
/* The returncode of the journalfile is ignored due to the
* fact that it does not exist when this class is instantiated !
* But there may such a file exist, if the application crashed.
* Therefore we try to load it to get the changes before the #
* crash happened...
*/
load (m_journalName, true);
return true;
}
void OContactAccessBackend_XML::clear ()
{
m_contactList.clear();
m_uidToContact.clear();
m_changed = false;
}
bool OContactAccessBackend_XML::wasChangedExternally()
{
QFileInfo fi( m_fileName );
QDateTime lastmod = fi.lastModified ();
return (lastmod != m_readtime);
}
QArray<int> OContactAccessBackend_XML::allRecords() const
{
QArray<int> uid_list( m_contactList.count() );
uint counter = 0;
QListIterator<OContact> it( m_contactList );
for( ; it.current(); ++it ){
uid_list[counter++] = (*it)->uid();
}
return ( uid_list );
}
OContact OContactAccessBackend_XML::find ( int uid ) const
{
OContact foundContact; //Create empty contact
OContact* found = m_uidToContact.find( QString().setNum( uid ) );
if ( found ){
foundContact = *found;
}
return ( foundContact );
}
QArray<int> OContactAccessBackend_XML::queryByExample ( const OContact &query, int settings,
const QDateTime& d )
{
QArray<int> m_currentQuery( m_contactList.count() );
QListIterator<OContact> it( m_contactList );
uint arraycounter = 0;
for( ; it.current(); ++it ){
/* Search all fields and compare them with query object. Store them into list
* if all fields matches.
*/
QDate* queryDate = 0l;
QDate* checkDate = 0l;
bool allcorrect = true;
for ( int i = 0; i < Qtopia::Groups; i++ ) {
// Birthday and anniversary are special nonstring fields and should
// be handled specially
switch ( i ){
case Qtopia::Birthday:
queryDate = new QDate( query.birthday() );
checkDate = new QDate( (*it)->birthday() );
case Qtopia::Anniversary:
if ( queryDate == 0l ){
queryDate = new QDate( query.anniversary() );
checkDate = new QDate( (*it)->anniversary() );
}
if ( queryDate->isValid() ){
if( checkDate->isValid() ){
if ( settings & OContactAccess::DateYear ){
if ( queryDate->year() != checkDate->year() )
allcorrect = false;
}
if ( settings & OContactAccess::DateMonth ){
if ( queryDate->month() != checkDate->month() )
allcorrect = false;
}
if ( settings & OContactAccess::DateDay ){
if ( queryDate->day() != checkDate->day() )
allcorrect = false;
}
if ( settings & OContactAccess::DateDiff ) {
QDate current;
// If we get an additional date, we
// will take this date instead of
// the current one..
if ( !d.date().isValid() )
current = QDate::currentDate();
else
current = d.date();
// We have to equalize the year, otherwise
// the search will fail..
checkDate->setYMD( current.year(),
checkDate->month(),
checkDate->day() );
if ( *checkDate < current )
checkDate->setYMD( current.year()+1,
checkDate->month(),
checkDate->day() );
// Check whether the birthday/anniversary date is between
// the current/given date and the maximum date
// ( maximum time range ) !
qWarning("Checking if %s is between %s and %s ! ",
checkDate->toString().latin1(),
current.toString().latin1(),
queryDate->toString().latin1() );
if ( current.daysTo( *queryDate ) >= 0 ){
if ( !( ( *checkDate >= current ) &&
( *checkDate <= *queryDate ) ) ){
allcorrect = false;
qWarning (" Nope!..");
}
}
}
} else{
// checkDate is invalid. Therefore this entry is always rejected
allcorrect = false;
}
}
delete queryDate;
queryDate = 0l;
delete checkDate;
checkDate = 0l;
break;
default:
/* Just compare fields which are not empty in the query object */
if ( !query.field(i).isEmpty() ){
switch ( settings & ~( OContactAccess::IgnoreCase
| OContactAccess::DateDiff
| OContactAccess::DateYear
| OContactAccess::DateMonth
| OContactAccess::DateDay
| OContactAccess::MatchOne
) ){
case OContactAccess::RegExp:{
QRegExp expr ( query.field(i),
!(settings & OContactAccess::IgnoreCase),
false );
if ( expr.find ( (*it)->field(i), 0 ) == -1 )
allcorrect = false;
}
break;
case OContactAccess::WildCards:{
QRegExp expr ( query.field(i),
!(settings & OContactAccess::IgnoreCase),
true );
if ( expr.find ( (*it)->field(i), 0 ) == -1 )
allcorrect = false;
}
break;
case OContactAccess::ExactMatch:{
if (settings & OContactAccess::IgnoreCase){
if ( query.field(i).upper() !=
(*it)->field(i).upper() )
allcorrect = false;
}else{
if ( query.field(i) != (*it)->field(i) )
allcorrect = false;
}
}
break;
}
}
}
}
if ( allcorrect ){
m_currentQuery[arraycounter++] = (*it)->uid();
}
}
// Shrink to fit..
m_currentQuery.resize(arraycounter);
return m_currentQuery;
}
QArray<int> OContactAccessBackend_XML::matchRegexp( const QRegExp &r ) const
{
QArray<int> m_currentQuery( m_contactList.count() );
QListIterator<OContact> it( m_contactList );
uint arraycounter = 0;
for( ; it.current(); ++it ){
if ( (*it)->match( r ) ){
m_currentQuery[arraycounter++] = (*it)->uid();
}
}
// Shrink to fit..
m_currentQuery.resize(arraycounter);
return m_currentQuery;
}
const uint OContactAccessBackend_XML::querySettings()
{
return ( OContactAccess::WildCards
| OContactAccess::IgnoreCase
| OContactAccess::RegExp
| OContactAccess::ExactMatch
| OContactAccess::DateDiff
| OContactAccess::DateYear
| OContactAccess::DateMonth
| OContactAccess::DateDay
);
}
bool OContactAccessBackend_XML::hasQuerySettings (uint querySettings) const
{
/* OContactAccess::IgnoreCase, DateDiff, DateYear, DateMonth, DateDay
* may be added with any of the other settings. IgnoreCase should never used alone.
* Wildcards, RegExp, ExactMatch should never used at the same time...
*/
+ // Step 1: Check whether the given settings are supported by this backend
+ if ( ( querySettings & (
+ OContactAccess::IgnoreCase
+ | OContactAccess::WildCards
+ | OContactAccess::DateDiff
+ | OContactAccess::DateYear
+ | OContactAccess::DateMonth
+ | OContactAccess::DateDay
+ | OContactAccess::RegExp
+ | OContactAccess::ExactMatch
+ ) ) != querySettings )
+ return false;
+
+ // Step 2: Check whether the given combinations are ok..
+
+ // IngoreCase alone is invalid
if ( querySettings == OContactAccess::IgnoreCase )
return false;
+ // WildCards, RegExp and ExactMatch should never used at the same time
switch ( querySettings & ~( OContactAccess::IgnoreCase
| OContactAccess::DateDiff
| OContactAccess::DateYear
| OContactAccess::DateMonth
| OContactAccess::DateDay
)
){
case OContactAccess::RegExp:
return ( true );
case OContactAccess::WildCards:
return ( true );
case OContactAccess::ExactMatch:
return ( true );
+ case 0: // one of the upper removed bits were set..
+ return ( true );
default:
return ( false );
}
}
// Currently only asc implemented..
QArray<int> OContactAccessBackend_XML::sorted( bool asc, int , int , int )
{
QMap<QString, int> nameToUid;
QStringList names;
QArray<int> m_currentQuery( m_contactList.count() );
// First fill map and StringList with all Names
// Afterwards sort namelist and use map to fill array to return..
QListIterator<OContact> it( m_contactList );
for( ; it.current(); ++it ){
names.append( (*it)->fileAs() + QString::number( (*it)->uid() ) );
nameToUid.insert( (*it)->fileAs() + QString::number( (*it)->uid() ), (*it)->uid() );
}
names.sort();
int i = 0;
if ( asc ){
for ( QStringList::Iterator it = names.begin(); it != names.end(); ++it )
m_currentQuery[i++] = nameToUid[ (*it) ];
}else{
for ( QStringList::Iterator it = names.end(); it != names.begin(); --it )
m_currentQuery[i++] = nameToUid[ (*it) ];
}
return m_currentQuery;
}
bool OContactAccessBackend_XML::add ( const OContact &newcontact )
{
//qWarning("odefaultbackend: ACTION::ADD");
updateJournal (newcontact, ACTION_ADD);
addContact_p( newcontact );
m_changed = true;
return true;
}
bool OContactAccessBackend_XML::replace ( const OContact &contact )
{
m_changed = true;
OContact* found = m_uidToContact.find ( QString().setNum( contact.uid() ) );
if ( found ) {
OContact* newCont = new OContact( contact );
updateJournal ( *newCont, ACTION_REPLACE);
m_contactList.removeRef ( found );
m_contactList.append ( newCont );
m_uidToContact.remove( QString().setNum( contact.uid() ) );
m_uidToContact.insert( QString().setNum( newCont->uid() ), newCont );
qWarning("Nur zur Sicherheit: %d == %d ?",contact.uid(), newCont->uid());
return true;
} else
return false;
}
bool OContactAccessBackend_XML::remove ( int uid )
{
m_changed = true;
OContact* found = m_uidToContact.find ( QString().setNum( uid ) );
if ( found ) {
updateJournal ( *found, ACTION_REMOVE);
m_contactList.removeRef ( found );
m_uidToContact.remove( QString().setNum( uid ) );
return true;
} else
return false;
}
bool OContactAccessBackend_XML::reload(){
/* Reload is the same as load in this implementation */
return ( load() );
}
void OContactAccessBackend_XML::addContact_p( const OContact &newcontact )
{
OContact* contRef = new OContact( newcontact );
m_contactList.append ( contRef );
m_uidToContact.insert( QString().setNum( newcontact.uid() ), contRef );
}
/* This function loads the xml-database and the journalfile */
bool OContactAccessBackend_XML::load( const QString filename, bool isJournal )
{
/* We use the time of the last read to check if the file was
* changed externally.
*/
if ( !isJournal ){
QFileInfo fi( filename );
m_readtime = fi.lastModified ();
}
const int JOURNALACTION = Qtopia::Notes + 1;
const int JOURNALROW = JOURNALACTION + 1;
bool foundAction = false;
journal_action action = ACTION_ADD;
int journalKey = 0;
QMap<int, QString> contactMap;
QMap<QString, QString> customMap;
QMap<QString, QString>::Iterator customIt;
QAsciiDict<int> dict( 47 );
dict.setAutoDelete( TRUE );
dict.insert( "Uid", new int(Qtopia::AddressUid) );
dict.insert( "Title", new int(Qtopia::Title) );
dict.insert( "FirstName", new int(Qtopia::FirstName) );
dict.insert( "MiddleName", new int(Qtopia::MiddleName) );
dict.insert( "LastName", new int(Qtopia::LastName) );
dict.insert( "Suffix", new int(Qtopia::Suffix) );
dict.insert( "FileAs", new int(Qtopia::FileAs) );
dict.insert( "Categories", new int(Qtopia::AddressCategory) );
dict.insert( "DefaultEmail", new int(Qtopia::DefaultEmail) );
dict.insert( "Emails", new int(Qtopia::Emails) );
dict.insert( "HomeStreet", new int(Qtopia::HomeStreet) );
dict.insert( "HomeCity", new int(Qtopia::HomeCity) );
dict.insert( "HomeState", new int(Qtopia::HomeState) );
dict.insert( "HomeZip", new int(Qtopia::HomeZip) );
dict.insert( "HomeCountry", new int(Qtopia::HomeCountry) );
dict.insert( "HomePhone", new int(Qtopia::HomePhone) );
dict.insert( "HomeFax", new int(Qtopia::HomeFax) );
dict.insert( "HomeMobile", new int(Qtopia::HomeMobile) );
dict.insert( "HomeWebPage", new int(Qtopia::HomeWebPage) );
dict.insert( "Company", new int(Qtopia::Company) );
dict.insert( "BusinessStreet", new int(Qtopia::BusinessStreet) );
dict.insert( "BusinessCity", new int(Qtopia::BusinessCity) );
dict.insert( "BusinessState", new int(Qtopia::BusinessState) );
dict.insert( "BusinessZip", new int(Qtopia::BusinessZip) );
dict.insert( "BusinessCountry", new int(Qtopia::BusinessCountry) );
dict.insert( "BusinessWebPage", new int(Qtopia::BusinessWebPage) );
dict.insert( "JobTitle", new int(Qtopia::JobTitle) );
dict.insert( "Department", new int(Qtopia::Department) );
dict.insert( "Office", new int(Qtopia::Office) );
dict.insert( "BusinessPhone", new int(Qtopia::BusinessPhone) );
dict.insert( "BusinessFax", new int(Qtopia::BusinessFax) );
dict.insert( "BusinessMobile", new int(Qtopia::BusinessMobile) );
dict.insert( "BusinessPager", new int(Qtopia::BusinessPager) );
dict.insert( "Profession", new int(Qtopia::Profession) );
dict.insert( "Assistant", new int(Qtopia::Assistant) );
dict.insert( "Manager", new int(Qtopia::Manager) );
dict.insert( "Spouse", new int(Qtopia::Spouse) );
dict.insert( "Children", new int(Qtopia::Children) );
dict.insert( "Gender", new int(Qtopia::Gender) );
dict.insert( "Birthday", new int(Qtopia::Birthday) );
dict.insert( "Anniversary", new int(Qtopia::Anniversary) );
dict.insert( "Nickname", new int(Qtopia::Nickname) );
dict.insert( "Notes", new int(Qtopia::Notes) );
dict.insert( "action", new int(JOURNALACTION) );
dict.insert( "actionrow", new int(JOURNALROW) );
//qWarning( "OContactDefaultBackEnd::loading %s", filename.latin1() );
XMLElement *root = XMLElement::load( filename );
if(root != 0l ){ // start parsing
/* Parse all XML-Elements and put the data into the
* Contact-Class
*/
XMLElement *element = root->firstChild();
//qWarning("OContactAccess::load tagName(): %s", root->tagName().latin1() );
element = element->firstChild();
/* Search Tag "Contacts" which is the parent of all Contacts */
while( element && !isJournal ){
if( element->tagName() != QString::fromLatin1("Contacts") ){
//qWarning ("OContactDefBack::Searching for Tag \"Contacts\"! Found: %s",
// element->tagName().latin1());
element = element->nextChild();
} else {
element = element->firstChild();
break;
}
}
/* Parse all Contacts and ignore unknown tags */
while( element ){
if( element->tagName() != QString::fromLatin1("Contact") ){
//qWarning ("OContactDefBack::Searching for Tag \"Contact\"! Found: %s",
// element->tagName().latin1());
element = element->nextChild();
continue;
}
/* Found alement with tagname "contact", now parse and store all
* attributes contained
*/
//qWarning("OContactDefBack::load element tagName() : %s",
// element->tagName().latin1() );
QString dummy;
foundAction = false;
XMLElement::AttributeMap aMap = element->attributes();
XMLElement::AttributeMap::Iterator it;
contactMap.clear();
customMap.clear();
for( it = aMap.begin(); it != aMap.end(); ++it ){
// qWarning ("Read Attribute: %s=%s", it.key().latin1(),it.data().latin1());
int *find = dict[ it.key() ];
/* Unknown attributes will be stored as "Custom" elements */
if ( !find ) {
qWarning("Attribute %s not known.", it.key().latin1());
//contact.setCustomField(it.key(), it.data());
customMap.insert( it.key(), it.data() );
continue;
}
/* Check if special conversion is needed and add attribute
* into Contact class
*/
switch( *find ) {
/*
case Qtopia::AddressUid:
contact.setUid( it.data().toInt() );
break;
case Qtopia::AddressCategory:
contact.setCategories( Qtopia::Record::idsFromString( it.data( )));
break;
*/
case JOURNALACTION:
action = journal_action(it.data().toInt());
foundAction = true;
qWarning ("ODefBack(journal)::ACTION found: %d", action);
break;
case JOURNALROW:
journalKey = it.data().toInt();
break;
default: // no conversion needed add them to the map
contactMap.insert( *find, it.data() );
break;
}
}
/* now generate the Contact contact */
OContact contact( contactMap );
for (customIt = customMap.begin(); customIt != customMap.end(); ++customIt ) {
contact.setCustomField( customIt.key(), customIt.data() );
}
if (foundAction){
foundAction = false;
switch ( action ) {
case ACTION_ADD: