/* This file is part of the Opie Project Copyright (C) Stefan Eilers (Eilers.Stefan@epost.de) =. Copyright (C) The Opie Team .=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_,=:_. -`: 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 #include #include #include #include #include #include #include #include #include #include #include /* QT */ #include #include /* STD */ #include #include #include #include #include #include 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 { OPimTodoAccessXML::OPimTodoAccessXML( 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.xml" ); } OPimTodoAccessXML::~OPimTodoAccessXML() { } bool OPimTodoAccessXML::load() { rec = 0; m_opened = true; m_changed = false; /* initialize dict */ /* * UPDATE dict if you change anything!!! */ QAsciiDict 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 = "= 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 OPimTodoAccessXML::reload() { m_events.clear(); return load(); } bool OPimTodoAccessXML::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 = "\n\n"; // for all todos QMap::Iterator it; for (it = m_events.begin(); it != m_events.end(); ++it ) { out+= "\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 += ""; 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 OPimTodoAccessXML::allRecords()const { QArray ids( m_events.count() ); QMap::ConstIterator it; int i = 0; for ( it = m_events.begin(); it != m_events.end(); ++it ) ids[i++] = it.key(); return ids; } OPimTodo OPimTodoAccessXML::find( int uid )const { OPimTodo todo; todo.setUid( 0 ); // isEmpty() QMap::ConstIterator it = m_events.find( uid ); if ( it != m_events.end() ) todo = it.data(); return todo; } void OPimTodoAccessXML::clear() { if (m_opened ) m_changed = true; m_events.clear(); } bool OPimTodoAccessXML::add( const OPimTodo& todo ) { m_changed = true; m_events.insert( todo.uid(), todo ); return true; } bool OPimTodoAccessXML::remove( int uid ) { m_changed = true; m_events.remove( uid ); return true; } bool OPimTodoAccessXML::replace( const OPimTodo& todo) { m_changed = true; m_events.replace( todo.uid(), todo ); return true; } QArray OPimTodoAccessXML::effectiveToDos( const QDate& start, const QDate& end, bool includeNoDates )const { QArray ids( m_events.count() ); QMap::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 OPimTodoAccessXML::overDue()const { QArray ids( m_events.count() ); int i = 0; QMap::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 OPimTodoAccessXML::todo( QAsciiDict* 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 customToXml(const QMap& customMap ) { QString buf(" "); for ( QMap::ConstIterator cit = customMap.begin(); cit != customMap.end(); ++cit) { buf += cit.key(); buf += "=\""; buf += Qtopia::escapeString(cit.data()); buf += "\" "; } return buf; } } QString OPimTodoAccessXML::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 extras = ev.extras(); QMap::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 += customToXml( ev.toExtraMap() ); return str; } QString OPimTodoAccessXML::toString( const QArray& ints ) const { return Qtopia::Record::idsToString( ints ); } QArray OPimTodoAccessXML::sorted( const UIDArray& events, bool asc, int sortOrder,int sortFilter, const QArray& 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 OPimTodoAccessXML::removeAllCompleted() { QMap events = m_events; for ( QMap::Iterator it = m_events.begin(); it != m_events.end(); ++it ) { if ( (*it).isCompleted() ) events.remove( it.key() ); } m_events = events; } QArray OPimTodoAccessXML::matchRegexp( const QRegExp &r ) const { QArray currentQuery( m_events.count() ); uint arraycounter = 0; QMap::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; } }