libkcal

icalformat.cpp
1 /*
2  This file is part of libkcal.
3 
4  Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 #include <tqdatetime.h>
23 #include <tqstring.h>
24 #include <tqptrlist.h>
25 #include <tqregexp.h>
26 #include <tqclipboard.h>
27 #include <tqfile.h>
28 #include <tqtextstream.h>
29 
30 #include <kdebug.h>
31 #include <klocale.h>
32 
33 extern "C" {
34  #include <libical/ical.h>
35  #include <libical/icalss.h>
36  #include <libical/icalparser.h>
37  #include <libical/icalrestriction.h>
38  #include <libical/icalmemory.h>
39 }
40 
41 #include "calendar.h"
42 #include "calendarlocal.h"
43 #include "journal.h"
44 
45 #include "icalformat.h"
46 #include "icalformatimpl.h"
47 #include <ksavefile.h>
48 
49 #include <stdio.h>
50 
51 #define _ICAL_VERSION "2.0"
52 
53 using namespace KCal;
54 
55 ICalFormat::ICalFormat() : mImpl(0)
56 {
57  setImplementation( new ICalFormatImpl( this ) );
58 
59  mTimeZoneId = "UTC";
60  mUtc = true;
61 }
62 
63 ICalFormat::~ICalFormat()
64 {
65  delete mImpl;
66 }
67 
68 void ICalFormat::setImplementation( ICalFormatImpl *impl )
69 {
70  if ( mImpl ) delete mImpl;
71  mImpl = impl;
72 }
73 
74 #if defined(_AIX) && defined(open)
75 #undef open
76 #endif
77 
78 bool ICalFormat::load( Calendar *calendar, const TQString &fileName)
79 {
80  kdDebug(5800) << "ICalFormat::load() " << fileName << endl;
81 
83 
84  TQFile file( fileName );
85  if (!file.open( IO_ReadOnly ) ) {
86  kdDebug(5800) << "ICalFormat::load() load error" << endl;
88  return false;
89  }
90  TQTextStream ts( &file );
91  ts.setEncoding( TQTextStream::Latin1 );
92  TQString text = ts.read();
93  file.close();
94 
95  if ( text.stripWhiteSpace().isEmpty() ) // empty files are valid
96  return true;
97  else
98  return fromRawString( calendar, text.latin1() );
99 }
100 
101 
102 bool ICalFormat::save( Calendar *calendar, const TQString &fileName )
103 {
104  kdDebug(5800) << "ICalFormat::save(): " << fileName << endl;
105 
106  clearException();
107 
108  TQString text = toString( calendar );
109 
110  if ( text.isNull() ) return false;
111 
112  // Write backup file
113  KSaveFile::backupFile( fileName );
114 
115  KSaveFile file( fileName );
116  if ( file.status() != 0 ) {
117  kdDebug(5800) << "ICalFormat::save() errno: " << strerror( file.status() )
118  << endl;
120  i18n( "Error saving to '%1'." ).arg( fileName ) ) );
121  return false;
122  }
123 
124  // Convert to UTF8 and save
125  TQCString textUtf8 = text.utf8();
126  file.file()->writeBlock( textUtf8.data(), textUtf8.size() - 1 );
127 
128  if ( !file.close() ) {
129  kdDebug(5800) << "KSaveFile: close: status was " << file.status() << ". See errno.h." << endl;
131  i18n("Could not save '%1'").arg(fileName)));
132  return false;
133  }
134 
135  return true;
136 }
137 
138 bool ICalFormat::fromString( Calendar *cal, const TQString &text )
139 {
140  return fromRawString( cal, text.utf8() );
141 }
142 
143 bool ICalFormat::fromRawString( Calendar *cal, const TQCString &text )
144 {
145  setTimeZone( cal->timeZoneId(), !cal->isLocalTime() );
146 
147  // Get first VCALENDAR component.
148  // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
149  icalcomponent *calendar;
150 
151  // Let's defend const correctness until the very gates of hell^Wlibical
152  calendar = icalcomponent_new_from_string( const_cast<char*>( (const char*)text ) );
153  // kdDebug(5800) << "Error: " << icalerror_perror() << endl;
154  if (!calendar) {
155  kdDebug(5800) << "ICalFormat::load() parse error" << endl;
157  return false;
158  }
159 
160  bool success = true;
161 
162  if (icalcomponent_isa(calendar) == ICAL_XROOT_COMPONENT) {
163  icalcomponent *comp;
164  for ( comp = icalcomponent_get_first_component(calendar, ICAL_VCALENDAR_COMPONENT);
165  comp != 0; comp = icalcomponent_get_next_component(calendar, ICAL_VCALENDAR_COMPONENT) ) {
166  // put all objects into their proper places
167  if ( !mImpl->populate( cal, comp ) ) {
168  kdDebug(5800) << "ICalFormat::load(): Could not populate calendar" << endl;
169  if ( !exception() ) {
171  }
172  success = false;
173  } else {
174  mLoadedProductId = mImpl->loadedProductId();
175  }
176  icalcomponent_free( comp );
177  }
178  } else if (icalcomponent_isa(calendar) != ICAL_VCALENDAR_COMPONENT) {
179  kdDebug(5800) << "ICalFormat::load(): No VCALENDAR component found" << endl;
181  success = false;
182  } else {
183  // put all objects into their proper places
184  if ( !mImpl->populate( cal, calendar ) ) {
185  kdDebug(5800) << "ICalFormat::load(): Could not populate calendar" << endl;
186  if ( !exception() ) {
188  }
189  success = false;
190  } else
191  mLoadedProductId = mImpl->loadedProductId();
192  }
193 
194  icalcomponent_free( calendar );
195  icalmemory_free_ring();
196 
197  return success;
198 }
199 
200 Incidence *ICalFormat::fromString( const TQString &text )
201 {
202  CalendarLocal cal( mTimeZoneId );
203  fromString(&cal, text);
204 
205  Incidence *ical = 0;
206  Event::List elist = cal.events();
207  if ( elist.count() > 0 ) {
208  ical = elist.first();
209  } else {
210  Todo::List tlist = cal.todos();
211  if ( tlist.count() > 0 ) {
212  ical = tlist.first();
213  } else {
214  Journal::List jlist = cal.journals();
215  if ( jlist.count() > 0 ) {
216  ical = jlist.first();
217  }
218  }
219  }
220 
221  return ical ? ical->clone() : 0;
222 }
223 
225 {
226  setTimeZone( cal->timeZoneId(), !cal->isLocalTime() );
227 
228  icalcomponent *calendar = mImpl->createCalendarComponent(cal);
229 
230  icalcomponent *component;
231 
232  // todos
233  Todo::List todoList = cal->rawTodos();
234  Todo::List::ConstIterator it;
235  for( it = todoList.begin(); it != todoList.end(); ++it ) {
236 // kdDebug(5800) << "ICalFormat::toString() write todo "
237 // << (*it)->uid() << endl;
238  component = mImpl->writeTodo( *it );
239  icalcomponent_add_component( calendar, component );
240  }
241 
242  // events
243  Event::List events = cal->rawEvents();
244  Event::List::ConstIterator it2;
245  for( it2 = events.begin(); it2 != events.end(); ++it2 ) {
246 // kdDebug(5800) << "ICalFormat::toString() write event "
247 // << (*it2)->uid() << endl;
248  component = mImpl->writeEvent( *it2 );
249  icalcomponent_add_component( calendar, component );
250  }
251 
252  // journals
253  Journal::List journals = cal->journals();
254  Journal::List::ConstIterator it3;
255  for( it3 = journals.begin(); it3 != journals.end(); ++it3 ) {
256  kdDebug(5800) << "ICalFormat::toString() write journal "
257  << (*it3)->uid() << endl;
258  component = mImpl->writeJournal( *it3 );
259  icalcomponent_add_component( calendar, component );
260  }
261 
262  TQString text = TQString::fromUtf8( icalcomponent_as_ical_string( calendar ) );
263 
264  icalcomponent_free( calendar );
265  icalmemory_free_ring();
266 
267  if (!text) {
269  i18n("libical error")));
270  return TQString();
271  }
272 
273  return text;
274 }
275 
276 TQString ICalFormat::toICalString( Incidence *incidence )
277 {
278  CalendarLocal cal( mTimeZoneId );
279  cal.addIncidence( incidence->clone() );
280  return toString( &cal );
281 }
282 
283 TQString ICalFormat::toString( Incidence *incidence )
284 {
285  icalcomponent *component;
286 
287  component = mImpl->writeIncidence( incidence );
288 
289  TQString text = TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
290 
291  icalcomponent_free( component );
292 
293  return text;
294 }
295 
296 TQString ICalFormat::toString( Incidence *incidence, Calendar *calendar )
297 {
298  icalcomponent *component;
299  TQString text = "";
300 
301  // See if there are any parent or child events that must be added to the string
302  if ( incidence->hasRecurrenceID() ) {
303  // Get the parent
304  IncidenceList il = incidence->childIncidences();
305  IncidenceListIterator it;
306  it = il.begin();
307  Incidence *parentIncidence;
308  parentIncidence = calendar->incidence(*it);
309  il = parentIncidence->childIncidences();
310  if (il.count() > 0) {
311  for ( it = il.begin(); it != il.end(); ++it ) {
312  component = mImpl->writeIncidence( calendar->incidence(*it) );
313  text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
314  icalcomponent_free( component );
315  }
316  }
317  component = mImpl->writeIncidence( parentIncidence );
318  text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
319  icalcomponent_free( component );
320  }
321  else {
322  // This incidence is a potential parent
323  IncidenceList il = incidence->childIncidences();
324  if (il.count() > 0) {
325  IncidenceListIterator it;
326  for ( it = il.begin(); it != il.end(); ++it ) {
327  component = mImpl->writeIncidence( calendar->incidence(*it) );
328  text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
329  icalcomponent_free( component );
330  }
331  }
332  component = mImpl->writeIncidence( incidence );
333  text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
334  icalcomponent_free( component );
335  }
336 
337  return text;
338 }
339 
340 TQString ICalFormat::toString( RecurrenceRule *recurrence )
341 {
342  icalproperty *property;
343  property = icalproperty_new_rrule( mImpl->writeRecurrenceRule( recurrence ) );
344  TQString text = TQString::fromUtf8( icalproperty_as_ical_string( property ) );
345  icalproperty_free( property );
346  return text;
347 }
348 
349 bool ICalFormat::fromString( RecurrenceRule * recurrence, const TQString& rrule )
350 {
351  if ( !recurrence ) return false;
352  bool success = true;
353  icalerror_clear_errno();
354  struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.latin1() );
355  if ( icalerrno != ICAL_NO_ERROR ) {
356  kdDebug(5800) << "Recurrence parsing error: " << icalerror_strerror( icalerrno ) << endl;
357  success = false;
358  }
359 
360  if ( success ) {
361  mImpl->readRecurrence( recur, recurrence );
362  }
363 
364  return success;
365 }
366 
367 
369  Scheduler::Method method)
370 {
371  icalcomponent *message = 0;
372 
373  // Handle scheduling ID being present
374  if ( incidence->type() == "Event" || incidence->type() == "Todo" ) {
375  Incidence* i = static_cast<Incidence*>( incidence );
376  if ( i->schedulingID() != i->uid() ) {
377  // We have a separation of scheduling ID and UID
378  i = i->clone();
379  i->setUid( i->schedulingID() );
380  i->setSchedulingID( TQString() );
381 
382  // Build the message with the cloned incidence
383  message = mImpl->createScheduleComponent( i, method );
384 
385  // And clean up
386  delete i;
387  }
388  }
389 
390  if ( message == 0 )
391  message = mImpl->createScheduleComponent(incidence,method);
392 
393  // FIXME TODO: Don't we have to free message? What about the ical_string? MEMLEAK
394  TQString messageText = TQString::fromUtf8( icalcomponent_as_ical_string(message) );
395 
396 #if 0
397  kdDebug(5800) << "ICalFormat::createScheduleMessage: message START\n"
398  << messageText
399  << "ICalFormat::createScheduleMessage: message END" << endl;
400 #endif
401 
402  return messageText;
403 }
404 
405 FreeBusy *ICalFormat::parseFreeBusy( const TQString &str )
406 {
407  clearException();
408 
409  icalcomponent *message;
410  message = icalparser_parse_string( str.utf8() );
411 
412  if ( !message ) return 0;
413 
414  FreeBusy *freeBusy = 0;
415 
416  icalcomponent *c;
417  for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
418  c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
419  FreeBusy *fb = mImpl->readFreeBusy( c );
420 
421  if ( freeBusy ) {
422  freeBusy->merge( fb );
423  delete fb;
424  } else {
425  freeBusy = fb;
426  }
427  }
428 
429  if ( !freeBusy )
430  kdDebug(5800) << "ICalFormat:parseFreeBusy: object is not a freebusy."
431  << endl;
432  return freeBusy;
433 }
434 
436  const TQString &messageText )
437 {
438  setTimeZone( cal->timeZoneId(), !cal->isLocalTime() );
439  clearException();
440 
441  if (messageText.isEmpty())
442  {
443  setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "messageText was empty, unable to parse into a ScheduleMessage" ) ) );
444  return 0;
445  }
446  // TODO FIXME: Don't we have to ical-free message??? MEMLEAK
447  icalcomponent *message;
448  message = icalparser_parse_string(messageText.utf8());
449 
450  if (!message)
451  {
452  setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "icalparser was unable to parse messageText into a ScheduleMessage" ) ) );
453  return 0;
454  }
455 
456  icalproperty *m = icalcomponent_get_first_property(message,
457  ICAL_METHOD_PROPERTY);
458  if (!m)
459  {
460  setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "message didn't contain an ICAL_METHOD_PROPERTY" ) ) );
461  return 0;
462  }
463 
464  icalcomponent *c;
465 
466  IncidenceBase *incidence = 0;
467  c = icalcomponent_get_first_component(message,ICAL_VEVENT_COMPONENT);
468  if (c) {
469  icalcomponent *ctz = icalcomponent_get_first_component(message,ICAL_VTIMEZONE_COMPONENT);
470  incidence = mImpl->readEvent(c, ctz);
471  }
472 
473  if (!incidence) {
474  c = icalcomponent_get_first_component(message,ICAL_VTODO_COMPONENT);
475  if (c) {
476  incidence = mImpl->readTodo(c);
477  }
478  }
479 
480  if (!incidence) {
481  c = icalcomponent_get_first_component(message,ICAL_VJOURNAL_COMPONENT);
482  if (c) {
483  incidence = mImpl->readJournal(c);
484  }
485  }
486 
487  if (!incidence) {
488  c = icalcomponent_get_first_component(message,ICAL_VFREEBUSY_COMPONENT);
489  if (c) {
490  incidence = mImpl->readFreeBusy(c);
491  }
492  }
493 
494 
495 
496  if (!incidence) {
497  kdDebug(5800) << "ICalFormat:parseScheduleMessage: object is not a freebusy, event, todo or journal" << endl;
498  setException( new ErrorFormat( ErrorFormat::ParseErrorKcal, TQString::fromLatin1( "object is not a freebusy, event, todo or journal" ) ) );
499  return 0;
500  }
501 
502  kdDebug(5800) << "ICalFormat::parseScheduleMessage() getting method..." << endl;
503 
504  icalproperty_method icalmethod = icalproperty_get_method(m);
505  Scheduler::Method method;
506 
507  switch (icalmethod) {
508  case ICAL_METHOD_PUBLISH:
509  method = Scheduler::Publish;
510  break;
511  case ICAL_METHOD_REQUEST:
512  method = Scheduler::Request;
513  break;
514  case ICAL_METHOD_REFRESH:
515  method = Scheduler::Refresh;
516  break;
517  case ICAL_METHOD_CANCEL:
518  method = Scheduler::Cancel;
519  break;
520  case ICAL_METHOD_ADD:
521  method = Scheduler::Add;
522  break;
523  case ICAL_METHOD_REPLY:
524  method = Scheduler::Reply;
525  break;
526  case ICAL_METHOD_COUNTER:
527  method = Scheduler::Counter;
528  break;
529  case ICAL_METHOD_DECLINECOUNTER:
530  method = Scheduler::Declinecounter;
531  break;
532  default:
533  method = Scheduler::NoMethod;
534  kdDebug(5800) << "ICalFormat::parseScheduleMessage(): Unknow method" << endl;
535  break;
536  }
537 
538  kdDebug(5800) << "ICalFormat::parseScheduleMessage() restriction..." << endl;
539 
540  if (!icalrestriction_check(message)) {
541  kdWarning(5800) << k_funcinfo << endl << "libkcal reported a problem while parsing:" << endl;
542  kdWarning(5800) << Scheduler::translatedMethodName(method) + ": " + mImpl->extractErrorProperty(c)<< endl;
543  /*
544  setException(new ErrorFormat(ErrorFormat::Restriction,
545  Scheduler::translatedMethodName(method) + ": " +
546  mImpl->extractErrorProperty(c)));
547  delete incidence;
548  return 0;
549  */
550  }
551  icalcomponent *calendarComponent = mImpl->createCalendarComponent(cal);
552 
553  Incidence *existingIncidence =
554  cal->incidenceFromSchedulingID(incidence->uid());
555  if (existingIncidence) {
556  // TODO: check, if cast is required, or if it can be done by virtual funcs.
557  // TODO: Use a visitor for this!
558  if (existingIncidence->type() == "Todo") {
559  Todo *todo = static_cast<Todo *>(existingIncidence);
560  icalcomponent_add_component(calendarComponent,
561  mImpl->writeTodo(todo));
562  }
563  if (existingIncidence->type() == "Event") {
564  Event *event = static_cast<Event *>(existingIncidence);
565  icalcomponent_add_component(calendarComponent,
566  mImpl->writeEvent(event));
567  }
568  } else {
569  calendarComponent = 0;
570  }
571 
572  kdDebug(5800) << "ICalFormat::parseScheduleMessage() classify..." << endl;
573 
574  icalproperty_xlicclass result = icalclassify( message, calendarComponent,
575  (char *)"" );
576 
577  kdDebug(5800) << "ICalFormat::parseScheduleMessage() returning..." << endl;
578  kdDebug(5800) << "ICalFormat::parseScheduleMessage(), result = " << result << endl;
579 
581 
582  switch (result) {
583  case ICAL_XLICCLASS_PUBLISHNEW:
584  status = ScheduleMessage::PublishNew;
585  break;
586  case ICAL_XLICCLASS_PUBLISHUPDATE:
587  status = ScheduleMessage::PublishUpdate;
588  break;
589  case ICAL_XLICCLASS_OBSOLETE:
590  status = ScheduleMessage::Obsolete;
591  break;
592  case ICAL_XLICCLASS_REQUESTNEW:
593  status = ScheduleMessage::RequestNew;
594  break;
595  case ICAL_XLICCLASS_REQUESTUPDATE:
596  status = ScheduleMessage::RequestUpdate;
597  break;
598  case ICAL_XLICCLASS_UNKNOWN:
599  default:
600  status = ScheduleMessage::Unknown;
601  break;
602  }
603 
604  kdDebug(5800) << "ICalFormat::parseScheduleMessage(), status = " << status << endl;
605 // TODO FIXME: Don't we have to free calendarComponent??? MEMLEAK
606 
607  return new ScheduleMessage(incidence,method,status);
608 }
609 
610 void ICalFormat::setTimeZone( const TQString &id, bool utc )
611 {
612  mTimeZoneId = id;
613  mUtc = utc;
614 }
615 
616 TQString ICalFormat::timeZoneId() const
617 {
618  return mTimeZoneId;
619 }
620 
621 bool ICalFormat::utc() const
622 {
623  return mUtc;
624 }