00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00032 #include "icalformat.h"
00033 #include "icalformat_p.h"
00034 #include "calendar.h"
00035 #include "calendarlocal.h"
00036 #include "icaltimezones.h"
00037
00038 extern "C" {
00039 #include <ical.h>
00040 #include <icalss.h>
00041 #include <icalparser.h>
00042 #include <icalrestriction.h>
00043 #include <icalmemory.h>
00044 }
00045
00046 #include <QtCore/QString>
00047 #include <QtCore/QRegExp>
00048 #include <QtCore/QFile>
00049 #include <QtCore/QTextStream>
00050 #include <QtCore/QByteArray>
00051 #include <QtGui/QClipboard>
00052
00053 #include <kdebug.h>
00054 #include <klocale.h>
00055 #include <ksavefile.h>
00056
00057 #include <stdio.h>
00058
00059 using namespace KCal;
00060
00061
00062 class KCal::ICalFormat::Private
00063 {
00064 public:
00065 Private( ICalFormat *parent )
00066 : mImpl( new ICalFormatImpl( parent ) ),
00067 mTimeSpec( KDateTime::UTC )
00068 {}
00069 ~Private() { delete mImpl; }
00070 ICalFormatImpl *mImpl;
00071 KDateTime::Spec mTimeSpec;
00072 };
00073
00074
00075 ICalFormat::ICalFormat()
00076 : d( new Private( this ) )
00077 {
00078 }
00079
00080 ICalFormat::~ICalFormat()
00081 {
00082 delete d;
00083 }
00084
00085 bool ICalFormat::load( Calendar *calendar, const QString &fileName )
00086 {
00087 kDebug() << fileName;
00088
00089 clearException();
00090
00091 QFile file( fileName );
00092 if ( !file.open( QIODevice::ReadOnly ) ) {
00093 kDebug() << "load error";
00094 setException( new ErrorFormat( ErrorFormat::LoadError ) );
00095 return false;
00096 }
00097 QTextStream ts( &file );
00098 ts.setCodec( "ISO 8859-1" );
00099 QByteArray text = ts.readAll().trimmed().toLatin1();
00100 file.close();
00101
00102 if ( text.isEmpty() ) {
00103
00104 return true;
00105 } else {
00106 return fromRawString( calendar, text );
00107 }
00108 }
00109
00110 bool ICalFormat::save( Calendar *calendar, const QString &fileName )
00111 {
00112 kDebug() << fileName;
00113
00114 clearException();
00115
00116 QString text = toString( calendar );
00117 if ( text.isNull() ) {
00118 return false;
00119 }
00120
00121
00122 KSaveFile::backupFile( fileName );
00123
00124 KSaveFile file( fileName );
00125 if ( !file.open() ) {
00126 kDebug() << "err:" << file.errorString();
00127 setException( new ErrorFormat( ErrorFormat::SaveError,
00128 i18n( "Error saving to '%1'.", fileName ) ) );
00129 return false;
00130 }
00131
00132
00133 QByteArray textUtf8 = text.toUtf8();
00134 file.write( textUtf8.data(), textUtf8.size() - 1 );
00135
00136 if ( !file.finalize() ) {
00137 kDebug() << "err:" << file.errorString();
00138 setException( new ErrorFormat( ErrorFormat::SaveError,
00139 i18n( "Could not save '%1'", fileName ) ) );
00140 return false;
00141 }
00142
00143 return true;
00144 }
00145
00146 bool ICalFormat::fromString( Calendar *cal, const QString &string )
00147 {
00148 return fromRawString( cal, string.toUtf8() );
00149 }
00150
00151 bool ICalFormat::fromRawString( Calendar *cal, const QByteArray &string )
00152 {
00153
00154
00155 icalcomponent *calendar;
00156
00157
00158 calendar = icalcomponent_new_from_string( const_cast<char*>( ( const char * )string ) );
00159 if ( !calendar ) {
00160 kDebug() << "parse error";
00161 setException( new ErrorFormat( ErrorFormat::ParseErrorIcal ) );
00162 return false;
00163 }
00164
00165 bool success = true;
00166
00167 if ( icalcomponent_isa( calendar ) == ICAL_XROOT_COMPONENT ) {
00168 icalcomponent *comp;
00169 for ( comp = icalcomponent_get_first_component( calendar, ICAL_VCALENDAR_COMPONENT );
00170 comp; comp = icalcomponent_get_next_component( calendar, ICAL_VCALENDAR_COMPONENT ) ) {
00171
00172 if ( !d->mImpl->populate( cal, comp ) ) {
00173 kDebug() << "Could not populate calendar";
00174 if ( !exception() ) {
00175 setException( new ErrorFormat( ErrorFormat::ParseErrorKcal ) );
00176 }
00177 success = false;
00178 } else {
00179 setLoadedProductId( d->mImpl->loadedProductId() );
00180 }
00181 }
00182 } else if ( icalcomponent_isa( calendar ) != ICAL_VCALENDAR_COMPONENT ) {
00183 kDebug() << "No VCALENDAR component found";
00184 setException( new ErrorFormat( ErrorFormat::NoCalendar ) );
00185 success = false;
00186 } else {
00187
00188 if ( !d->mImpl->populate( cal, calendar ) ) {
00189 kDebug() << "Could not populate calendar";
00190 if ( !exception() ) {
00191 setException( new ErrorFormat( ErrorFormat::ParseErrorKcal ) );
00192 }
00193 success = false;
00194 } else {
00195 setLoadedProductId( d->mImpl->loadedProductId() );
00196 }
00197 }
00198
00199 icalcomponent_free( calendar );
00200 icalmemory_free_ring();
00201
00202 return success;
00203 }
00204
00205 Incidence *ICalFormat::fromString( const QString &string )
00206 {
00207 CalendarLocal cal( d->mTimeSpec );
00208 fromString( &cal, string );
00209
00210 Incidence *ical = 0;
00211 Event::List elist = cal.events();
00212 if ( elist.count() > 0 ) {
00213 ical = elist.first();
00214 } else {
00215 Todo::List tlist = cal.todos();
00216 if ( tlist.count() > 0 ) {
00217 ical = tlist.first();
00218 } else {
00219 Journal::List jlist = cal.journals();
00220 if ( jlist.count() > 0 ) {
00221 ical = jlist.first();
00222 }
00223 }
00224 }
00225
00226 return ical ? ical->clone() : 0;
00227 }
00228
00229 QString ICalFormat::toString( Calendar *cal )
00230 {
00231 icalcomponent *calendar = d->mImpl->createCalendarComponent( cal );
00232 icalcomponent *component;
00233
00234 ICalTimeZones *tzlist = cal->timeZones();
00235 ICalTimeZones tzUsedList;
00236
00237
00238 Todo::List todoList = cal->rawTodos();
00239 Todo::List::ConstIterator it;
00240 for ( it = todoList.begin(); it != todoList.end(); ++it ) {
00241 component = d->mImpl->writeTodo( *it, tzlist, &tzUsedList );
00242 icalcomponent_add_component( calendar, component );
00243 }
00244
00245
00246 Event::List events = cal->rawEvents();
00247 Event::List::ConstIterator it2;
00248 for ( it2 = events.begin(); it2 != events.end(); ++it2 ) {
00249 if ( *it2 ) {
00250 component = d->mImpl->writeEvent( *it2, tzlist, &tzUsedList );
00251 icalcomponent_add_component( calendar, component );
00252 }
00253 }
00254
00255
00256 Journal::List journals = cal->journals();
00257 Journal::List::ConstIterator it3;
00258 for ( it3 = journals.begin(); it3 != journals.end(); ++it3 ) {
00259 component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList );
00260 icalcomponent_add_component( calendar, component );
00261 }
00262
00263
00264 const ICalTimeZones::ZoneMap zones = tzUsedList.zones();
00265 for ( ICalTimeZones::ZoneMap::ConstIterator it = zones.begin(); it != zones.end(); ++it ) {
00266 icaltimezone *tz = (*it).icalTimezone();
00267 if ( !tz ) {
00268 kError() << "bad time zone";
00269 } else {
00270 component = icalcomponent_new_clone( icaltimezone_get_component( tz ) );
00271 icalcomponent_add_component( calendar, component );
00272 icaltimezone_free( tz, 1 );
00273 }
00274 }
00275
00276 QString text = QString::fromUtf8( icalcomponent_as_ical_string( calendar ) );
00277
00278 icalcomponent_free( calendar );
00279 icalmemory_free_ring();
00280
00281 if ( text.isNull() ) {
00282 setException( new ErrorFormat( ErrorFormat::SaveError,
00283 i18n( "libical error" ) ) );
00284 return QString();
00285 }
00286
00287 return text;
00288 }
00289
00290 QString ICalFormat::toICalString( Incidence *incidence )
00291 {
00292 CalendarLocal cal( d->mTimeSpec );
00293 cal.addIncidence( incidence->clone() );
00294 return toString( &cal );
00295 }
00296
00297 QString ICalFormat::toString( Incidence *incidence )
00298 {
00299 icalcomponent *component;
00300
00301 component = d->mImpl->writeIncidence( incidence );
00302
00303 QString text = QString::fromUtf8( icalcomponent_as_ical_string( component ) );
00304
00305 icalcomponent_free( component );
00306
00307 return text;
00308 }
00309
00310 QString ICalFormat::toString( RecurrenceRule *recurrence )
00311 {
00312 icalproperty *property;
00313 property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) );
00314 QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) );
00315 icalproperty_free( property );
00316 return text;
00317 }
00318
00319 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule )
00320 {
00321 if ( !recurrence ) {
00322 return false;
00323 }
00324 bool success = true;
00325 icalerror_clear_errno();
00326 struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() );
00327 if ( icalerrno != ICAL_NO_ERROR ) {
00328 kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno );
00329 success = false;
00330 }
00331
00332 if ( success ) {
00333 d->mImpl->readRecurrence( recur, recurrence );
00334 }
00335
00336 return success;
00337 }
00338
00339 QString ICalFormat::createScheduleMessage( IncidenceBase *incidence,
00340 iTIPMethod method )
00341 {
00342 icalcomponent *message = 0;
00343
00344
00345 if ( incidence->type() == "Event" || incidence->type() == "Todo" ) {
00346 Incidence *i = static_cast<Incidence*>( incidence );
00347 if ( i->schedulingID() != i->uid() ) {
00348
00349 i = i->clone();
00350 i->setUid( i->schedulingID() );
00351 i->setSchedulingID( QString() );
00352
00353
00354 message = d->mImpl->createScheduleComponent( i, method );
00355
00356
00357 delete i;
00358 }
00359 }
00360
00361 if ( message == 0 ) {
00362 message = d->mImpl->createScheduleComponent( incidence, method );
00363 }
00364
00365 QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) );
00366
00367 icalcomponent_free( message );
00368 return messageText;
00369 }
00370
00371 FreeBusy *ICalFormat::parseFreeBusy( const QString &str )
00372 {
00373 clearException();
00374
00375 icalcomponent *message;
00376 message = icalparser_parse_string( str.toUtf8() );
00377
00378 if ( !message ) {
00379 return 0;
00380 }
00381
00382 FreeBusy *freeBusy = 0;
00383
00384 icalcomponent *c;
00385 for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00386 c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
00387 FreeBusy *fb = d->mImpl->readFreeBusy( c );
00388
00389 if ( freeBusy ) {
00390 freeBusy->merge( fb );
00391 delete fb;
00392 } else {
00393 freeBusy = fb;
00394 }
00395 }
00396
00397 if ( !freeBusy ) {
00398 kDebug() << "object is not a freebusy.";
00399 }
00400 return freeBusy;
00401 }
00402
00403 ScheduleMessage *ICalFormat::parseScheduleMessage( Calendar *cal,
00404 const QString &messageText )
00405 {
00406 setTimeSpec( cal->timeSpec() );
00407 clearException();
00408
00409 if ( messageText.isEmpty() ) {
00410 setException(
00411 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00412 QLatin1String( "messageText is empty, unable "
00413 "to parse into a ScheduleMessage" ) ) );
00414 return 0;
00415 }
00416
00417 icalcomponent *message;
00418 message = icalparser_parse_string( messageText.toUtf8() );
00419
00420 if ( !message ) {
00421 setException(
00422 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00423 QLatin1String( "icalparser is unable to parse "
00424 "messageText into a ScheduleMessage" ) ) );
00425 return 0;
00426 }
00427
00428 icalproperty *m =
00429 icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY );
00430 if ( !m ) {
00431 setException(
00432 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00433 QLatin1String( "message does not contain an "
00434 "ICAL_METHOD_PROPERTY" ) ) );
00435 return 0;
00436 }
00437
00438
00439 ICalTimeZones tzlist;
00440 ICalTimeZoneSource tzs;
00441 tzs.parse( message, tzlist );
00442
00443 icalcomponent *c;
00444
00445 IncidenceBase *incidence = 0;
00446 c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT );
00447 if ( c ) {
00448 incidence = d->mImpl->readEvent( c, &tzlist );
00449 }
00450
00451 if ( !incidence ) {
00452 c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT );
00453 if ( c ) {
00454 incidence = d->mImpl->readTodo( c, &tzlist );
00455 }
00456 }
00457
00458 if ( !incidence ) {
00459 c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT );
00460 if ( c ) {
00461 incidence = d->mImpl->readJournal( c, &tzlist );
00462 }
00463 }
00464
00465 if ( !incidence ) {
00466 c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00467 if ( c ) {
00468 incidence = d->mImpl->readFreeBusy( c );
00469 }
00470 }
00471
00472 if ( !incidence ) {
00473 kDebug() << "object is not a freebusy, event, todo or journal";
00474 setException(
00475 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00476 QLatin1String( "object is not a freebusy, event, "
00477 "todo or journal" ) ) );
00478 return 0;
00479 }
00480
00481 kDebug() << "getting method...";
00482
00483 icalproperty_method icalmethod = icalproperty_get_method( m );
00484 iTIPMethod method;
00485
00486 switch ( icalmethod ) {
00487 case ICAL_METHOD_PUBLISH:
00488 method = iTIPPublish;
00489 break;
00490 case ICAL_METHOD_REQUEST:
00491 method = iTIPRequest;
00492 break;
00493 case ICAL_METHOD_REFRESH:
00494 method = iTIPRefresh;
00495 break;
00496 case ICAL_METHOD_CANCEL:
00497 method = iTIPCancel;
00498 break;
00499 case ICAL_METHOD_ADD:
00500 method = iTIPAdd;
00501 break;
00502 case ICAL_METHOD_REPLY:
00503 method = iTIPReply;
00504 break;
00505 case ICAL_METHOD_COUNTER:
00506 method = iTIPCounter;
00507 break;
00508 case ICAL_METHOD_DECLINECOUNTER:
00509 method = iTIPDeclineCounter;
00510 break;
00511 default:
00512 method = iTIPNoMethod;
00513 kDebug() << "Unknown method";
00514 break;
00515 }
00516
00517 kDebug() << "restriction...";
00518
00519 if ( !icalrestriction_check( message ) ) {
00520 kWarning() << endl
00521 << "kcal library reported a problem while parsing:";
00522 kWarning() << Scheduler::translatedMethodName( method ) << ":"
00523 << d->mImpl->extractErrorProperty( c );
00524 }
00525
00526 Incidence *existingIncidence = cal->incidenceFromSchedulingID( incidence->uid() );
00527
00528 icalcomponent *calendarComponent;
00529 if ( existingIncidence ) {
00530 calendarComponent = d->mImpl->createCalendarComponent( cal );
00531
00532
00533
00534 if ( existingIncidence->type() == "Todo" ) {
00535 Todo *todo = static_cast<Todo *>( existingIncidence );
00536 icalcomponent_add_component( calendarComponent,
00537 d->mImpl->writeTodo( todo ) );
00538 }
00539 if ( existingIncidence->type() == "Event" ) {
00540 Event *event = static_cast<Event *>( existingIncidence );
00541 icalcomponent_add_component( calendarComponent,
00542 d->mImpl->writeEvent( event ) );
00543 }
00544 } else {
00545 calendarComponent = 0;
00546 }
00547
00548 kDebug() << "classify...";
00549
00550 icalproperty_xlicclass result =
00551 icalclassify( message, calendarComponent, (char *)"" );
00552
00553 kDebug() << "returning with result = " << result;
00554
00555 ScheduleMessage::Status status;
00556
00557 switch ( result ) {
00558 case ICAL_XLICCLASS_PUBLISHNEW:
00559 status = ScheduleMessage::PublishNew;
00560 break;
00561 case ICAL_XLICCLASS_PUBLISHUPDATE:
00562 status = ScheduleMessage::PublishUpdate;
00563 break;
00564 case ICAL_XLICCLASS_OBSOLETE:
00565 status = ScheduleMessage::Obsolete;
00566 break;
00567 case ICAL_XLICCLASS_REQUESTNEW:
00568 status = ScheduleMessage::RequestNew;
00569 break;
00570 case ICAL_XLICCLASS_REQUESTUPDATE:
00571 status = ScheduleMessage::RequestUpdate;
00572 break;
00573 case ICAL_XLICCLASS_UNKNOWN:
00574 default:
00575 status = ScheduleMessage::Unknown;
00576 break;
00577 }
00578
00579 kDebug() << "status =" << status;
00580
00581 icalcomponent_free( message );
00582 icalcomponent_free( calendarComponent );
00583 return new ScheduleMessage( incidence, method, status );
00584 }
00585
00586 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec )
00587 {
00588 d->mTimeSpec = timeSpec;
00589 }
00590
00591 KDateTime::Spec ICalFormat::timeSpec() const
00592 {
00593 return d->mTimeSpec;
00594 }
00595
00596 QString ICalFormat::timeZoneId() const
00597 {
00598 KTimeZone tz = d->mTimeSpec.timeZone();
00599 return tz.isValid() ? tz.name() : QString();
00600 }