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() << "err:" << 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() << "err:" << 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 icalcomponent *component; 00316 00317 component = d->mImpl->writeIncidence( incidence ); 00318 00319 QString text = QString::fromUtf8( icalcomponent_as_ical_string( component ) ); 00320 00321 icalcomponent_free( component ); 00322 00323 return text; 00324 } 00325 00326 QString ICalFormat::toString( RecurrenceRule *recurrence ) 00327 { 00328 icalproperty *property; 00329 property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) ); 00330 QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) ); 00331 icalproperty_free( property ); 00332 return text; 00333 } 00334 00335 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule ) 00336 { 00337 if ( !recurrence ) { 00338 return false; 00339 } 00340 bool success = true; 00341 icalerror_clear_errno(); 00342 struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() ); 00343 if ( icalerrno != ICAL_NO_ERROR ) { 00344 kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno ); 00345 success = false; 00346 } 00347 00348 if ( success ) { 00349 d->mImpl->readRecurrence( recur, recurrence ); 00350 } 00351 00352 return success; 00353 } 00354 00355 QString ICalFormat::createScheduleMessage( const IncidenceBase::Ptr &incidence, 00356 iTIPMethod method ) 00357 { 00358 icalcomponent *message = 0; 00359 00360 // Handle scheduling ID being present 00361 if ( incidence->type() == Incidence::TypeEvent || 00362 incidence->type() == Incidence::TypeTodo ) { 00363 Incidence::Ptr i = incidence.staticCast<Incidence>(); 00364 if ( i->schedulingID() != i->uid() ) { 00365 // We have a separation of scheduling ID and UID 00366 i = Incidence::Ptr( i->clone() ); 00367 i->setSchedulingID( QString(), i->schedulingID() ); 00368 00369 // Build the message with the cloned incidence 00370 message = d->mImpl->createScheduleComponent( i, method ); 00371 } 00372 } 00373 00374 if ( message == 0 ) { 00375 message = d->mImpl->createScheduleComponent( incidence, method ); 00376 } 00377 00378 QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) ); 00379 00380 icalcomponent_free( message ); 00381 return messageText; 00382 } 00383 00384 FreeBusy::Ptr ICalFormat::parseFreeBusy( const QString &str ) 00385 { 00386 clearException(); 00387 00388 icalcomponent *message; 00389 message = icalparser_parse_string( str.toUtf8() ); 00390 00391 if ( !message ) { 00392 return FreeBusy::Ptr(); 00393 } 00394 00395 FreeBusy::Ptr freeBusy; 00396 00397 icalcomponent *c; 00398 for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT ); 00399 c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) { 00400 FreeBusy::Ptr fb = d->mImpl->readFreeBusy( c ); 00401 00402 if ( freeBusy ) { 00403 freeBusy->merge( fb ); 00404 } else { 00405 freeBusy = fb; 00406 } 00407 } 00408 00409 if ( !freeBusy ) { 00410 kDebug() << "object is not a freebusy."; 00411 } 00412 00413 icalcomponent_free( message ); 00414 00415 return freeBusy; 00416 } 00417 00418 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage( const Calendar::Ptr &cal, 00419 const QString &messageText ) 00420 { 00421 setTimeSpec( cal->timeSpec() ); 00422 clearException(); 00423 00424 if ( messageText.isEmpty() ) { 00425 setException( 00426 new Exception( Exception::ParseErrorEmptyMessage ) ); 00427 return ScheduleMessage::Ptr(); 00428 } 00429 00430 icalcomponent *message; 00431 message = icalparser_parse_string( messageText.toUtf8() ); 00432 00433 if ( !message ) { 00434 setException( 00435 new Exception( Exception::ParseErrorUnableToParse ) ); 00436 00437 return ScheduleMessage::Ptr(); 00438 } 00439 00440 icalproperty *m = 00441 icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY ); 00442 if ( !m ) { 00443 setException( 00444 new Exception( Exception::ParseErrorMethodProperty ) ); 00445 00446 return ScheduleMessage::Ptr(); 00447 } 00448 00449 // Populate the message's time zone collection with all VTIMEZONE components 00450 ICalTimeZones tzlist; 00451 ICalTimeZoneSource tzs; 00452 tzs.parse( message, tzlist ); 00453 00454 icalcomponent *c; 00455 00456 IncidenceBase::Ptr incidence; 00457 c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT ); 00458 if ( c ) { 00459 incidence = d->mImpl->readEvent( c, &tzlist ).staticCast<IncidenceBase>(); 00460 } 00461 00462 if ( !incidence ) { 00463 c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT ); 00464 if ( c ) { 00465 incidence = d->mImpl->readTodo( c, &tzlist ).staticCast<IncidenceBase>(); 00466 } 00467 } 00468 00469 if ( !incidence ) { 00470 c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT ); 00471 if ( c ) { 00472 incidence = d->mImpl->readJournal( c, &tzlist ).staticCast<IncidenceBase>(); 00473 } 00474 } 00475 00476 if ( !incidence ) { 00477 c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT ); 00478 if ( c ) { 00479 incidence = d->mImpl->readFreeBusy( c ).staticCast<IncidenceBase>(); 00480 } 00481 } 00482 00483 if ( !incidence ) { 00484 kDebug() << "object is not a freebusy, event, todo or journal"; 00485 setException( new Exception( Exception::ParseErrorNotIncidence ) ); 00486 00487 return ScheduleMessage::Ptr(); 00488 } 00489 00490 icalproperty_method icalmethod = icalproperty_get_method( m ); 00491 iTIPMethod method; 00492 00493 switch ( icalmethod ) { 00494 case ICAL_METHOD_PUBLISH: 00495 method = iTIPPublish; 00496 break; 00497 case ICAL_METHOD_REQUEST: 00498 method = iTIPRequest; 00499 break; 00500 case ICAL_METHOD_REFRESH: 00501 method = iTIPRefresh; 00502 break; 00503 case ICAL_METHOD_CANCEL: 00504 method = iTIPCancel; 00505 break; 00506 case ICAL_METHOD_ADD: 00507 method = iTIPAdd; 00508 break; 00509 case ICAL_METHOD_REPLY: 00510 method = iTIPReply; 00511 break; 00512 case ICAL_METHOD_COUNTER: 00513 method = iTIPCounter; 00514 break; 00515 case ICAL_METHOD_DECLINECOUNTER: 00516 method = iTIPDeclineCounter; 00517 break; 00518 default: 00519 method = iTIPNoMethod; 00520 kDebug() << "Unknown method"; 00521 break; 00522 } 00523 00524 if ( !icalrestriction_check( message ) ) { 00525 kWarning() << endl 00526 << "kcalcore library reported a problem while parsing:"; 00527 kWarning() << ScheduleMessage::methodName( method ) << ":" //krazy:exclude=kdebug 00528 << d->mImpl->extractErrorProperty( c ); 00529 } 00530 00531 Incidence::Ptr existingIncidence = cal->incidence( incidence->uid() ); 00532 00533 icalcomponent *calendarComponent = 0; 00534 if ( existingIncidence ) { 00535 calendarComponent = d->mImpl->createCalendarComponent( cal ); 00536 00537 // TODO: check, if cast is required, or if it can be done by virtual funcs. 00538 // TODO: Use a visitor for this! 00539 if ( existingIncidence->type() == Incidence::TypeTodo ) { 00540 Todo::Ptr todo = existingIncidence.staticCast<Todo>(); 00541 icalcomponent_add_component( calendarComponent, 00542 d->mImpl->writeTodo( todo ) ); 00543 } 00544 if ( existingIncidence->type() == Incidence::TypeEvent ) { 00545 Event::Ptr event = existingIncidence.staticCast<Event>(); 00546 icalcomponent_add_component( calendarComponent, 00547 d->mImpl->writeEvent( event ) ); 00548 } 00549 } else { 00550 icalcomponent_free( message ); 00551 return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, 00552 ScheduleMessage::Unknown ) ); 00553 } 00554 00555 icalproperty_xlicclass result = 00556 icalclassify( message, calendarComponent, static_cast<const char *>( "" ) ); 00557 00558 ScheduleMessage::Status status; 00559 00560 switch ( result ) { 00561 case ICAL_XLICCLASS_PUBLISHNEW: 00562 status = ScheduleMessage::PublishNew; 00563 break; 00564 case ICAL_XLICCLASS_PUBLISHUPDATE: 00565 status = ScheduleMessage::PublishUpdate; 00566 break; 00567 case ICAL_XLICCLASS_OBSOLETE: 00568 status = ScheduleMessage::Obsolete; 00569 break; 00570 case ICAL_XLICCLASS_REQUESTNEW: 00571 status = ScheduleMessage::RequestNew; 00572 break; 00573 case ICAL_XLICCLASS_REQUESTUPDATE: 00574 status = ScheduleMessage::RequestUpdate; 00575 break; 00576 case ICAL_XLICCLASS_UNKNOWN: 00577 default: 00578 status = ScheduleMessage::Unknown; 00579 break; 00580 } 00581 00582 icalcomponent_free( message ); 00583 icalcomponent_free( calendarComponent ); 00584 00585 return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, status ) ); 00586 } 00587 00588 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec ) 00589 { 00590 d->mTimeSpec = timeSpec; 00591 } 00592 00593 KDateTime::Spec ICalFormat::timeSpec() const 00594 { 00595 return d->mTimeSpec; 00596 } 00597 00598 QString ICalFormat::timeZoneId() const 00599 { 00600 KTimeZone tz = d->mTimeSpec.timeZone(); 00601 return tz.isValid() ? tz.name() : QString(); 00602 } 00603 00604 void ICalFormat::virtual_hook( int id, void *data ) 00605 { 00606 Q_UNUSED( id ); 00607 Q_UNUSED( data ); 00608 Q_ASSERT( false ); 00609 }