00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include <opie2/opimnotifymanager.h>
00032 #include <opie2/opimrecurrence.h>
00033 #include <opie2/opimtimezone.h>
00034 #include <opie2/odatebookaccessbackend_xml.h>
00035 #include <opie2/odebug.h>
00036
00037 #include <qtopia/global.h>
00038 #include <qtopia/stringutil.h>
00039 #include <qtopia/timeconversion.h>
00040
00041
00042 #include <qasciidict.h>
00043 #include <qfile.h>
00044
00045
00046 #include <errno.h>
00047 #include <fcntl.h>
00048
00049 #include <stdio.h>
00050 #include <stdlib.h>
00051
00052 #include <sys/types.h>
00053 #include <sys/mman.h>
00054 #include <sys/stat.h>
00055
00056 #include <unistd.h>
00057
00058
00059 using namespace Opie;
00060
00061 namespace {
00062
00063 char *strstrlen(const char *haystack, int hLen, const char* needle, int nLen)
00064 {
00065 char needleChar;
00066 char haystackChar;
00067 if (!needle || !haystack || !hLen || !nLen)
00068 return 0;
00069
00070 const char* hsearch = haystack;
00071
00072 if ((needleChar = *needle++) != 0) {
00073 nLen--;
00074 do {
00075 do {
00076 if ((haystackChar = *hsearch++) == 0)
00077 return (0);
00078 if (hsearch >= haystack + hLen)
00079 return (0);
00080 } while (haystackChar != needleChar);
00081 } while (strncmp(hsearch, needle, QMIN(hLen - (hsearch - haystack), nLen)) != 0);
00082 hsearch--;
00083 }
00084 return ((char *)hsearch);
00085 }
00086 }
00087
00088 namespace {
00089 time_t start, end, created, rp_end;
00090 OPimRecurrence* rec;
00091 static OPimRecurrence* recur() {
00092 if (!rec)
00093 rec = new OPimRecurrence;
00094
00095 return rec;
00096 }
00097 int alarmTime;
00098 int snd;
00099 enum Attribute{
00100 FDescription = 0,
00101 FLocation,
00102 FCategories,
00103 FUid,
00104 FType,
00105 FAlarm,
00106 FSound,
00107 FRType,
00108 FRWeekdays,
00109 FRPosition,
00110 FRFreq,
00111 FRHasEndDate,
00112 FREndDate,
00113 FRStart,
00114 FREnd,
00115 FNote,
00116 FCreated,
00117 FTimeZone,
00118 FRecParent,
00119 FRecChildren,
00120 FExceptions
00121 };
00122
00123
00124 static void save( const OPimEvent& ev, QString& buf ) {
00125 buf += " description=\"" + Qtopia::escapeString(ev.description() ) + "\"";
00126 if (!ev.location().isEmpty() )
00127 buf += " location=\"" + Qtopia::escapeString(ev.location() ) + "\"";
00128
00129 if (!ev.categories().isEmpty() )
00130 buf += " categories=\""+ Qtopia::escapeString( Qtopia::Record::idsToString( ev.categories() ) ) + "\"";
00131
00132 buf += " uid=\"" + QString::number( ev.uid() ) + "\"";
00133
00134 if (ev.isAllDay() )
00135 buf += " type=\"AllDay\"";
00136
00137 if (ev.hasNotifiers() ) {
00138 OPimAlarm alarm = ev.notifiers().alarms()[0];
00139 int minutes = alarm.dateTime().secsTo( ev.startDateTime() ) / 60;
00140 buf += " alarm=\"" + QString::number(minutes) + "\" sound=\"";
00141 if ( alarm.sound() == OPimAlarm::Loud )
00142 buf += "loud";
00143 else
00144 buf += "silent";
00145 buf += "\"";
00146 }
00147 if ( ev.hasRecurrence() ) {
00148 buf += ev.recurrence().toString();
00149 }
00150
00151
00152
00153
00154
00155
00156 OPimTimeZone zone( (ev.timeZone().isEmpty()||ev.isAllDay()) ? OPimTimeZone::utc() : OPimTimeZone::current() );
00157 buf += " start=\"" + QString::number( zone.fromDateTime( ev.startDateTime())) + "\"";
00158 buf += " end=\"" + QString::number( zone.fromDateTime( ev.endDateTime() )) + "\"";
00159 if (!ev.note().isEmpty() ) {
00160 buf += " note=\"" + Qtopia::escapeString( ev.note() ) + "\"";
00161 }
00162
00163
00164
00165
00166
00167 if (!ev.isAllDay() ) {
00168
00169 buf += " timezone=\"";
00170 if ( ev.timeZone().isEmpty() )
00171 buf += "None";
00172 else
00173 buf += ev.timeZone();
00174 buf += "\"";
00175 }
00176
00177 if (ev.parent() != 0 ) {
00178 buf += " recparent=\""+QString::number(ev.parent() )+"\"";
00179 }
00180
00181 if (ev.children().count() != 0 ) {
00182 QArray<int> children = ev.children();
00183 buf += " recchildren=\"";
00184 for ( uint i = 0; i < children.count(); i++ ) {
00185 if ( i != 0 ) buf += " ";
00186 buf += QString::number( children[i] );
00187 }
00188 buf+= "\"";
00189 }
00190
00191
00192 }
00193
00194 static bool saveEachEvent( const QMap<int, OPimEvent>& list, QFile& file ) {
00195 QMap<int, OPimEvent>::ConstIterator it;
00196 QString buf;
00197 QCString str;
00198 int total_written;
00199 for ( it = list.begin(); it != list.end(); ++it ) {
00200 buf = "<event";
00201 save( it.data(), buf );
00202 buf += " />\n";
00203 str = buf.utf8();
00204
00205 total_written = file.writeBlock(str.data(), str.length() );
00206 if ( total_written != int(str.length() ) )
00207 return false;
00208 }
00209 return true;
00210 }
00211 }
00212
00213 namespace Opie {
00214 ODateBookAccessBackend_XML::ODateBookAccessBackend_XML( const QString& ,
00215 const QString& fileName )
00216 : ODateBookAccessBackend() {
00217 m_name = fileName.isEmpty() ? Global::applicationFileName( "datebook", "datebook.xml" ) : fileName;
00218 m_changed = false;
00219 }
00220 ODateBookAccessBackend_XML::~ODateBookAccessBackend_XML() {
00221 }
00222 bool ODateBookAccessBackend_XML::load() {
00223 return loadFile();
00224 }
00225 bool ODateBookAccessBackend_XML::reload() {
00226 clear();
00227 return load();
00228 }
00229 bool ODateBookAccessBackend_XML::save() {
00230 if (!m_changed) return true;
00231
00232 int total_written;
00233 QString strFileNew = m_name + ".new";
00234
00235 QFile f( strFileNew );
00236 if (!f.open( IO_WriteOnly | IO_Raw ) ) return false;
00237
00238 QString buf( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
00239 buf += "<!DOCTYPE DATEBOOK><DATEBOOK>\n";
00240 buf += "<events>\n";
00241 QCString str = buf.utf8();
00242 total_written = f.writeBlock( str.data(), str.length() );
00243 if ( total_written != int(str.length() ) ) {
00244 f.close();
00245 QFile::remove( strFileNew );
00246 return false;
00247 }
00248
00249 if (!saveEachEvent( m_raw, f ) ) {
00250 f.close();
00251 QFile::remove( strFileNew );
00252 return false;
00253 }
00254 if (!saveEachEvent( m_rep, f ) ) {
00255 f.close();
00256 QFile::remove( strFileNew );
00257 return false;
00258 }
00259
00260 buf = "</events>\n</DATEBOOK>\n";
00261 str = buf.utf8();
00262 total_written = f.writeBlock( str.data(), str.length() );
00263 if ( total_written != int(str.length() ) ) {
00264 f.close();
00265 QFile::remove( strFileNew );
00266 return false;
00267 }
00268 f.close();
00269
00270 if ( ::rename( strFileNew, m_name ) < 0 ) {
00271 QFile::remove( strFileNew );
00272 return false;
00273 }
00274
00275 m_changed = false;
00276 return true;
00277 }
00278 QArray<int> ODateBookAccessBackend_XML::allRecords()const {
00279 QArray<int> ints( m_raw.count()+ m_rep.count() );
00280 uint i = 0;
00281 QMap<int, OPimEvent>::ConstIterator it;
00282
00283 for ( it = m_raw.begin(); it != m_raw.end(); ++it ) {
00284 ints[i] = it.key();
00285 i++;
00286 }
00287 for ( it = m_rep.begin(); it != m_rep.end(); ++it ) {
00288 ints[i] = it.key();
00289 i++;
00290 }
00291
00292 return ints;
00293 }
00294 QArray<int> ODateBookAccessBackend_XML::queryByExample(const OPimEvent&, int, const QDateTime& ) {
00295 return QArray<int>();
00296 }
00297 void ODateBookAccessBackend_XML::clear() {
00298 m_changed = true;
00299 m_raw.clear();
00300 m_rep.clear();
00301 }
00302 OPimEvent ODateBookAccessBackend_XML::find( int uid ) const{
00303 if ( m_raw.contains( uid ) )
00304 return m_raw[uid];
00305 else
00306 return m_rep[uid];
00307 }
00308 bool ODateBookAccessBackend_XML::add( const OPimEvent& ev ) {
00309 m_changed = true;
00310 if (ev.hasRecurrence() )
00311 m_rep.insert( ev.uid(), ev );
00312 else
00313 m_raw.insert( ev.uid(), ev );
00314
00315 return true;
00316 }
00317 bool ODateBookAccessBackend_XML::remove( int uid ) {
00318 m_changed = true;
00319 m_raw.remove( uid );
00320 m_rep.remove( uid );
00321
00322 return true;
00323 }
00324 bool ODateBookAccessBackend_XML::replace( const OPimEvent& ev ) {
00325 replace( ev.uid() );
00326 return add( ev );
00327 }
00328
00329 QArray<int> ODateBookAccessBackend_XML::rawRepeats()const {
00330 QArray<int> ints( m_rep.count() );
00331 uint i = 0;
00332 QMap<int, OPimEvent>::ConstIterator it;
00333
00334 for ( it = m_rep.begin(); it != m_rep.end(); ++it ) {
00335 ints[i] = it.key();
00336 i++;
00337 }
00338
00339 return ints;
00340 }
00341 QArray<int> ODateBookAccessBackend_XML::nonRepeats()const {
00342 QArray<int> ints( m_raw.count() );
00343 uint i = 0;
00344 QMap<int, OPimEvent>::ConstIterator it;
00345
00346 for ( it = m_raw.begin(); it != m_raw.end(); ++it ) {
00347 ints[i] = it.key();
00348 i++;
00349 }
00350
00351 return ints;
00352 }
00353 OPimEvent::ValueList ODateBookAccessBackend_XML::directNonRepeats()const {
00354 OPimEvent::ValueList list;
00355 QMap<int, OPimEvent>::ConstIterator it;
00356 for (it = m_raw.begin(); it != m_raw.end(); ++it )
00357 list.append( it.data() );
00358
00359 return list;
00360 }
00361 OPimEvent::ValueList ODateBookAccessBackend_XML::directRawRepeats()const {
00362 OPimEvent::ValueList list;
00363 QMap<int, OPimEvent>::ConstIterator it;
00364 for (it = m_rep.begin(); it != m_rep.end(); ++it )
00365 list.append( it.data() );
00366
00367 return list;
00368 }
00369
00370
00371 bool ODateBookAccessBackend_XML::loadFile() {
00372 m_changed = false;
00373
00374 int fd = ::open( QFile::encodeName(m_name).data(), O_RDONLY );
00375 if ( fd < 0 ) return false;
00376
00377 struct stat attribute;
00378 if ( ::fstat(fd, &attribute ) == -1 ) {
00379 ::close( fd );
00380 return false;
00381 }
00382 void* map_addr = ::mmap(NULL, attribute.st_size, PROT_READ, MAP_SHARED, fd, 0 );
00383 if ( map_addr == ( (caddr_t)-1) ) {
00384 ::close( fd );
00385 return false;
00386 }
00387
00388 ::madvise( map_addr, attribute.st_size, MADV_SEQUENTIAL );
00389 ::close( fd );
00390
00391 QAsciiDict<int> dict(FExceptions+1);
00392 dict.setAutoDelete( true );
00393 dict.insert( "description", new int(FDescription) );
00394 dict.insert( "location", new int(FLocation) );
00395 dict.insert( "categories", new int(FCategories) );
00396 dict.insert( "uid", new int(FUid) );
00397 dict.insert( "type", new int(FType) );
00398 dict.insert( "alarm", new int(FAlarm) );
00399 dict.insert( "sound", new int(FSound) );
00400 dict.insert( "rtype", new int(FRType) );
00401 dict.insert( "rweekdays", new int(FRWeekdays) );
00402 dict.insert( "rposition", new int(FRPosition) );
00403 dict.insert( "rfreq", new int(FRFreq) );
00404 dict.insert( "rhasenddate", new int(FRHasEndDate) );
00405 dict.insert( "enddt", new int(FREndDate) );
00406 dict.insert( "start", new int(FRStart) );
00407 dict.insert( "end", new int(FREnd) );
00408 dict.insert( "note", new int(FNote) );
00409 dict.insert( "created", new int(FCreated) );
00410 dict.insert( "recparent", new int(FRecParent) );
00411 dict.insert( "recchildren", new int(FRecChildren) );
00412 dict.insert( "exceptions", new int(FExceptions) );
00413 dict.insert( "timezone", new int(FTimeZone) );
00414
00415
00416
00417 m_noTimeZone = true;
00418
00419 char* dt = (char*)map_addr;
00420 int len = attribute.st_size;
00421 int i = 0;
00422 char* point;
00423 const char* collectionString = "<event ";
00424 int strLen = ::strlen(collectionString);
00425 int *find;
00426 while ( ( point = ::strstrlen( dt+i, len -i, collectionString, strLen ) ) != 0 ) {
00427 i = point -dt;
00428 i+= strLen;
00429
00430 alarmTime = -1;
00431 snd = 0;
00432
00433 OPimEvent ev;
00434 rec = 0;
00435
00436 while ( TRUE ) {
00437 while ( i < len && (dt[i] == ' ' || dt[i] == '\n' || dt[i] == '\r') )
00438 ++i;
00439 if ( i >= len-2 || (dt[i] == '/' && dt[i+1] == '>') )
00440 break;
00441
00442
00443
00444 int j = i;
00445 while ( j < len && dt[j] != '=' )
00446 ++j;
00447 QCString attr( dt+i, j-i+1);
00448
00449 i = ++j;
00450
00451
00452 while ( i < len && dt[i] != '"' )
00453 ++i;
00454 j = ++i;
00455
00456 bool haveUtf = FALSE;
00457 bool haveEnt = FALSE;
00458 while ( j < len && dt[j] != '"' ) {
00459 if ( ((unsigned char)dt[j]) > 0x7f )
00460 haveUtf = TRUE;
00461 if ( dt[j] == '&' )
00462 haveEnt = TRUE;
00463 ++j;
00464 }
00465 if ( i == j ) {
00466
00467 i = j + 1;
00468 continue;
00469 }
00470
00471 QCString value( dt+i, j-i+1 );
00472 i = j + 1;
00473
00474 QString str = (haveUtf ? QString::fromUtf8( value )
00475 : QString::fromLatin1( value ) );
00476 if ( haveEnt )
00477 str = Qtopia::plainString( str );
00478
00479
00480
00481
00482 find = dict[attr.data()];
00483 if (!find)
00484 ev.setCustomField( attr, str );
00485 else {
00486 setField( ev, *find, str );
00487 }
00488 }
00489
00490 finalizeRecord( ev );
00491 delete rec;
00492 m_noTimeZone = true;
00493 }
00494 ::munmap(map_addr, attribute.st_size );
00495 m_changed = false;
00496
00497 return true;
00498 }
00499
00500
00501 void ODateBookAccessBackend_XML::finalizeRecord( OPimEvent& ev ) {
00502
00503
00504
00505
00506
00507 if (m_noTimeZone )
00508 ev.setTimeZone( OPimTimeZone::current().timeZone() );
00509
00510
00511
00512
00513 if ( ev.isAllDay() ) {
00514 OPimTimeZone utc = OPimTimeZone::utc();
00515 ev.setStartDateTime( utc.toDateTime( start ) );
00516 ev.setEndDateTime ( utc.toDateTime( end ) );
00517 }else {
00518
00519 OPimTimeZone to_zone( ev.timeZone().isEmpty() ? OPimTimeZone::utc() : OPimTimeZone::current() );
00520
00521 ev.setStartDateTime(to_zone.toDateTime( start));
00522 ev.setEndDateTime (to_zone.toDateTime( end));
00523 }
00524 if ( rec && rec->doesRecur() ) {
00525 OPimTimeZone utc = OPimTimeZone::utc();
00526 OPimRecurrence recu( *rec );
00527 recu.setEndDate ( utc.toDateTime( rp_end ).date() );
00528 recu.setCreatedDateTime( utc.toDateTime( created ) );
00529 recu.setStart( ev.startDateTime().date() );
00530 ev.setRecurrence( recu );
00531 }
00532
00533 if (alarmTime != -1 ) {
00534 QDateTime dt = ev.startDateTime().addSecs( -1*alarmTime*60 );
00535 OPimAlarm al( snd , dt );
00536 ev.notifiers().add( al );
00537 }
00538 if ( m_raw.contains( ev.uid() ) || m_rep.contains( ev.uid() ) ) {
00539 ev.setUid( 1 );
00540 }
00541
00542 if ( ev.hasRecurrence() )
00543 m_rep.insert( ev.uid(), ev );
00544 else
00545 m_raw.insert( ev.uid(), ev );
00546
00547 }
00548 void ODateBookAccessBackend_XML::setField( OPimEvent& e, int id, const QString& value) {
00549 switch( id ) {
00550 case FDescription:
00551 e.setDescription( value );
00552 break;
00553 case FLocation:
00554 e.setLocation( value );
00555 break;
00556 case FCategories:
00557 e.setCategories( e.idsFromString( value ) );
00558 break;
00559 case FUid:
00560 e.setUid( value.toInt() );
00561 break;
00562 case FType:
00563 if ( value == "AllDay" ) {
00564 e.setAllDay( true );
00565 }
00566 break;
00567 case FAlarm:
00568 alarmTime = value.toInt();
00569 break;
00570 case FSound:
00571 snd = value == "loud" ? OPimAlarm::Loud : OPimAlarm::Silent;
00572 break;
00573
00574 case FRType:
00575 if ( value == "Daily" )
00576 recur()->setType( OPimRecurrence::Daily );
00577 else if ( value == "Weekly" )
00578 recur()->setType( OPimRecurrence::Weekly);
00579 else if ( value == "MonthlyDay" )
00580 recur()->setType( OPimRecurrence::MonthlyDay );
00581 else if ( value == "MonthlyDate" )
00582 recur()->setType( OPimRecurrence::MonthlyDate );
00583 else if ( value == "Yearly" )
00584 recur()->setType( OPimRecurrence::Yearly );
00585 else
00586 recur()->setType( OPimRecurrence::NoRepeat );
00587 break;
00588 case FRWeekdays:
00589 recur()->setDays( value.toInt() );
00590 break;
00591 case FRPosition:
00592 recur()->setPosition( value.toInt() );
00593 break;
00594 case FRFreq:
00595 recur()->setFrequency( value.toInt() );
00596 break;
00597 case FRHasEndDate:
00598 recur()->setHasEndDate( value.toInt() );
00599 break;
00600 case FREndDate: {
00601 rp_end = (time_t) value.toLong();
00602 break;
00603 }
00604 case FRStart: {
00605 start = (time_t) value.toLong();
00606 break;
00607 }
00608 case FREnd: {
00609 end = ( (time_t) value.toLong() );
00610 break;
00611 }
00612 case FNote:
00613 e.setNote( value );
00614 break;
00615 case FCreated:
00616 created = value.toInt();
00617 break;
00618 case FRecParent:
00619 e.setParent( value.toInt() );
00620 break;
00621 case FRecChildren:{
00622 QStringList list = QStringList::split(' ', value );
00623 for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
00624 e.addChild( (*it).toInt() );
00625 }
00626 }
00627 break;
00628 case FExceptions:{
00629 QStringList list = QStringList::split(' ', value );
00630 for (QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
00631 QDate date( (*it).left(4).toInt(), (*it).mid(4, 2).toInt(), (*it).right(2).toInt() );
00632 recur()->exceptions().append( date );
00633 }
00634 }
00635 break;
00636 case FTimeZone:
00637 m_noTimeZone = false;
00638 if ( value != "None" )
00639 e.setTimeZone( value );
00640 break;
00641 default:
00642 break;
00643 }
00644 }
00645 QArray<int> ODateBookAccessBackend_XML::matchRegexp( const QRegExp &r ) const
00646 {
00647 QArray<int> m_currentQuery( m_raw.count()+ m_rep.count() );
00648 uint arraycounter = 0;
00649 QMap<int, OPimEvent>::ConstIterator it;
00650
00651 for ( it = m_raw.begin(); it != m_raw.end(); ++it )
00652 if ( it.data().match( r ) )
00653 m_currentQuery[arraycounter++] = it.data().uid();
00654 for ( it = m_rep.begin(); it != m_rep.end(); ++it )
00655 if ( it.data().match( r ) )
00656 m_currentQuery[arraycounter++] = it.data().uid();
00657
00658
00659 m_currentQuery.resize(arraycounter);
00660
00661 return m_currentQuery;
00662 }
00663
00664 }