Re: PimConverter - addressbook export to CSV - CSV backend creation

"Marcos \"Japa\" Umino" <[email protected]>
Newsgroups gmane.comp.handhelds.opie.devel
Message-ID <[email protected]>
Oh, I forgot to attach new files. I had the silly idea that cvs diff
took care of new files too.

They go into

opie/libopie2/opiepim/backend

Namely, they're the text backend itself.

TIA,

Marcos 'TheJapa'

On 7/15/06, Lorn Potter <[email protected]> wrote:
> Thanks for your effort.
> I can look into and patch.
> Will do if someone else doesnt get to it first.
>
>
>
> On Saturday 15 July 2006 11:56, Marcos "Japa" Umino wrote:
> > Hello opie-devs
> >
> > This is my first attempt submitting code, so if I did something wrong,
> > go ahead and tell me (but please tell me what I did wrong) so I can
> > try to fix.
> >
> > I generated these files using 'cvs diff -Nau' against opie HEAD tag.
> >
> > Basically what I implemented is a little backend working only for
> > addressbook to do CSV export (or pimbackend's save() function), so we
> > can export addressbook data to CSV, useful to send to your
> > friend/secretary/manager non-vcard supporting contacts program.
> > (Namely microsoft outlook)
> >
> > I'll implement CSV export for datebook and todo, and import from CSV
> > too, that is, if you have no objections to it.
> >
> > It's primary use, as I see it, is for users migrating from Outlook
> > based PDAs, like Windows Mobile iPaqs. I know from personal experience
> > that once migration to linux on pda has started, the first concern is
> > fast conversion of PIM data, and setting up opensync and syncing isn't
> > exactly fast or easy. (for the beginner)
> >
> > It was necessary to create a new Rules.Make entry, as I designed
> > everything as a new TEXT_BACKEND option. This way I hope text support
> > will only be added if the distro maintainer finds it useful at all.
> >
> > Since this is my first adventure submitting code, I may have forgotten
> > to modify some changelog-like file, IIRC, I haven't found any such
> > file.
> >
> > Sorry for the long post, if you have any sort of criticism to make,
> > please do, I want to do it right.
> >
> > Marcos "Thejapa" Umino
>
> --
> Lorn 'ljp' Potter
> Trolltech Qtopia Community Manager
> Opie Core Developer
> http://qtopia.net
>

_______________________________________________

http://opie.handhelds.org/cgi-bin/moin.cgi/DeveloperWikiIndex

Opie-devel mailing list
[email protected]
https://handhelds.org/mailman/listinfo/opie-devel
ocontactaccessbackend_text.cpp (text/x-c++src, 17.6 KB)
/*
                             This file is part of the Opie Project
                             Copyright (C) Stefan Eilers ([email protected])
              =.             Copyright (C) The Opie Team <[email protected]>
            .=l.
           .>+-=
 _;:,     .>    :=|.         This program is free software; you can
.> <`_,   >  .   <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
.="- .-=="i,     .._         License as published by the Free Software
 - .   .-<_>     .<>         Foundation; either version 2 of the License,
     ._= =}       :          or (at your option) any later version.
    .%`+i>       _;_.
    .i_,=:_.      -<s.       This program is distributed in the hope that
     +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
    : ..    .:,     . . .    without even the implied warranty of
    =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
  _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
..}^=.=       =       ;      Library General Public License for more
++=   -.     .`     .:       details.
 :     =  ...= . :.=-
 -.   .:....=;==+<;          You should have received a copy of the GNU
  -_. . .   )=.  =           Library General Public License along with
    --        :-=`           this library; see the file COPYING.LIB.
                             If not, write to the Free Software Foundation,
                             Inc., 59 Temple Place - Suite 330,
                             Boston, MA 02111-1307, USA.
*/
/*
 * Text Backend for the OPIE-Contact Database.
 */


/* OPIE */
#include <opie2/ocontactaccessbackend_text.h>
#include <opie2/xmltree.h>
#include <opie2/ocontactaccessbackend.h>
#include <opie2/ocontactaccess.h>
#include <opie2/odebug.h>
#include <opie2/opimcontact.h>

#include <qpe/global.h>
#include <qpe/stringutil.h>

/* QT */
#include <qasciidict.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qregexp.h>
#include <qarray.h>
#include <qmap.h>

/* STD */
#include <stdlib.h>
#include <errno.h>

using namespace Opie::Core;


namespace Opie {
OPimContactAccessBackend_Text::OPimContactAccessBackend_Text ( 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.csv" );
    } else
        m_fileName = filename;

    /* Load Database now */
    load ();
}

bool OPimContactAccessBackend_Text::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;

    // CSV in popular uses field names in first line and each record uses 1 line. You
    // just separate fields using ','.
    // The first shot will be using all fields and exporting empty fields as plain empty string.

    // Write all contacts
    QListIterator<OPimContact> it( m_contactList );

    // Write Header, abusing first item to get fields()
    static QStringList SLFIELDS = (*it)->fields();

    //uid is the first field, no categories yet
    out += "\"uid\", \"category id\", ";
    for ( QStringList::Iterator it2 = SLFIELDS.begin(); it2 != SLFIELDS.end(); ++it2 ) {
      const QString &field = *it2;
      out += "\"" + Qtopia::escapeString( field ) + "\",";
    }
    out += "\"EMPTY\"\n";

    QCString cstr = out.utf8();
    f.writeBlock( cstr.data(), cstr.length() );
    idx_offset += cstr.length();
    out = "";

    //now writing contacts
    for ( ; it.current(); ++it ) {
        (*it)->CSV( out );
        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 += "\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 ) {
        // remove the tmp file...
        QFile::remove( strNewFile );
    }

    /* The journalfile should be removed now... */
    removeJournal();

    m_changed = false;
    return true;
}

bool OPimContactAccessBackend_Text::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 OPimContactAccessBackend_Text::clear ()
{
    m_contactList.clear();
    m_uidToContact.clear();

    m_changed = false;
}

bool OPimContactAccessBackend_Text::wasChangedExternally()
{
    QFileInfo fi( m_fileName );

    QDateTime lastmod = fi.lastModified ();

    return (lastmod != m_readtime);
}

UIDArray OPimContactAccessBackend_Text::allRecords() const
{
    UIDArray uid_list( m_contactList.count() );

    uint counter = 0;
    QListIterator<OPimContact> it( m_contactList );
    for( ; it.current(); ++it ){
        uid_list[counter++] = (*it)->uid();
    }

    return ( uid_list );
}

OPimContact OPimContactAccessBackend_Text::find ( int uid ) const
{
    OPimContact foundContact; //Create empty contact

    OPimContact* found = m_uidToContact.find( QString().setNum( uid ) );

    if ( found ){
        foundContact = *found;
    }

    return ( foundContact );
}


UIDArray OPimContactAccessBackend_Text::matchRegexp(  const QRegExp &r ) const
{
    UIDArray m_currentQuery( m_contactList.count() );
    QListIterator<OPimContact> 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;
}



#if 0
// Currently only asc implemented..
UIDArray OPimContactAccessBackend_Text::sorted( bool asc,  int , int ,  int )
{
    QMap<QString, int> nameToUid;
    QStringList names;
    UIDArray 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<OPimContact> 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;

}
#endif


bool OPimContactAccessBackend_Text::add ( const OPimContact &newcontact )
{
    updateJournal (newcontact, ACTION_ADD);
    addContact_p( newcontact );

    m_changed = true;

    return true;
}

bool OPimContactAccessBackend_Text::replace ( const OPimContact &contact )
{
    m_changed = true;

    OPimContact* found = m_uidToContact.find ( QString().setNum( contact.uid() ) );

    if ( found ) {
        OPimContact* newCont = new OPimContact( 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 );

        return true;
    } else
        return false;
}

bool OPimContactAccessBackend_Text::remove ( int uid )
{
    m_changed = true;

    OPimContact* 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 OPimContactAccessBackend_Text::reload(){
    /* Reload is the same as load in this implementation */
    return ( load() );
}

void OPimContactAccessBackend_Text::addContact_p( const OPimContact &newcontact )
{
    OPimContact* contRef = new OPimContact( newcontact );

    m_contactList.append ( contRef );
    m_uidToContact.insert( QString().setNum( newcontact.uid() ), contRef );
}

/* This function loads the xml-database and the journalfile */
bool OPimContactAccessBackend_Text::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) );

    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();
        element = element ? element->firstChild() : 0;

        /* Search Tag "Contacts" which is the parent of all Contacts */
        while( element && !isJournal ){
            if( element->tagName() != QString::fromLatin1("Contacts") ){
                element = element->nextChild();
            } else {
                element = element->firstChild();
                break;
            }
        }
        /* Parse all Contacts and ignore unknown tags */
        while( element ){
            if( element->tagName() != QString::fromLatin1("Contact") ){
                element = element->nextChild();
                continue;
            }
            /* Found alement with tagname "contact", now parse and store all
             * attributes contained
             */
            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 ){
                int *find = dict[ it.key() ];
                /* Unknown attributes will be stored as "Custom" elements */
                if ( !find ) {
                    //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;
                    owarn << "ODefBack(journal)::ACTION found: " << action << oendl;
                    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 */
            OPimContact 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()) )
                        owarn << "ODefBack(journal)::Unable to remove uid: " << contact.uid() << oendl;
                    break;
                case ACTION_REPLACE:
                    if ( !replace ( contact ) )
                        owarn << "ODefBack(journal)::Unable to replace uid: " << contact.uid() << oendl;
                    break;
                default:
                    owarn << "Unknown action: ignored !" << oendl;
                    break;
                }
            }else{
                /* Add contact to list */
                addContact_p (contact);
            }

            /* Move to next element */
            element = element->nextChild();
        }
    }else {
    }
    delete root;
    return true;
}


void OPimContactAccessBackend_Text::updateJournal( const OPimContact& 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 Text-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 OPimContactAccessBackend_Text::removeJournal()
{
    QFile f ( m_journalName );
    if ( f.exists() )
        f.remove();
}

}
ocontactaccessbackend_text.h (text/x-chdr, 3.1 KB)
/*
                             This file is part of the Opie Project
                             Copyright (C) Stefan Eilers ([email protected])
              =.             Copyright (C) The Opie Team <[email protected]>
            .=l.
           .>+-=
 _;:,     .>    :=|.         This program is free software; you can
.> <`_,   >  .   <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
.="- .-=="i,     .._         License as published by the Free Software
 - .   .-<_>     .<>         Foundation; either version 2 of the License,
     ._= =}       :          or (at your option) any later version.
    .%`+i>       _;_.
    .i_,=:_.      -<s.       This program is distributed in the hope that
     +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
    : ..    .:,     . . .    without even the implied warranty of
    =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
  _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
..}^=.=       =       ;      Library General Public License for more
++=   -.     .`     .:       details.
 :     =  ...= . :.=-
 -.   .:....=;==+<;          You should have received a copy of the GNU
  -_. . .   )=.  =           Library General Public License along with
    --        :-=`           this library; see the file COPYING.LIB.
                             If not, write to the Free Software Foundation,
                             Inc., 59 Temple Place - Suite 330,
                             Boston, MA 02111-1307, USA.
*/
/*
 * XML Backend for the OPIE-Contact Database.
 */

#ifndef _OPimContactAccessBackend_Text_
#define _OPimContactAccessBackend_Text_

#include <opie2/ocontactaccessbackend.h>
#include <opie2/ocontactaccess.h>

#include <qlist.h>
#include <qdict.h>

namespace Opie {
/* the Text implementation, adapted from XML by thejapa*/
/**
 * This class is the Text implementation of a Contact backend
 * it does implement everything available for OPimContact.
 * @see OPimAccessBackend for more information of available methods
 */
class OPimContactAccessBackend_Text : public OPimContactAccessBackend {
 public:
	OPimContactAccessBackend_Text ( const QString& appname, const QString& filename = QString::null );

	bool save();

	bool load ();

	void clear ();

	bool wasChangedExternally();

	UIDArray allRecords() const;

	OPimContact find ( int uid ) const;

	UIDArray matchRegexp(  const QRegExp &r ) const;

	bool add ( const OPimContact &newcontact );

	bool replace ( const OPimContact &contact );

	bool remove ( int uid );
	bool reload();

 private:

	enum journal_action { ACTION_ADD, ACTION_REMOVE, ACTION_REPLACE };

	void addContact_p( const OPimContact &newcontact );

	/* This function loads the xml-database and the journalfile */
	bool load( const QString filename, bool isJournal );


	void updateJournal( const OPimContact& cnt, journal_action action );
	void removeJournal();

 protected:
	bool m_changed;
	QString m_journalName;
	QString m_fileName;
	QString m_appName;
	QList<OPimContact> m_contactList;
	QDateTime m_readtime;

	QDict<OPimContact> m_uidToContact;
};

}

#endif
odatebookaccessbackend_text.cpp (text/x-c++src, 19.3 KB)
/*
                             This file is part of the Opie Project
                             Copyright (C) Stefan Eilers ([email protected])
              =.             Copyright (C) The Opie Team <[email protected]>
            .=l.
           .>+-=
 _;:,     .>    :=|.         This program is free software; you can
.> <`_,   >  .   <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
.="- .-=="i,     .._         License as published by the Free Software
 - .   .-<_>     .<>         Foundation; either version 2 of the License,
     ._= =}       :          or (at your option) any later version.
    .%`+i>       _;_.
    .i_,=:_.      -<s.       This program is distributed in the hope that
     +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
    : ..    .:,     . . .    without even the implied warranty of
    =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
  _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
..}^=.=       =       ;      Library General Public License for more
++=   -.     .`     .:       details.
 :     =  ...= . :.=-
 -.   .:....=;==+<;          You should have received a copy of the GNU
  -_. . .   )=.  =           Library General Public License along with
    --        :-=`           this library; see the file COPYING.LIB.
                             If not, write to the Free Software Foundation,
                             Inc., 59 Temple Place - Suite 330,
                             Boston, MA 02111-1307, USA.
*/

/* OPIE */
#include <opie2/opimnotifymanager.h>
#include <opie2/opimrecurrence.h>
#include <opie2/opimtimezone.h>
#include <opie2/odatebookaccessbackend_text.h>
#include <opie2/odebug.h>

#include <qtopia/global.h>
#include <qtopia/stringutil.h>
#include <qtopia/timeconversion.h>

/* QT */
#include <qasciidict.h>
#include <qfile.h>

/* STD */
#include <errno.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include <unistd.h>


using namespace Opie;

namespace {
    // FROM TT again
char *strstrlen(const char *haystack, int hLen, const char* needle, int nLen)
{
    char needleChar;
    char haystackChar;
    if (!needle || !haystack || !hLen || !nLen)
    return 0;

    const char* hsearch = haystack;

    if ((needleChar = *needle++) != 0) {
    nLen--; //(to make up for needle++)
    do {
        do {
        if ((haystackChar = *hsearch++) == 0)
            return (0);
        if (hsearch >= haystack + hLen)
            return (0);
        } while (haystackChar != needleChar);
    } while (strncmp(hsearch, needle, QMIN(hLen - (hsearch - haystack), nLen)) != 0);
    hsearch--;
    }
    return ((char *)hsearch);
}
}

namespace {
    time_t start, end, created, rp_end;
    OPimRecurrence* rec;
    static OPimRecurrence* recur() {
        if (!rec)
            rec = new OPimRecurrence;

        return rec;
    }
    int alarmTime;
    int snd;
    enum Attribute{
        FDescription = 0,
        FLocation,
        FCategories,
        FUid,
        FType,
        FAlarm,
        FSound,
        FRType,
        FRWeekdays,
        FRPosition,
        FRFreq,
        FRHasEndDate,
        FREndDate,
        FRStart,
        FREnd,
        FNote,
        FCreated,      // Should't this be called FRCreated ?
        FTimeZone,
        FRecParent,
        FRecChildren,
        FExceptions
    };

    // FIXME: Use OPimEvent::toMap() here !! (eilers)
    static void save( const OPimEvent& ev, QString& buf ) {
        buf += " description=\"" + Qtopia::escapeString(ev.description() ) + "\"";
        if (!ev.location().isEmpty() )
            buf += " location=\"" + Qtopia::escapeString(ev.location() ) + "\"";

        if (!ev.categories().isEmpty() )
            buf += " categories=\""+ Qtopia::escapeString( Qtopia::Record::idsToString( ev.categories() ) ) + "\"";

        buf += " uid=\"" + QString::number( ev.uid() ) + "\"";

        if (ev.isAllDay() )
            buf += " type=\"AllDay\""; // is that all ?? (eilers)

        if (ev.hasNotifiers() ) {
            OPimAlarm alarm = ev.notifiers().alarms()[0]; // take only the first
            int minutes = alarm.dateTime().secsTo( ev.startDateTime() ) / 60;
            buf += " alarm=\"" + QString::number(minutes) + "\" sound=\"";
            if ( alarm.sound() == OPimAlarm::Loud )
                buf += "loud";
            else
                buf += "silent";
            buf += "\"";
        }
        if ( ev.hasRecurrence() ) {
            buf += ev.recurrence().toString();
        }

        /*
         * fscking timezones :) well, we'll first convert
         * the QDateTime to a QDateTime in UTC time
         * and then we'll create a nice time_t
         */
        OPimTimeZone zone( (ev.timeZone().isEmpty()||ev.isAllDay()) ? OPimTimeZone::utc() : OPimTimeZone::current() );
        buf += " start=\"" + QString::number( zone.fromDateTime( ev.startDateTime()))  + "\"";
        buf += " end=\""   + QString::number( zone.fromDateTime( ev.endDateTime()  ))  + "\"";
        if (!ev.note().isEmpty() ) {
            buf += " note=\"" + Qtopia::escapeString( ev.note() ) + "\"";
        }

        /*
         * Don't save a timezone if AllDay Events
         * as they're UTC only anyway
         */
        if (!ev.isAllDay() ) {

            buf += " timezone=\"";
            if ( ev.timeZone().isEmpty() )
                buf += "None";
            else
                buf += ev.timeZone();
            buf += "\"";
        }

        if (ev.parent() != 0 ) {
            buf += " recparent=\""+QString::number(ev.parent() )+"\"";
        }

        if (ev.children().count() != 0 ) {
            QArray<int> children = ev.children();
            buf += " recchildren=\"";
            for ( uint i = 0; i < children.count(); i++ ) {
                if ( i != 0 ) buf += " ";
                buf += QString::number( children[i] );
            }
            buf+= "\"";
        }

        // skip custom writing
    }

    static bool saveEachEvent( const QMap<int, OPimEvent>& list, QFile& file ) {
        QMap<int, OPimEvent>::ConstIterator it;
        QString buf;
        QCString str;
        int total_written;
        for ( it = list.begin(); it != list.end(); ++it ) {
            buf = "<event";
            save( it.data(), buf );
            buf += " />\n";
            str = buf.utf8();

            total_written = file.writeBlock(str.data(), str.length() );
            if ( total_written != int(str.length() ) )
                return false;
        }
        return true;
    }
}

namespace Opie {
ODateBookAccessBackend_Text::ODateBookAccessBackend_Text( const QString& ,
                                                        const QString& fileName )
    : ODateBookAccessBackend() {
    m_name = fileName.isEmpty() ? Global::applicationFileName( "datebook", "datebook.csv" ) : fileName;
    m_changed = false;
}
ODateBookAccessBackend_Text::~ODateBookAccessBackend_Text() {
}
bool ODateBookAccessBackend_Text::load() {
    return loadFile();
}
bool ODateBookAccessBackend_Text::reload() {
    clear();
    return load();
}
bool ODateBookAccessBackend_Text::save() {
    if (!m_changed) return true;

    int total_written;
    QString strFileNew = m_name + ".new";

    QFile f( strFileNew );
    if (!f.open( IO_WriteOnly | IO_Raw ) ) return false;

    QString buf( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
    buf += "<!DOCTYPE DATEBOOK><DATEBOOK>\n";
    buf += "<events>\n";
    QCString str = buf.utf8();
    total_written = f.writeBlock( str.data(), str.length() );
    if ( total_written != int(str.length() ) ) {
        f.close();
        QFile::remove( strFileNew );
        return false;
    }

    if (!saveEachEvent( m_raw, f ) ) {
        f.close();
        QFile::remove( strFileNew );
        return false;
    }
    if (!saveEachEvent( m_rep, f ) ) {
        f.close();
        QFile::remove( strFileNew );
        return false;
    }

    buf = "</events>\n</DATEBOOK>\n";
    str = buf.utf8();
    total_written = f.writeBlock( str.data(), str.length() );
    if ( total_written != int(str.length() ) ) {
        f.close();
        QFile::remove( strFileNew );
        return false;
    }
    f.close();

    if ( ::rename( strFileNew, m_name ) < 0 ) {
        QFile::remove( strFileNew );
        return false;
    }

    m_changed = false;
    return true;
}
QArray<int> ODateBookAccessBackend_Text::allRecords()const {
    QArray<int> ints( m_raw.count()+ m_rep.count() );
    uint i = 0;
    QMap<int, OPimEvent>::ConstIterator it;

    for ( it = m_raw.begin(); it != m_raw.end(); ++it ) {
        ints[i] = it.key();
        i++;
    }
    for ( it = m_rep.begin(); it != m_rep.end(); ++it ) {
        ints[i] = it.key();
        i++;
    }

    return ints;
}
QArray<int> ODateBookAccessBackend_Text::queryByExample(const OPimEvent&, int,  const QDateTime& ) {
    return QArray<int>();
}
void ODateBookAccessBackend_Text::clear() {
    m_changed = true;
    m_raw.clear();
    m_rep.clear();
}
OPimEvent ODateBookAccessBackend_Text::find( int uid ) const{
    if ( m_raw.contains( uid ) )
        return m_raw[uid];
    else
        return m_rep[uid];
}
bool ODateBookAccessBackend_Text::add( const OPimEvent& ev ) {
    m_changed = true;
    if (ev.hasRecurrence() )
        m_rep.insert( ev.uid(), ev );
    else
        m_raw.insert( ev.uid(), ev );

    return true;
}
bool ODateBookAccessBackend_Text::remove( int uid ) {
    m_changed = true;
    m_raw.remove( uid );
    m_rep.remove( uid );

    return true;
}
bool ODateBookAccessBackend_Text::replace( const OPimEvent& ev ) {
    replace( ev.uid() ); // ??? Shouldn't this be "remove( ev.uid() ) ??? (eilers)
    return add( ev );
}

QArray<int> ODateBookAccessBackend_Text::rawRepeats()const {
    QArray<int> ints( m_rep.count() );
    uint i = 0;
    QMap<int, OPimEvent>::ConstIterator it;

    for ( it = m_rep.begin(); it != m_rep.end(); ++it ) {
        ints[i] = it.key();
        i++;
    }

    return ints;
}
QArray<int> ODateBookAccessBackend_Text::nonRepeats()const {
    QArray<int> ints( m_raw.count() );
    uint i = 0;
    QMap<int, OPimEvent>::ConstIterator it;

    for ( it = m_raw.begin(); it != m_raw.end(); ++it ) {
        ints[i] = it.key();
        i++;
    }

    return ints;
}
OPimEvent::ValueList ODateBookAccessBackend_Text::directNonRepeats()const {
    OPimEvent::ValueList list;
    QMap<int, OPimEvent>::ConstIterator it;
    for (it = m_raw.begin(); it != m_raw.end(); ++it )
        list.append( it.data() );

    return list;
}
OPimEvent::ValueList ODateBookAccessBackend_Text::directRawRepeats()const {
    OPimEvent::ValueList list;
    QMap<int, OPimEvent>::ConstIterator it;
    for (it = m_rep.begin(); it != m_rep.end(); ++it )
        list.append( it.data() );

    return list;
}

// FIXME: Use OPimEvent::fromMap() (eilers)
bool ODateBookAccessBackend_Text::loadFile() {
    m_changed = false;

    int fd = ::open( QFile::encodeName(m_name).data(), O_RDONLY );
    if ( fd < 0 ) return false;

    struct stat attribute;
    if ( ::fstat(fd, &attribute ) == -1 ) {
        ::close( fd );
        return false;
    }
    void* map_addr = ::mmap(NULL, attribute.st_size, PROT_READ, MAP_SHARED, fd, 0 );
    if ( map_addr == ( (caddr_t)-1) ) {
        ::close( fd );
        return false;
    }

    ::madvise( map_addr, attribute.st_size, MADV_SEQUENTIAL );
    ::close( fd );

    QAsciiDict<int> dict(FExceptions+1);
    dict.setAutoDelete( true );
    dict.insert( "description", new int(FDescription) );
    dict.insert( "location", new int(FLocation) );
    dict.insert( "categories", new int(FCategories) );
    dict.insert( "uid", new int(FUid) );
    dict.insert( "type", new int(FType) );
    dict.insert( "alarm", new int(FAlarm) );
    dict.insert( "sound", new int(FSound) );
    dict.insert( "rtype", new int(FRType) );
    dict.insert( "rweekdays", new int(FRWeekdays) );
    dict.insert( "rposition", new int(FRPosition) );
    dict.insert( "rfreq", new int(FRFreq) );
    dict.insert( "rhasenddate", new int(FRHasEndDate) );
    dict.insert( "enddt", new int(FREndDate) );
    dict.insert( "start", new int(FRStart) );
    dict.insert( "end", new int(FREnd) );
    dict.insert( "note", new int(FNote) );
    dict.insert( "created", new int(FCreated) );  // Shouldn't this be FRCreated ??
    dict.insert( "recparent", new int(FRecParent) );
    dict.insert( "recchildren", new int(FRecChildren) );
    dict.insert( "exceptions", new int(FExceptions) );
    dict.insert( "timezone", new int(FTimeZone) );


    // initialiaze db hack
    m_noTimeZone = true;

    char* dt = (char*)map_addr;
    int len = attribute.st_size;
    int i = 0;
    char* point;
    const char* collectionString = "<event ";
    int strLen = ::strlen(collectionString);
    int *find;
    while ( (  point = ::strstrlen( dt+i, len -i, collectionString, strLen ) ) != 0  ) {
        i = point -dt;
        i+= strLen;

        alarmTime = -1;
        snd = 0; // silent

        OPimEvent ev;
        rec = 0;

        while ( TRUE ) {
            while ( i < len && (dt[i] == ' ' || dt[i] == '\n' || dt[i] == '\r') )
        ++i;
        if ( i >= len-2 || (dt[i] == '/' && dt[i+1] == '>') )
        break;


        // we have another attribute, read it.
        int j = i;
        while ( j < len && dt[j] != '=' )
        ++j;
        QCString attr( dt+i, j-i+1);

        i = ++j; // skip =

        // find the start of quotes
        while ( i < len && dt[i] != '"' )
        ++i;
        j = ++i;

        bool haveUtf = FALSE;
        bool haveEnt = FALSE;
        while ( j < len && dt[j] != '"' ) {
        if ( ((unsigned char)dt[j]) > 0x7f )
            haveUtf = TRUE;
        if ( dt[j] == '&' )
            haveEnt = TRUE;
        ++j;
        }
        if ( i == j ) {
        // empty value
        i = j + 1;
        continue;
        }

        QCString value( dt+i, j-i+1 );
        i = j + 1;

        QString str = (haveUtf ? QString::fromUtf8( value )
            : QString::fromLatin1( value ) );
        if ( haveEnt )
        str = Qtopia::plainString( str );

            /*
             * add key + value
             */
            find = dict[attr.data()];
            if (!find)
                ev.setCustomField( attr, str );
            else {
                setField( ev, *find, str );
            }
        }
        /* time to finalize */
        finalizeRecord( ev );
        delete rec;
        m_noTimeZone = true;
    }
    ::munmap(map_addr, attribute.st_size );
    m_changed = false; // changed during add

    return true;
}

// FIXME: Use OPimEvent::fromMap() which makes this obsolete.. (eilers)
void ODateBookAccessBackend_Text::finalizeRecord( OPimEvent& ev ) {

    /*
     * quirk to import datebook files. They normally don't have a
     * timeZone attribute and we treat this as to use OPimTimeZone::current()
     */
    if (m_noTimeZone )
        ev.setTimeZone( OPimTimeZone::current().timeZone() );



    /* AllDay is alway in UTC */
    if ( ev.isAllDay() ) {
        OPimTimeZone utc = OPimTimeZone::utc();
        ev.setStartDateTime( utc.toDateTime( start ) );
        ev.setEndDateTime  ( utc.toDateTime( end   ) );
    }else {
        /* to current date time */
        OPimTimeZone   to_zone( ev.timeZone().isEmpty() ? OPimTimeZone::utc() : OPimTimeZone::current() );

        ev.setStartDateTime(to_zone.toDateTime( start));
        ev.setEndDateTime  (to_zone.toDateTime( end));
    }
    if ( rec && rec->doesRecur() ) {
        OPimTimeZone utc = OPimTimeZone::utc();
        OPimRecurrence recu( *rec ); // call copy c'tor;
        recu.setEndDate ( utc.toDateTime( rp_end ).date() );
        recu.setCreatedDateTime( utc.toDateTime( created ) );
        recu.setStart( ev.startDateTime().date() );
        ev.setRecurrence( recu );
    }

    if (alarmTime != -1 ) {
        QDateTime dt = ev.startDateTime().addSecs( -1*alarmTime*60 );
        OPimAlarm al( snd ,  dt  );
        ev.notifiers().add( al );
    }
    if ( m_raw.contains( ev.uid() ) || m_rep.contains( ev.uid() ) ) {
        ev.setUid( 1 );
    }

    if ( ev.hasRecurrence() )
        m_rep.insert( ev.uid(), ev );
    else
        m_raw.insert( ev.uid(), ev );

}
void ODateBookAccessBackend_Text::setField( OPimEvent& e, int id, const QString& value) {
    switch( id ) {
    case FDescription:
        e.setDescription( value );
        break;
    case FLocation:
        e.setLocation( value );
        break;
    case FCategories:
        e.setCategories( e.idsFromString( value ) );
        break;
    case FUid:
        e.setUid( value.toInt() );
        break;
    case FType:
        if ( value == "AllDay" ) {
            e.setAllDay( true );
        }
        break;
    case FAlarm:
        alarmTime = value.toInt();
        break;
    case FSound:
        snd = value == "loud" ? OPimAlarm::Loud : OPimAlarm::Silent;
        break;
        // recurrence stuff
    case FRType:
        if ( value == "Daily" )
            recur()->setType( OPimRecurrence::Daily );
        else if ( value == "Weekly" )
            recur()->setType( OPimRecurrence::Weekly);
        else if ( value == "MonthlyDay" )
            recur()->setType( OPimRecurrence::MonthlyDay );
        else if ( value == "MonthlyDate" )
            recur()->setType( OPimRecurrence::MonthlyDate );
        else if ( value == "Yearly" )
            recur()->setType( OPimRecurrence::Yearly );
        else
            recur()->setType( OPimRecurrence::NoRepeat );
        break;
    case FRWeekdays:
        recur()->setDays( value.toInt() );
        break;
    case FRPosition:
        recur()->setPosition( value.toInt() );
        break;
    case FRFreq:
        recur()->setFrequency( value.toInt() );
        break;
    case FRHasEndDate:
        recur()->setHasEndDate( value.toInt() );
        break;
    case FREndDate: {
        rp_end = (time_t) value.toLong();
        break;
    }
    case FRStart: {
        start =  (time_t) value.toLong();
        break;
    }
    case FREnd: {
        end = ( (time_t) value.toLong() );
        break;
    }
    case FNote:
        e.setNote( value );
        break;
    case FCreated:
        created = value.toInt();
        break;
    case FRecParent:
        e.setParent( value.toInt() );
        break;
    case FRecChildren:{
        QStringList list = QStringList::split(' ', value );
        for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
            e.addChild( (*it).toInt() );
        }
    }
        break;
    case FExceptions:{
        QStringList list = QStringList::split(' ', value );
        for (QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
            QDate date( (*it).left(4).toInt(), (*it).mid(4, 2).toInt(), (*it).right(2).toInt() );
            recur()->exceptions().append( date );
        }
    }
        break;
    case FTimeZone:
        m_noTimeZone = false;
        if ( value != "None" )
            e.setTimeZone( value );
        break;
    default:
        break;
    }
}
QArray<int> ODateBookAccessBackend_Text::matchRegexp(  const QRegExp &r ) const
{
    QArray<int> m_currentQuery( m_raw.count()+ m_rep.count() );
    uint arraycounter = 0;
    QMap<int, OPimEvent>::ConstIterator it;

    for ( it = m_raw.begin(); it != m_raw.end(); ++it )
        if ( it.data().match( r ) )
            m_currentQuery[arraycounter++] = it.data().uid();
    for ( it = m_rep.begin(); it != m_rep.end(); ++it )
        if ( it.data().match( r ) )
            m_currentQuery[arraycounter++] = it.data().uid();

    // Shrink to fit..
    m_currentQuery.resize(arraycounter);

    return m_currentQuery;
}

}
odatebookaccessbackend_text.h (text/x-chdr, 3 KB)
/*
                             This file is part of the Opie Project
                             Copyright (C) Stefan Eilers ([email protected])
              =.             Copyright (C) The Opie Team <[email protected]>
            .=l.
           .>+-=
 _;:,     .>    :=|.         This program is free software; you can
.> <`_,   >  .   <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
.="- .-=="i,     .._         License as published by the Free Software
 - .   .-<_>     .<>         Foundation; either version 2 of the License,
     ._= =}       :          or (at your option) any later version.
    .%`+i>       _;_.
    .i_,=:_.      -<s.       This program is distributed in the hope that
     +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
    : ..    .:,     . . .    without even the implied warranty of
    =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
  _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
..}^=.=       =       ;      Library General Public License for more
++=   -.     .`     .:       details.
 :     =  ...= . :.=-
 -.   .:....=;==+<;          You should have received a copy of the GNU
  -_. . .   )=.  =           Library General Public License along with
    --        :-=`           this library; see the file COPYING.LIB.
                             If not, write to the Free Software Foundation,
                             Inc., 59 Temple Place - Suite 330,
                             Boston, MA 02111-1307, USA.
*/
#ifndef OPIE_DATE_BOOK_ACCESS_BACKEND_TEXT__H
#define OPIE_DATE_BOOK_ACCESS_BACKEND_TEXT__H

#include <qmap.h>

#include <opie2/odatebookaccessbackend.h>

namespace Opie {
/**
 * Adapted from XML backend. Any thoughts please share with thejapa.
 * @see ODateBookAccessBackend
 * @see OPimAccessBackend
 */
class ODateBookAccessBackend_Text : public ODateBookAccessBackend {
public:
    ODateBookAccessBackend_Text( const QString& appName,
                                const QString& fileName = QString::null);
    ~ODateBookAccessBackend_Text();

    bool load();
    bool reload();
    bool save();

    QArray<int> allRecords()const;
    QArray<int> matchRegexp(const QRegExp &r) const;
    QArray<int> queryByExample( const OPimEvent&, int, const QDateTime& d = QDateTime() );
    OPimEvent find( int uid )const;
    void clear();
    bool add( const OPimEvent& ev );
    bool remove( int uid );
    bool replace( const OPimEvent& ev );

    QArray<UID> rawEvents()const;
    QArray<UID> rawRepeats()const;
    QArray<UID> nonRepeats()const;

    OPimEvent::ValueList directNonRepeats()const;
    OPimEvent::ValueList directRawRepeats()const;

private:
    bool m_changed :1 ;
    bool m_noTimeZone : 1;

    bool loadFile();
    inline void finalizeRecord( OPimEvent& ev );
    inline void setField( OPimEvent&, int field, const QString& val );
    QString m_name;
    QMap<int, OPimEvent> m_raw;
    QMap<int, OPimEvent> m_rep;

    struct Data;
    Data* data;
    class Private;
    Private *d;
};

}

#endif
otodoaccesstext.cpp (text/x-c++src, 22.1 KB)
/*
                             This file is part of the Opie Project
                             Copyright (C) Stefan Eilers ([email protected])
              =.             Copyright (C) The Opie Team <[email protected]>
            .=l.
           .>+-=
 _;:,     .>    :=|.         This program is free software; you can
.> <`_,   >  .   <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
.="- .-=="i,     .._         License as published by the Free Software
 - .   .-<_>     .<>         Foundation; either version 2 of the License,
     ._= =}       :          or (at your option) any later version.
    .%`+i>       _;_.
    .i_,=:_.      -<s.       This program is distributed in the hope that
     +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
    : ..    .:,     . . .    without even the implied warranty of
    =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
  _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
..}^=.=       =       ;      Library General Public License for more
++=   -.     .`     .:       details.
 :     =  ...= . :.=-
 -.   .:....=;==+<;          You should have received a copy of the GNU
  -_. . .   )=.  =           Library General Public License along with
    --        :-=`           this library; see the file COPYING.LIB.
                             If not, write to the Free Software Foundation,
                             Inc., 59 Temple Place - Suite 330,
                             Boston, MA 02111-1307, USA.
*/

/* OPIE */
#include <opie2/opimdateconversion.h>
#include <opie2/opimstate.h>
#include <opie2/opimtimezone.h>
#include <opie2/opimnotifymanager.h>
#include <opie2/opimrecurrence.h>
#include <opie2/otodoaccesstext.h>
#include <opie2/otodoaccess.h>
#include <opie2/odebug.h>

#include <opie2/private/opimtodosortvector.h>

#include <qpe/global.h>
#include <qpe/stringutil.h>
#include <qpe/timeconversion.h>

/* QT */
#include <qfile.h>
#include <qvector.h>

/* STD */
#include <errno.h>
#include <fcntl.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <unistd.h>


using namespace Opie;

namespace {
    time_t rp_end;
    OPimRecurrence* rec;
    OPimRecurrence *recur() {
        if (!rec ) rec = new OPimRecurrence;
        return rec;
    }
    int snd;
    enum MoreAttributes {
        FRType = OPimTodo::CompletedDate + 2,
        FRWeekdays,
        FRPosition,
        FRFreq,
        FRHasEndDate,
        FREndDate,
        FRStart,
        FREnd
    };
    // FROM TT again
char *strstrlen(const char *haystack, int hLen, const char* needle, int nLen)
{
    char needleChar;
    char haystackChar;
    if (!needle || !haystack || !hLen || !nLen)
    return 0;

    const char* hsearch = haystack;

    if ((needleChar = *needle++) != 0) {
    nLen--; //(to make up for needle++)
    do {
        do {
        if ((haystackChar = *hsearch++) == 0)
            return (0);
        if (hsearch >= haystack + hLen)
            return (0);
        } while (haystackChar != needleChar);
    } while (strncmp(hsearch, needle, QMIN(hLen - (hsearch - haystack), nLen)) != 0);
    hsearch--;
    }
    return ((char *)hsearch);
}
}

namespace Opie {

OPimTodoAccessText::OPimTodoAccessText( const QString& appName,
                                const QString& fileName )
    : OPimTodoAccessBackend(), m_app( appName ),  m_opened( false ), m_changed( false )
{
    if (!fileName.isEmpty() )
        m_file = fileName;
    else
        m_file = Global::applicationFileName( "todolist", "todolist.csv" );
}
OPimTodoAccessText::~OPimTodoAccessText() {

}
bool OPimTodoAccessText::load() {
    rec = 0;
    m_opened = true;
    m_changed = false;
    /* initialize dict */
    /*
     * UPDATE dict if you change anything!!!
     */
    QAsciiDict<int> dict(26);
    dict.setAutoDelete( TRUE );
    dict.insert("Categories" ,     new int(OPimTodo::Category)         );
    dict.insert("Uid" ,            new int(OPimTodo::Uid)              );
    dict.insert("HasDate" ,        new int(OPimTodo::HasDate)          );
    dict.insert("Completed" ,      new int(OPimTodo::Completed)        );
    dict.insert("Description" ,    new int(OPimTodo::Description)      );
    dict.insert("Summary" ,        new int(OPimTodo::Summary)          );
    dict.insert("Priority" ,       new int(OPimTodo::Priority)         );
    dict.insert("DateDay" ,        new int(OPimTodo::DateDay)          );
    dict.insert("DateMonth" ,      new int(OPimTodo::DateMonth)        );
    dict.insert("DateYear" ,       new int(OPimTodo::DateYear)         );
    dict.insert("Progress" ,       new int(OPimTodo::Progress)         );
    dict.insert("CompletedDate",   new int(OPimTodo::CompletedDate)    );
    dict.insert("StartDate",       new int(OPimTodo::StartDate)        );
    dict.insert("CrossReference",  new int(OPimTodo::CrossReference)   );
    dict.insert("State",           new int(OPimTodo::State)            );
    dict.insert("Alarms",          new int(OPimTodo::Alarms)           );
    dict.insert("Reminders",       new int(OPimTodo::Reminders)        );
    dict.insert("Maintainer",      new int(OPimTodo::Maintainer)       );
    dict.insert("rtype",           new int(FRType)                  );
    dict.insert("rweekdays",       new int(FRWeekdays)              );
    dict.insert("rposition",       new int(FRPosition)              );
    dict.insert("rfreq",           new int(FRFreq)                  );
    dict.insert("start",           new int(FRStart)                 );
    dict.insert("rhasenddate",     new int(FRHasEndDate)            );
    dict.insert("enddt",           new int(FREndDate)               );

    // here the custom XML parser from TT it's GPL
    // but we want to push OpiePIM... to TT.....
    // mmap part from zecke :)
    int fd = ::open( QFile::encodeName(m_file).data(), O_RDONLY );
    struct stat attribut;
    if ( fd < 0 ) return false;

    if ( fstat(fd, &attribut ) == -1 ) {
        ::close( fd );
        return false;
    }
    void* map_addr = ::mmap(NULL,  attribut.st_size, PROT_READ, MAP_SHARED, fd, 0 );
    if ( map_addr == ( (caddr_t)-1) ) {
        ::close(fd );
        return false;
    }
    /* advise the kernel who we want to read it */
    ::madvise( map_addr,  attribut.st_size,  MADV_SEQUENTIAL );
    /* we do not the file any more */
    ::close( fd );

    char* dt = (char*)map_addr;
    int len = attribut.st_size;
    int i = 0;
    char *point;
    const char* collectionString = "<Task ";
    int strLen = strlen(collectionString);
    while ( ( point = strstrlen( dt+i, len -i, collectionString, strLen ) ) != 0l ) {
        i = point -dt;
        i+= strLen;

        OPimTodo ev;
        m_year = m_month = m_day = 0;

        while ( TRUE ) {
            while ( i < len && (dt[i] == ' ' || dt[i] == '\n' || dt[i] == '\r') )
        ++i;
        if ( i >= len-2 || (dt[i] == '/' && dt[i+1] == '>') )
        break;

        // we have another attribute, read it.
        int j = i;
        while ( j < len && dt[j] != '=' )
        ++j;
        QCString attr( dt+i, j-i+1);

        i = ++j; // skip =

        // find the start of quotes
        while ( i < len && dt[i] != '"' )
        ++i;
        j = ++i;

        bool haveUtf = FALSE;
        bool haveEnt = FALSE;
        while ( j < len && dt[j] != '"' ) {
        if ( ((unsigned char)dt[j]) > 0x7f )
            haveUtf = TRUE;
        if ( dt[j] == '&' )
            haveEnt = TRUE;
        ++j;
        }
        if ( i == j ) {
        // empty value
        i = j + 1;
        continue;
        }

        QCString value( dt+i, j-i+1 );
        i = j + 1;

        QString str = (haveUtf ? QString::fromUtf8( value )
            : QString::fromLatin1( value ) );
        if ( haveEnt )
        str = Qtopia::plainString( str );

            /*
             * add key + value
             */
            todo( &dict, ev, attr, str );

        }
        /*
         * now add it
         */
        if (m_events.contains( ev.uid() ) || ev.uid() == 0) {
            ev.setUid( 1 );
            m_changed = true;
        }
        if ( ev.hasDueDate() ) {
            ev.setDueDate( QDate(m_year, m_month, m_day) );
        }
        if ( rec && rec->doesRecur() ) {
            OPimTimeZone utc = OPimTimeZone::utc();
            OPimRecurrence recu( *rec ); // call copy c'tor
            recu.setEndDate( utc.fromUTCDateTime( rp_end ).date() );
            recu.setStart( ev.dueDate() );
            ev.setRecurrence( recu );
        }
        m_events.insert(ev.uid(), ev );
        m_year = m_month = m_day = -1;
        delete rec;
        rec = 0;
    }

    munmap(map_addr, attribut.st_size );

    return true;
}
bool OPimTodoAccessText::reload() {
    m_events.clear();
    return load();
}
bool OPimTodoAccessText::save() {
    if (!m_opened || !m_changed ) {
        return true;
    }
    QString strNewFile = m_file + ".new";
    QFile f( strNewFile );
    if (!f.open( IO_WriteOnly|IO_Raw ) )
        return false;

    int written;
    QString out;
    out = "<!DOCTYPE Tasks>\n<Tasks>\n";

    // for all todos
    QMap<int, OPimTodo>::Iterator it;
    for (it = m_events.begin(); it != m_events.end(); ++it ) {
        out+= "<Task " + toString( (*it) ) + " />\n";
        QCString cstr = out.utf8();
        written = f.writeBlock( cstr.data(),  cstr.length() );

        /* less written then we wanted */
        if ( written != (int)cstr.length() ) {
            f.close();
            QFile::remove( strNewFile );
            return false;
        }
        out = QString::null;
    }

    out +=  "</Tasks>";
    QCString cstr = out.utf8();
    written = f.writeBlock( cstr.data(), cstr.length() );

    if ( written != (int)cstr.length() ) {
        f.close();
        QFile::remove( strNewFile );
        return false;
    }
    /* flush before renaming */
    f.close();

    if( ::rename( strNewFile.latin1(),  m_file.latin1() ) < 0 ) {
        QFile::remove( strNewFile );
    }

    m_changed = false;
    return true;
}
QArray<int> OPimTodoAccessText::allRecords()const {
    QArray<int> ids( m_events.count() );
    QMap<int, OPimTodo>::ConstIterator it;
    int i = 0;

    for ( it = m_events.begin(); it != m_events.end(); ++it )
        ids[i++] = it.key();


    return ids;
}

OPimTodo OPimTodoAccessText::find( int uid )const {
    OPimTodo todo;
    todo.setUid( 0 ); // isEmpty()
    QMap<int, OPimTodo>::ConstIterator it = m_events.find( uid );
    if ( it != m_events.end() )
        todo = it.data();

    return todo;
}
void OPimTodoAccessText::clear() {
    if (m_opened )
        m_changed = true;

    m_events.clear();
}
bool OPimTodoAccessText::add( const OPimTodo& todo ) {
    m_changed = true;
    m_events.insert( todo.uid(), todo );

    return true;
}
bool OPimTodoAccessText::remove( int uid ) {
    m_changed = true;
    m_events.remove( uid );

    return true;
}
bool OPimTodoAccessText::replace( const OPimTodo& todo) {
    m_changed = true;
    m_events.replace( todo.uid(), todo );

    return true;
}
QArray<int> OPimTodoAccessText::effectiveToDos( const QDate& start,
                                            const QDate& end,
                                            bool includeNoDates )const {
    QArray<int> ids( m_events.count() );
    QMap<int, OPimTodo>::ConstIterator it;

    int i = 0;
    for ( it = m_events.begin(); it != m_events.end(); ++it ) {
        if ( !it.data().hasDueDate() && includeNoDates) {
                ids[i++] = it.key();
        }else if ( it.data().dueDate() >= start &&
                   it.data().dueDate() <= end ) {
            ids[i++] = it.key();
        }
    }
    ids.resize( i );
    return ids;
}
QArray<int> OPimTodoAccessText::overDue()const {
    QArray<int> ids( m_events.count() );
    int i = 0;

    QMap<int, OPimTodo>::ConstIterator it;
    for ( it = m_events.begin(); it != m_events.end(); ++it ) {
        if ( it.data().isOverdue() ) {
            ids[i] = it.key();
            i++;
        }
    }
    ids.resize( i );
    return ids;
}


/* private */
void OPimTodoAccessText::todo( QAsciiDict<int>* dict, OPimTodo& ev,
                            const QCString& attr, const QString& val) {

    int *find=0;

    find = (*dict)[ attr.data() ];
    if (!find ) {
        ev.setCustomField( attr, val );
        return;
    }

    switch( *find ) {
    case OPimTodo::Uid:
        ev.setUid( val.toInt() );
        break;
    case OPimTodo::Category:
        ev.setCategories( ev.idsFromString( val ) );
        break;
    case OPimTodo::HasDate:
        ev.setHasDueDate( val.toInt() );
        break;
    case OPimTodo::Completed:
        ev.setCompleted( val.toInt() );
        break;
    case OPimTodo::Description:
        ev.setDescription( val );
        break;
    case OPimTodo::Summary:
        ev.setSummary( val );
        break;
    case OPimTodo::Priority:
        ev.setPriority( val.toInt() );
        break;
    case OPimTodo::DateDay:
        m_day = val.toInt();
        break;
    case OPimTodo::DateMonth:
        m_month = val.toInt();
        break;
    case OPimTodo::DateYear:
        m_year = val.toInt();
        break;
    case OPimTodo::Progress:
        ev.setProgress( val.toInt() );
        break;
    case OPimTodo::CompletedDate:
        ev.setCompletedDate( OPimDateConversion::dateFromString( val ) );
        break;
    case OPimTodo::StartDate:
        ev.setStartDate( OPimDateConversion::dateFromString( val ) );
        break;
    case OPimTodo::State:
        ev.setState( val.toInt() );
        break;
    case OPimTodo::Alarms:{
        OPimNotifyManager &manager = ev.notifiers();
        QStringList als = QStringList::split(";", val );
        for (QStringList::Iterator it = als.begin(); it != als.end(); ++it ) {
            QStringList alarm = QStringList::split(":", (*it), TRUE ); // allow empty
            OPimAlarm al( alarm[2].toInt(), OPimDateConversion::dateTimeFromString( alarm[0] ), alarm[1].toInt() );
            manager.add( al );
        }
    }
        break;
    case OPimTodo::Reminders:{
        OPimNotifyManager &manager = ev.notifiers();
        QStringList rems = QStringList::split(";", val );
        for (QStringList::Iterator it = rems.begin(); it != rems.end(); ++it ) {
            OPimReminder rem( (*it).toInt() );
            manager.add( rem );
        }
    }
        break;
    case OPimTodo::CrossReference:
    {
        /*
         * A cross refernce looks like
         * appname,id;appname,id
         * we need to split it up
         */
        QStringList  refs = QStringList::split(';', val );
        QStringList::Iterator strIt;
        for (strIt = refs.begin(); strIt != refs.end(); ++strIt ) {
            int pos = (*strIt).find(',');
            if ( pos > -1 )
                ; // ev.addRelation( (*strIt).left(pos),  (*strIt).mid(pos+1).toInt() );

        }
        break;
    }
    /* Recurrence stuff below + post processing later */
    case FRType:
        if ( val == "Daily" )
            recur()->setType( OPimRecurrence::Daily );
        else if ( val == "Weekly" )
            recur()->setType( OPimRecurrence::Weekly);
        else if ( val == "MonthlyDay" )
            recur()->setType( OPimRecurrence::MonthlyDay );
        else if ( val == "MonthlyDate" )
            recur()->setType( OPimRecurrence::MonthlyDate );
        else if ( val == "Yearly" )
            recur()->setType( OPimRecurrence::Yearly );
        else
            recur()->setType( OPimRecurrence::NoRepeat );
        break;
    case FRWeekdays:
        recur()->setDays( val.toInt() );
        break;
    case FRPosition:
        recur()->setPosition( val.toInt() );
        break;
    case FRFreq:
        recur()->setFrequency( val.toInt() );
        break;
    case FRHasEndDate:
        recur()->setHasEndDate( val.toInt() );
        break;
    case FREndDate: {
        rp_end = (time_t) val.toLong();
        break;
    }
    default:
        ev.setCustomField( attr, val );
        break;
    }
}

// from PalmtopRecord... GPL ### FIXME
namespace {
QString customToText(const QMap<QString, QString>& customMap )
{
    QString buf(" ");
    for ( QMap<QString, QString>::ConstIterator cit = customMap.begin();
        cit != customMap.end(); ++cit) {
    buf += cit.key();
    buf += "=\"";
    buf += Qtopia::escapeString(cit.data());
    buf += "\" ";
    }
    return buf;
}


}

QString OPimTodoAccessText::toString( const OPimTodo& ev )const {
    QString str;

    str += "Completed=\"" + QString::number( ev.isCompleted() ) + "\" ";
    str += "HasDate=\"" + QString::number( ev.hasDueDate() ) + "\" ";
    str += "Priority=\"" + QString::number( ev.priority() ) + "\" ";
    str += "Progress=\"" + QString::number(ev.progress() ) + "\" ";

    str += "Categories=\"" + toString( ev.categories() ) + "\" ";
    str += "Description=\"" + Qtopia::escapeString( ev.description() ) + "\" ";
    str += "Summary=\"" + Qtopia::escapeString( ev.summary() ) + "\" ";

    if ( ev.hasDueDate() ) {
        str += "DateYear=\"" + QString::number( ev.dueDate().year() ) + "\" ";
        str += "DateMonth=\"" + QString::number( ev.dueDate().month() ) + "\" ";
        str += "DateDay=\"" + QString::number( ev.dueDate().day() ) + "\" ";
    }
    str += "Uid=\"" + QString::number( ev.uid() ) + "\" ";

// append the extra options
    /* FIXME Qtopia::Record this is currently not
     * possible you can set custom fields
     * but don' iterate over the list
     * I may do #define private protected
     * for this case - cough  --zecke
     */
    /*
    QMap<QString, QString> extras = ev.extras();
    QMap<QString, QString>::Iterator extIt;
    for (extIt = extras.begin(); extIt != extras.end(); ++extIt )
        str += extIt.key() + "=\"" +  extIt.data() + "\" ";
    */
    // cross refernce
    if ( ev.hasRecurrence() ) {
        str += ev.recurrence().toString();
    }
    if ( ev.hasStartDate() )
        str += "StartDate=\""+ OPimDateConversion::dateToString( ev.startDate() ) +"\" ";
    if ( ev.hasCompletedDate() )
        str += "CompletedDate=\""+ OPimDateConversion::dateToString( ev.completedDate() ) +"\" ";
    if ( ev.hasState() )
        str += "State=\""+QString::number( ev.state().state() )+"\" ";

    /*
     * save reminders and notifiers!
     * DATE_TIME:DURATION:SOUND:NOT_USED_YET;OTHER_DATE_TIME:OTHER_DURATION:SOUND:....
     */
    if ( ev.hasNotifiers() ) {
        OPimNotifyManager manager = ev.notifiers();
        OPimNotifyManager::Alarms alarms = manager.alarms();
        if (!alarms.isEmpty() ) {
            QStringList als;
            OPimNotifyManager::Alarms::Iterator it = alarms.begin();
            for ( ; it != alarms.end(); ++it ) {
                /* only if time is valid */
                if ( (*it).dateTime().isValid() ) {
                    als << OPimDateConversion::dateTimeToString( (*it).dateTime() )
                        + ":" + QString::number( (*it).duration() )
                        + ":" + QString::number( (*it).sound() )
                        + ":";
                }
            }
            // now write the list
            str += "Alarms=\""+als.join(";") +"\" ";
        }

        /*
         * now the same for reminders but more easy. We just save the uid of the OPimEvent.
         */
        OPimNotifyManager::Reminders reminders = manager.reminders();
        if (!reminders.isEmpty() ) {
            OPimNotifyManager::Reminders::Iterator it = reminders.begin();
            QStringList records;
            for ( ; it != reminders.end(); ++it ) {
                records << QString::number( (*it).recordUid() );
            }
            str += "Reminders=\""+ records.join(";") +"\" ";
        }
    }
    str += customToText( ev.toExtraMap() );


    return str;
}
QString OPimTodoAccessText::toString( const QArray<int>& ints ) const {
    return Qtopia::Record::idsToString( ints );
}


QArray<int> OPimTodoAccessText::sorted( const UIDArray& events, bool asc,
                                       int sortOrder,int sortFilter,
                                       const QArray<int>& categories )const {
    Internal::OPimTodoSortVector vector(events.count(), asc,sortOrder );
    int item = 0;

    bool bCat = sortFilter  & OPimTodoAccess::FilterCategory ? true : false;
    bool bOnly = sortFilter & OPimTodoAccess::OnlyOverDue ? true : false;
    bool comp = sortFilter  & OPimTodoAccess::DoNotShowCompleted ? true : false;
    bool catPassed = false;
    int cat;

    for ( uint i = 0; i < events.count(); ++i ) {
        /* Guard against creating a new item... */
        if ( !m_events.contains( events[i] ) )
            continue;

        OPimTodo todo = m_events[events[i]];

        /* show category */
        /* -1 == unfiled */
        catPassed = false;
        for ( uint cat_nu = 0; cat_nu < categories.count(); ++cat_nu ) {
            cat = categories[cat_nu];
            if ( bCat && cat == -1 ) {
                if(!todo.categories().isEmpty() )
                    continue;
            } else if ( bCat && cat != 0)
                if (!todo.categories().contains( cat ) )
                    continue;
            catPassed = true;
            break;
        }

        /*
         * If none of the Categories matched
         * continue
         */
        if ( !catPassed )
            continue;
        if ( !todo.isOverdue() && bOnly )
            continue;
        if (todo.isCompleted() && comp )
            continue;

        vector.insert(item++, todo );
    }

    vector.resize( item );
    /* sort it now */
    vector.sort();
    /* now get the uids */
    UIDArray array( vector.count() );
    for (uint i= 0; i < vector.count(); i++ )
        array[i] = vector.uidAt( i );

    return array;
}

void OPimTodoAccessText::removeAllCompleted() {
    QMap<int, OPimTodo> events = m_events;
    for ( QMap<int, OPimTodo>::Iterator it = m_events.begin(); it != m_events.end(); ++it ) {
        if ( (*it).isCompleted() )
            events.remove( it.key() );
    }
    m_events = events;
}

QArray<int> OPimTodoAccessText::matchRegexp(  const QRegExp &r ) const
{
    QArray<int> currentQuery( m_events.count() );
    uint arraycounter = 0;

    QMap<int, OPimTodo>::ConstIterator it;
    for (it = m_events.begin(); it != m_events.end(); ++it ) {
        if ( it.data().match( r ) )
            currentQuery[arraycounter++] = it.data().uid();

    }
    // Shrink to fit..
    currentQuery.resize(arraycounter);

    return currentQuery;
}

}
otodoaccesstext.h (text/x-chdr, 3 KB)
/*
                             This file is part of the Opie Project
                             Copyright (C) Stefan Eilers ([email protected])
              =.             Copyright (C) The Opie Team <[email protected]>
            .=l.
           .>+-=
 _;:,     .>    :=|.         This program is free software; you can
.> <`_,   >  .   <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.--   :           the terms of the GNU Library General Public
.="- .-=="i,     .._         License as published by the Free Software
 - .   .-<_>     .<>         Foundation; either version 2 of the License,
     ._= =}       :          or (at your option) any later version.
    .%`+i>       _;_.
    .i_,=:_.      -<s.       This program is distributed in the hope that
     +  .  -:.       =       it will be useful,  but WITHOUT ANY WARRANTY;
    : ..    .:,     . . .    without even the implied warranty of
    =_        +     =;=|`    MERCHANTABILITY or FITNESS FOR A
  _.=:.       :    :=>`:     PARTICULAR PURPOSE. See the GNU
..}^=.=       =       ;      Library General Public License for more
++=   -.     .`     .:       details.
 :     =  ...= . :.=-
 -.   .:....=;==+<;          You should have received a copy of the GNU
  -_. . .   )=.  =           Library General Public License along with
    --        :-=`           this library; see the file COPYING.LIB.
                             If not, write to the Free Software Foundation,
                             Inc., 59 Temple Place - Suite 330,
                             Boston, MA 02111-1307, USA.
*/
#ifndef OPIE_TODO_ACCESS_TEXT_H
#define OPIE_TODO_ACCESS_TEXT_H

#include <qasciidict.h>
#include <qmap.h>

#include <opie2/otodoaccessbackend.h>

namespace Opie {
    class TextElement;

class OPimTodoAccessText : public OPimTodoAccessBackend {
public:
    /**
     * fileName if Empty we will use the default path
     */
    OPimTodoAccessText( const QString& appName,
                    const QString& fileName = QString::null );
    ~OPimTodoAccessText();

    bool load();
    bool reload();
    bool save();

    QArray<int> allRecords()const;
    QArray<int> matchRegexp(const QRegExp &r) const;
    OPimTodo find( int uid )const;
    void clear();
    bool add( const OPimTodo& );
    bool remove( int uid );
    void removeAllCompleted();
    bool replace( const OPimTodo& );

    /* our functions */
    QArray<int> effectiveToDos( const QDate& start,
                                const QDate& end,
                                bool includeNoDates )const;
    QArray<int> overDue()const;

//@{
    UIDArray sorted( const UIDArray&, bool, int, int, const QArray<int>& )const;
//@}
private:
    void todo( QAsciiDict<int>*, OPimTodo&,const QCString&,const QString& );
    QString toString( const OPimTodo& )const;
    QString toString( const QArray<int>& ints ) const;
    QMap<int, OPimTodo> m_events;
    QString m_file;
    QString m_app;
    bool m_opened : 1;
    bool m_changed : 1;
    class OPimTodoAccessTextPrivate;
    OPimTodoAccessTextPrivate* d;
    int m_year, m_month, m_day;
};

};

#endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.