Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

ocontactaccessbackend_xml.cpp

Go to the documentation of this file.
00001 /*
00002                              This file is part of the Opie Project
00003                              Copyright (C) Stefan Eilers (Eilers.Stefan@epost.de)
00004               =.             Copyright (C) The Opie Team <opie-devel@handhelds.org>
00005             .=l.
00006            .>+-=
00007  _;:,     .>    :=|.         This program is free software; you can
00008 .> <`_,   >  .   <=          redistribute it and/or  modify it under
00009 :`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
00010 .="- .-=="i,     .._         License as published by the Free Software
00011  - .   .-<_>     .<>         Foundation; either version 2 of the License,
00012      ._= =}       :          or (at your option) any later version.
00013     .%`+i>       _;_.
00014     .i_,=:_.      -<s.       This program is distributed in the hope that
00015      +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
00016     : ..    .:,     . . .    without even the implied warranty of
00017     =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
00018   _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
00019 ..}^=.=       =       ;      Library General Public License for more
00020 ++=   -.     .`     .:       details.
00021  :     =  ...= . :.=-
00022  -.   .:....=;==+<;          You should have received a copy of the GNU
00023   -_. . .   )=.  =           Library General Public License along with
00024     --        :-=`           this library; see the file COPYING.LIB.
00025                              If not, write to the Free Software Foundation,
00026                              Inc., 59 Temple Place - Suite 330,
00027                              Boston, MA 02111-1307, USA.
00028 */
00029 /*
00030  * XML Backend for the OPIE-Contact Database.
00031  */
00032 
00033 
00034 /* OPIE */
00035 #include <opie2/ocontactaccessbackend_xml.h>
00036 #include <opie2/xmltree.h>
00037 #include <opie2/ocontactaccessbackend.h>
00038 #include <opie2/ocontactaccess.h>
00039 #include <opie2/odebug.h>
00040 
00041 #include <qpe/global.h>
00042 
00043 /* QT */
00044 #include <qasciidict.h>
00045 #include <qfile.h>
00046 #include <qfileinfo.h>
00047 #include <qregexp.h>
00048 #include <qarray.h>
00049 #include <qmap.h>
00050 
00051 /* STD */
00052 #include <stdlib.h>
00053 #include <errno.h>
00054 
00055 using namespace Opie::Core;
00056 
00057 
00058 namespace Opie {
00059 OPimContactAccessBackend_XML::OPimContactAccessBackend_XML ( const QString& appname, const QString& filename ):
00060     m_changed( false )
00061 {
00062     // Just m_contactlist should call delete if an entry
00063     // is removed.
00064     m_contactList.setAutoDelete( true );
00065     m_uidToContact.setAutoDelete( false );
00066 
00067     m_appName = appname;
00068 
00069     /* Set journalfile name ... */
00070     m_journalName = getenv("HOME");
00071     m_journalName +="/.abjournal" + appname;
00072 
00073     /* Expecting to access the default filename if nothing else is set */
00074     if ( filename.isEmpty() ){
00075         m_fileName = Global::applicationFileName( "addressbook","addressbook.xml" );
00076     } else
00077         m_fileName = filename;
00078 
00079     /* Load Database now */
00080     load ();
00081 }
00082 
00083 bool OPimContactAccessBackend_XML::save()
00084 {
00085 
00086     if ( !m_changed )
00087         return true;
00088 
00089     QString strNewFile = m_fileName + ".new";
00090     QFile f( strNewFile );
00091     if ( !f.open( IO_WriteOnly|IO_Raw ) )
00092         return false;
00093 
00094     int total_written;
00095     int idx_offset = 0;
00096     QString out;
00097 
00098     // Write Header
00099     out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE Addressbook ><AddressBook>\n"
00100         " <Groups>\n"
00101         " </Groups>\n"
00102         " <Contacts>\n";
00103     QCString cstr = out.utf8();
00104     f.writeBlock( cstr.data(), cstr.length() );
00105     idx_offset += cstr.length();
00106     out = "";
00107 
00108     // Write all contacts
00109     QListIterator<OPimContact> it( m_contactList );
00110     for ( ; it.current(); ++it ) {
00111         out += "<Contact ";
00112         (*it)->save( out );
00113         out += "/>\n";
00114         cstr = out.utf8();
00115         total_written = f.writeBlock( cstr.data(), cstr.length() );
00116         idx_offset += cstr.length();
00117         if ( total_written != int(cstr.length()) ) {
00118             f.close();
00119             QFile::remove( strNewFile );
00120             return false;
00121         }
00122         out = "";
00123     }
00124     out += " </Contacts>\n</AddressBook>\n";
00125 
00126     // Write Footer
00127     cstr = out.utf8();
00128     total_written = f.writeBlock( cstr.data(), cstr.length() );
00129     if ( total_written != int( cstr.length() ) ) {
00130         f.close();
00131         QFile::remove( strNewFile );
00132         return false;
00133     }
00134     f.close();
00135 
00136     // move the file over, I'm just going to use the system call
00137     // because, I don't feel like using QDir.
00138     if ( ::rename( strNewFile.latin1(), m_fileName.latin1() ) < 0 ) {
00139         // remove the tmp file...
00140         QFile::remove( strNewFile );
00141     }
00142 
00143     /* The journalfile should be removed now... */
00144     removeJournal();
00145 
00146     m_changed = false;
00147     return true;
00148 }
00149 
00150 bool OPimContactAccessBackend_XML::load ()
00151 {
00152     m_contactList.clear();
00153     m_uidToContact.clear();
00154 
00155     /* Load XML-File and journal if it exists */
00156     if ( !load ( m_fileName, false ) )
00157         return false;
00158     /* The returncode of the journalfile is ignored due to the
00159      * fact that it does not exist when this class is instantiated !
00160      * But there may such a file exist, if the application crashed.
00161      * Therefore we try to load it to get the changes before the #
00162      * crash happened...
00163      */
00164     load (m_journalName, true);
00165 
00166     return true;
00167 }
00168 
00169 void OPimContactAccessBackend_XML::clear ()
00170 {
00171     m_contactList.clear();
00172     m_uidToContact.clear();
00173 
00174     m_changed = false;
00175 }
00176 
00177 bool OPimContactAccessBackend_XML::wasChangedExternally()
00178 {
00179     QFileInfo fi( m_fileName );
00180 
00181     QDateTime lastmod = fi.lastModified ();
00182 
00183     return (lastmod != m_readtime);
00184 }
00185 
00186 UIDArray OPimContactAccessBackend_XML::allRecords() const
00187 {
00188     UIDArray uid_list( m_contactList.count() );
00189 
00190     uint counter = 0;
00191     QListIterator<OPimContact> it( m_contactList );
00192     for( ; it.current(); ++it ){
00193         uid_list[counter++] = (*it)->uid();
00194     }
00195 
00196     return ( uid_list );
00197 }
00198 
00199 OPimContact OPimContactAccessBackend_XML::find ( int uid ) const
00200 {
00201     OPimContact foundContact; //Create empty contact
00202 
00203     OPimContact* found = m_uidToContact.find( QString().setNum( uid ) );
00204 
00205     if ( found ){
00206         foundContact = *found;
00207     }
00208 
00209     return ( foundContact );
00210 }
00211 
00212 
00213 UIDArray OPimContactAccessBackend_XML::matchRegexp(  const QRegExp &r ) const
00214 {
00215     UIDArray m_currentQuery( m_contactList.count() );
00216     QListIterator<OPimContact> it( m_contactList );
00217     uint arraycounter = 0;
00218 
00219     for( ; it.current(); ++it ){
00220         if ( (*it)->match( r ) ){
00221             m_currentQuery[arraycounter++] = (*it)->uid();
00222         }
00223 
00224     }
00225     // Shrink to fit..
00226     m_currentQuery.resize(arraycounter);
00227 
00228     return m_currentQuery;
00229 }
00230 
00231 
00232 
00233 #if 0
00234 // Currently only asc implemented..
00235 UIDArray OPimContactAccessBackend_XML::sorted( bool asc,  int , int ,  int )
00236 {
00237     QMap<QString, int> nameToUid;
00238     QStringList names;
00239     UIDArray m_currentQuery( m_contactList.count() );
00240 
00241     // First fill map and StringList with all Names
00242     // Afterwards sort namelist and use map to fill array to return..
00243     QListIterator<OPimContact> it( m_contactList );
00244     for( ; it.current(); ++it ){
00245         names.append( (*it)->fileAs() + QString::number( (*it)->uid() ) );
00246         nameToUid.insert( (*it)->fileAs() + QString::number( (*it)->uid() ), (*it)->uid() );
00247     }
00248     names.sort();
00249 
00250     int i = 0;
00251     if ( asc ){
00252         for ( QStringList::Iterator it = names.begin(); it != names.end(); ++it )
00253             m_currentQuery[i++] = nameToUid[ (*it) ];
00254     }else{
00255         for ( QStringList::Iterator it = names.end(); it != names.begin(); --it )
00256             m_currentQuery[i++] = nameToUid[ (*it) ];
00257     }
00258 
00259     return m_currentQuery;
00260 
00261 }
00262 #endif
00263 
00264 
00265 bool OPimContactAccessBackend_XML::add ( const OPimContact &newcontact )
00266 {
00267     updateJournal (newcontact, ACTION_ADD);
00268     addContact_p( newcontact );
00269 
00270     m_changed = true;
00271 
00272     return true;
00273 }
00274 
00275 bool OPimContactAccessBackend_XML::replace ( const OPimContact &contact )
00276 {
00277     m_changed = true;
00278 
00279     OPimContact* found = m_uidToContact.find ( QString().setNum( contact.uid() ) );
00280 
00281     if ( found ) {
00282         OPimContact* newCont = new OPimContact( contact );
00283 
00284         updateJournal ( *newCont, ACTION_REPLACE);
00285         m_contactList.removeRef ( found );
00286         m_contactList.append ( newCont );
00287         m_uidToContact.remove( QString().setNum( contact.uid() ) );
00288         m_uidToContact.insert( QString().setNum( newCont->uid() ), newCont );
00289 
00290         return true;
00291     } else
00292         return false;
00293 }
00294 
00295 bool OPimContactAccessBackend_XML::remove ( int uid )
00296 {
00297     m_changed = true;
00298 
00299     OPimContact* found = m_uidToContact.find ( QString().setNum( uid ) );
00300 
00301     if ( found ) {
00302         updateJournal ( *found, ACTION_REMOVE);
00303         m_contactList.removeRef ( found );
00304         m_uidToContact.remove( QString().setNum( uid ) );
00305 
00306         return true;
00307     } else
00308         return false;
00309 }
00310 
00311 bool OPimContactAccessBackend_XML::reload(){
00312     /* Reload is the same as load in this implementation */
00313     return ( load() );
00314 }
00315 
00316 void OPimContactAccessBackend_XML::addContact_p( const OPimContact &newcontact )
00317 {
00318     OPimContact* contRef = new OPimContact( newcontact );
00319 
00320     m_contactList.append ( contRef );
00321     m_uidToContact.insert( QString().setNum( newcontact.uid() ), contRef );
00322 }
00323 
00324 /* This function loads the xml-database and the journalfile */
00325 bool OPimContactAccessBackend_XML::load( const QString filename, bool isJournal )
00326 {
00327 
00328     /* We use the time of the last read to check if the file was
00329      * changed externally.
00330      */
00331     if ( !isJournal ){
00332         QFileInfo fi( filename );
00333         m_readtime = fi.lastModified ();
00334     }
00335 
00336     const int JOURNALACTION = Qtopia::Notes + 1;
00337     const int JOURNALROW = JOURNALACTION + 1;
00338 
00339     bool foundAction = false;
00340     journal_action action = ACTION_ADD;
00341     int journalKey = 0;
00342     QMap<int, QString> contactMap;
00343     QMap<QString, QString> customMap;
00344     QMap<QString, QString>::Iterator customIt;
00345     QAsciiDict<int> dict( 47 );
00346 
00347     dict.setAutoDelete( TRUE );
00348     dict.insert( "Uid", new int(Qtopia::AddressUid) );
00349     dict.insert( "Title", new int(Qtopia::Title) );
00350     dict.insert( "FirstName", new int(Qtopia::FirstName) );
00351     dict.insert( "MiddleName", new int(Qtopia::MiddleName) );
00352     dict.insert( "LastName", new int(Qtopia::LastName) );
00353     dict.insert( "Suffix", new int(Qtopia::Suffix) );
00354     dict.insert( "FileAs", new int(Qtopia::FileAs) );
00355     dict.insert( "Categories", new int(Qtopia::AddressCategory) );
00356     dict.insert( "DefaultEmail", new int(Qtopia::DefaultEmail) );
00357     dict.insert( "Emails", new int(Qtopia::Emails) );
00358     dict.insert( "HomeStreet", new int(Qtopia::HomeStreet) );
00359     dict.insert( "HomeCity", new int(Qtopia::HomeCity) );
00360     dict.insert( "HomeState", new int(Qtopia::HomeState) );
00361     dict.insert( "HomeZip", new int(Qtopia::HomeZip) );
00362     dict.insert( "HomeCountry", new int(Qtopia::HomeCountry) );
00363     dict.insert( "HomePhone", new int(Qtopia::HomePhone) );
00364     dict.insert( "HomeFax", new int(Qtopia::HomeFax) );
00365     dict.insert( "HomeMobile", new int(Qtopia::HomeMobile) );
00366     dict.insert( "HomeWebPage", new int(Qtopia::HomeWebPage) );
00367     dict.insert( "Company", new int(Qtopia::Company) );
00368     dict.insert( "BusinessStreet", new int(Qtopia::BusinessStreet) );
00369     dict.insert( "BusinessCity", new int(Qtopia::BusinessCity) );
00370     dict.insert( "BusinessState", new int(Qtopia::BusinessState) );
00371     dict.insert( "BusinessZip", new int(Qtopia::BusinessZip) );
00372     dict.insert( "BusinessCountry", new int(Qtopia::BusinessCountry) );
00373     dict.insert( "BusinessWebPage", new int(Qtopia::BusinessWebPage) );
00374     dict.insert( "JobTitle", new int(Qtopia::JobTitle) );
00375     dict.insert( "Department", new int(Qtopia::Department) );
00376     dict.insert( "Office", new int(Qtopia::Office) );
00377     dict.insert( "BusinessPhone", new int(Qtopia::BusinessPhone) );
00378     dict.insert( "BusinessFax", new int(Qtopia::BusinessFax) );
00379     dict.insert( "BusinessMobile", new int(Qtopia::BusinessMobile) );
00380     dict.insert( "BusinessPager", new int(Qtopia::BusinessPager) );
00381     dict.insert( "Profession", new int(Qtopia::Profession) );
00382     dict.insert( "Assistant", new int(Qtopia::Assistant) );
00383     dict.insert( "Manager", new int(Qtopia::Manager) );
00384     dict.insert( "Spouse", new int(Qtopia::Spouse) );
00385     dict.insert( "Children", new int(Qtopia::Children) );
00386     dict.insert( "Gender", new int(Qtopia::Gender) );
00387     dict.insert( "Birthday", new int(Qtopia::Birthday) );
00388     dict.insert( "Anniversary", new int(Qtopia::Anniversary) );
00389     dict.insert( "Nickname", new int(Qtopia::Nickname) );
00390     dict.insert( "Notes", new int(Qtopia::Notes) );
00391     dict.insert( "action", new int(JOURNALACTION) );
00392     dict.insert( "actionrow", new int(JOURNALROW) );
00393 
00394     XMLElement *root = XMLElement::load( filename );
00395     if(root != 0l ){ // start parsing
00396         /* Parse all XML-Elements and put the data into the
00397          * Contact-Class
00398          */
00399         XMLElement *element = root->firstChild();
00400         element = element ? element->firstChild() : 0;
00401 
00402         /* Search Tag "Contacts" which is the parent of all Contacts */
00403         while( element && !isJournal ){
00404             if( element->tagName() != QString::fromLatin1("Contacts") ){
00405                 element = element->nextChild();
00406             } else {
00407                 element = element->firstChild();
00408                 break;
00409             }
00410         }
00411         /* Parse all Contacts and ignore unknown tags */
00412         while( element ){
00413             if( element->tagName() != QString::fromLatin1("Contact") ){
00414                 element = element->nextChild();
00415                 continue;
00416             }
00417             /* Found alement with tagname "contact", now parse and store all
00418              * attributes contained
00419              */
00420             QString dummy;
00421             foundAction = false;
00422 
00423             XMLElement::AttributeMap aMap = element->attributes();
00424             XMLElement::AttributeMap::Iterator it;
00425             contactMap.clear();
00426             customMap.clear();
00427             for( it = aMap.begin(); it != aMap.end(); ++it ){
00428                 int *find = dict[ it.key() ];
00429                 /* Unknown attributes will be stored as "Custom" elements */
00430                 if ( !find ) {
00431                     //contact.setCustomField(it.key(), it.data());
00432                     customMap.insert( it.key(),  it.data() );
00433                     continue;
00434                 }
00435 
00436                 /* Check if special conversion is needed and add attribute
00437                  * into Contact class
00438                  */
00439                 switch( *find ) {
00440                     /*
00441                       case Qtopia::AddressUid:
00442                       contact.setUid( it.data().toInt() );
00443                       break;
00444                       case Qtopia::AddressCategory:
00445                       contact.setCategories( Qtopia::Record::idsFromString( it.data( )));
00446                       break;
00447                     */
00448                 case JOURNALACTION:
00449                     action = journal_action(it.data().toInt());
00450                     foundAction = true;
00451                     owarn << "ODefBack(journal)::ACTION found: " << action << oendl;
00452                     break;
00453                 case JOURNALROW:
00454                     journalKey = it.data().toInt();
00455                     break;
00456                 default: // no conversion needed add them to the map
00457                     contactMap.insert( *find, it.data() );
00458                     break;
00459                 }
00460             }
00461             /* now generate the Contact contact */
00462             OPimContact contact( contactMap );
00463 
00464             for (customIt = customMap.begin(); customIt != customMap.end(); ++customIt ) {
00465                 contact.setCustomField( customIt.key(),  customIt.data() );
00466             }
00467 
00468             if (foundAction){
00469                 foundAction = false;
00470                 switch ( action ) {
00471                 case ACTION_ADD:
00472                     addContact_p (contact);
00473                     break;
00474                 case ACTION_REMOVE:
00475                     if ( !remove (contact.uid()) )
00476                         owarn << "ODefBack(journal)::Unable to remove uid: " << contact.uid() << oendl;
00477                     break;
00478                 case ACTION_REPLACE:
00479                     if ( !replace ( contact ) )
00480                         owarn << "ODefBack(journal)::Unable to replace uid: " << contact.uid() << oendl;
00481                     break;
00482                 default:
00483                     owarn << "Unknown action: ignored !" << oendl;
00484                     break;
00485                 }
00486             }else{
00487                 /* Add contact to list */
00488                 addContact_p (contact);
00489             }
00490 
00491             /* Move to next element */
00492             element = element->nextChild();
00493         }
00494     }else {
00495     }
00496     delete root;
00497     return true;
00498 }
00499 
00500 
00501 void OPimContactAccessBackend_XML::updateJournal( const OPimContact& cnt,
00502                            journal_action action )
00503 {
00504     QFile f( m_journalName );
00505     bool created = !f.exists();
00506     if ( !f.open(IO_WriteOnly|IO_Append) )
00507         return;
00508 
00509     QString buf;
00510     QCString str;
00511 
00512     // if the file was created, we have to set the Tag "<CONTACTS>" to
00513     // get a XML-File which is readable by our parser.
00514     // This is just a cheat, but better than rewrite the parser.
00515     if ( created ){
00516         buf = "<Contacts>";
00517         QCString cstr = buf.utf8();
00518         f.writeBlock( cstr.data(), cstr.length() );
00519     }
00520 
00521     buf = "<Contact ";
00522     cnt.save( buf );
00523     buf += " action=\"" + QString::number( (int)action ) + "\" ";
00524     buf += "/>\n";
00525     QCString cstr = buf.utf8();
00526     f.writeBlock( cstr.data(), cstr.length() );
00527 }
00528 
00529 void OPimContactAccessBackend_XML::removeJournal()
00530 {
00531     QFile f ( m_journalName );
00532     if ( f.exists() )
00533         f.remove();
00534 }
00535 
00536 }

Generated on Sat Nov 5 16:16:15 2005 for OPIE by  doxygen 1.4.2