author | eilers <eilers> | 2003-02-09 15:05:01 (UTC) |
---|---|---|
committer | eilers <eilers> | 2003-02-09 15:05:01 (UTC) |
commit | a0eb099d4d39e61a4d3016f3b609cad732c07248 (patch) (side-by-side diff) | |
tree | 97241be48adf6e4fe781ec8a31d04b783690e5ad | |
parent | 0179e7c5799c845cb55f0e2095d1215658ad9062 (diff) | |
download | opie-a0eb099d4d39e61a4d3016f3b609cad732c07248.zip opie-a0eb099d4d39e61a4d3016f3b609cad732c07248.tar.gz opie-a0eb099d4d39e61a4d3016f3b609cad732c07248.tar.bz2 |
Nothing happened.. Just some cleanup before I will start..
-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 @@ +/* + * 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.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 ( QString appname, QString filename = 0l ): + m_changed( 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; + QString out; + out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE Addressbook ><AddressBook>\n" + " <Groups>\n" + " </Groups>\n" + " <Contacts>\n"; + //QValueList<Contact>::iterator it; + QValueListConstIterator<OContact> it; + for ( it = m_contactList.begin(); it != m_contactList.end(); ++it ) { + out += "<Contact "; + (*it).save( out ); + out += "/>\n"; + QCString cstr = out.utf8(); + total_written = f.writeBlock( cstr.data(), cstr.length() ); + if ( total_written != int(cstr.length()) ) { + f.close(); + QFile::remove( strNewFile ); + return false; + } + out = ""; + } + out += " </Contacts>\n</AddressBook>\n"; + + QCString 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(); + + /* 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_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; + QValueListConstIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + uid_list[counter++] = (*it).uid(); + } + + return ( uid_list ); +} + +OContact OContactAccessBackend_XML::find ( int uid ) const +{ + bool found = false; + OContact foundContact; //Create empty contact + + QValueListConstIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + if ((*it).uid() == uid){ + found = true; + break; + } + } + if ( found ){ + foundContact = *it; + } + + return ( foundContact ); +} + +QArray<int> OContactAccessBackend_XML::queryByExample ( const OContact &query, int settings ) +{ + + QArray<int> m_currentQuery( m_contactList.count() ); + QValueListConstIterator<OContact> it; + uint arraycounter = 0; + + for( it = m_contactList.begin(); it != m_contactList.end(); ++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 = QDate::currentDate(); + // 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() ); + 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. Therfore 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() ); + QValueListConstIterator<OContact> it; + uint arraycounter = 0; + + for( it = m_contactList.begin(); it != m_contactList.end(); ++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... + */ + + if ( querySettings == OContactAccess::IgnoreCase ) + return false; + + 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 ); + 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.. + QValueListConstIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++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; + + bool found = false; + + QValueListIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + if ( (*it).uid() == contact.uid() ){ + found = true; + break; + } + } + if (found) { + updateJournal (contact, ACTION_REPLACE); + m_contactList.remove (it); + m_contactList.append (contact); + return true; + } else + return false; +} + +bool OContactAccessBackend_XML::remove ( int uid ) +{ + m_changed = true; + + bool found = false; + QValueListIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + if ((*it).uid() == uid){ + found = true; + break; + } + } + if (found) { + updateJournal ( *it, ACTION_REMOVE); + m_contactList.remove (it); + 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 ) +{ + m_contactList.append (newcontact); +} + +/* 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: + addContact_p (contact); + break; + case ACTION_REMOVE: + if ( !remove (contact.uid()) ) + qWarning ("ODefBack(journal)::Unable to remove uid: %d", + contact.uid() ); + break; + case ACTION_REPLACE: + if ( !replace ( contact ) ) + qWarning ("ODefBack(journal)::Unable to replace uid: %d", + contact.uid() ); + break; + default: + qWarning ("Unknown action: ignored !"); + break; + } + }else{ + /* Add contact to list */ + addContact_p (contact); + } + + /* Move to next element */ + element = element->nextChild(); + } + }else { + qWarning("ODefBack::could not load"); + } + delete root; + qWarning("returning from loading" ); + return true; +} + + +void OContactAccessBackend_XML::updateJournal( const OContact& cnt, + journal_action action ) +{ + QFile f( m_journalName ); + bool created = !f.exists(); + if ( !f.open(IO_WriteOnly|IO_Append) ) + return; + + QString buf; + QCString str; + + // if the file was created, we have to set the Tag "<CONTACTS>" to + // get a XML-File which is readable by our parser. + // This is just a cheat, but better than rewrite the parser. + if ( created ){ + buf = "<Contacts>"; + QCString cstr = buf.utf8(); + f.writeBlock( cstr.data(), cstr.length() ); + } + + buf = "<Contact "; + cnt.save( buf ); + buf += " action=\"" + QString::number( (int)action ) + "\" "; + buf += "/>\n"; + QCString cstr = buf.utf8(); + f.writeBlock( cstr.data(), cstr.length() ); +} + +void OContactAccessBackend_XML::removeJournal() +{ + QFile f ( m_journalName ); + if ( f.exists() ) + f.remove(); +} + 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 @@ +/* + * 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.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 ( QString appname, QString filename = 0l ): + m_changed( 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; + QString out; + out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE Addressbook ><AddressBook>\n" + " <Groups>\n" + " </Groups>\n" + " <Contacts>\n"; + //QValueList<Contact>::iterator it; + QValueListConstIterator<OContact> it; + for ( it = m_contactList.begin(); it != m_contactList.end(); ++it ) { + out += "<Contact "; + (*it).save( out ); + out += "/>\n"; + QCString cstr = out.utf8(); + total_written = f.writeBlock( cstr.data(), cstr.length() ); + if ( total_written != int(cstr.length()) ) { + f.close(); + QFile::remove( strNewFile ); + return false; + } + out = ""; + } + out += " </Contacts>\n</AddressBook>\n"; + + QCString 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(); + + /* 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_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; + QValueListConstIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + uid_list[counter++] = (*it).uid(); + } + + return ( uid_list ); +} + +OContact OContactAccessBackend_XML::find ( int uid ) const +{ + bool found = false; + OContact foundContact; //Create empty contact + + QValueListConstIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + if ((*it).uid() == uid){ + found = true; + break; + } + } + if ( found ){ + foundContact = *it; + } + + return ( foundContact ); +} + +QArray<int> OContactAccessBackend_XML::queryByExample ( const OContact &query, int settings ) +{ + + QArray<int> m_currentQuery( m_contactList.count() ); + QValueListConstIterator<OContact> it; + uint arraycounter = 0; + + for( it = m_contactList.begin(); it != m_contactList.end(); ++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 = QDate::currentDate(); + // 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() ); + 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. Therfore 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() ); + QValueListConstIterator<OContact> it; + uint arraycounter = 0; + + for( it = m_contactList.begin(); it != m_contactList.end(); ++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... + */ + + if ( querySettings == OContactAccess::IgnoreCase ) + return false; + + 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 ); + 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.. + QValueListConstIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++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; + + bool found = false; + + QValueListIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + if ( (*it).uid() == contact.uid() ){ + found = true; + break; + } + } + if (found) { + updateJournal (contact, ACTION_REPLACE); + m_contactList.remove (it); + m_contactList.append (contact); + return true; + } else + return false; +} + +bool OContactAccessBackend_XML::remove ( int uid ) +{ + m_changed = true; + + bool found = false; + QValueListIterator<OContact> it; + for( it = m_contactList.begin(); it != m_contactList.end(); ++it ){ + if ((*it).uid() == uid){ + found = true; + break; + } + } + if (found) { + updateJournal ( *it, ACTION_REMOVE); + m_contactList.remove (it); + 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 ) +{ + m_contactList.append (newcontact); +} + +/* 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: + addContact_p (contact); + break; + case ACTION_REMOVE: + if ( !remove (contact.uid()) ) + qWarning ("ODefBack(journal)::Unable to remove uid: %d", + contact.uid() ); + break; + case ACTION_REPLACE: + if ( !replace ( contact ) ) + qWarning ("ODefBack(journal)::Unable to replace uid: %d", + contact.uid() ); + break; + default: + qWarning ("Unknown action: ignored !"); + break; + } + }else{ + /* Add contact to list */ + addContact_p (contact); + } + + /* Move to next element */ + element = element->nextChild(); + } + }else { + qWarning("ODefBack::could not load"); + } + delete root; + qWarning("returning from loading" ); + return true; +} + + +void OContactAccessBackend_XML::updateJournal( const OContact& cnt, + journal_action action ) +{ + QFile f( m_journalName ); + bool created = !f.exists(); + if ( !f.open(IO_WriteOnly|IO_Append) ) + return; + + QString buf; + QCString str; + + // if the file was created, we have to set the Tag "<CONTACTS>" to + // get a XML-File which is readable by our parser. + // This is just a cheat, but better than rewrite the parser. + if ( created ){ + buf = "<Contacts>"; + QCString cstr = buf.utf8(); + f.writeBlock( cstr.data(), cstr.length() ); + } + + buf = "<Contact "; + cnt.save( buf ); + buf += " action=\"" + QString::number( (int)action ) + "\" "; + buf += "/>\n"; + QCString cstr = buf.utf8(); + f.writeBlock( cstr.data(), cstr.length() ); +} + +void OContactAccessBackend_XML::removeJournal() +{ + QFile f ( m_journalName ); + if ( f.exists() ) + f.remove(); +} + |