diff --git a/kcal/icalformat.cpp b/kcal/icalformat.cpp index 2daa147c8..d6d375d2e 100644 --- a/kcal/icalformat.cpp +++ b/kcal/icalformat.cpp @@ -1,592 +1,592 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the ICalFormat class. @brief iCalendar format implementation: a layer of abstraction for libical. @author Cornelius Schumacher \ */ #include "icalformat.h" #include "icalformat_p.h" #include "calendar.h" #include "calendarlocal.h" #include "icaltimezones.h" extern "C" { #include #include #include #include #include } #include #include #include #include #include #include #include #include #include #include using namespace KCal; //@cond PRIVATE class KCal::ICalFormat::Private { public: Private( ICalFormat *parent ) : mImpl( new ICalFormatImpl( parent ) ), mTimeSpec( KDateTime::UTC ) {} ~Private() { delete mImpl; } ICalFormatImpl *mImpl; KDateTime::Spec mTimeSpec; }; //@endcond ICalFormat::ICalFormat() : d( new Private( this ) ) { } ICalFormat::~ICalFormat() { delete d; } bool ICalFormat::load( Calendar *calendar, const QString &fileName ) { kDebug() << fileName; clearException(); QFile file( fileName ); if ( !file.open( QIODevice::ReadOnly ) ) { kDebug() << "load error"; setException( new ErrorFormat( ErrorFormat::LoadError ) ); return false; } QTextStream ts( &file ); ts.setCodec( "ISO 8859-1" ); QByteArray text = ts.readAll().trimmed().toLatin1(); file.close(); if ( text.isEmpty() ) { // empty files are valid return true; } else { return fromRawString( calendar, text ); } } bool ICalFormat::save( Calendar *calendar, const QString &fileName ) { kDebug() << fileName; clearException(); QString text = toString( calendar ); if ( text.isEmpty() ) { return false; } // Write backup file KSaveFile::backupFile( fileName ); KSaveFile file( fileName ); if ( !file.open() ) { kDebug() << "err:" << file.errorString(); setException( new ErrorFormat( ErrorFormat::SaveError, i18n( "Error saving to '%1'.", fileName ) ) ); return false; } // Convert to UTF8 and save QByteArray textUtf8 = text.toUtf8(); - file.write( textUtf8.data(), textUtf8.size() - 1 ); + file.write( textUtf8.data(), textUtf8.size() ); if ( !file.finalize() ) { kDebug() << "err:" << file.errorString(); setException( new ErrorFormat( ErrorFormat::SaveError, i18n( "Could not save '%1'", fileName ) ) ); return false; } return true; } bool ICalFormat::fromString( Calendar *cal, const QString &string ) { return fromRawString( cal, string.toUtf8() ); } bool ICalFormat::fromRawString( Calendar *cal, const QByteArray &string ) { // Get first VCALENDAR component. // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components icalcomponent *calendar; // Let's defend const correctness until the very gates of hell^Wlibical calendar = icalcomponent_new_from_string( const_cast( ( const char * )string ) ); if ( !calendar ) { kDebug() << "parse error"; setException( new ErrorFormat( ErrorFormat::ParseErrorIcal ) ); return false; } bool success = true; if ( icalcomponent_isa( calendar ) == ICAL_XROOT_COMPONENT ) { icalcomponent *comp; for ( comp = icalcomponent_get_first_component( calendar, ICAL_VCALENDAR_COMPONENT ); comp; comp = icalcomponent_get_next_component( calendar, ICAL_VCALENDAR_COMPONENT ) ) { // put all objects into their proper places if ( !d->mImpl->populate( cal, comp ) ) { kDebug() << "Could not populate calendar"; if ( !exception() ) { setException( new ErrorFormat( ErrorFormat::ParseErrorKcal ) ); } success = false; } else { setLoadedProductId( d->mImpl->loadedProductId() ); } } } else if ( icalcomponent_isa( calendar ) != ICAL_VCALENDAR_COMPONENT ) { kDebug() << "No VCALENDAR component found"; setException( new ErrorFormat( ErrorFormat::NoCalendar ) ); success = false; } else { // put all objects into their proper places if ( !d->mImpl->populate( cal, calendar ) ) { kDebug() << "Could not populate calendar"; if ( !exception() ) { setException( new ErrorFormat( ErrorFormat::ParseErrorKcal ) ); } success = false; } else { setLoadedProductId( d->mImpl->loadedProductId() ); } } icalcomponent_free( calendar ); icalmemory_free_ring(); return success; } Incidence *ICalFormat::fromString( const QString &string ) { CalendarLocal cal( d->mTimeSpec ); fromString( &cal, string ); Incidence *ical = 0; Event::List elist = cal.events(); if ( elist.count() > 0 ) { ical = elist.first(); } else { Todo::List tlist = cal.todos(); if ( tlist.count() > 0 ) { ical = tlist.first(); } else { Journal::List jlist = cal.journals(); if ( jlist.count() > 0 ) { ical = jlist.first(); } } } return ical ? ical->clone() : 0; } QString ICalFormat::toString( Calendar *cal ) { icalcomponent *calendar = d->mImpl->createCalendarComponent( cal ); icalcomponent *component; ICalTimeZones *tzlist = cal->timeZones(); // time zones possibly used in the calendar ICalTimeZones tzUsedList; // time zones actually used in the calendar // todos Todo::List todoList = cal->rawTodos(); Todo::List::ConstIterator it; for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) { component = d->mImpl->writeTodo( *it, tzlist, &tzUsedList ); icalcomponent_add_component( calendar, component ); } // events Event::List events = cal->rawEvents(); Event::List::ConstIterator it2; for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) { if ( *it2 ) { component = d->mImpl->writeEvent( *it2, tzlist, &tzUsedList ); icalcomponent_add_component( calendar, component ); } } // journals Journal::List journals = cal->journals(); Journal::List::ConstIterator it3; for ( it3 = journals.constBegin(); it3 != journals.constEnd(); ++it3 ) { component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList ); icalcomponent_add_component( calendar, component ); } // time zones const ICalTimeZones::ZoneMap zones = tzUsedList.zones(); for ( ICalTimeZones::ZoneMap::ConstIterator it=zones.constBegin(); it != zones.constEnd(); ++it ) { icaltimezone *tz = (*it).icalTimezone(); if ( !tz ) { kError() << "bad time zone"; } else { component = icalcomponent_new_clone( icaltimezone_get_component( tz ) ); icalcomponent_add_component( calendar, component ); icaltimezone_free( tz, 1 ); } } QString text = QString::fromUtf8( icalcomponent_as_ical_string( calendar ) ); icalcomponent_free( calendar ); icalmemory_free_ring(); if ( text.isEmpty() ) { setException( new ErrorFormat( ErrorFormat::SaveError, i18n( "libical error" ) ) ); } return text; } QString ICalFormat::toICalString( Incidence *incidence ) { CalendarLocal cal( d->mTimeSpec ); cal.addIncidence( incidence->clone() ); return toString( &cal ); } QString ICalFormat::toString( Incidence *incidence ) { icalcomponent *component; component = d->mImpl->writeIncidence( incidence ); QString text = QString::fromUtf8( icalcomponent_as_ical_string( component ) ); icalcomponent_free( component ); return text; } QString ICalFormat::toString( RecurrenceRule *recurrence ) { icalproperty *property; property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) ); QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) ); icalproperty_free( property ); return text; } bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule ) { if ( !recurrence ) { return false; } bool success = true; icalerror_clear_errno(); struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() ); if ( icalerrno != ICAL_NO_ERROR ) { kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno ); success = false; } if ( success ) { d->mImpl->readRecurrence( recur, recurrence ); } return success; } QString ICalFormat::createScheduleMessage( IncidenceBase *incidence, iTIPMethod method ) { icalcomponent *message = 0; // Handle scheduling ID being present if ( incidence->type() == "Event" || incidence->type() == "Todo" ) { Incidence *i = static_cast( incidence ); if ( i->schedulingID() != i->uid() ) { // We have a separation of scheduling ID and UID i = i->clone(); i->setUid( i->schedulingID() ); i->setSchedulingID( QString() ); // Build the message with the cloned incidence message = d->mImpl->createScheduleComponent( i, method ); // And clean up delete i; } } if ( message == 0 ) { message = d->mImpl->createScheduleComponent( incidence, method ); } QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) ); icalcomponent_free( message ); return messageText; } FreeBusy *ICalFormat::parseFreeBusy( const QString &str ) { clearException(); icalcomponent *message; message = icalparser_parse_string( str.toUtf8() ); if ( !message ) { return 0; } FreeBusy *freeBusy = 0; icalcomponent *c; for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT ); c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) { FreeBusy *fb = d->mImpl->readFreeBusy( c ); if ( freeBusy ) { freeBusy->merge( fb ); delete fb; } else { freeBusy = fb; } } if ( !freeBusy ) { kDebug() << "object is not a freebusy."; } return freeBusy; } ScheduleMessage *ICalFormat::parseScheduleMessage( Calendar *cal, const QString &messageText ) { setTimeSpec( cal->timeSpec() ); clearException(); if ( messageText.isEmpty() ) { setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, QLatin1String( "messageText is empty, unable " "to parse into a ScheduleMessage" ) ) ); return 0; } icalcomponent *message; message = icalparser_parse_string( messageText.toUtf8() ); if ( !message ) { setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, QLatin1String( "icalparser is unable to parse " "messageText into a ScheduleMessage" ) ) ); return 0; } icalproperty *m = icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY ); if ( !m ) { setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, QLatin1String( "message does not contain an " "ICAL_METHOD_PROPERTY" ) ) ); return 0; } // Populate the message's time zone collection with all VTIMEZONE components ICalTimeZones tzlist; ICalTimeZoneSource tzs; tzs.parse( message, tzlist ); icalcomponent *c; IncidenceBase *incidence = 0; c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT ); if ( c ) { incidence = d->mImpl->readEvent( c, &tzlist ); } if ( !incidence ) { c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT ); if ( c ) { incidence = d->mImpl->readTodo( c, &tzlist ); } } if ( !incidence ) { c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT ); if ( c ) { incidence = d->mImpl->readJournal( c, &tzlist ); } } if ( !incidence ) { c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT ); if ( c ) { incidence = d->mImpl->readFreeBusy( c ); } } if ( !incidence ) { kDebug() << "object is not a freebusy, event, todo or journal"; setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, QLatin1String( "object is not a freebusy, event, " "todo or journal" ) ) ); return 0; } icalproperty_method icalmethod = icalproperty_get_method( m ); iTIPMethod method; switch ( icalmethod ) { case ICAL_METHOD_PUBLISH: method = iTIPPublish; break; case ICAL_METHOD_REQUEST: method = iTIPRequest; break; case ICAL_METHOD_REFRESH: method = iTIPRefresh; break; case ICAL_METHOD_CANCEL: method = iTIPCancel; break; case ICAL_METHOD_ADD: method = iTIPAdd; break; case ICAL_METHOD_REPLY: method = iTIPReply; break; case ICAL_METHOD_COUNTER: method = iTIPCounter; break; case ICAL_METHOD_DECLINECOUNTER: method = iTIPDeclineCounter; break; default: method = iTIPNoMethod; kDebug() << "Unknown method"; break; } if ( !icalrestriction_check( message ) ) { kWarning() << endl << "kcal library reported a problem while parsing:"; kWarning() << Scheduler::translatedMethodName( method ) << ":" << d->mImpl->extractErrorProperty( c ); } Incidence *existingIncidence = cal->incidence( incidence->uid() ); icalcomponent *calendarComponent = 0; if ( existingIncidence ) { calendarComponent = d->mImpl->createCalendarComponent( cal ); // TODO: check, if cast is required, or if it can be done by virtual funcs. // TODO: Use a visitor for this! if ( existingIncidence->type() == "Todo" ) { Todo *todo = static_cast( existingIncidence ); icalcomponent_add_component( calendarComponent, d->mImpl->writeTodo( todo ) ); } if ( existingIncidence->type() == "Event" ) { Event *event = static_cast( existingIncidence ); icalcomponent_add_component( calendarComponent, d->mImpl->writeEvent( event ) ); } } else { icalcomponent_free( message ); return new ScheduleMessage( incidence, method, ScheduleMessage::Unknown ); } icalproperty_xlicclass result = icalclassify( message, calendarComponent, (char *)"" ); ScheduleMessage::Status status; switch ( result ) { case ICAL_XLICCLASS_PUBLISHNEW: status = ScheduleMessage::PublishNew; break; case ICAL_XLICCLASS_PUBLISHUPDATE: status = ScheduleMessage::PublishUpdate; break; case ICAL_XLICCLASS_OBSOLETE: status = ScheduleMessage::Obsolete; break; case ICAL_XLICCLASS_REQUESTNEW: status = ScheduleMessage::RequestNew; break; case ICAL_XLICCLASS_REQUESTUPDATE: status = ScheduleMessage::RequestUpdate; break; case ICAL_XLICCLASS_UNKNOWN: default: status = ScheduleMessage::Unknown; break; } icalcomponent_free( message ); icalcomponent_free( calendarComponent ); return new ScheduleMessage( incidence, method, status ); } void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec ) { d->mTimeSpec = timeSpec; } KDateTime::Spec ICalFormat::timeSpec() const { return d->mTimeSpec; } QString ICalFormat::timeZoneId() const { KTimeZone tz = d->mTimeSpec.timeZone(); return tz.isValid() ? tz.name() : QString(); }