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 }