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