• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

KCalCore Library

icalformat.cpp

Go to the documentation of this file.
00001 /*
00002   This file is part of the kcalcore library.
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 */
00031 #include "icalformat.h"
00032 #include "icalformat_p.h"
00033 #include "icaltimezones.h"
00034 #include "freebusy.h"
00035 #include "memorycalendar.h"
00036 
00037 #include <KDebug>
00038 #include <KSaveFile>
00039 
00040 #include <QtCore/QFile>
00041 
00042 extern "C" {
00043   #include <libical/ical.h>
00044   #include <libical/icalss.h>
00045   #include <libical/icalparser.h>
00046   #include <libical/icalrestriction.h>
00047   #include <libical/icalmemory.h>
00048 }
00049 
00050 using namespace KCalCore;
00051 
00052 //@cond PRIVATE
00053 class KCalCore::ICalFormat::Private
00054 {
00055   public:
00056     Private( ICalFormat *parent )
00057       : mImpl( new ICalFormatImpl( parent ) ),
00058         mTimeSpec( KDateTime::UTC )
00059     {}
00060     ~Private()  { delete mImpl; }
00061     ICalFormatImpl *mImpl;
00062     KDateTime::Spec mTimeSpec;
00063 };
00064 //@endcond
00065 
00066 ICalFormat::ICalFormat()
00067   : d( new Private( this ) )
00068 {
00069 }
00070 
00071 ICalFormat::~ICalFormat()
00072 {
00073   delete d;
00074 }
00075 
00076 bool ICalFormat::load( const Calendar::Ptr &calendar, const QString &fileName )
00077 {
00078   kDebug() << fileName;
00079 
00080   clearException();
00081 
00082   QFile file( fileName );
00083   if ( !file.open( QIODevice::ReadOnly ) ) {
00084     kDebug() << "load error";
00085     setException( new Exception( Exception::LoadError ) );
00086     return false;
00087   }
00088   QTextStream ts( &file );
00089   ts.setCodec( "UTF-8" );
00090   QByteArray text = ts.readAll().trimmed().toUtf8();
00091   file.close();
00092 
00093   if ( text.isEmpty() ) {
00094     // empty files are valid
00095     return true;
00096   } else {
00097     return fromRawString( calendar, text, false, fileName );
00098   }
00099 }
00100 
00101 bool ICalFormat::save( const Calendar::Ptr &calendar, const QString &fileName )
00102 {
00103   kDebug() << fileName;
00104 
00105   clearException();
00106 
00107   QString text = toString( calendar );
00108   if ( text.isEmpty() ) {
00109     return false;
00110   }
00111 
00112   // Write backup file
00113   KSaveFile::backupFile( fileName );
00114 
00115   KSaveFile file( fileName );
00116   if ( !file.open() ) {
00117     kDebug() << "err:" << file.errorString();
00118     setException( new Exception( Exception::SaveErrorOpenFile,
00119                                  QStringList( fileName ) ) );
00120 
00121     return false;
00122   }
00123 
00124   // Convert to UTF8 and save
00125   QByteArray textUtf8 = text.toUtf8();
00126   file.write( textUtf8.data(), textUtf8.size() );
00127 
00128   if ( !file.finalize() ) {
00129     kDebug() << "err:" << file.errorString();
00130     setException( new Exception( Exception::SaveErrorSaveFile,
00131                                  QStringList( fileName ) ) );
00132 
00133     return false;
00134   }
00135 
00136   return true;
00137 }
00138 
00139 bool ICalFormat::fromString( const Calendar::Ptr &cal, const QString &string,
00140                              bool deleted, const QString &notebook )
00141 {
00142   return fromRawString( cal, string.toUtf8(), deleted, notebook );
00143 }
00144 
00145 bool ICalFormat::fromRawString( const Calendar::Ptr &cal, const QByteArray &string,
00146                                 bool deleted, const QString &notebook )
00147 {
00148   Q_UNUSED( notebook );
00149   // Get first VCALENDAR component.
00150   // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
00151   icalcomponent *calendar;
00152 
00153   // Let's defend const correctness until the very gates of hell^Wlibical
00154   calendar = icalcomponent_new_from_string( const_cast<char*>( ( const char * )string ) );
00155   if ( !calendar ) {
00156     kDebug() << "parse error";
00157     setException( new Exception( Exception::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; comp = icalcomponent_get_next_component( calendar, ICAL_VCALENDAR_COMPONENT ) ) {
00167       // put all objects into their proper places
00168       if ( !d->mImpl->populate( cal, comp, deleted ) ) {
00169         kDebug() << "Could not populate calendar";
00170         if ( !exception() ) {
00171           setException( new Exception( Exception::ParseErrorKcal ) );
00172         }
00173         success = false;
00174       } else {
00175         setLoadedProductId( d->mImpl->loadedProductId() );
00176       }
00177     }
00178   } else if ( icalcomponent_isa( calendar ) != ICAL_VCALENDAR_COMPONENT ) {
00179     kDebug() << "No VCALENDAR component found";
00180     setException( new Exception( Exception::NoCalendar ) );
00181     success = false;
00182   } else {
00183     // put all objects into their proper places
00184     if ( !d->mImpl->populate( cal, calendar, deleted ) ) {
00185       kDebug() << "Could not populate calendar";
00186       if ( !exception() ) {
00187         setException( new Exception( Exception::ParseErrorKcal ) );
00188       }
00189       success = false;
00190     } else {
00191       setLoadedProductId( d->mImpl->loadedProductId() );
00192     }
00193   }
00194 
00195   icalcomponent_free( calendar );
00196   icalmemory_free_ring();
00197 
00198   return success;
00199 }
00200 
00201 Incidence::Ptr ICalFormat::fromString( const QString &string )
00202 {
00203   MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) );
00204   fromString( cal, string );
00205 
00206   Incidence::Ptr ical;
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 ? Incidence::Ptr( ical->clone() ) : Incidence::Ptr();
00223 }
00224 
00225 QString ICalFormat::toString( const Calendar::Ptr &cal,
00226                               const QString &notebook, bool deleted )
00227 {
00228   icalcomponent *calendar = d->mImpl->createCalendarComponent( cal );
00229   icalcomponent *component;
00230 
00231   ICalTimeZones *tzlist = cal->timeZones();  // time zones possibly used in the calendar
00232   ICalTimeZones tzUsedList;                  // time zones actually used in the calendar
00233 
00234   // todos
00235   Todo::List todoList = deleted ? cal->deletedTodos() : cal->rawTodos();
00236   Todo::List::ConstIterator it;
00237   for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
00238     if ( !deleted || !cal->todo( (*it)->uid(), (*it)->recurrenceId() ) ) {
00239       // use existing ones, or really deleted ones
00240       if ( notebook.isEmpty() ||
00241            ( !cal->notebook( *it ).isEmpty() && notebook.endsWith( cal->notebook( *it ) ) ) ) {
00242         component = d->mImpl->writeTodo( *it, tzlist, &tzUsedList );
00243         icalcomponent_add_component( calendar, component );
00244       }
00245     }
00246   }
00247   // events
00248   Event::List events = deleted ? cal->deletedEvents() : cal->rawEvents();
00249   Event::List::ConstIterator it2;
00250   for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
00251     if ( !deleted || !cal->event( (*it2)->uid(), (*it2)->recurrenceId() ) ) {
00252       // use existing ones, or really deleted ones
00253       if ( notebook.isEmpty() ||
00254            ( !cal->notebook( *it2 ).isEmpty() && notebook.endsWith( cal->notebook( *it2 ) ) ) ) {
00255         component = d->mImpl->writeEvent( *it2, tzlist, &tzUsedList );
00256         icalcomponent_add_component( calendar, component );
00257       }
00258     }
00259   }
00260 
00261   // journals
00262   Journal::List journals = deleted ? cal->deletedJournals() : cal->rawJournals();
00263   Journal::List::ConstIterator it3;
00264   for ( it3 = journals.constBegin(); it3 != journals.constEnd(); ++it3 ) {
00265     if ( !deleted || !cal->journal( (*it3)->uid(), (*it3)->recurrenceId() ) ) {
00266       // use existing ones, or really deleted ones
00267       if ( notebook.isEmpty() ||
00268            ( !cal->notebook( *it3 ).isEmpty() && notebook.endsWith( cal->notebook( *it3 ) ) ) ) {
00269         component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList );
00270         icalcomponent_add_component( calendar, component );
00271       }
00272     }
00273   }
00274 
00275   // time zones
00276   ICalTimeZones::ZoneMap zones = tzUsedList.zones();
00277   if ( todoList.isEmpty() && events.isEmpty() && journals.isEmpty() ) {
00278     // no incidences means no used timezones, use all timezones
00279     // this will export a calendar having only timezone definitions
00280     zones = tzlist->zones();
00281   }
00282   for ( ICalTimeZones::ZoneMap::ConstIterator it=zones.constBegin();
00283         it != zones.constEnd(); ++it ) {
00284     icaltimezone *tz = (*it).icalTimezone();
00285     if ( !tz ) {
00286       kError() << "bad time zone";
00287     } else {
00288       component = icalcomponent_new_clone( icaltimezone_get_component( tz ) );
00289       icalcomponent_add_component( calendar, component );
00290       icaltimezone_free( tz, 1 );
00291     }
00292   }
00293 
00294   QString text = QString::fromUtf8( icalcomponent_as_ical_string( calendar ) );
00295 
00296   icalcomponent_free( calendar );
00297   icalmemory_free_ring();
00298 
00299   if ( text.isEmpty() ) {
00300     setException( new Exception( Exception::LibICalError ) );
00301   }
00302 
00303   return text;
00304 }
00305 
00306 QString ICalFormat::toICalString( const Incidence::Ptr &incidence )
00307 {
00308   MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) );
00309   cal->addIncidence( Incidence::Ptr( incidence->clone() ) );
00310   return toString( cal.staticCast<Calendar>() );
00311 }
00312 
00313 QString ICalFormat::toString( const Incidence::Ptr &incidence )
00314 {
00315   icalcomponent *component;
00316 
00317   component = d->mImpl->writeIncidence( incidence );
00318 
00319   QString text = QString::fromUtf8( icalcomponent_as_ical_string( component ) );
00320 
00321   icalcomponent_free( component );
00322 
00323   return text;
00324 }
00325 
00326 QString ICalFormat::toString( RecurrenceRule *recurrence )
00327 {
00328   icalproperty *property;
00329   property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) );
00330   QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) );
00331   icalproperty_free( property );
00332   return text;
00333 }
00334 
00335 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule )
00336 {
00337   if ( !recurrence ) {
00338     return false;
00339   }
00340   bool success = true;
00341   icalerror_clear_errno();
00342   struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() );
00343   if ( icalerrno != ICAL_NO_ERROR ) {
00344     kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno );
00345     success = false;
00346   }
00347 
00348   if ( success ) {
00349     d->mImpl->readRecurrence( recur, recurrence );
00350   }
00351 
00352   return success;
00353 }
00354 
00355 QString ICalFormat::createScheduleMessage( const IncidenceBase::Ptr &incidence,
00356                                            iTIPMethod method )
00357 {
00358   icalcomponent *message = 0;
00359 
00360   // Handle scheduling ID being present
00361   if ( incidence->type() == Incidence::TypeEvent ||
00362        incidence->type() == Incidence::TypeTodo ) {
00363     Incidence::Ptr i = incidence.staticCast<Incidence>();
00364     if ( i->schedulingID() != i->uid() ) {
00365       // We have a separation of scheduling ID and UID
00366       i = Incidence::Ptr( i->clone() );
00367       i->setSchedulingID( QString(), i->schedulingID() );
00368 
00369       // Build the message with the cloned incidence
00370       message = d->mImpl->createScheduleComponent( i, method );
00371     }
00372   }
00373 
00374   if ( message == 0 ) {
00375     message = d->mImpl->createScheduleComponent( incidence, method );
00376   }
00377 
00378   QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) );
00379 
00380   icalcomponent_free( message );
00381   return messageText;
00382 }
00383 
00384 FreeBusy::Ptr ICalFormat::parseFreeBusy( const QString &str )
00385 {
00386   clearException();
00387 
00388   icalcomponent *message;
00389   message = icalparser_parse_string( str.toUtf8() );
00390 
00391   if ( !message ) {
00392     return FreeBusy::Ptr();
00393   }
00394 
00395   FreeBusy::Ptr freeBusy;
00396 
00397   icalcomponent *c;
00398   for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00399         c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
00400     FreeBusy::Ptr fb = d->mImpl->readFreeBusy( c );
00401 
00402     if ( freeBusy ) {
00403       freeBusy->merge( fb );
00404     } else {
00405       freeBusy = fb;
00406     }
00407   }
00408 
00409   if ( !freeBusy ) {
00410     kDebug() << "object is not a freebusy.";
00411   }
00412 
00413   icalcomponent_free( message );
00414 
00415   return freeBusy;
00416 }
00417 
00418 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage( const Calendar::Ptr &cal,
00419                                                        const QString &messageText )
00420 {
00421   setTimeSpec( cal->timeSpec() );
00422   clearException();
00423 
00424   if ( messageText.isEmpty() ) {
00425     setException(
00426       new Exception( Exception::ParseErrorEmptyMessage ) );
00427     return ScheduleMessage::Ptr();
00428   }
00429 
00430   icalcomponent *message;
00431   message = icalparser_parse_string( messageText.toUtf8() );
00432 
00433   if ( !message ) {
00434     setException(
00435       new Exception( Exception::ParseErrorUnableToParse ) );
00436 
00437     return ScheduleMessage::Ptr();
00438   }
00439 
00440   icalproperty *m =
00441     icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY );
00442   if ( !m ) {
00443     setException(
00444       new Exception( Exception::ParseErrorMethodProperty ) );
00445 
00446     return ScheduleMessage::Ptr();
00447   }
00448 
00449   // Populate the message's time zone collection with all VTIMEZONE components
00450   ICalTimeZones tzlist;
00451   ICalTimeZoneSource tzs;
00452   tzs.parse( message, tzlist );
00453 
00454   icalcomponent *c;
00455 
00456   IncidenceBase::Ptr incidence;
00457   c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT );
00458   if ( c ) {
00459     incidence = d->mImpl->readEvent( c, &tzlist ).staticCast<IncidenceBase>();
00460   }
00461 
00462   if ( !incidence ) {
00463     c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT );
00464     if ( c ) {
00465       incidence = d->mImpl->readTodo( c, &tzlist ).staticCast<IncidenceBase>();
00466     }
00467   }
00468 
00469   if ( !incidence ) {
00470     c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT );
00471     if ( c ) {
00472       incidence = d->mImpl->readJournal( c, &tzlist ).staticCast<IncidenceBase>();
00473     }
00474   }
00475 
00476   if ( !incidence ) {
00477     c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00478     if ( c ) {
00479       incidence = d->mImpl->readFreeBusy( c ).staticCast<IncidenceBase>();
00480     }
00481   }
00482 
00483   if ( !incidence ) {
00484     kDebug() << "object is not a freebusy, event, todo or journal";
00485     setException( new Exception( Exception::ParseErrorNotIncidence ) );
00486 
00487     return ScheduleMessage::Ptr();
00488   }
00489 
00490   icalproperty_method icalmethod = icalproperty_get_method( m );
00491   iTIPMethod method;
00492 
00493   switch ( icalmethod ) {
00494   case ICAL_METHOD_PUBLISH:
00495     method = iTIPPublish;
00496     break;
00497   case ICAL_METHOD_REQUEST:
00498     method = iTIPRequest;
00499     break;
00500   case ICAL_METHOD_REFRESH:
00501     method = iTIPRefresh;
00502     break;
00503   case ICAL_METHOD_CANCEL:
00504     method = iTIPCancel;
00505     break;
00506   case ICAL_METHOD_ADD:
00507     method = iTIPAdd;
00508     break;
00509   case ICAL_METHOD_REPLY:
00510     method = iTIPReply;
00511     break;
00512   case ICAL_METHOD_COUNTER:
00513     method = iTIPCounter;
00514     break;
00515   case ICAL_METHOD_DECLINECOUNTER:
00516     method = iTIPDeclineCounter;
00517     break;
00518   default:
00519     method = iTIPNoMethod;
00520     kDebug() << "Unknown method";
00521     break;
00522   }
00523 
00524   if ( !icalrestriction_check( message ) ) {
00525     kWarning() << endl
00526                << "kcalcore library reported a problem while parsing:";
00527     kWarning() << ScheduleMessage::methodName( method ) << ":"  //krazy:exclude=kdebug
00528                << d->mImpl->extractErrorProperty( c );
00529   }
00530 
00531   Incidence::Ptr existingIncidence = cal->incidence( incidence->uid() );
00532 
00533   icalcomponent *calendarComponent = 0;
00534   if ( existingIncidence ) {
00535     calendarComponent = d->mImpl->createCalendarComponent( cal );
00536 
00537     // TODO: check, if cast is required, or if it can be done by virtual funcs.
00538     // TODO: Use a visitor for this!
00539     if ( existingIncidence->type() == Incidence::TypeTodo ) {
00540       Todo::Ptr todo = existingIncidence.staticCast<Todo>();
00541       icalcomponent_add_component( calendarComponent,
00542                                    d->mImpl->writeTodo( todo ) );
00543     }
00544     if ( existingIncidence->type() == Incidence::TypeEvent ) {
00545       Event::Ptr event = existingIncidence.staticCast<Event>();
00546       icalcomponent_add_component( calendarComponent,
00547                                    d->mImpl->writeEvent( event ) );
00548     }
00549   } else {
00550     icalcomponent_free( message );
00551     return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method,
00552                                                       ScheduleMessage::Unknown ) );
00553   }
00554 
00555   icalproperty_xlicclass result =
00556     icalclassify( message, calendarComponent, static_cast<const char *>( "" ) );
00557 
00558   ScheduleMessage::Status status;
00559 
00560   switch ( result ) {
00561   case ICAL_XLICCLASS_PUBLISHNEW:
00562     status = ScheduleMessage::PublishNew;
00563     break;
00564   case ICAL_XLICCLASS_PUBLISHUPDATE:
00565     status = ScheduleMessage::PublishUpdate;
00566     break;
00567   case ICAL_XLICCLASS_OBSOLETE:
00568     status = ScheduleMessage::Obsolete;
00569     break;
00570   case ICAL_XLICCLASS_REQUESTNEW:
00571     status = ScheduleMessage::RequestNew;
00572     break;
00573   case ICAL_XLICCLASS_REQUESTUPDATE:
00574     status = ScheduleMessage::RequestUpdate;
00575     break;
00576   case ICAL_XLICCLASS_UNKNOWN:
00577   default:
00578     status = ScheduleMessage::Unknown;
00579     break;
00580   }
00581 
00582   icalcomponent_free( message );
00583   icalcomponent_free( calendarComponent );
00584 
00585   return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, status ) );
00586 }
00587 
00588 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec )
00589 {
00590   d->mTimeSpec = timeSpec;
00591 }
00592 
00593 KDateTime::Spec ICalFormat::timeSpec() const
00594 {
00595   return d->mTimeSpec;
00596 }
00597 
00598 QString ICalFormat::timeZoneId() const
00599 {
00600   KTimeZone tz = d->mTimeSpec.timeZone();
00601   return tz.isValid() ? tz.name() : QString();
00602 }
00603 
00604 void ICalFormat::virtual_hook( int id, void *data )
00605 {
00606   Q_UNUSED( id );
00607   Q_UNUSED( data );
00608   Q_ASSERT( false );
00609 }

KCalCore Library

Skip menu "KCalCore Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal