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