libkcal

icalformat.cpp
00001 /*
00002     This file is part of libkcal.
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 */
00021 
00022 #include <tqdatetime.h>
00023 #include <tqstring.h>
00024 #include <tqptrlist.h>
00025 #include <tqregexp.h>
00026 #include <tqclipboard.h>
00027 #include <tqfile.h>
00028 #include <tqtextstream.h>
00029 
00030 #include <kdebug.h>
00031 #include <tdelocale.h>
00032 
00033 extern "C" {
00034   #include <libical/ical.h>
00035   #include <libical/icalss.h>
00036   #include <libical/icalparser.h>
00037   #include <libical/icalrestriction.h>
00038   #include <libical/icalmemory.h>
00039 }
00040 
00041 #include "calendar.h"
00042 #include "calendarlocal.h"
00043 #include "journal.h"
00044 
00045 #include "icalformat.h"
00046 #include "icalformatimpl.h"
00047 #include <ksavefile.h>
00048 
00049 #include <stdio.h>
00050 
00051 #define _ICAL_VERSION "2.0"
00052 
00053 using namespace KCal;
00054 
00055 ICalFormat::ICalFormat() : mImpl(0)
00056 {
00057   setImplementation( new ICalFormatImpl( this ) );
00058 
00059   mTimeZoneId = "UTC";
00060   mUtc = true;
00061 }
00062 
00063 ICalFormat::~ICalFormat()
00064 {
00065   delete mImpl;
00066 }
00067 
00068 void ICalFormat::setImplementation( ICalFormatImpl *impl )
00069 {
00070   if ( mImpl ) delete mImpl;
00071   mImpl = impl;
00072 }
00073 
00074 #if defined(_AIX) && defined(open)
00075 #undef open
00076 #endif
00077 
00078 bool ICalFormat::load( Calendar *calendar, const TQString &fileName)
00079 {
00080   kdDebug(5800) << "ICalFormat::load() " << fileName << endl;
00081 
00082   clearException();
00083 
00084   TQFile file( fileName );
00085   if (!file.open( IO_ReadOnly ) ) {
00086     kdDebug(5800) << "ICalFormat::load() load error" << endl;
00087     setException(new ErrorFormat(ErrorFormat::LoadError));
00088     return false;
00089   }
00090   TQTextStream ts( &file );
00091   ts.setEncoding( TQTextStream::UnicodeUTF8 );
00092   TQString text = ts.read();
00093   file.close();
00094 
00095   if ( text.stripWhiteSpace().isEmpty() ) // empty files are valid
00096     return true;
00097   else
00098     return fromRawString( calendar, text.utf8() );
00099 }
00100 
00101 
00102 bool ICalFormat::save( Calendar *calendar, const TQString &fileName )
00103 {
00104   kdDebug(5800) << "ICalFormat::save(): " << fileName << endl;
00105 
00106   clearException();
00107 
00108   TQString text = toString( calendar );
00109 
00110   if ( text.isNull() ) return false;
00111 
00112   // Write backup file
00113   KSaveFile::backupFile( fileName );
00114 
00115   KSaveFile file( fileName );
00116   if ( file.status() != 0 ) {
00117     kdDebug(5800) << "ICalFormat::save() errno: " << strerror( file.status() )
00118               << endl;
00119     setException( new ErrorFormat( ErrorFormat::SaveError,
00120                   i18n( "Error saving to '%1'." ).arg( fileName ) ) );
00121     return false;
00122   }
00123 
00124   // Convert to UTF8 and save
00125   TQCString textUtf8 = text.utf8();
00126   file.textStream()->setEncoding( TQTextStream::UnicodeUTF8 );
00127   file.file()->writeBlock(textUtf8.data(),textUtf8.size()-1);
00128 
00129   if ( !file.close() ) {
00130     kdDebug(5800) << "KSaveFile: close: status was " << file.status() << ". See errno.h." << endl;
00131     setException(new ErrorFormat(ErrorFormat::SaveError,
00132                  i18n("Could not save '%1'").arg(fileName)));
00133     return false;
00134   }
00135 
00136   return true;
00137 }
00138 
00139 bool ICalFormat::fromString( Calendar *cal, const TQString &text )
00140 {
00141   return fromRawString( cal, text.utf8() );
00142 }
00143 
00144 bool ICalFormat::fromRawString( Calendar *cal, const TQCString &text )
00145 {
00146   setTimeZone( cal->timeZoneId(), !cal->isLocalTime() );
00147 
00148   // Get first VCALENDAR component.
00149   // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
00150   icalcomponent *calendar;
00151 
00152   // Let's defend const correctness until the very gates of hell
00153   calendar = icalcomponent_new_from_string( const_cast<char*>( (const char*)text ) );
00154   //  kdDebug(5800) << "Error: " << icalerror_perror() << endl;
00155   if (!calendar) {
00156     kdDebug(5800) << "ICalFormat::load() parse error" << endl;
00157     setException(new ErrorFormat(ErrorFormat::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 != 0; comp = icalcomponent_get_next_component(calendar, ICAL_VCALENDAR_COMPONENT) ) {
00167       // put all objects into their proper places
00168       if ( !mImpl->populate( cal, comp ) ) {
00169         kdDebug(5800) << "ICalFormat::load(): Could not populate calendar" << endl;
00170         if ( !exception() ) {
00171           setException(new ErrorFormat(ErrorFormat::ParseErrorKcal));
00172         }
00173         success = false;
00174       } else {
00175         mLoadedProductId = mImpl->loadedProductId();
00176       }
00177       icalcomponent_free( comp );
00178     }
00179   } else if (icalcomponent_isa(calendar) != ICAL_VCALENDAR_COMPONENT) {
00180     kdDebug(5800) << "ICalFormat::load(): No VCALENDAR component found" << endl;
00181     setException(new ErrorFormat(ErrorFormat::NoCalendar));
00182     success = false;
00183   } else {
00184     // put all objects into their proper places
00185     if ( !mImpl->populate( cal, calendar ) ) {
00186       kdDebug(5800) << "ICalFormat::load(): Could not populate calendar" << endl;
00187       if ( !exception() ) {
00188         setException(new ErrorFormat(ErrorFormat::ParseErrorKcal));
00189       }
00190       success = false;
00191     } else
00192       mLoadedProductId = mImpl->loadedProductId();
00193   }
00194 
00195   icalcomponent_free( calendar );
00196   icalmemory_free_ring();
00197 
00198   return success;
00199 }
00200 
00201 Incidence *ICalFormat::fromString( const TQString &text )
00202 {
00203   CalendarLocal cal( mTimeZoneId );
00204   fromString(&cal, text);
00205 
00206   Incidence *ical = 0;
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 ? ical->clone() : 0;
00223 }
00224 
00225 TQString ICalFormat::toString( Calendar *cal )
00226 {
00227   setTimeZone( cal->timeZoneId(), !cal->isLocalTime() );
00228 
00229   icalcomponent *calendar = mImpl->createCalendarComponent(cal);
00230 
00231   icalcomponent *component;
00232 
00233   // todos
00234   Todo::List todoList = cal->rawTodos();
00235   Todo::List::ConstIterator it;
00236   for( it = todoList.begin(); it != todoList.end(); ++it ) {
00237 //    kdDebug(5800) << "ICalFormat::toString() write todo "
00238 //                  << (*it)->uid() << endl;
00239     component = mImpl->writeTodo( *it );
00240     icalcomponent_add_component( calendar, component );
00241   }
00242 
00243   // events
00244   Event::List events = cal->rawEvents();
00245   Event::List::ConstIterator it2;
00246   for( it2 = events.begin(); it2 != events.end(); ++it2 ) {
00247 //    kdDebug(5800) << "ICalFormat::toString() write event "
00248 //                  << (*it2)->uid() << endl;
00249     component = mImpl->writeEvent( *it2 );
00250     icalcomponent_add_component( calendar, component );
00251   }
00252 
00253   // journals
00254   Journal::List journals = cal->journals();
00255   Journal::List::ConstIterator it3;
00256   for( it3 = journals.begin(); it3 != journals.end(); ++it3 ) {
00257     kdDebug(5800) << "ICalFormat::toString() write journal "
00258                   << (*it3)->uid() << endl;
00259     component = mImpl->writeJournal( *it3 );
00260     icalcomponent_add_component( calendar, component );
00261   }
00262 
00263   TQString text = TQString::fromUtf8( icalcomponent_as_ical_string( calendar ) );
00264 
00265   icalcomponent_free( calendar );
00266   icalmemory_free_ring();
00267 
00268   if (!text) {
00269     setException(new ErrorFormat(ErrorFormat::SaveError,
00270                  i18n("libical error")));
00271     return TQString();
00272   }
00273 
00274   return text;
00275 }
00276 
00277 TQString ICalFormat::toICalString( Incidence *incidence )
00278 {
00279   CalendarLocal cal( mTimeZoneId );
00280   cal.addIncidence( incidence->clone() );
00281   return toString( &cal );
00282 }
00283 
00284 TQString ICalFormat::toString( Incidence *incidence )
00285 {
00286   icalcomponent *component;
00287 
00288   component = mImpl->writeIncidence( incidence );
00289 
00290   TQString text = TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
00291 
00292   icalcomponent_free( component );
00293 
00294   return text;
00295 }
00296 
00297 TQString ICalFormat::toString( Incidence *incidence, Calendar *calendar )
00298 {
00299   icalcomponent *component;
00300   TQString text = "";
00301 
00302   // See if there are any parent or child events that must be added to the string
00303   if ( incidence->hasRecurrenceID() ) {
00304     // Get the parent
00305     IncidenceList il = incidence->childIncidences();
00306     IncidenceListIterator it;
00307     it = il.begin();
00308     Incidence *parentIncidence;
00309     parentIncidence = calendar->incidence(*it);
00310     il = parentIncidence->childIncidences();
00311     if (il.count() > 0) {
00312       for ( it = il.begin(); it != il.end(); ++it ) {
00313         component = mImpl->writeIncidence( calendar->incidence(*it) );
00314         text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
00315         icalcomponent_free( component );
00316       }
00317     }
00318     component = mImpl->writeIncidence( parentIncidence );
00319     text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
00320     icalcomponent_free( component );
00321   }
00322   else {
00323     // This incidence is a potential parent
00324     IncidenceList il = incidence->childIncidences();
00325     if (il.count() > 0) {
00326       IncidenceListIterator it;
00327       for ( it = il.begin(); it != il.end(); ++it ) {
00328         component = mImpl->writeIncidence( calendar->incidence(*it) );
00329         text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
00330         icalcomponent_free( component );
00331       }
00332     }
00333     component = mImpl->writeIncidence( incidence );
00334     text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
00335     icalcomponent_free( component );
00336   }
00337 
00338   return text;
00339 }
00340 
00341 TQString ICalFormat::toString( RecurrenceRule *recurrence )
00342 {
00343   icalproperty *property;
00344   property = icalproperty_new_rrule( mImpl->writeRecurrenceRule( recurrence ) );
00345   TQString text = TQString::fromUtf8( icalproperty_as_ical_string( property ) );
00346   icalproperty_free( property );
00347   return text;
00348 }
00349 
00350 bool ICalFormat::fromString( RecurrenceRule * recurrence, const TQString& rrule )
00351 {
00352   if ( !recurrence ) return false;
00353   bool success = true;
00354   icalerror_clear_errno();
00355   struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.latin1() );
00356   if ( icalerrno != ICAL_NO_ERROR ) {
00357     kdDebug(5800) << "Recurrence parsing error: " << icalerror_strerror( icalerrno ) << endl;
00358     success = false;
00359   }
00360 
00361   if ( success ) {
00362     mImpl->readRecurrence( recur, recurrence );
00363   }
00364 
00365   return success;
00366 }
00367 
00368 
00369 TQString ICalFormat::createScheduleMessage(IncidenceBase *incidence,
00370                                           Scheduler::Method method)
00371 {
00372   icalcomponent *message = 0;
00373 
00374   // Handle scheduling ID being present
00375   if ( incidence->type() == "Event" || incidence->type() == "Todo" ) {
00376     Incidence* i = static_cast<Incidence*>( incidence );
00377     if ( i->schedulingID() != i->uid() ) {
00378       // We have a separation of scheduling ID and UID
00379       i = i->clone();
00380       i->setUid( i->schedulingID() );
00381       i->setSchedulingID( TQString() );
00382 
00383       // Build the message with the cloned incidence
00384       message = mImpl->createScheduleComponent( i, method );
00385 
00386       // And clean up
00387       delete i;
00388     }
00389   }
00390 
00391   if ( message == 0 )
00392     message = mImpl->createScheduleComponent(incidence,method);
00393 
00394   // FIXME TODO: Don't we have to free message? What about the ical_string? MEMLEAK
00395   TQString messageText = TQString::fromUtf8( icalcomponent_as_ical_string(message) );
00396 
00397 #if 0
00398   kdDebug(5800) << "ICalFormat::createScheduleMessage: message START\n"
00399             << messageText
00400             << "ICalFormat::createScheduleMessage: message END" << endl;
00401 #endif
00402 
00403   return messageText;
00404 }
00405 
00406 FreeBusy *ICalFormat::parseFreeBusy( const TQString &str )
00407 {
00408   clearException();
00409 
00410   icalcomponent *message;
00411   message = icalparser_parse_string( str.utf8() );
00412 
00413   if ( !message ) return 0;
00414 
00415   FreeBusy *freeBusy = 0;
00416 
00417   icalcomponent *c;
00418   for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00419         c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
00420     FreeBusy *fb = mImpl->readFreeBusy( c );
00421 
00422     if ( freeBusy ) {
00423       freeBusy->merge( fb );
00424       delete fb;
00425     } else {
00426       freeBusy = fb;
00427     }
00428   }
00429 
00430   if ( !freeBusy )
00431     kdDebug(5800) << "ICalFormat:parseFreeBusy: object is not a freebusy."
00432                   << endl;
00433   return freeBusy;
00434 }
00435 
00436 ScheduleMessage *ICalFormat::parseScheduleMessage( Calendar *cal,
00437                                                    const TQString &messageText )
00438 {
00439   setTimeZone( cal->timeZoneId(), !cal->isLocalTime() );
00440   clearException();
00441 
00442   if (messageText.isEmpty())
00443   {
00444     setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "messageText was empty, unable to parse into a ScheduleMessage" ) ) );
00445     return 0;
00446   }
00447   // TODO FIXME: Don't we have to ical-free message??? MEMLEAK
00448   icalcomponent *message;
00449   message = icalparser_parse_string(messageText.utf8());
00450 
00451   if (!message)
00452   {
00453     setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "icalparser was unable to parse messageText into a ScheduleMessage" ) ) );
00454     return 0;
00455   }
00456 
00457   icalproperty *m = icalcomponent_get_first_property(message,
00458                                                      ICAL_METHOD_PROPERTY);
00459   if (!m)
00460   {
00461     setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "message didn't contain an ICAL_METHOD_PROPERTY" ) ) );
00462     return 0;
00463   }
00464 
00465   icalcomponent *c;
00466 
00467   IncidenceBase *incidence = 0;
00468   c = icalcomponent_get_first_component(message,ICAL_VEVENT_COMPONENT);
00469   if (c) {
00470     icalcomponent *ctz = icalcomponent_get_first_component(message,ICAL_VTIMEZONE_COMPONENT);
00471     incidence = mImpl->readEvent(c, ctz);
00472   }
00473 
00474   if (!incidence) {
00475     c = icalcomponent_get_first_component(message,ICAL_VTODO_COMPONENT);
00476     if (c) {
00477       incidence = mImpl->readTodo(c);
00478     }
00479   }
00480 
00481   if (!incidence) {
00482     c = icalcomponent_get_first_component(message,ICAL_VJOURNAL_COMPONENT);
00483     if (c) {
00484       incidence = mImpl->readJournal(c);
00485     }
00486   }
00487 
00488   if (!incidence) {
00489     c = icalcomponent_get_first_component(message,ICAL_VFREEBUSY_COMPONENT);
00490     if (c) {
00491       incidence = mImpl->readFreeBusy(c);
00492     }
00493   }
00494 
00495 
00496 
00497   if (!incidence) {
00498     kdDebug(5800) << "ICalFormat:parseScheduleMessage: object is not a freebusy, event, todo or journal" << endl;
00499     setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "object is not a freebusy, event, todo or journal" ) ) );
00500     return 0;
00501   }
00502 
00503   kdDebug(5800) << "ICalFormat::parseScheduleMessage() getting method..." << endl;
00504 
00505   icalproperty_method icalmethod = icalproperty_get_method(m);
00506   Scheduler::Method method;
00507 
00508   switch (icalmethod) {
00509     case ICAL_METHOD_PUBLISH:
00510       method = Scheduler::Publish;
00511       break;
00512     case ICAL_METHOD_REQUEST:
00513       method = Scheduler::Request;
00514       break;
00515     case ICAL_METHOD_REFRESH:
00516       method = Scheduler::Refresh;
00517       break;
00518     case ICAL_METHOD_CANCEL:
00519       method = Scheduler::Cancel;
00520       break;
00521     case ICAL_METHOD_ADD:
00522       method = Scheduler::Add;
00523       break;
00524     case ICAL_METHOD_REPLY:
00525       method = Scheduler::Reply;
00526       break;
00527     case ICAL_METHOD_COUNTER:
00528       method = Scheduler::Counter;
00529       break;
00530     case ICAL_METHOD_DECLINECOUNTER:
00531       method = Scheduler::Declinecounter;
00532       break;
00533     default:
00534       method = Scheduler::NoMethod;
00535       kdDebug(5800) << "ICalFormat::parseScheduleMessage(): Unknow method" << endl;
00536       break;
00537   }
00538 
00539   kdDebug(5800) << "ICalFormat::parseScheduleMessage() restriction..." << endl;
00540 
00541   if (!icalrestriction_check(message)) {
00542     kdWarning(5800) << k_funcinfo << endl << "libkcal reported a problem while parsing:" << endl;
00543     kdWarning(5800) << Scheduler::translatedMethodName(method) + ": " + mImpl->extractErrorProperty(c)<< endl;
00544     /*
00545     setException(new ErrorFormat(ErrorFormat::Restriction,
00546                                    Scheduler::translatedMethodName(method) + ": " +
00547                                    mImpl->extractErrorProperty(c)));
00548     delete incidence;
00549     return 0;
00550     */
00551   }
00552   icalcomponent *calendarComponent = mImpl->createCalendarComponent(cal);
00553 
00554   Incidence *existingIncidence =
00555     cal->incidenceFromSchedulingID(incidence->uid());
00556   if (existingIncidence) {
00557     // TODO: check, if cast is required, or if it can be done by virtual funcs.
00558     // TODO: Use a visitor for this!
00559     if (existingIncidence->type() == "Todo") {
00560       Todo *todo = static_cast<Todo *>(existingIncidence);
00561       icalcomponent_add_component(calendarComponent,
00562                                   mImpl->writeTodo(todo));
00563     }
00564     if (existingIncidence->type() == "Event") {
00565       Event *event = static_cast<Event *>(existingIncidence);
00566       icalcomponent_add_component(calendarComponent,
00567                                   mImpl->writeEvent(event));
00568     }
00569   } else {
00570     calendarComponent = 0;
00571   }
00572 
00573   kdDebug(5800) << "ICalFormat::parseScheduleMessage() classify..." << endl;
00574 
00575   icalproperty_xlicclass result = icalclassify( message, calendarComponent,
00576                                                 (char *)"" );
00577 
00578   kdDebug(5800) << "ICalFormat::parseScheduleMessage() returning..." << endl;
00579   kdDebug(5800) << "ICalFormat::parseScheduleMessage(), result = " << result << endl;
00580 
00581   ScheduleMessage::Status status;
00582 
00583   switch (result) {
00584     case ICAL_XLICCLASS_PUBLISHNEW:
00585       status = ScheduleMessage::PublishNew;
00586       break;
00587     case ICAL_XLICCLASS_PUBLISHUPDATE:
00588       status = ScheduleMessage::PublishUpdate;
00589       break;
00590     case ICAL_XLICCLASS_OBSOLETE:
00591       status = ScheduleMessage::Obsolete;
00592       break;
00593     case ICAL_XLICCLASS_REQUESTNEW:
00594       status = ScheduleMessage::RequestNew;
00595       break;
00596     case ICAL_XLICCLASS_REQUESTUPDATE:
00597       status = ScheduleMessage::RequestUpdate;
00598       break;
00599     case ICAL_XLICCLASS_UNKNOWN:
00600     default:
00601       status = ScheduleMessage::Unknown;
00602       break;
00603   }
00604 
00605   kdDebug(5800) << "ICalFormat::parseScheduleMessage(), status = " << status << endl;
00606 // TODO FIXME: Don't we have to free calendarComponent??? MEMLEAK
00607 
00608   return new ScheduleMessage(incidence,method,status);
00609 }
00610 
00611 void ICalFormat::setTimeZone( const TQString &id, bool utc )
00612 {
00613   mTimeZoneId = id;
00614   mUtc = utc;
00615 }
00616 
00617 TQString ICalFormat::timeZoneId() const
00618 {
00619   return mTimeZoneId;
00620 }
00621 
00622 bool ICalFormat::utc() const
00623 {
00624   return mUtc;
00625 }