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(5800) << fileName;
00088
00089 clearException();
00090
00091 QFile file( fileName );
00092 if ( !file.open( QIODevice::ReadOnly ) ) {
00093 kDebug(5800) << "load error";
00094 setException( new ErrorFormat( ErrorFormat::LoadError ) );
00095 return false;
00096 }
00097 QTextStream ts( &file );
00098 ts.setCodec( "ISO 8859-1" );
00099 QString text = ts.readAll();
00100 file.close();
00101
00102 if ( text.trimmed().isEmpty() ) {
00103
00104 return true;
00105 } else {
00106 return fromRawString( calendar, text.toLatin1() );
00107 }
00108 }
00109
00110 bool ICalFormat::save( Calendar *calendar, const QString &fileName )
00111 {
00112 kDebug(5800) << 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(5800) << "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(5800) << "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(5800) << "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(5800) << "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(5800) << "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(5800) << "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 kDebug(5800) << "write journal" << (*it3)->uid();
00260 component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList );
00261 icalcomponent_add_component( calendar, component );
00262 }
00263
00264
00265 const ICalTimeZones::ZoneMap zones = tzUsedList.zones();
00266 for ( ICalTimeZones::ZoneMap::ConstIterator it = zones.begin(); it != zones.end(); ++it ) {
00267 icaltimezone *tz = (*it).icalTimezone();
00268 if ( !tz ) {
00269 kError(5800) << "ICalFormat::toString(): bad time zone";
00270 } else {
00271 component = icalcomponent_new_clone( icaltimezone_get_component( tz ) );
00272 icalcomponent_add_component( calendar, component );
00273 icaltimezone_free( tz, 1 );
00274 }
00275 }
00276
00277 QString text = QString::fromUtf8( icalcomponent_as_ical_string( calendar ) );
00278
00279 icalcomponent_free( calendar );
00280 icalmemory_free_ring();
00281
00282 if ( text.isNull() ) {
00283 setException( new ErrorFormat( ErrorFormat::SaveError,
00284 i18n( "libical error" ) ) );
00285 return QString();
00286 }
00287
00288 return text;
00289 }
00290
00291 QString ICalFormat::toICalString( Incidence *incidence )
00292 {
00293 CalendarLocal cal( d->mTimeSpec );
00294 cal.addIncidence( incidence->clone() );
00295 return toString( &cal );
00296 }
00297
00298 QString ICalFormat::toString( Incidence *incidence )
00299 {
00300 icalcomponent *component;
00301
00302 component = d->mImpl->writeIncidence( incidence );
00303
00304 QString text = QString::fromUtf8( icalcomponent_as_ical_string( component ) );
00305
00306 icalcomponent_free( component );
00307
00308 return text;
00309 }
00310
00311 QString ICalFormat::toString( RecurrenceRule *recurrence )
00312 {
00313 icalproperty *property;
00314 property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) );
00315 QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) );
00316 icalproperty_free( property );
00317 return text;
00318 }
00319
00320 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule )
00321 {
00322 if ( !recurrence ) {
00323 return false;
00324 }
00325 bool success = true;
00326 icalerror_clear_errno();
00327 struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() );
00328 if ( icalerrno != ICAL_NO_ERROR ) {
00329 kDebug(5800) << "Recurrence parsing error:" << icalerror_strerror( icalerrno );
00330 success = false;
00331 }
00332
00333 if ( success ) {
00334 d->mImpl->readRecurrence( recur, recurrence );
00335 }
00336
00337 return success;
00338 }
00339
00340 QString ICalFormat::createScheduleMessage( IncidenceBase *incidence,
00341 iTIPMethod method )
00342 {
00343 icalcomponent *message = 0;
00344
00345
00346 if ( incidence->type() == "Event" || incidence->type() == "Todo" ) {
00347 Incidence *i = static_cast<Incidence*>( incidence );
00348 if ( i->schedulingID() != i->uid() ) {
00349
00350 i = i->clone();
00351 i->setUid( i->schedulingID() );
00352 i->setSchedulingID( QString() );
00353
00354
00355 message = d->mImpl->createScheduleComponent( i, method );
00356
00357
00358 delete i;
00359 }
00360 }
00361
00362 if ( message == 0 ) {
00363 message = d->mImpl->createScheduleComponent( incidence, method );
00364 }
00365
00366 QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) );
00367
00368 icalcomponent_free( message );
00369 return messageText;
00370 }
00371
00372 FreeBusy *ICalFormat::parseFreeBusy( const QString &str )
00373 {
00374 clearException();
00375
00376 icalcomponent *message;
00377 message = icalparser_parse_string( str.toUtf8() );
00378
00379 if ( !message ) {
00380 return 0;
00381 }
00382
00383 FreeBusy *freeBusy = 0;
00384
00385 icalcomponent *c;
00386 for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00387 c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
00388 FreeBusy *fb = d->mImpl->readFreeBusy( c );
00389
00390 if ( freeBusy ) {
00391 freeBusy->merge( fb );
00392 delete fb;
00393 } else {
00394 freeBusy = fb;
00395 }
00396 }
00397
00398 if ( !freeBusy ) {
00399 kDebug(5800) << "object is not a freebusy.";
00400 }
00401 return freeBusy;
00402 }
00403
00404 ScheduleMessage *ICalFormat::parseScheduleMessage( Calendar *cal,
00405 const QString &messageText )
00406 {
00407 setTimeSpec( cal->timeSpec() );
00408 clearException();
00409
00410 if ( messageText.isEmpty() ) {
00411 setException(
00412 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00413 QLatin1String( "messageText is empty, unable "
00414 "to parse into a ScheduleMessage" ) ) );
00415 return 0;
00416 }
00417
00418 icalcomponent *message;
00419 message = icalparser_parse_string( messageText.toUtf8() );
00420
00421 if ( !message ) {
00422 setException(
00423 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00424 QLatin1String( "icalparser is unable to parse "
00425 "messageText into a ScheduleMessage" ) ) );
00426 return 0;
00427 }
00428
00429 icalproperty *m =
00430 icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY );
00431 if ( !m ) {
00432 setException(
00433 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00434 QLatin1String( "message does not contain an "
00435 "ICAL_METHOD_PROPERTY" ) ) );
00436 return 0;
00437 }
00438
00439
00440 ICalTimeZones tzlist;
00441 ICalTimeZoneSource tzs;
00442 tzs.parse( message, tzlist );
00443
00444 icalcomponent *c;
00445
00446 IncidenceBase *incidence = 0;
00447 c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT );
00448 if ( c ) {
00449 incidence = d->mImpl->readEvent( c, &tzlist );
00450 }
00451
00452 if ( !incidence ) {
00453 c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT );
00454 if ( c ) {
00455 incidence = d->mImpl->readTodo( c, &tzlist );
00456 }
00457 }
00458
00459 if ( !incidence ) {
00460 c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT );
00461 if ( c ) {
00462 incidence = d->mImpl->readJournal( c, &tzlist );
00463 }
00464 }
00465
00466 if ( !incidence ) {
00467 c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00468 if ( c ) {
00469 incidence = d->mImpl->readFreeBusy( c );
00470 }
00471 }
00472
00473 if ( !incidence ) {
00474 kDebug(5800) << "object is not a freebusy, event, todo or journal";
00475 setException(
00476 new ErrorFormat( ErrorFormat::ParseErrorKcal,
00477 QLatin1String( "object is not a freebusy, event, "
00478 "todo or journal" ) ) );
00479 return 0;
00480 }
00481
00482 kDebug(5800) << "getting method...";
00483
00484 icalproperty_method icalmethod = icalproperty_get_method( m );
00485 iTIPMethod method;
00486
00487 switch ( icalmethod ) {
00488 case ICAL_METHOD_PUBLISH:
00489 method = iTIPPublish;
00490 break;
00491 case ICAL_METHOD_REQUEST:
00492 method = iTIPRequest;
00493 break;
00494 case ICAL_METHOD_REFRESH:
00495 method = iTIPRefresh;
00496 break;
00497 case ICAL_METHOD_CANCEL:
00498 method = iTIPCancel;
00499 break;
00500 case ICAL_METHOD_ADD:
00501 method = iTIPAdd;
00502 break;
00503 case ICAL_METHOD_REPLY:
00504 method = iTIPReply;
00505 break;
00506 case ICAL_METHOD_COUNTER:
00507 method = iTIPCounter;
00508 break;
00509 case ICAL_METHOD_DECLINECOUNTER:
00510 method = iTIPDeclineCounter;
00511 break;
00512 default:
00513 method = iTIPNoMethod;
00514 kDebug(5800) << "Unknown method";
00515 break;
00516 }
00517
00518 kDebug(5800) << "restriction...";
00519
00520 if ( !icalrestriction_check( message ) ) {
00521 kWarning(5800) << endl
00522 << "kcal library reported a problem while parsing:";
00523 kWarning(5800) << Scheduler::translatedMethodName( method ) << ":"
00524 << d->mImpl->extractErrorProperty( c );
00525 }
00526
00527 Incidence *existingIncidence = cal->incidenceFromSchedulingID( incidence->uid() );
00528
00529 icalcomponent *calendarComponent;
00530 if ( existingIncidence ) {
00531 calendarComponent = d->mImpl->createCalendarComponent( cal );
00532
00533
00534
00535 if ( existingIncidence->type() == "Todo" ) {
00536 Todo *todo = static_cast<Todo *>( existingIncidence );
00537 icalcomponent_add_component( calendarComponent,
00538 d->mImpl->writeTodo( todo ) );
00539 }
00540 if ( existingIncidence->type() == "Event" ) {
00541 Event *event = static_cast<Event *>( existingIncidence );
00542 icalcomponent_add_component( calendarComponent,
00543 d->mImpl->writeEvent( event ) );
00544 }
00545 } else {
00546 calendarComponent = 0;
00547 }
00548
00549 kDebug(5800) << "classify...";
00550
00551 icalproperty_xlicclass result =
00552 icalclassify( message, calendarComponent, (char *)"" );
00553
00554 kDebug(5800) << "returning with result = " << result;
00555
00556 ScheduleMessage::Status status;
00557
00558 switch ( result ) {
00559 case ICAL_XLICCLASS_PUBLISHNEW:
00560 status = ScheduleMessage::PublishNew;
00561 break;
00562 case ICAL_XLICCLASS_PUBLISHUPDATE:
00563 status = ScheduleMessage::PublishUpdate;
00564 break;
00565 case ICAL_XLICCLASS_OBSOLETE:
00566 status = ScheduleMessage::Obsolete;
00567 break;
00568 case ICAL_XLICCLASS_REQUESTNEW:
00569 status = ScheduleMessage::RequestNew;
00570 break;
00571 case ICAL_XLICCLASS_REQUESTUPDATE:
00572 status = ScheduleMessage::RequestUpdate;
00573 break;
00574 case ICAL_XLICCLASS_UNKNOWN:
00575 default:
00576 status = ScheduleMessage::Unknown;
00577 break;
00578 }
00579
00580 kDebug(5800) << "status =" << status;
00581
00582 icalcomponent_free( message );
00583 icalcomponent_free( calendarComponent );
00584 return new ScheduleMessage( incidence, method, status );
00585 }
00586
00587 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec )
00588 {
00589 d->mTimeSpec = timeSpec;
00590 }
00591
00592 KDateTime::Spec ICalFormat::timeSpec() const
00593 {
00594 return d->mTimeSpec;
00595 }
00596
00597 QString ICalFormat::timeZoneId() const
00598 {
00599 KTimeZone tz = d->mTimeSpec.timeZone();
00600 return tz.isValid() ? tz.name() : QString();
00601 }