• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.5 API Reference
  • KDE Home
  • Contact Us
 

KCalCore Library

  • kcalcore
icalformat.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the kcalcore library.
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 */
31 #include "icalformat.h"
32 #include "icalformat_p.h"
33 #include "icaltimezones.h"
34 #include "freebusy.h"
35 #include "memorycalendar.h"
36 
37 #include <KDebug>
38 #include <KSaveFile>
39 
40 #include <QtCore/QFile>
41 
42 extern "C" {
43  #include <libical/ical.h>
44  #include <libical/icalss.h>
45  #include <libical/icalparser.h>
46  #include <libical/icalrestriction.h>
47  #include <libical/icalmemory.h>
48 }
49 
50 using namespace KCalCore;
51 
52 //@cond PRIVATE
53 class KCalCore::ICalFormat::Private
54 {
55  public:
56  Private( ICalFormat *parent )
57  : mImpl( new ICalFormatImpl( parent ) ),
58  mTimeSpec( KDateTime::UTC )
59  {}
60  ~Private() { delete mImpl; }
61  ICalFormatImpl *mImpl;
62  KDateTime::Spec mTimeSpec;
63 };
64 //@endcond
65 
66 ICalFormat::ICalFormat()
67  : d( new Private( this ) )
68 {
69 }
70 
71 ICalFormat::~ICalFormat()
72 {
73  icalmemory_free_ring();
74  delete d;
75 }
76 
77 bool ICalFormat::load( const Calendar::Ptr &calendar, const QString &fileName )
78 {
79  kDebug() << fileName;
80 
81  clearException();
82 
83  QFile file( fileName );
84  if ( !file.open( QIODevice::ReadOnly ) ) {
85  kError() << "load error";
86  setException( new Exception( Exception::LoadError ) );
87  return false;
88  }
89  QTextStream ts( &file );
90  ts.setCodec( "UTF-8" );
91  QByteArray text = ts.readAll().trimmed().toUtf8();
92  file.close();
93 
94  if ( text.isEmpty() ) {
95  // empty files are valid
96  return true;
97  } else {
98  return fromRawString( calendar, text, false, fileName );
99  }
100 }
101 
102 bool ICalFormat::save( const Calendar::Ptr &calendar, const QString &fileName )
103 {
104  kDebug() << fileName;
105 
106  clearException();
107 
108  QString text = toString( calendar );
109  if ( text.isEmpty() ) {
110  return false;
111  }
112 
113  // Write backup file
114  KSaveFile::backupFile( fileName );
115 
116  KSaveFile file( fileName );
117  if ( !file.open() ) {
118  kDebug() << "file open error:" << file.errorString();
119  setException( new Exception( Exception::SaveErrorOpenFile,
120  QStringList( fileName ) ) );
121 
122  return false;
123  }
124 
125  // Convert to UTF8 and save
126  QByteArray textUtf8 = text.toUtf8();
127  file.write( textUtf8.data(), textUtf8.size() );
128 
129  if ( !file.finalize() ) {
130  kDebug() << "file finalize error:" << file.errorString();
131  setException( new Exception( Exception::SaveErrorSaveFile,
132  QStringList( fileName ) ) );
133 
134  return false;
135  }
136 
137  return true;
138 }
139 
140 bool ICalFormat::fromString( const Calendar::Ptr &cal, const QString &string,
141  bool deleted, const QString &notebook )
142 {
143  return fromRawString( cal, string.toUtf8(), deleted, notebook );
144 }
145 
146 bool ICalFormat::fromRawString( const Calendar::Ptr &cal, const QByteArray &string,
147  bool deleted, const QString &notebook )
148 {
149  Q_UNUSED( notebook );
150  // Get first VCALENDAR component.
151  // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
152  icalcomponent *calendar;
153 
154  // Let's defend const correctness until the very gates of hell^Wlibical
155  calendar = icalcomponent_new_from_string( const_cast<char*>( ( const char * )string ) );
156  if ( !calendar ) {
157  kError() << "parse error ; string is empty?" << string.isEmpty();
158  setException( new Exception( Exception::ParseErrorIcal ) );
159  return false;
160  }
161 
162  bool success = true;
163 
164  if ( icalcomponent_isa( calendar ) == ICAL_XROOT_COMPONENT ) {
165  icalcomponent *comp;
166  for ( comp = icalcomponent_get_first_component( calendar, ICAL_VCALENDAR_COMPONENT );
167  comp; comp = icalcomponent_get_next_component( calendar, ICAL_VCALENDAR_COMPONENT ) ) {
168  // put all objects into their proper places
169  if ( !d->mImpl->populate( cal, comp, deleted ) ) {
170  kError() << "Could not populate calendar";
171  if ( !exception() ) {
172  setException( new Exception( Exception::ParseErrorKcal ) );
173  }
174  success = false;
175  } else {
176  setLoadedProductId( d->mImpl->loadedProductId() );
177  }
178  }
179  } else if ( icalcomponent_isa( calendar ) != ICAL_VCALENDAR_COMPONENT ) {
180  kDebug() << "No VCALENDAR component found";
181  setException( new Exception( Exception::NoCalendar ) );
182  success = false;
183  } else {
184  // put all objects into their proper places
185  if ( !d->mImpl->populate( cal, calendar, deleted ) ) {
186  kDebug() << "Could not populate calendar";
187  if ( !exception() ) {
188  setException( new Exception( Exception::ParseErrorKcal ) );
189  }
190  success = false;
191  } else {
192  setLoadedProductId( d->mImpl->loadedProductId() );
193  }
194  }
195 
196  icalcomponent_free( calendar );
197  icalmemory_free_ring();
198 
199  return success;
200 }
201 
202 Incidence::Ptr ICalFormat::fromString( const QString &string )
203 {
204  MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) );
205  fromString( cal, string );
206 
207  Incidence::Ptr ical;
208  Event::List elist = cal->events();
209  if ( elist.count() > 0 ) {
210  ical = elist.first();
211  } else {
212  Todo::List tlist = cal->todos();
213  if ( tlist.count() > 0 ) {
214  ical = tlist.first();
215  } else {
216  Journal::List jlist = cal->journals();
217  if ( jlist.count() > 0 ) {
218  ical = jlist.first();
219  }
220  }
221  }
222 
223  return ical ? Incidence::Ptr( ical->clone() ) : Incidence::Ptr();
224 }
225 
226 QString ICalFormat::toString( const Calendar::Ptr &cal,
227  const QString &notebook, bool deleted )
228 {
229  icalcomponent *calendar = d->mImpl->createCalendarComponent( cal );
230  icalcomponent *component;
231 
232  ICalTimeZones *tzlist = cal->timeZones(); // time zones possibly used in the calendar
233  ICalTimeZones tzUsedList; // time zones actually used in the calendar
234 
235  // todos
236  Todo::List todoList = deleted ? cal->deletedTodos() : cal->rawTodos();
237  Todo::List::ConstIterator it;
238  for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
239  if ( !deleted || !cal->todo( ( *it )->uid(), ( *it )->recurrenceId() ) ) {
240  // use existing ones, or really deleted ones
241  if ( notebook.isEmpty() ||
242  ( !cal->notebook( *it ).isEmpty() && notebook.endsWith( cal->notebook( *it ) ) ) ) {
243  component = d->mImpl->writeTodo( *it, tzlist, &tzUsedList );
244  icalcomponent_add_component( calendar, component );
245  }
246  }
247  }
248  // events
249  Event::List events = deleted ? cal->deletedEvents() : cal->rawEvents();
250  Event::List::ConstIterator it2;
251  for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
252  if ( !deleted || !cal->event( ( *it2 )->uid(), ( *it2 )->recurrenceId() ) ) {
253  // use existing ones, or really deleted ones
254  if ( notebook.isEmpty() ||
255  ( !cal->notebook( *it2 ).isEmpty() && notebook.endsWith( cal->notebook( *it2 ) ) ) ) {
256  component = d->mImpl->writeEvent( *it2, tzlist, &tzUsedList );
257  icalcomponent_add_component( calendar, component );
258  }
259  }
260  }
261 
262  // journals
263  Journal::List journals = deleted ? cal->deletedJournals() : cal->rawJournals();
264  Journal::List::ConstIterator it3;
265  for ( it3 = journals.constBegin(); it3 != journals.constEnd(); ++it3 ) {
266  if ( !deleted || !cal->journal( ( *it3 )->uid(), ( *it3 )->recurrenceId() ) ) {
267  // use existing ones, or really deleted ones
268  if ( notebook.isEmpty() ||
269  ( !cal->notebook( *it3 ).isEmpty() && notebook.endsWith( cal->notebook( *it3 ) ) ) ) {
270  component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList );
271  icalcomponent_add_component( calendar, component );
272  }
273  }
274  }
275 
276  // time zones
277  ICalTimeZones::ZoneMap zones = tzUsedList.zones();
278  if ( todoList.isEmpty() && events.isEmpty() && journals.isEmpty() ) {
279  // no incidences means no used timezones, use all timezones
280  // this will export a calendar having only timezone definitions
281  zones = tzlist->zones();
282  }
283  for ( ICalTimeZones::ZoneMap::ConstIterator it = zones.constBegin();
284  it != zones.constEnd(); ++it ) {
285  icaltimezone *tz = ( *it ).icalTimezone();
286  if ( !tz ) {
287  kError() << "bad time zone";
288  } else {
289  component = icalcomponent_new_clone( icaltimezone_get_component( tz ) );
290  icalcomponent_add_component( calendar, component );
291  icaltimezone_free( tz, 1 );
292  }
293  }
294 
295  char *const componentString = icalcomponent_as_ical_string_r( calendar );
296  const QString &text = QString::fromUtf8( componentString );
297  free( componentString );
298 
299  icalcomponent_free( calendar );
300  icalmemory_free_ring();
301 
302  if ( text.isEmpty() ) {
303  setException( new Exception( Exception::LibICalError ) );
304  }
305 
306  return text;
307 }
308 
309 QString ICalFormat::toICalString( const Incidence::Ptr &incidence )
310 {
311  MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) );
312  cal->addIncidence( Incidence::Ptr( incidence->clone() ) );
313  return toString( cal.staticCast<Calendar>() );
314 }
315 
316 QString ICalFormat::toString( const Incidence::Ptr &incidence )
317 {
318  return QString::fromUtf8( toRawString( incidence ) );
319 }
320 
321 QByteArray ICalFormat::toRawString( const Incidence::Ptr &incidence )
322 {
323  icalcomponent *component;
324  ICalTimeZones tzlist;
325  ICalTimeZones tzUsedList;
326 
327  component = d->mImpl->writeIncidence( incidence, iTIPRequest, &tzlist, &tzUsedList );
328 
329  QByteArray text = icalcomponent_as_ical_string( component );
330 
331  // time zones
332  ICalTimeZones::ZoneMap zones = tzUsedList.zones();
333  for ( ICalTimeZones::ZoneMap::ConstIterator it = zones.constBegin();
334  it != zones.constEnd(); ++it ) {
335  icaltimezone *tz = ( *it ).icalTimezone();
336  if ( !tz ) {
337  kError() << "bad time zone";
338  } else {
339  icalcomponent *tzcomponent = icaltimezone_get_component( tz );
340  icalcomponent_add_component( component, component );
341  text.append( icalcomponent_as_ical_string( tzcomponent ) );
342  icaltimezone_free( tz, 1 );
343  }
344  }
345 
346  icalcomponent_free( component );
347 
348  return text;
349 }
350 
351 QString ICalFormat::toString( RecurrenceRule *recurrence )
352 {
353  icalproperty *property;
354  property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) );
355  QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) );
356  icalproperty_free( property );
357  return text;
358 }
359 
360 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule )
361 {
362  if ( !recurrence ) {
363  return false;
364  }
365  bool success = true;
366  icalerror_clear_errno();
367  struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() );
368  if ( icalerrno != ICAL_NO_ERROR ) {
369  kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno );
370  success = false;
371  }
372 
373  if ( success ) {
374  d->mImpl->readRecurrence( recur, recurrence );
375  }
376 
377  return success;
378 }
379 
380 QString ICalFormat::createScheduleMessage( const IncidenceBase::Ptr &incidence,
381  iTIPMethod method )
382 {
383  icalcomponent *message = 0;
384 
385  if ( incidence->type() == Incidence::TypeEvent ||
386  incidence->type() == Incidence::TypeTodo ) {
387 
388  Incidence::Ptr i = incidence.staticCast<Incidence>();
389 
390  // Recurring events need timezone information to allow proper calculations
391  // across timezones with different DST.
392  const bool useUtcTimes = !i->recurs();
393 
394  const bool hasSchedulingId = ( i->schedulingID() != i->uid() );
395 
396  const bool incidenceNeedChanges = ( useUtcTimes || hasSchedulingId );
397 
398  if ( incidenceNeedChanges ) {
399  // The incidence need changes, so clone it before we continue
400  i = Incidence::Ptr( i->clone() );
401 
402  // Handle conversion to UTC times
403  if ( useUtcTimes ) {
404  i->shiftTimes( KDateTime::Spec::UTC(), KDateTime::Spec::UTC() );
405  }
406 
407  // Handle scheduling ID being present
408  if ( hasSchedulingId ) {
409  // We have a separation of scheduling ID and UID
410  i->setSchedulingID( QString(), i->schedulingID() );
411 
412  }
413 
414  // Build the message with the cloned incidence
415  message = d->mImpl->createScheduleComponent( i, method );
416  }
417  }
418 
419  if ( message == 0 ) {
420  message = d->mImpl->createScheduleComponent( incidence, method );
421  }
422 
423  QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) );
424 
425  icalcomponent_free( message );
426  return messageText;
427 }
428 
429 FreeBusy::Ptr ICalFormat::parseFreeBusy( const QString &str )
430 {
431  clearException();
432 
433  icalcomponent *message;
434  message = icalparser_parse_string( str.toUtf8() );
435 
436  if ( !message ) {
437  return FreeBusy::Ptr();
438  }
439 
440  FreeBusy::Ptr freeBusy;
441 
442  icalcomponent *c;
443  for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
444  c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
445  FreeBusy::Ptr fb = d->mImpl->readFreeBusy( c );
446 
447  if ( freeBusy ) {
448  freeBusy->merge( fb );
449  } else {
450  freeBusy = fb;
451  }
452  }
453 
454  if ( !freeBusy ) {
455  kDebug() << "object is not a freebusy.";
456  }
457 
458  icalcomponent_free( message );
459 
460  return freeBusy;
461 }
462 
463 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage( const Calendar::Ptr &cal,
464  const QString &messageText )
465 {
466  setTimeSpec( cal->timeSpec() );
467  clearException();
468 
469  if ( messageText.isEmpty() ) {
470  setException(
471  new Exception( Exception::ParseErrorEmptyMessage ) );
472  return ScheduleMessage::Ptr();
473  }
474 
475  icalcomponent *message;
476  message = icalparser_parse_string( messageText.toUtf8() );
477 
478  if ( !message ) {
479  setException(
480  new Exception( Exception::ParseErrorUnableToParse ) );
481 
482  return ScheduleMessage::Ptr();
483  }
484 
485  icalproperty *m =
486  icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY );
487  if ( !m ) {
488  setException(
489  new Exception( Exception::ParseErrorMethodProperty ) );
490 
491  return ScheduleMessage::Ptr();
492  }
493 
494  // Populate the message's time zone collection with all VTIMEZONE components
495  ICalTimeZones tzlist;
496  ICalTimeZoneSource tzs;
497  tzs.parse( message, tzlist );
498 
499  icalcomponent *c;
500 
501  IncidenceBase::Ptr incidence;
502  c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT );
503  if ( c ) {
504  incidence = d->mImpl->readEvent( c, &tzlist ).staticCast<IncidenceBase>();
505  }
506 
507  if ( !incidence ) {
508  c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT );
509  if ( c ) {
510  incidence = d->mImpl->readTodo( c, &tzlist ).staticCast<IncidenceBase>();
511  }
512  }
513 
514  if ( !incidence ) {
515  c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT );
516  if ( c ) {
517  incidence = d->mImpl->readJournal( c, &tzlist ).staticCast<IncidenceBase>();
518  }
519  }
520 
521  if ( !incidence ) {
522  c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
523  if ( c ) {
524  incidence = d->mImpl->readFreeBusy( c ).staticCast<IncidenceBase>();
525  }
526  }
527 
528  if ( !incidence ) {
529  kDebug() << "object is not a freebusy, event, todo or journal";
530  setException( new Exception( Exception::ParseErrorNotIncidence ) );
531 
532  return ScheduleMessage::Ptr();
533  }
534 
535  icalproperty_method icalmethod = icalproperty_get_method( m );
536  iTIPMethod method;
537 
538  switch ( icalmethod ) {
539  case ICAL_METHOD_PUBLISH:
540  method = iTIPPublish;
541  break;
542  case ICAL_METHOD_REQUEST:
543  method = iTIPRequest;
544  break;
545  case ICAL_METHOD_REFRESH:
546  method = iTIPRefresh;
547  break;
548  case ICAL_METHOD_CANCEL:
549  method = iTIPCancel;
550  break;
551  case ICAL_METHOD_ADD:
552  method = iTIPAdd;
553  break;
554  case ICAL_METHOD_REPLY:
555  method = iTIPReply;
556  break;
557  case ICAL_METHOD_COUNTER:
558  method = iTIPCounter;
559  break;
560  case ICAL_METHOD_DECLINECOUNTER:
561  method = iTIPDeclineCounter;
562  break;
563  default:
564  method = iTIPNoMethod;
565  kDebug() << "Unknown method";
566  break;
567  }
568 
569  if ( !icalrestriction_check( message ) ) {
570  kWarning() << endl
571  << "kcalcore library reported a problem while parsing:";
572  kWarning() << ScheduleMessage::methodName( method ) << ":" //krazy:exclude=kdebug
573  << d->mImpl->extractErrorProperty( c );
574  }
575 
576  Incidence::Ptr existingIncidence = cal->incidence( incidence->uid() );
577 
578  icalcomponent *calendarComponent = 0;
579  if ( existingIncidence ) {
580  calendarComponent = d->mImpl->createCalendarComponent( cal );
581 
582  // TODO: check, if cast is required, or if it can be done by virtual funcs.
583  // TODO: Use a visitor for this!
584  if ( existingIncidence->type() == Incidence::TypeTodo ) {
585  Todo::Ptr todo = existingIncidence.staticCast<Todo>();
586  icalcomponent_add_component( calendarComponent,
587  d->mImpl->writeTodo( todo ) );
588  }
589  if ( existingIncidence->type() == Incidence::TypeEvent ) {
590  Event::Ptr event = existingIncidence.staticCast<Event>();
591  icalcomponent_add_component( calendarComponent,
592  d->mImpl->writeEvent( event ) );
593  }
594  } else {
595  icalcomponent_free( message );
596  return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method,
597  ScheduleMessage::Unknown ) );
598  }
599 
600  icalproperty_xlicclass result =
601  icalclassify( message, calendarComponent, static_cast<const char *>( "" ) );
602 
603  ScheduleMessage::Status status;
604 
605  switch ( result ) {
606  case ICAL_XLICCLASS_PUBLISHNEW:
607  status = ScheduleMessage::PublishNew;
608  break;
609  case ICAL_XLICCLASS_PUBLISHUPDATE:
610  status = ScheduleMessage::PublishUpdate;
611  break;
612  case ICAL_XLICCLASS_OBSOLETE:
613  status = ScheduleMessage::Obsolete;
614  break;
615  case ICAL_XLICCLASS_REQUESTNEW:
616  status = ScheduleMessage::RequestNew;
617  break;
618  case ICAL_XLICCLASS_REQUESTUPDATE:
619  status = ScheduleMessage::RequestUpdate;
620  break;
621  case ICAL_XLICCLASS_UNKNOWN:
622  default:
623  status = ScheduleMessage::Unknown;
624  break;
625  }
626 
627  icalcomponent_free( message );
628  icalcomponent_free( calendarComponent );
629 
630  return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, status ) );
631 }
632 
633 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec )
634 {
635  d->mTimeSpec = timeSpec;
636 }
637 
638 KDateTime::Spec ICalFormat::timeSpec() const
639 {
640  return d->mTimeSpec;
641 }
642 
643 QString ICalFormat::timeZoneId() const
644 {
645  KTimeZone tz = d->mTimeSpec.timeZone();
646  return tz.isValid() ? tz.name() : QString();
647 }
648 
649 void ICalFormat::virtual_hook( int id, void *data )
650 {
651  Q_UNUSED( id );
652  Q_UNUSED( data );
653  Q_ASSERT( false );
654 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:24:51 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KCalCore Library

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

kdepimlibs-4.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal