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(); 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() ); 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 }