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