KCalCore Library
icalformat.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the kcalcore library. 00003 00004 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License as published by the Free Software Foundation; either 00009 version 2 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00031 #include "icalformat.h" 00032 #include "icalformat_p.h" 00033 #include "icaltimezones.h" 00034 #include "freebusy.h" 00035 #include "memorycalendar.h" 00036 00037 #include <KDebug> 00038 #include <KSaveFile> 00039 00040 #include <QtCore/QFile> 00041 00042 extern "C" { 00043 #include <libical/ical.h> 00044 #include <libical/icalss.h> 00045 #include <libical/icalparser.h> 00046 #include <libical/icalrestriction.h> 00047 #include <libical/icalmemory.h> 00048 } 00049 00050 using namespace KCalCore; 00051 00052 //@cond PRIVATE 00053 class KCalCore::ICalFormat::Private 00054 { 00055 public: 00056 Private( ICalFormat *parent ) 00057 : mImpl( new ICalFormatImpl( parent ) ), 00058 mTimeSpec( KDateTime::UTC ) 00059 {} 00060 ~Private() { delete mImpl; } 00061 ICalFormatImpl *mImpl; 00062 KDateTime::Spec mTimeSpec; 00063 }; 00064 //@endcond 00065 00066 ICalFormat::ICalFormat() 00067 : d( new Private( this ) ) 00068 { 00069 } 00070 00071 ICalFormat::~ICalFormat() 00072 { 00073 delete d; 00074 } 00075 00076 bool ICalFormat::load( const Calendar::Ptr &calendar, const QString &fileName ) 00077 { 00078 kDebug() << fileName; 00079 00080 clearException(); 00081 00082 QFile file( fileName ); 00083 if ( !file.open( QIODevice::ReadOnly ) ) { 00084 kDebug() << "load error"; 00085 setException( new Exception( Exception::LoadError ) ); 00086 return false; 00087 } 00088 QTextStream ts( &file ); 00089 ts.setCodec( "UTF-8" ); 00090 QByteArray text = ts.readAll().trimmed().toUtf8(); 00091 file.close(); 00092 00093 if ( text.isEmpty() ) { 00094 // empty files are valid 00095 return true; 00096 } else { 00097 return fromRawString( calendar, text, false, fileName ); 00098 } 00099 } 00100 00101 bool ICalFormat::save( const Calendar::Ptr &calendar, const QString &fileName ) 00102 { 00103 kDebug() << fileName; 00104 00105 clearException(); 00106 00107 QString text = toString( calendar ); 00108 if ( text.isEmpty() ) { 00109 return false; 00110 } 00111 00112 // Write backup file 00113 KSaveFile::backupFile( fileName ); 00114 00115 KSaveFile file( fileName ); 00116 if ( !file.open() ) { 00117 kDebug() << "file open error:" << file.errorString(); 00118 setException( new Exception( Exception::SaveErrorOpenFile, 00119 QStringList( fileName ) ) ); 00120 00121 return false; 00122 } 00123 00124 // Convert to UTF8 and save 00125 QByteArray textUtf8 = text.toUtf8(); 00126 file.write( textUtf8.data(), textUtf8.size() ); 00127 00128 if ( !file.finalize() ) { 00129 kDebug() << "file finalize error:" << file.errorString(); 00130 setException( new Exception( Exception::SaveErrorSaveFile, 00131 QStringList( fileName ) ) ); 00132 00133 return false; 00134 } 00135 00136 return true; 00137 } 00138 00139 bool ICalFormat::fromString( const Calendar::Ptr &cal, const QString &string, 00140 bool deleted, const QString ¬ebook ) 00141 { 00142 return fromRawString( cal, string.toUtf8(), deleted, notebook ); 00143 } 00144 00145 bool ICalFormat::fromRawString( const Calendar::Ptr &cal, const QByteArray &string, 00146 bool deleted, const QString ¬ebook ) 00147 { 00148 Q_UNUSED( notebook ); 00149 // Get first VCALENDAR component. 00150 // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components 00151 icalcomponent *calendar; 00152 00153 // Let's defend const correctness until the very gates of hell^Wlibical 00154 calendar = icalcomponent_new_from_string( const_cast<char*>( ( const char * )string ) ); 00155 if ( !calendar ) { 00156 kDebug() << "parse error"; 00157 setException( new Exception( Exception::ParseErrorIcal ) ); 00158 return false; 00159 } 00160 00161 bool success = true; 00162 00163 if ( icalcomponent_isa( calendar ) == ICAL_XROOT_COMPONENT ) { 00164 icalcomponent *comp; 00165 for ( comp = icalcomponent_get_first_component( calendar, ICAL_VCALENDAR_COMPONENT ); 00166 comp; comp = icalcomponent_get_next_component( calendar, ICAL_VCALENDAR_COMPONENT ) ) { 00167 // put all objects into their proper places 00168 if ( !d->mImpl->populate( cal, comp, deleted ) ) { 00169 kDebug() << "Could not populate calendar"; 00170 if ( !exception() ) { 00171 setException( new Exception( Exception::ParseErrorKcal ) ); 00172 } 00173 success = false; 00174 } else { 00175 setLoadedProductId( d->mImpl->loadedProductId() ); 00176 } 00177 } 00178 } else if ( icalcomponent_isa( calendar ) != ICAL_VCALENDAR_COMPONENT ) { 00179 kDebug() << "No VCALENDAR component found"; 00180 setException( new Exception( Exception::NoCalendar ) ); 00181 success = false; 00182 } else { 00183 // put all objects into their proper places 00184 if ( !d->mImpl->populate( cal, calendar, deleted ) ) { 00185 kDebug() << "Could not populate calendar"; 00186 if ( !exception() ) { 00187 setException( new Exception( Exception::ParseErrorKcal ) ); 00188 } 00189 success = false; 00190 } else { 00191 setLoadedProductId( d->mImpl->loadedProductId() ); 00192 } 00193 } 00194 00195 icalcomponent_free( calendar ); 00196 icalmemory_free_ring(); 00197 00198 return success; 00199 } 00200 00201 Incidence::Ptr ICalFormat::fromString( const QString &string ) 00202 { 00203 MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) ); 00204 fromString( cal, string ); 00205 00206 Incidence::Ptr ical; 00207 Event::List elist = cal->events(); 00208 if ( elist.count() > 0 ) { 00209 ical = elist.first(); 00210 } else { 00211 Todo::List tlist = cal->todos(); 00212 if ( tlist.count() > 0 ) { 00213 ical = tlist.first(); 00214 } else { 00215 Journal::List jlist = cal->journals(); 00216 if ( jlist.count() > 0 ) { 00217 ical = jlist.first(); 00218 } 00219 } 00220 } 00221 00222 return ical ? Incidence::Ptr( ical->clone() ) : Incidence::Ptr(); 00223 } 00224 00225 QString ICalFormat::toString( const Calendar::Ptr &cal, 00226 const QString ¬ebook, bool deleted ) 00227 { 00228 icalcomponent *calendar = d->mImpl->createCalendarComponent( cal ); 00229 icalcomponent *component; 00230 00231 ICalTimeZones *tzlist = cal->timeZones(); // time zones possibly used in the calendar 00232 ICalTimeZones tzUsedList; // time zones actually used in the calendar 00233 00234 // todos 00235 Todo::List todoList = deleted ? cal->deletedTodos() : cal->rawTodos(); 00236 Todo::List::ConstIterator it; 00237 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) { 00238 if ( !deleted || !cal->todo( (*it)->uid(), (*it)->recurrenceId() ) ) { 00239 // use existing ones, or really deleted ones 00240 if ( notebook.isEmpty() || 00241 ( !cal->notebook( *it ).isEmpty() && notebook.endsWith( cal->notebook( *it ) ) ) ) { 00242 component = d->mImpl->writeTodo( *it, tzlist, &tzUsedList ); 00243 icalcomponent_add_component( calendar, component ); 00244 } 00245 } 00246 } 00247 // events 00248 Event::List events = deleted ? cal->deletedEvents() : cal->rawEvents(); 00249 Event::List::ConstIterator it2; 00250 for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) { 00251 if ( !deleted || !cal->event( (*it2)->uid(), (*it2)->recurrenceId() ) ) { 00252 // use existing ones, or really deleted ones 00253 if ( notebook.isEmpty() || 00254 ( !cal->notebook( *it2 ).isEmpty() && notebook.endsWith( cal->notebook( *it2 ) ) ) ) { 00255 component = d->mImpl->writeEvent( *it2, tzlist, &tzUsedList ); 00256 icalcomponent_add_component( calendar, component ); 00257 } 00258 } 00259 } 00260 00261 // journals 00262 Journal::List journals = deleted ? cal->deletedJournals() : cal->rawJournals(); 00263 Journal::List::ConstIterator it3; 00264 for ( it3 = journals.constBegin(); it3 != journals.constEnd(); ++it3 ) { 00265 if ( !deleted || !cal->journal( (*it3)->uid(), (*it3)->recurrenceId() ) ) { 00266 // use existing ones, or really deleted ones 00267 if ( notebook.isEmpty() || 00268 ( !cal->notebook( *it3 ).isEmpty() && notebook.endsWith( cal->notebook( *it3 ) ) ) ) { 00269 component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList ); 00270 icalcomponent_add_component( calendar, component ); 00271 } 00272 } 00273 } 00274 00275 // time zones 00276 ICalTimeZones::ZoneMap zones = tzUsedList.zones(); 00277 if ( todoList.isEmpty() && events.isEmpty() && journals.isEmpty() ) { 00278 // no incidences means no used timezones, use all timezones 00279 // this will export a calendar having only timezone definitions 00280 zones = tzlist->zones(); 00281 } 00282 for ( ICalTimeZones::ZoneMap::ConstIterator it=zones.constBegin(); 00283 it != zones.constEnd(); ++it ) { 00284 icaltimezone *tz = (*it).icalTimezone(); 00285 if ( !tz ) { 00286 kError() << "bad time zone"; 00287 } else { 00288 component = icalcomponent_new_clone( icaltimezone_get_component( tz ) ); 00289 icalcomponent_add_component( calendar, component ); 00290 icaltimezone_free( tz, 1 ); 00291 } 00292 } 00293 00294 QString text = QString::fromUtf8( icalcomponent_as_ical_string( calendar ) ); 00295 00296 icalcomponent_free( calendar ); 00297 icalmemory_free_ring(); 00298 00299 if ( text.isEmpty() ) { 00300 setException( new Exception( Exception::LibICalError ) ); 00301 } 00302 00303 return text; 00304 } 00305 00306 QString ICalFormat::toICalString( const Incidence::Ptr &incidence ) 00307 { 00308 MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) ); 00309 cal->addIncidence( Incidence::Ptr( incidence->clone() ) ); 00310 return toString( cal.staticCast<Calendar>() ); 00311 } 00312 00313 QString ICalFormat::toString( const Incidence::Ptr &incidence ) 00314 { 00315 return QString::fromUtf8( toRawString( incidence ) ); 00316 } 00317 00318 QByteArray ICalFormat::toRawString( const Incidence::Ptr &incidence ) 00319 { 00320 icalcomponent *component; 00321 00322 component = d->mImpl->writeIncidence( incidence ); 00323 00324 QByteArray text = icalcomponent_as_ical_string( component ); 00325 00326 icalcomponent_free( component ); 00327 00328 return text; 00329 } 00330 00331 QString ICalFormat::toString( RecurrenceRule *recurrence ) 00332 { 00333 icalproperty *property; 00334 property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) ); 00335 QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) ); 00336 icalproperty_free( property ); 00337 return text; 00338 } 00339 00340 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule ) 00341 { 00342 if ( !recurrence ) { 00343 return false; 00344 } 00345 bool success = true; 00346 icalerror_clear_errno(); 00347 struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() ); 00348 if ( icalerrno != ICAL_NO_ERROR ) { 00349 kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno ); 00350 success = false; 00351 } 00352 00353 if ( success ) { 00354 d->mImpl->readRecurrence( recur, recurrence ); 00355 } 00356 00357 return success; 00358 } 00359 00360 QString ICalFormat::createScheduleMessage( const IncidenceBase::Ptr &incidence, 00361 iTIPMethod method ) 00362 { 00363 icalcomponent *message = 0; 00364 00365 if ( incidence->type() == Incidence::TypeEvent || 00366 incidence->type() == Incidence::TypeTodo ) { 00367 00368 Incidence::Ptr i = incidence.staticCast<Incidence>(); 00369 00370 // Recurring events need timezone information to allow proper calculations 00371 // across timezones with different DST. 00372 const bool useUtcTimes = !i->recurs(); 00373 00374 const bool hasSchedulingId = (i->schedulingID() != i->uid()); 00375 00376 const bool incidenceNeedChanges = (useUtcTimes || hasSchedulingId); 00377 00378 if ( incidenceNeedChanges ) { 00379 // The incidence need changes, so clone it before we continue 00380 i = Incidence::Ptr( i->clone() ); 00381 00382 // Handle conversion to UTC times 00383 if ( useUtcTimes ) { 00384 i->shiftTimes( KDateTime::Spec::UTC(), KDateTime::Spec::UTC() ); 00385 } 00386 00387 // Handle scheduling ID being present 00388 if ( hasSchedulingId ) { 00389 // We have a separation of scheduling ID and UID 00390 i->setSchedulingID( QString(), i->schedulingID() ); 00391 00392 } 00393 00394 // Build the message with the cloned incidence 00395 message = d->mImpl->createScheduleComponent( i, method ); 00396 } 00397 } 00398 00399 if ( message == 0 ) { 00400 message = d->mImpl->createScheduleComponent( incidence, method ); 00401 } 00402 00403 QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) ); 00404 00405 icalcomponent_free( message ); 00406 return messageText; 00407 } 00408 00409 FreeBusy::Ptr ICalFormat::parseFreeBusy( const QString &str ) 00410 { 00411 clearException(); 00412 00413 icalcomponent *message; 00414 message = icalparser_parse_string( str.toUtf8() ); 00415 00416 if ( !message ) { 00417 return FreeBusy::Ptr(); 00418 } 00419 00420 FreeBusy::Ptr freeBusy; 00421 00422 icalcomponent *c; 00423 for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT ); 00424 c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) { 00425 FreeBusy::Ptr fb = d->mImpl->readFreeBusy( c ); 00426 00427 if ( freeBusy ) { 00428 freeBusy->merge( fb ); 00429 } else { 00430 freeBusy = fb; 00431 } 00432 } 00433 00434 if ( !freeBusy ) { 00435 kDebug() << "object is not a freebusy."; 00436 } 00437 00438 icalcomponent_free( message ); 00439 00440 return freeBusy; 00441 } 00442 00443 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage( const Calendar::Ptr &cal, 00444 const QString &messageText ) 00445 { 00446 setTimeSpec( cal->timeSpec() ); 00447 clearException(); 00448 00449 if ( messageText.isEmpty() ) { 00450 setException( 00451 new Exception( Exception::ParseErrorEmptyMessage ) ); 00452 return ScheduleMessage::Ptr(); 00453 } 00454 00455 icalcomponent *message; 00456 message = icalparser_parse_string( messageText.toUtf8() ); 00457 00458 if ( !message ) { 00459 setException( 00460 new Exception( Exception::ParseErrorUnableToParse ) ); 00461 00462 return ScheduleMessage::Ptr(); 00463 } 00464 00465 icalproperty *m = 00466 icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY ); 00467 if ( !m ) { 00468 setException( 00469 new Exception( Exception::ParseErrorMethodProperty ) ); 00470 00471 return ScheduleMessage::Ptr(); 00472 } 00473 00474 // Populate the message's time zone collection with all VTIMEZONE components 00475 ICalTimeZones tzlist; 00476 ICalTimeZoneSource tzs; 00477 tzs.parse( message, tzlist ); 00478 00479 icalcomponent *c; 00480 00481 IncidenceBase::Ptr incidence; 00482 c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT ); 00483 if ( c ) { 00484 incidence = d->mImpl->readEvent( c, &tzlist ).staticCast<IncidenceBase>(); 00485 } 00486 00487 if ( !incidence ) { 00488 c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT ); 00489 if ( c ) { 00490 incidence = d->mImpl->readTodo( c, &tzlist ).staticCast<IncidenceBase>(); 00491 } 00492 } 00493 00494 if ( !incidence ) { 00495 c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT ); 00496 if ( c ) { 00497 incidence = d->mImpl->readJournal( c, &tzlist ).staticCast<IncidenceBase>(); 00498 } 00499 } 00500 00501 if ( !incidence ) { 00502 c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT ); 00503 if ( c ) { 00504 incidence = d->mImpl->readFreeBusy( c ).staticCast<IncidenceBase>(); 00505 } 00506 } 00507 00508 if ( !incidence ) { 00509 kDebug() << "object is not a freebusy, event, todo or journal"; 00510 setException( new Exception( Exception::ParseErrorNotIncidence ) ); 00511 00512 return ScheduleMessage::Ptr(); 00513 } 00514 00515 icalproperty_method icalmethod = icalproperty_get_method( m ); 00516 iTIPMethod method; 00517 00518 switch ( icalmethod ) { 00519 case ICAL_METHOD_PUBLISH: 00520 method = iTIPPublish; 00521 break; 00522 case ICAL_METHOD_REQUEST: 00523 method = iTIPRequest; 00524 break; 00525 case ICAL_METHOD_REFRESH: 00526 method = iTIPRefresh; 00527 break; 00528 case ICAL_METHOD_CANCEL: 00529 method = iTIPCancel; 00530 break; 00531 case ICAL_METHOD_ADD: 00532 method = iTIPAdd; 00533 break; 00534 case ICAL_METHOD_REPLY: 00535 method = iTIPReply; 00536 break; 00537 case ICAL_METHOD_COUNTER: 00538 method = iTIPCounter; 00539 break; 00540 case ICAL_METHOD_DECLINECOUNTER: 00541 method = iTIPDeclineCounter; 00542 break; 00543 default: 00544 method = iTIPNoMethod; 00545 kDebug() << "Unknown method"; 00546 break; 00547 } 00548 00549 if ( !icalrestriction_check( message ) ) { 00550 kWarning() << endl 00551 << "kcalcore library reported a problem while parsing:"; 00552 kWarning() << ScheduleMessage::methodName( method ) << ":" //krazy:exclude=kdebug 00553 << d->mImpl->extractErrorProperty( c ); 00554 } 00555 00556 Incidence::Ptr existingIncidence = cal->incidence( incidence->uid() ); 00557 00558 icalcomponent *calendarComponent = 0; 00559 if ( existingIncidence ) { 00560 calendarComponent = d->mImpl->createCalendarComponent( cal ); 00561 00562 // TODO: check, if cast is required, or if it can be done by virtual funcs. 00563 // TODO: Use a visitor for this! 00564 if ( existingIncidence->type() == Incidence::TypeTodo ) { 00565 Todo::Ptr todo = existingIncidence.staticCast<Todo>(); 00566 icalcomponent_add_component( calendarComponent, 00567 d->mImpl->writeTodo( todo ) ); 00568 } 00569 if ( existingIncidence->type() == Incidence::TypeEvent ) { 00570 Event::Ptr event = existingIncidence.staticCast<Event>(); 00571 icalcomponent_add_component( calendarComponent, 00572 d->mImpl->writeEvent( event ) ); 00573 } 00574 } else { 00575 icalcomponent_free( message ); 00576 return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, 00577 ScheduleMessage::Unknown ) ); 00578 } 00579 00580 icalproperty_xlicclass result = 00581 icalclassify( message, calendarComponent, static_cast<const char *>( "" ) ); 00582 00583 ScheduleMessage::Status status; 00584 00585 switch ( result ) { 00586 case ICAL_XLICCLASS_PUBLISHNEW: 00587 status = ScheduleMessage::PublishNew; 00588 break; 00589 case ICAL_XLICCLASS_PUBLISHUPDATE: 00590 status = ScheduleMessage::PublishUpdate; 00591 break; 00592 case ICAL_XLICCLASS_OBSOLETE: 00593 status = ScheduleMessage::Obsolete; 00594 break; 00595 case ICAL_XLICCLASS_REQUESTNEW: 00596 status = ScheduleMessage::RequestNew; 00597 break; 00598 case ICAL_XLICCLASS_REQUESTUPDATE: 00599 status = ScheduleMessage::RequestUpdate; 00600 break; 00601 case ICAL_XLICCLASS_UNKNOWN: 00602 default: 00603 status = ScheduleMessage::Unknown; 00604 break; 00605 } 00606 00607 icalcomponent_free( message ); 00608 icalcomponent_free( calendarComponent ); 00609 00610 return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, status ) ); 00611 } 00612 00613 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec ) 00614 { 00615 d->mTimeSpec = timeSpec; 00616 } 00617 00618 KDateTime::Spec ICalFormat::timeSpec() const 00619 { 00620 return d->mTimeSpec; 00621 } 00622 00623 QString ICalFormat::timeZoneId() const 00624 { 00625 KTimeZone tz = d->mTimeSpec.timeZone(); 00626 return tz.isValid() ? tz.name() : QString(); 00627 } 00628 00629 void ICalFormat::virtual_hook( int id, void *data ) 00630 { 00631 Q_UNUSED( id ); 00632 Q_UNUSED( data ); 00633 Q_ASSERT( false ); 00634 }