00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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
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
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
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
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
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
00150
00151 icalcomponent *calendar;
00152
00153
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
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
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();
00232 ICalTimeZones tzUsedList;
00233
00234
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
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
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
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
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
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
00276 ICalTimeZones::ZoneMap zones = tzUsedList.zones();
00277 if ( todoList.isEmpty() && events.isEmpty() && journals.isEmpty() ) {
00278
00279
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
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
00366 i = Incidence::Ptr( i->clone() );
00367 i->setSchedulingID( QString(), i->schedulingID() );
00368
00369
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
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 ) << ":"
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
00538
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 }