diff options
Diffstat (limited to 'tderesources/kolab/kcal')
-rw-r--r-- | tderesources/kolab/kcal/CMakeLists.txt | 57 | ||||
-rw-r--r-- | tderesources/kolab/kcal/Makefile.am | 27 | ||||
-rw-r--r-- | tderesources/kolab/kcal/event.cpp | 223 | ||||
-rw-r--r-- | tderesources/kolab/kcal/event.h | 102 | ||||
-rw-r--r-- | tderesources/kolab/kcal/incidence.cpp | 1039 | ||||
-rw-r--r-- | tderesources/kolab/kcal/incidence.h | 175 | ||||
-rw-r--r-- | tderesources/kolab/kcal/journal.cpp | 184 | ||||
-rw-r--r-- | tderesources/kolab/kcal/journal.h | 103 | ||||
-rw-r--r-- | tderesources/kolab/kcal/kolab.desktop | 52 | ||||
-rw-r--r-- | tderesources/kolab/kcal/resourcekolab.cpp | 1342 | ||||
-rw-r--r-- | tderesources/kolab/kcal/resourcekolab.h | 284 | ||||
-rw-r--r-- | tderesources/kolab/kcal/resourcekolab_plugin.cpp | 49 | ||||
-rw-r--r-- | tderesources/kolab/kcal/task.cpp | 461 | ||||
-rw-r--r-- | tderesources/kolab/kcal/task.h | 143 |
14 files changed, 4241 insertions, 0 deletions
diff --git a/tderesources/kolab/kcal/CMakeLists.txt b/tderesources/kolab/kcal/CMakeLists.txt new file mode 100644 index 000000000..9f932b5e1 --- /dev/null +++ b/tderesources/kolab/kcal/CMakeLists.txt @@ -0,0 +1,57 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../shared + ${CMAKE_CURRENT_SOURCE_DIR}/../kcal + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/libtdepim + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( + FILES kolab.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/tderesources/kcal ) + +install( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/../uninstall.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/tderesources/kcal + RENAME imap.desktop) + + +##### kcal_kolab (module) ####################### + +tde_add_kpart( kcal_kolab AUTOMOC + SOURCES + resourcekolab_plugin.cpp + LINK kcalkolab-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### kcalkolab (shared) ######################## + +tde_add_library( kcalkolab SHARED AUTOMOC + SOURCES + incidence.cpp event.cpp task.cpp journal.cpp resourcekolab.cpp + VERSION 0.0.0 + LINK resourcekolabshared-static kgroupwarebase-shared + DESTINATION ${LIB_INSTALL_DIR} +) diff --git a/tderesources/kolab/kcal/Makefile.am b/tderesources/kolab/kcal/Makefile.am new file mode 100644 index 000000000..a25027919 --- /dev/null +++ b/tderesources/kolab/kcal/Makefile.am @@ -0,0 +1,27 @@ +METASOURCES = AUTO + +INCLUDES = -I$(top_srcdir)/tderesources/kolab/shared -I$(top_srcdir) \ + -I$(top_builddir)/libtdepim $(all_includes) + +# The kolab wizard links to this library too +lib_LTLIBRARIES = libkcalkolab.la + +libkcalkolab_la_SOURCES = incidence.cpp event.cpp task.cpp journal.cpp resourcekolab.cpp +libkcalkolab_la_LDFLAGS = $(all_libraries) -no-undefined +libkcalkolab_la_LIBADD = $(top_builddir)/libkcal/libkcal.la \ + $(top_builddir)/tderesources/kolab/shared/libresourcekolabshared.la \ + -ltderesources + +kde_module_LTLIBRARIES = kcal_kolab.la + +kcal_kolab_la_SOURCES = resourcekolab_plugin.cpp +kcal_kolab_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined +kcal_kolab_la_LIBADD = libkcalkolab.la + +servicedir = $(kde_servicesdir)/tderesources/kcal +service_DATA = kolab.desktop + +install-data-local: $(srcdir)/../uninstall.desktop + $(mkinstalldirs) $(DESTDIR)$(servicedir) + $(INSTALL_DATA) $(srcdir)/../uninstall.desktop $(DESTDIR)$(servicedir)/imap.desktop + diff --git a/tderesources/kolab/kcal/event.cpp b/tderesources/kolab/kcal/event.cpp new file mode 100644 index 000000000..c15c567d8 --- /dev/null +++ b/tderesources/kolab/kcal/event.cpp @@ -0,0 +1,223 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "event.h" + +#include <libkcal/event.h> +#include <kdebug.h> + +using namespace Kolab; + + +KCal::Event* Event::xmlToEvent( const TQString& xml, const TQString& tz, KCal::ResourceKolab* res, + const TQString& subResource, TQ_UINT32 sernum ) +{ + Event event( res, subResource, sernum, tz ); + event.load( xml ); + KCal::Event* kcalEvent = new KCal::Event(); + event.saveTo( kcalEvent ); + return kcalEvent; +} + +TQString Event::eventToXML( KCal::Event* kcalEvent, const TQString& tz ) +{ + Event event( 0, TQString(), 0, tz, kcalEvent ); + return event.saveXML(); +} + +Event::Event( KCal::ResourceKolab *res, const TQString &subResource, TQ_UINT32 sernum, + const TQString& tz, KCal::Event* event ) + : Incidence( res, subResource, sernum, tz ), + mShowTimeAs( KCal::Event::Opaque ), mHasEndDate( false ) +{ + if ( event ) + setFields( event ); +} + +Event::~Event() +{ +} + +void Event::setTransparency( KCal::Event::Transparency transparency ) +{ + mShowTimeAs = transparency; +} + +KCal::Event::Transparency Event::transparency() const +{ + return mShowTimeAs; +} + +void Event::setEndDate( const TQDateTime& date ) +{ + mEndDate = date; + mHasEndDate = true; + if ( mFloatingStatus == AllDay ) + kdDebug() << "ERROR: Time on end date but no time on the event\n"; + mFloatingStatus = HasTime; +} + +void Event::setEndDate( const TQDate& date ) +{ + mEndDate = date; + mHasEndDate = true; + if ( mFloatingStatus == HasTime ) + kdDebug() << "ERROR: No time on end date but time on the event\n"; + mFloatingStatus = AllDay; +} + +void Event::setEndDate( const TQString& endDate ) +{ + if ( endDate.length() > 10 ) + // This is a date + time + setEndDate( stringToDateTime( endDate ) ); + else + // This is only a date + setEndDate( stringToDate( endDate ) ); +} + +TQDateTime Event::endDate() const +{ + return mEndDate; +} + +bool Event::loadAttribute( TQDomElement& element ) +{ + // This method doesn't handle the color-label tag yet + TQString tagName = element.tagName(); + + if ( tagName == "show-time-as" ) { + // TODO: Support tentative and outofoffice + if ( element.text() == "free" ) + setTransparency( KCal::Event::Transparent ); + else + setTransparency( KCal::Event::Opaque ); + } else if ( tagName == "end-date" ) + setEndDate( element.text() ); + else + return Incidence::loadAttribute( element ); + + // We handled this + return true; +} + +bool Event::saveAttributes( TQDomElement& element ) const +{ + // Save the base class elements + Incidence::saveAttributes( element ); + + // TODO: Support tentative and outofoffice + if ( transparency() == KCal::Event::Transparent ) + writeString( element, "show-time-as", "free" ); + else + writeString( element, "show-time-as", "busy" ); + if ( mHasEndDate ) { + if ( mFloatingStatus == HasTime ) + writeString( element, "end-date", dateTimeToString( endDate() ) ); + else + writeString( element, "end-date", dateToString( endDate().date() ) ); + } + + return true; +} + + +bool Event::loadXML( const TQDomDocument& document ) +{ + TQDomElement top = document.documentElement(); + + if ( top.tagName() != "event" ) { + tqWarning( "XML error: Top tag was %s instead of the expected event", + top.tagName().ascii() ); + return false; + } + + for ( TQDomNode n = top.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + loadAttribute( e ); + } else + kdDebug() << "Node is not a comment or an element???" << endl; + } + + loadAttachments(); + return true; +} + +TQString Event::saveXML() const +{ + TQDomDocument document = domTree(); + TQDomElement element = document.createElement( "event" ); + element.setAttribute( "version", "1.0" ); + saveAttributes( element ); + document.appendChild( element ); + return document.toString(); +} + +void Event::setFields( const KCal::Event* event ) +{ + Incidence::setFields( event ); + + // note: if hasEndDate() is false and hasDuration() is true + // dtEnd() returns start+duration + if ( event->hasEndDate() || event->hasDuration() ) { + if ( event->doesFloat() ) { + // This is a floating event. Don't timezone move this one + mFloatingStatus = AllDay; + setEndDate( event->dtEnd().date() ); + } else { + mFloatingStatus = HasTime; + setEndDate( localToUTC( event->dtEnd() ) ); + } + } else { + mHasEndDate = false; + } + setTransparency( event->transparency() ); +} + +void Event::saveTo( KCal::Event* event ) +{ + Incidence::saveTo( event ); + + event->setHasEndDate( mHasEndDate ); + if ( mHasEndDate ) { + if ( mFloatingStatus == AllDay ) + // This is a floating event. Don't timezone move this one + event->setDtEnd( endDate() ); + else + event->setDtEnd( utcToLocal( endDate() ) ); + } + event->setTransparency( transparency() ); +} diff --git a/tderesources/kolab/kcal/event.h b/tderesources/kolab/kcal/event.h new file mode 100644 index 000000000..57aa3358f --- /dev/null +++ b/tderesources/kolab/kcal/event.h @@ -0,0 +1,102 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef KOLAB_EVENT_H +#define KOLAB_EVENT_H + +#include "incidence.h" + +#include <libkcal/event.h> + +class TQDomElement; + + +namespace Kolab { + +/** + * This class represents an event, and knows how to load/save it + * from/to XML, and from/to a KCal::Event. + * The instances of this class are temporary, only used to convert + * one to the other. + */ +class Event : public Incidence { +public: + /// Use this to parse an xml string to a event entry + /// The caller is responsible for deleting the returned event + static KCal::Event* xmlToEvent( const TQString& xml, const TQString& tz, KCal::ResourceKolab* res = 0, + const TQString& subResource = TQString(), TQ_UINT32 sernum = 0 ); + + /// Use this to get an xml string describing this event entry + static TQString eventToXML( KCal::Event*, const TQString& tz ); + + /// Create a event object and + explicit Event( KCal::ResourceKolab *res, const TQString &subResource, TQ_UINT32 sernum, + const TQString& tz, KCal::Event* event = 0 ); + virtual ~Event(); + + void saveTo( KCal::Event* event ); + + virtual TQString type() const { return "Event"; } + + virtual void setTransparency( KCal::Event::Transparency transparency ); + virtual KCal::Event::Transparency transparency() const; + + virtual void setEndDate( const TQDateTime& date ); + virtual void setEndDate( const TQDate& date ); + virtual void setEndDate( const TQString& date ); + virtual TQDateTime endDate() const; + + // Load the attributes of this class + virtual bool loadAttribute( TQDomElement& ); + + // Save the attributes of this class + virtual bool saveAttributes( TQDomElement& ) const; + + // Load this event by reading the XML file + virtual bool loadXML( const TQDomDocument& xml ); + + // Serialize this event to an XML string + virtual TQString saveXML() const; + +protected: + // Read all known fields from this ical incidence + void setFields( const KCal::Event* ); + + KCal::Event::Transparency mShowTimeAs; + TQDateTime mEndDate; + bool mHasEndDate; +}; + +} + +#endif // KOLAB_EVENT_H diff --git a/tderesources/kolab/kcal/incidence.cpp b/tderesources/kolab/kcal/incidence.cpp new file mode 100644 index 000000000..ddc31491b --- /dev/null +++ b/tderesources/kolab/kcal/incidence.cpp @@ -0,0 +1,1039 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "incidence.h" +#include "resourcekolab.h" + +#include <tqfile.h> +#include <tqvaluelist.h> + +#include <libkcal/journal.h> +#include <korganizer/version.h> +#include <libemailfunctions/email.h> + +#include <kdebug.h> +#include <kmdcodec.h> +#include <kurl.h> +#include <kio/netaccess.h> + +using namespace Kolab; + + +Incidence::Incidence( KCal::ResourceKolab *res, const TQString &subResource, TQ_UINT32 sernum, + const TQString& tz ) + : KolabBase( tz ), mFloatingStatus( Unset ), mHasAlarm( false ), + mResource( res ), + mSubResource( subResource ), + mSernum( sernum ) +{ +} + +Incidence::~Incidence() +{ +} + +void Incidence::setSummary( const TQString& summary ) +{ + mSummary = summary; +} + +TQString Incidence::summary() const +{ + return mSummary; +} + +void Incidence::setLocation( const TQString& location ) +{ + mLocation = location; +} + +TQString Incidence::location() const +{ + return mLocation; +} + +void Incidence::setOrganizer( const Email& organizer ) +{ + mOrganizer = organizer; +} + +KolabBase::Email Incidence::organizer() const +{ + return mOrganizer; +} + +void Incidence::setStartDate( const TQDateTime& startDate ) +{ + mStartDate = startDate; + if ( mFloatingStatus == AllDay ) + kdDebug() << "ERROR: Time on start date but no time on the event\n"; + mFloatingStatus = HasTime; +} + +void Incidence::setStartDate( const TQDate& startDate ) +{ + mStartDate = startDate; + if ( mFloatingStatus == HasTime ) + kdDebug() << "ERROR: No time on start date but time on the event\n"; + mFloatingStatus = AllDay; +} + +void Incidence::setStartDate( const TQString& startDate ) +{ + if ( startDate.length() > 10 ) + // This is a date + time + setStartDate( stringToDateTime( startDate ) ); + else + // This is only a date + setStartDate( stringToDate( startDate ) ); +} + +TQDateTime Incidence::startDate() const +{ + return mStartDate; +} + +void Incidence::setAlarm( float alarm ) +{ + mAlarm = alarm; + mHasAlarm = true; +} + +float Incidence::alarm() const +{ + return mAlarm; +} + +Incidence::Recurrence Incidence::recurrence() const +{ + return mRecurrence; +} + +void Incidence::addAttendee( const Attendee& attendee ) +{ + mAttendees.append( attendee ); +} + +TQValueList<Incidence::Attendee>& Incidence::attendees() +{ + return mAttendees; +} + +const TQValueList<Incidence::Attendee>& Incidence::attendees() const +{ + return mAttendees; +} + +void Incidence::setInternalUID( const TQString& iuid ) +{ + mInternalUID = iuid; +} + +TQString Incidence::internalUID() const +{ + return mInternalUID; +} + +bool Incidence::loadAttendeeAttribute( TQDomElement& element, + Attendee& attendee ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "display-name" ) { + // Quote the text in case it contains commas or other quotable chars. + TQString tusername = KPIM::quoteNameIfNecessary( e.text() ); + + TQString tname, temail; + // ignore the return value because it will always be false since + // tusername does not contain "@domain". + KPIM::getNameAndMail( tusername, tname, temail ); + attendee.displayName = tname; + } + else if ( tagName == "smtp-address" ) + attendee.smtpAddress = e.text(); + else if ( tagName == "status" ) + attendee.status = e.text(); + else if ( tagName == "request-response" ) + // This sets reqResp to false, if the text is "false". Otherwise it + // sets it to true. This means the default setting is true. + attendee.requestResponse = ( e.text().lower() != "false" ); + else if ( tagName == "invitation-sent" ) + // Like above, only this defaults to false + attendee.invitationSent = ( e.text().lower() != "true" ); + else if ( tagName == "role" ) + attendee.role = e.text(); + else if ( tagName == "delegated-to" ) + attendee.delegate = e.text(); + else if ( tagName == "delegated-from" ) + attendee.delegator = e.text(); + else + // TODO: Unhandled tag - save for later storage + kdDebug() << "Warning: Unhandled tag " << e.tagName() << endl; + } else + kdDebug() << "Node is not a comment or an element???" << endl; + } + + return true; +} + +void Incidence::saveAttendeeAttribute( TQDomElement& element, + const Attendee& attendee ) const +{ + TQDomElement e = element.ownerDocument().createElement( "attendee" ); + element.appendChild( e ); + writeString( e, "display-name", attendee.displayName ); + writeString( e, "smtp-address", attendee.smtpAddress ); + writeString( e, "status", attendee.status ); + writeString( e, "request-response", + ( attendee.requestResponse ? "true" : "false" ) ); + writeString( e, "invitation-sent", + ( attendee.invitationSent ? "true" : "false" ) ); + writeString( e, "role", attendee.role ); + writeString( e, "delegated-to", attendee.delegate ); + writeString( e, "delegated-from", attendee.delegator ); +} + +void Incidence::saveAttendees( TQDomElement& element ) const +{ + TQValueList<Attendee>::ConstIterator it = mAttendees.begin(); + for ( ; it != mAttendees.end(); ++it ) + saveAttendeeAttribute( element, *it ); +} + +void Incidence::saveAttachments( TQDomElement& element ) const +{ + KCal::Attachment::List::ConstIterator it = mAttachments.begin(); + for ( ; it != mAttachments.end(); ++it ) { + KCal::Attachment *a = (*it); + if ( a->isUri() ) { + writeString( element, "link-attachment", a->uri() ); + } else if ( a->isBinary() ) { + writeString( element, "inline-attachment", a->label() ); + } + } +} + +void Incidence::saveAlarms( TQDomElement& element ) const +{ + if ( mAlarms.isEmpty() ) return; + + TQDomElement list = element.ownerDocument().createElement( "advanced-alarms" ); + element.appendChild( list ); + for ( KCal::Alarm::List::ConstIterator it = mAlarms.constBegin(); it != mAlarms.constEnd(); ++it ) { + KCal::Alarm* a = *it; + TQDomElement e = list.ownerDocument().createElement( "alarm" ); + list.appendChild( e ); + + writeString( e, "enabled", a->enabled() ? "1" : "0" ); + if ( a->hasStartOffset() ) { + writeString( e, "start-offset", TQString::number( a->startOffset().asSeconds()/60 ) ); + } + if ( a->hasEndOffset() ) { + writeString( e, "end-offset", TQString::number( a->endOffset().asSeconds()/60 ) ); + } + if ( a->repeatCount() ) { + writeString( e, "repeat-count", TQString::number( a->repeatCount() ) ); + writeString( e, "repeat-interval", TQString::number( a->snoozeTime() ) ); + } + + switch ( a->type() ) { + case KCal::Alarm::Invalid: + break; + case KCal::Alarm::Display: + e.setAttribute( "type", "display" ); + writeString( e, "text", a->text() ); + break; + case KCal::Alarm::Procedure: + e.setAttribute( "type", "procedure" ); + writeString( e, "program", a->programFile() ); + writeString( e, "arguments", a->programArguments() ); + break; + case KCal::Alarm::Email: + { + e.setAttribute( "type", "email" ); + TQDomElement addresses = e.ownerDocument().createElement( "addresses" ); + e.appendChild( addresses ); + for ( TQValueList<KCal::Person>::ConstIterator it = a->mailAddresses().constBegin(); it != a->mailAddresses().constEnd(); ++it ) { + writeString( addresses, "address", (*it).fullName() ); + } + writeString( e, "subject", a->mailSubject() ); + writeString( e, "mail-text", a->mailText() ); + TQDomElement attachments = e.ownerDocument().createElement( "attachments" ); + e.appendChild( attachments ); + for ( TQStringList::ConstIterator it = a->mailAttachments().constBegin(); it != a->mailAttachments().constEnd(); ++it ) { + writeString( attachments, "attachment", *it ); + } + break; + } + case KCal::Alarm::Audio: + e.setAttribute( "type", "audio" ); + writeString( e, "file", a->audioFile() ); + break; + default: + kdWarning() << "Unhandled alarm type:" << a->type() << endl; + break; + } + } +} + +void Incidence::saveRecurrence( TQDomElement& element ) const +{ + TQDomElement e = element.ownerDocument().createElement( "recurrence" ); + element.appendChild( e ); + e.setAttribute( "cycle", mRecurrence.cycle ); + if ( !mRecurrence.type.isEmpty() ) + e.setAttribute( "type", mRecurrence.type ); + writeString( e, "interval", TQString::number( mRecurrence.interval ) ); + for( TQStringList::ConstIterator it = mRecurrence.days.begin(); it != mRecurrence.days.end(); ++it ) { + writeString( e, "day", *it ); + } + if ( !mRecurrence.dayNumber.isEmpty() ) + writeString( e, "daynumber", mRecurrence.dayNumber ); + if ( !mRecurrence.month.isEmpty() ) + writeString( e, "month", mRecurrence.month ); + if ( !mRecurrence.rangeType.isEmpty() ) { + TQDomElement range = element.ownerDocument().createElement( "range" ); + e.appendChild( range ); + range.setAttribute( "type", mRecurrence.rangeType ); + TQDomText t = element.ownerDocument().createTextNode( mRecurrence.range ); + range.appendChild( t ); + } + for( TQValueList<TQDate>::ConstIterator it = mRecurrence.exclusions.begin(); + it != mRecurrence.exclusions.end(); ++it ) { + writeString( e, "exclusion", dateToString( *it ) ); + } +} + +void Incidence::loadRecurrence( const TQDomElement& element ) +{ + mRecurrence.interval = 0; + mRecurrence.cycle = element.attribute( "cycle" ); + mRecurrence.type = element.attribute( "type" ); + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "interval" ) { + //kolab/issue4229, sometimes the interval value can be empty + if ( e.text().isEmpty() || e.text().toInt() <= 0 ) { + mRecurrence.interval = 1; + } else { + mRecurrence.interval = e.text().toInt(); + } + } + else if ( tagName == "day" ) // can be present multiple times + mRecurrence.days.append( e.text() ); + else if ( tagName == "daynumber" ) + mRecurrence.dayNumber = e.text(); + else if ( tagName == "month" ) + mRecurrence.month = e.text(); + else if ( tagName == "range" ) { + mRecurrence.rangeType = e.attribute( "type" ); + mRecurrence.range = e.text(); + } else if ( tagName == "exclusion" ) { + mRecurrence.exclusions.append( stringToDate( e.text() ) ); + } else + // TODO: Unhandled tag - save for later storage + kdDebug() << "Warning: Unhandled tag " << e.tagName() << endl; + } + } +} + +static void loadAddressesHelper( const TQDomElement& element, KCal::Alarm* a ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "address" ) { + a->addMailAddress( KCal::Person( e.text() ) ); + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +static void loadAttachmentsHelper( const TQDomElement& element, KCal::Alarm* a ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "attachment" ) { + a->addMailAttachment( e.text() ); + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +static void loadAlarmHelper( const TQDomElement& element, KCal::Alarm* a ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "start-offset" ) { + a->setStartOffset( e.text().toInt()*60 ); + } else if ( tagName == "end-offset" ) { + a->setEndOffset( e.text().toInt()*60 ); + } else if ( tagName == "repeat-count" ) { + a->setRepeatCount( e.text().toInt() ); + } else if ( tagName == "repeat-interval" ) { + a->setSnoozeTime( e.text().toInt() ); + } else if ( tagName == "text" ) { + a->setText( e.text() ); + } else if ( tagName == "program" ) { + a->setProgramFile( e.text() ); + } else if ( tagName == "arguments" ) { + a->setProgramArguments( e.text() ); + } else if ( tagName == "addresses" ) { + loadAddressesHelper( e, a ); + } else if ( tagName == "subject" ) { + a->setMailSubject( e.text() ); + } else if ( tagName == "mail-text" ) { + a->setMailText( e.text() ); + } else if ( tagName == "attachments" ) { + loadAttachmentsHelper( e, a ); + } else if ( tagName == "file" ) { + a->setAudioFile( e.text() ); + } else if ( tagName == "enabled" ) { + a->setEnabled( e.text().toInt() != 0 ); + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +void Incidence::loadAlarms( const TQDomElement& element ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "alarm" ) { + KCal::Alarm *a = new KCal::Alarm( 0 ); + a->setEnabled( true ); // default to enabled, unless some XML attribute says otherwise. + TQString type = e.attribute( "type" ); + if ( type == "display" ) { + a->setType( KCal::Alarm::Display ); + } else if ( type == "procedure" ) { + a->setType( KCal::Alarm::Procedure ); + } else if ( type == "email" ) { + a->setType( KCal::Alarm::Email ); + } else if ( type == "audio" ) { + a->setType( KCal::Alarm::Audio ); + } else { + kdWarning() << "Unhandled alarm type:" << type << endl; + } + + loadAlarmHelper( e, a ); + mAlarms << a; + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +bool Incidence::loadAttribute( TQDomElement& element ) +{ + TQString tagName = element.tagName(); + + if ( tagName == "summary" ) + setSummary( element.text() ); + else if ( tagName == "location" ) + setLocation( element.text() ); + else if ( tagName == "organizer" ) { + Email email; + if ( loadEmailAttribute( element, email ) ) { + setOrganizer( email ); + return true; + } else + return false; + } else if ( tagName == "start-date" ) + setStartDate( element.text() ); + else if ( tagName == "recurrence" ) + loadRecurrence( element ); + else if ( tagName == "attendee" ) { + Attendee attendee; + if ( loadAttendeeAttribute( element, attendee ) ) { + addAttendee( attendee ); + return true; + } else + return false; + } else if ( tagName == "link-attachment" ) { + mAttachments.push_back( new KCal::Attachment( element.text() ) ); + } else if ( tagName == "alarm" ) + // Alarms should be minutes before. Libkcal uses event time + alarm time + setAlarm( - element.text().toInt() ); + else if ( tagName == "advanced-alarms" ) + loadAlarms( element ); + else if ( tagName == "x-kde-internaluid" ) + setInternalUID( element.text() ); + else if ( tagName == "x-custom" ) + loadCustomAttributes( element ); + else { + bool ok = KolabBase::loadAttribute( element ); + if ( !ok ) { + // Unhandled tag - save for later storage + //kdDebug() << "Saving unhandled tag " << element.tagName() << endl; + Custom c; + c.key = TQCString( "X-TDE-KolabUnhandled-" ) + element.tagName().latin1(); + c.value = element.text(); + mCustomList.append( c ); + } + } + // We handled this + return true; +} + +bool Incidence::saveAttributes( TQDomElement& element ) const +{ + // Save the base class elements + KolabBase::saveAttributes( element ); + + if ( mFloatingStatus == HasTime ) + writeString( element, "start-date", dateTimeToString( startDate() ) ); + else + writeString( element, "start-date", dateToString( startDate().date() ) ); + writeString( element, "summary", summary() ); + writeString( element, "location", location() ); + saveEmailAttribute( element, organizer(), "organizer" ); + if ( !mRecurrence.cycle.isEmpty() ) + saveRecurrence( element ); + saveAttendees( element ); + saveAttachments( element ); + if ( mHasAlarm ) { + // Alarms should be minutes before. Libkcal uses event time + alarm time + int alarmTime = tqRound( -alarm() ); + writeString( element, "alarm", TQString::number( alarmTime ) ); + } + saveAlarms( element ); + writeString( element, "x-kde-internaluid", internalUID() ); + saveCustomAttributes( element ); + return true; +} + +void Incidence::saveCustomAttributes( TQDomElement& element ) const +{ + TQValueList<Custom>::ConstIterator it = mCustomList.begin(); + for ( ; it != mCustomList.end(); ++it ) { + TQString key = (*it).key; + Q_ASSERT( !key.isEmpty() ); + if ( key.startsWith( "X-TDE-KolabUnhandled-" ) ) { + key = key.mid( strlen( "X-TDE-KolabUnhandled-" ) ); + writeString( element, key, (*it).value ); + } else { + // Let's use attributes so that other tag-preserving-code doesn't need sub-elements + TQDomElement e = element.ownerDocument().createElement( "x-custom" ); + element.appendChild( e ); + e.setAttribute( "key", key ); + e.setAttribute( "value", (*it).value ); + } + } +} + +void Incidence::loadCustomAttributes( TQDomElement& element ) +{ + Custom custom; + custom.key = element.attribute( "key" ).latin1(); + custom.value = element.attribute( "value" ); + mCustomList.append( custom ); +} + +static KCal::Attendee::PartStat attendeeStringToStatus( const TQString& s ) +{ + if ( s == "none" ) + return KCal::Attendee::NeedsAction; + if ( s == "tentative" ) + return KCal::Attendee::Tentative; + if ( s == "accepted" ) + return KCal::Attendee::Accepted; + if ( s == "declined" ) + return KCal::Attendee::Declined; + if ( s == "delegated" ) + return KCal::Attendee::Delegated; + + // Default: + return KCal::Attendee::None; +} + +static TQString attendeeStatusToString( KCal::Attendee::PartStat status ) +{ + switch( status ) { + case KCal::Attendee::NeedsAction: + return "none"; + case KCal::Attendee::Accepted: + return "accepted"; + case KCal::Attendee::Declined: + return "declined"; + case KCal::Attendee::Tentative: + return "tentative"; + case KCal::Attendee::Delegated: + return "delegated"; + case KCal::Attendee::Completed: + case KCal::Attendee::InProcess: + // These don't have any meaning in the Kolab format, so just use: + return "accepted"; + } + + // Default for the case that there are more added later: + return "accepted"; +} + +static KCal::Attendee::Role attendeeStringToRole( const TQString& s ) +{ + if ( s == "optional" ) + return KCal::Attendee::OptParticipant; + if ( s == "resource" ) + return KCal::Attendee::NonParticipant; + return KCal::Attendee::ReqParticipant; +} + +static TQString attendeeRoleToString( KCal::Attendee::Role role ) +{ + switch( role ) { + case KCal::Attendee::ReqParticipant: + return "required"; + case KCal::Attendee::OptParticipant: + return "optional"; + case KCal::Attendee::Chair: + // We don't have the notion of chair, so use + return "required"; + case KCal::Attendee::NonParticipant: + // In Kolab, a non-participant is a resource + return "resource"; + } + + // Default for the case that there are more added later: + return "required"; +} + +static const char *s_weekDayName[] = +{ + "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday" +}; + +static const char *s_monthName[] = +{ + "january", "february", "march", "april", "may", "june", "july", + "august", "september", "october", "november", "december" +}; + +void Incidence::setRecurrence( KCal::Recurrence* recur ) +{ + mRecurrence.interval = recur->frequency(); + switch ( recur->recurrenceType() ) { + case KCal::Recurrence::rMinutely: // Not handled by the kolab XML + mRecurrence.cycle = "minutely"; + break; + case KCal::Recurrence::rHourly: // Not handled by the kolab XML + mRecurrence.cycle = "hourly"; + break; + case KCal::Recurrence::rDaily: + mRecurrence.cycle = "daily"; + break; + case KCal::Recurrence::rWeekly: // every X weeks + mRecurrence.cycle = "weekly"; + { + TQBitArray arr = recur->days(); + for ( uint idx = 0 ; idx < 7 ; ++idx ) + if ( arr.testBit( idx ) ) + mRecurrence.days.append( s_weekDayName[idx] ); + } + break; + case KCal::Recurrence::rMonthlyPos: { + mRecurrence.cycle = "monthly"; + mRecurrence.type = "weekday"; + TQValueList<KCal::RecurrenceRule::WDayPos> monthPositions = recur->monthPositions(); + if ( !monthPositions.isEmpty() ) { + KCal::RecurrenceRule::WDayPos monthPos = monthPositions.first(); + // TODO: Handle multiple days in the same week + mRecurrence.dayNumber = TQString::number( monthPos.pos() ); + mRecurrence.days.append( s_weekDayName[ monthPos.day()-1 ] ); + // Not (properly) handled(?): monthPos.negative (nth days before end of month) + } + break; + } + case KCal::Recurrence::rMonthlyDay: { + mRecurrence.cycle = "monthly"; + mRecurrence.type = "daynumber"; + TQValueList<int> monthDays = recur->monthDays(); + // ####### Kolab XML limitation: only the first month day is used + if ( !monthDays.isEmpty() ) + mRecurrence.dayNumber = TQString::number( monthDays.first() ); + break; + } + case KCal::Recurrence::rYearlyMonth: // (day n of Month Y) + { + mRecurrence.cycle = "yearly"; + mRecurrence.type = "monthday"; + TQValueList<int> rmd = recur->yearDates(); + int day = !rmd.isEmpty() ? rmd.first() : recur->startDate().day(); + mRecurrence.dayNumber = TQString::number( day ); + TQValueList<int> months = recur->yearMonths(); + if ( !months.isEmpty() ) + mRecurrence.month = s_monthName[ months.first() - 1 ]; // #### Kolab XML limitation: only one month specified + break; + } + case KCal::Recurrence::rYearlyDay: // YearlyDay (day N of the year). Not supported by Outlook + mRecurrence.cycle = "yearly"; + mRecurrence.type = "yearday"; + mRecurrence.dayNumber = TQString::number( recur->yearDays().first() ); + break; + case KCal::Recurrence::rYearlyPos: // (weekday X of week N of month Y) + mRecurrence.cycle = "yearly"; + mRecurrence.type = "weekday"; + TQValueList<int> months = recur->yearMonths(); + if ( !months.isEmpty() ) + mRecurrence.month = s_monthName[ months.first() - 1 ]; // #### Kolab XML limitation: only one month specified + TQValueList<KCal::RecurrenceRule::WDayPos> monthPositions = recur->yearPositions(); + if ( !monthPositions.isEmpty() ) { + KCal::RecurrenceRule::WDayPos monthPos = monthPositions.first(); + // TODO: Handle multiple days in the same week + mRecurrence.dayNumber = TQString::number( monthPos.pos() ); + mRecurrence.days.append( s_weekDayName[ monthPos.day()-1 ] ); + + //mRecurrence.dayNumber = TQString::number( *recur->yearNums().getFirst() ); + // Not handled: monthPos.negative (nth days before end of month) + } + break; + } + int howMany = recur->duration(); + if ( howMany > 0 ) { + mRecurrence.rangeType = "number"; + mRecurrence.range = TQString::number( howMany ); + } else if ( howMany == 0 ) { + mRecurrence.rangeType = "date"; + mRecurrence.range = dateToString( recur->endDate() ); + } else { + mRecurrence.rangeType = "none"; + } +} + +void Incidence::setFields( const KCal::Incidence* incidence ) +{ + KolabBase::setFields( incidence ); + + if ( incidence->doesFloat() ) { + // This is a floating event. Don't timezone move this one + mFloatingStatus = AllDay; + setStartDate( incidence->dtStart().date() ); + } else { + mFloatingStatus = HasTime; + setStartDate( localToUTC( incidence->dtStart() ) ); + } + + setSummary( incidence->summary() ); + setLocation( incidence->location() ); + + // Alarm + mHasAlarm = false; // Will be set to true, if we actually have one + if ( incidence->isAlarmEnabled() ) { + const KCal::Alarm::List& alarms = incidence->alarms(); + if ( !alarms.isEmpty() ) { + const KCal::Alarm* alarm = alarms.first(); + if ( alarm->hasStartOffset() ) { + int dur = alarm->startOffset().asSeconds(); + setAlarm( (float)dur / 60.0 ); + } + } + } + + Email org( incidence->organizer().name(), incidence->organizer().email() ); + setOrganizer( org ); + + // Attendees: + KCal::Attendee::List attendees = incidence->attendees(); + KCal::Attendee::List::ConstIterator it; + for ( it = attendees.begin(); it != attendees.end(); ++it ) { + KCal::Attendee* kcalAttendee = *it; + Attendee attendee; + + attendee.displayName = kcalAttendee->name(); + attendee.smtpAddress = kcalAttendee->email(); + attendee.status = attendeeStatusToString( kcalAttendee->status() ); + attendee.requestResponse = kcalAttendee->RSVP(); + // TODO: KCal::Attendee::mFlag is not accessible + // attendee.invitationSent = kcalAttendee->mFlag; + // DF: Hmm? mFlag is set to true and never used at all.... Did you mean another field? + attendee.role = attendeeRoleToString( kcalAttendee->role() ); + attendee.delegate = kcalAttendee->delegate(); + attendee.delegator = kcalAttendee->delegator(); + + addAttendee( attendee ); + } + + mAttachments.clear(); + + // Attachments + KCal::Attachment::List attachments = incidence->attachments(); + KCal::Attachment::List::ConstIterator it2; + for ( it2 = attachments.begin(); it2 != attachments.end(); ++it2 ) { + KCal::Attachment *a = *it2; + mAttachments.push_back( a ); + } + + mAlarms.clear(); + + // Alarms + const KCal::Alarm::List alarms = incidence->alarms(); + for ( KCal::Alarm::List::ConstIterator it = alarms.begin(); it != alarms.end(); ++it ) { + mAlarms.push_back( *it ); + } + + if ( incidence->doesRecur() ) { + setRecurrence( incidence->recurrence() ); + mRecurrence.exclusions = incidence->recurrence()->exDates(); + } + + // Handle the scheduling ID + if ( incidence->schedulingID() == incidence->uid() ) { + // There is no scheduling ID + setInternalUID( TQString() ); + } else { + // We've internally been using a different uid, so save that as the + // temporary (internal) uid and restore the original uid, the one that + // is used in the folder and the outside world + setUid( incidence->schedulingID() ); + setInternalUID( incidence->uid() ); + } + + if ( incidence->pilotId() != 0 ) + setPilotSyncId( incidence->pilotId() ); + + setPilotSyncStatus( incidence->syncStatus() ); + + // Unhandled tags and other custom properties (see libkcal/customproperties.h) + const TQMap<TQCString, TQString> map = incidence->customProperties(); + TQMap<TQCString, TQString>::ConstIterator cit = map.begin(); + for ( ; cit != map.end() ; ++cit ) { + Custom c; + c.key = cit.key(); + c.value = cit.data(); + mCustomList.append( c ); + } +} + +static TQBitArray daysListToBitArray( const TQStringList& days ) +{ + TQBitArray arr( 7 ); + arr.fill( false ); + for( TQStringList::ConstIterator it = days.begin(); it != days.end(); ++it ) { + for ( uint i = 0; i < 7 ; ++i ) + if ( *it == s_weekDayName[i] ) + arr.setBit( i, true ); + } + return arr; +} + + +void Incidence::saveTo( KCal::Incidence* incidence ) +{ + KolabBase::saveTo( incidence ); + + if ( mFloatingStatus == AllDay ) { + // This is a floating event. Don't timezone move this one + incidence->setDtStart( startDate() ); + incidence->setFloats( true ); + } else { + incidence->setDtStart( utcToLocal( startDate() ) ); + incidence->setFloats( false ); + } + + incidence->setSummary( summary() ); + incidence->setLocation( location() ); + + if ( mHasAlarm && mAlarms.isEmpty() ) { + KCal::Alarm* alarm = incidence->newAlarm(); + alarm->setStartOffset( tqRound( mAlarm * 60.0 ) ); + alarm->setEnabled( true ); + alarm->setType( KCal::Alarm::Display ); + } else if ( !mAlarms.isEmpty() ) { + for ( KCal::Alarm::List::ConstIterator it = mAlarms.constBegin(); it != mAlarms.constEnd(); ++it ) { + KCal::Alarm *alarm = *it; + alarm->setParent( incidence ); + incidence->addAlarm( alarm ); + } + } + + if ( organizer().displayName.isEmpty() ) + incidence->setOrganizer( organizer().smtpAddress ); + else + incidence->setOrganizer( organizer().displayName + "<" + + organizer().smtpAddress + ">" ); + + incidence->clearAttendees(); + TQValueList<Attendee>::ConstIterator it; + for ( it = mAttendees.begin(); it != mAttendees.end(); ++it ) { + KCal::Attendee::PartStat status = attendeeStringToStatus( (*it).status ); + KCal::Attendee::Role role = attendeeStringToRole( (*it).role ); + KCal::Attendee* attendee = new KCal::Attendee( (*it).displayName, + (*it).smtpAddress, + (*it).requestResponse, + status, role ); + attendee->setDelegate( (*it).delegate ); + attendee->setDelegator( (*it).delegator ); + incidence->addAttendee( attendee ); + } + + incidence->clearAttachments(); + KCal::Attachment::List::ConstIterator it2; + for ( it2 = mAttachments.begin(); it2 != mAttachments.end(); ++it2 ) { + KCal::Attachment *a = (*it2); + // TODO should we copy? + incidence->addAttachment( a ); + } + + if ( !mRecurrence.cycle.isEmpty() ) { + KCal::Recurrence* recur = incidence->recurrence(); // yeah, this creates it + // done below recur->setFrequency( mRecurrence.interval ); + if ( mRecurrence.cycle == "minutely" ) { + recur->setMinutely( mRecurrence.interval ); + } else if ( mRecurrence.cycle == "hourly" ) { + recur->setHourly( mRecurrence.interval ); + } else if ( mRecurrence.cycle == "daily" ) { + recur->setDaily( mRecurrence.interval ); + } else if ( mRecurrence.cycle == "weekly" ) { + TQBitArray rDays = daysListToBitArray( mRecurrence.days ); + recur->setWeekly( mRecurrence.interval, rDays ); + } else if ( mRecurrence.cycle == "monthly" ) { + recur->setMonthly( mRecurrence.interval ); + if ( mRecurrence.type == "weekday" ) { + recur->addMonthlyPos( mRecurrence.dayNumber.toInt(), daysListToBitArray( mRecurrence.days ) ); + } else if ( mRecurrence.type == "daynumber" ) { + recur->addMonthlyDate( mRecurrence.dayNumber.toInt() ); + } else kdWarning() << "Unhandled monthly recurrence type " << mRecurrence.type << endl; + } else if ( mRecurrence.cycle == "yearly" ) { + recur->setYearly( mRecurrence.interval ); + if ( mRecurrence.type == "monthday" ) { + recur->addYearlyDate( mRecurrence.dayNumber.toInt() ); + for ( int i = 0; i < 12; ++i ) + if ( s_monthName[ i ] == mRecurrence.month ) + recur->addYearlyMonth( i+1 ); + } else if ( mRecurrence.type == "yearday" ) { + recur->addYearlyDay( mRecurrence.dayNumber.toInt() ); + } else if ( mRecurrence.type == "weekday" ) { + for ( int i = 0; i < 12; ++i ) + if ( s_monthName[ i ] == mRecurrence.month ) + recur->addYearlyMonth( i+1 ); + recur->addYearlyPos( mRecurrence.dayNumber.toInt(), daysListToBitArray( mRecurrence.days ) ); + } else kdWarning() << "Unhandled yearly recurrence type " << mRecurrence.type << endl; + } else kdWarning() << "Unhandled recurrence cycle " << mRecurrence.cycle << endl; + + if ( mRecurrence.rangeType == "number" ) { + recur->setDuration( mRecurrence.range.toInt() ); + } else if ( mRecurrence.rangeType == "date" ) { + recur->setEndDate( stringToDate( mRecurrence.range ) ); + } // "none" is default since tje set*ly methods set infinite recurrence + + incidence->recurrence()->setExDates( mRecurrence.exclusions ); + + } + /* If we've stored a uid to be used internally instead of the real one + * (to deal with duplicates of events in different folders) before, then + * restore it, so it does not change. Keep the original uid around for + * scheduling purposes. */ + if ( !internalUID().isEmpty() ) { + incidence->setUid( internalUID() ); + incidence->setSchedulingID( uid() ); + } + if ( hasPilotSyncId() ) + incidence->setPilotId( pilotSyncId() ); + if ( hasPilotSyncStatus() ) + incidence->setSyncStatus( pilotSyncStatus() ); + + for( TQValueList<Custom>::ConstIterator it = mCustomList.constBegin(); it != mCustomList.constEnd(); ++it ) { + incidence->setNonKDECustomProperty( (*it).key, (*it).value ); + } + +} + +void Incidence::loadAttachments() +{ + TQStringList attachments; + if ( mResource->kmailListAttachments( attachments, mSubResource, mSernum ) ) { + for ( TQStringList::ConstIterator it = attachments.constBegin(); it != attachments.constEnd(); ++it ) { + TQByteArray data; + KURL url; + if ( mResource->kmailGetAttachment( url, mSubResource, mSernum, *it ) && !url.isEmpty() ) { + TQFile f( url.path() ); + if ( f.open( IO_ReadOnly ) ) { + data = f.readAll(); + TQString mimeType; + if ( !mResource->kmailAttachmentMimetype( mimeType, mSubResource, mSernum, *it ) ) + mimeType = "application/octet-stream"; + KCal::Attachment *a = new KCal::Attachment( KCodecs::base64Encode( data ).data(), mimeType ); + a->setLabel( *it ); + mAttachments.append( a ); + f.close(); + } + f.remove(); + } + } + } +} + +TQString Incidence::productID() const +{ + return TQString( "KOrganizer %1, Kolab resource" ).arg( korgVersion ); +} + +// Unhandled KCal::Incidence fields: +// revision, status (unused), priority (done in tasks), attendee.uid, +// mComments, mReadOnly + diff --git a/tderesources/kolab/kcal/incidence.h b/tderesources/kolab/kcal/incidence.h new file mode 100644 index 000000000..926ec1e4f --- /dev/null +++ b/tderesources/kolab/kcal/incidence.h @@ -0,0 +1,175 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef KOLAB_INCIDENCE_H +#define KOLAB_INCIDENCE_H + +#include <kolabbase.h> + +class TQDomElement; + +namespace KCal { + class Incidence; + class Recurrence; + class Alarm; + class Attachment; + class ResourceKolab; +} + +namespace Kolab { + +/** + * This abstract class represents an incidence which has the shared + * fields, of events and tasks and knows how to load/save these + * from/to XML, and from/to a KCal::Incidence. + */ +class Incidence : public KolabBase { +public: + struct Recurrence { + TQString cycle; + TQString type; + int interval; + TQStringList days; // list of days-of-the-week + TQString dayNumber; + TQString month; + TQString rangeType; + TQString range; // date or number or nothing + TQValueList<TQDate> exclusions; + }; + + struct Attendee : Email { + Attendee() : requestResponse( true ), invitationSent( false ) {} + TQString status; + bool requestResponse; + bool invitationSent; + TQString role; + TQString delegate; + TQString delegator; + }; + + explicit Incidence( KCal::ResourceKolab *res, const TQString &subResource, TQ_UINT32 sernum, + const TQString& tz ); + virtual ~Incidence(); + + void saveTo( KCal::Incidence* incidence ); + + virtual void setSummary( const TQString& summary ); + virtual TQString summary() const; + + virtual void setLocation( const TQString& location ); + virtual TQString location() const; + + virtual void setOrganizer( const Email& organizer ); + virtual Email organizer() const; + + virtual void setStartDate( const TQDateTime& startDate ); + virtual void setStartDate( const TQDate& startDate ); + virtual void setStartDate( const TQString& startDate ); + virtual TQDateTime startDate() const; + + virtual void setAlarm( float alarm ); + virtual float alarm() const; + + virtual void setRecurrence( KCal::Recurrence* recur ); + virtual Recurrence recurrence() const; + + virtual void addAttendee( const Attendee& attendee ); + TQValueList<Attendee>& attendees(); + const TQValueList<Attendee>& attendees() const; + + /** + * The internal uid is used as the uid inside KOrganizer whenever + * two or more events with the same uid appear, which KOrganizer + * can't handle. To avoid keep that interal uid from changing all the + * time, it is persisted in the XML between a save and the next load. + */ + void setInternalUID( const TQString& iuid ); + TQString internalUID() const; + + // Load the attributes of this class + virtual bool loadAttribute( TQDomElement& ); + + // Save the attributes of this class + virtual bool saveAttributes( TQDomElement& ) const; + +protected: + enum FloatingStatus { Unset, AllDay, HasTime }; + + // Read all known fields from this ical incidence + void setFields( const KCal::Incidence* ); + + bool loadAttendeeAttribute( TQDomElement&, Attendee& ); + void saveAttendeeAttribute( TQDomElement& element, + const Attendee& attendee ) const; + void saveAttendees( TQDomElement& element ) const; + void saveAttachments( TQDomElement& element ) const; + + void loadAlarms( const TQDomElement& element ); + void saveAlarms( TQDomElement& element ) const; + + void loadRecurrence( const TQDomElement& element ); + void saveRecurrence( TQDomElement& element ) const; + void saveCustomAttributes( TQDomElement& element ) const; + void loadCustomAttributes( TQDomElement& element ); + + void loadAttachments(); + + TQString productID() const; + + TQString mSummary; + TQString mLocation; + Email mOrganizer; + TQDateTime mStartDate; + FloatingStatus mFloatingStatus; + float mAlarm; + bool mHasAlarm; + Recurrence mRecurrence; + TQValueList<Attendee> mAttendees; + TQValueList<KCal::Alarm*> mAlarms; + TQValueList<KCal::Attachment*> mAttachments; + TQString mInternalUID; + + struct Custom { + TQCString key; + TQString value; + }; + TQValueList<Custom> mCustomList; + + KCal::ResourceKolab *mResource; + TQString mSubResource; + TQ_UINT32 mSernum; +}; + +} + +#endif // KOLAB_INCIDENCE_H diff --git a/tderesources/kolab/kcal/journal.cpp b/tderesources/kolab/kcal/journal.cpp new file mode 100644 index 000000000..acfc4e71d --- /dev/null +++ b/tderesources/kolab/kcal/journal.cpp @@ -0,0 +1,184 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "journal.h" + +#include <libkcal/journal.h> +#include <korganizer/version.h> +#include <kdebug.h> + +using namespace Kolab; + + +KCal::Journal* Journal::xmlToJournal( const TQString& xml, const TQString& tz ) +{ + Journal journal( tz ); + journal.load( xml ); + KCal::Journal* kcalJournal = new KCal::Journal(); + journal.saveTo( kcalJournal ); + return kcalJournal; +} + +TQString Journal::journalToXML( KCal::Journal* kcalJournal, const TQString& tz ) +{ + Journal journal( tz, kcalJournal ); + return journal.saveXML(); +} + +Journal::Journal( const TQString& tz, KCal::Journal* journal ) + : KolabBase( tz ) +{ + if ( journal ) + setFields( journal ); +} + +Journal::~Journal() +{ +} + +void Journal::setSummary( const TQString& summary ) +{ + mSummary = summary; +} + +TQString Journal::summary() const +{ + return mSummary; +} + +void Journal::setStartDate( const TQDateTime& startDate ) +{ + mStartDate = startDate; +} + +TQDateTime Journal::startDate() const +{ + return mStartDate; +} + +void Journal::setEndDate( const TQDateTime& endDate ) +{ + mEndDate = endDate; +} + +TQDateTime Journal::endDate() const +{ + return mEndDate; +} + +bool Journal::loadAttribute( TQDomElement& element ) +{ + TQString tagName = element.tagName(); + + if ( tagName == "summary" ) + setSummary( element.text() ); + else if ( tagName == "start-date" ) + setStartDate( stringToDateTime( element.text() ) ); + else + // Not handled here + return KolabBase::loadAttribute( element ); + + // We handled this + return true; +} + +bool Journal::saveAttributes( TQDomElement& element ) const +{ + // Save the base class elements + KolabBase::saveAttributes( element ); + + writeString( element, "summary", summary() ); + writeString( element, "start-date", dateTimeToString( startDate() ) ); + + return true; +} + + +bool Journal::loadXML( const TQDomDocument& document ) +{ + TQDomElement top = document.documentElement(); + + if ( top.tagName() != "journal" ) { + tqWarning( "XML error: Top tag was %s instead of the expected Journal", + top.tagName().ascii() ); + return false; + } + + for ( TQDomNode n = top.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + if ( !loadAttribute( e ) ) { + // Unhandled tag - save for later storage + //tqDebug( "Unhandled tag: %s", e.toCString().data() ); + } + } else + tqDebug( "Node is not a comment or an element???" ); + } + + return true; +} + +TQString Journal::saveXML() const +{ + TQDomDocument document = domTree(); + TQDomElement element = document.createElement( "journal" ); + element.setAttribute( "version", "1.0" ); + saveAttributes( element ); + document.appendChild( element ); + return document.toString(); +} + +void Journal::saveTo( KCal::Journal* journal ) +{ + KolabBase::saveTo( journal ); + + journal->setSummary( summary() ); + journal->setDtStart( utcToLocal( startDate() ) ); +} + +void Journal::setFields( const KCal::Journal* journal ) +{ + // Set baseclass fields + KolabBase::setFields( journal ); + + // Set our own fields + setSummary( journal->summary() ); + setStartDate( localToUTC( journal->dtStart() ) ); +} + +TQString Journal::productID() const +{ + return TQString( "KOrganizer " ) + korgVersion + ", Kolab resource"; +} diff --git a/tderesources/kolab/kcal/journal.h b/tderesources/kolab/kcal/journal.h new file mode 100644 index 000000000..cd583c6ca --- /dev/null +++ b/tderesources/kolab/kcal/journal.h @@ -0,0 +1,103 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef KOLAB_JOURNAL_H +#define KOLAB_JOURNAL_H + +#include <kolabbase.h> + +class TQDomElement; + +namespace KCal { + class Journal; +} + +namespace Kolab { + +/** + * This class represents a journal entry, and knows how to load/save it + * from/to XML, and from/to a KCal::Journal. + * The instances of this class are temporary, only used to convert + * one to the other. + */ +class Journal : public KolabBase { +public: + /// Use this to parse an xml string to a journal entry + /// The caller is responsible for deleting the returned journal + static KCal::Journal* xmlToJournal( const TQString& xml, const TQString& tz ); + + /// Use this to get an xml string describing this journal entry + static TQString journalToXML( KCal::Journal*, const TQString& tz ); + + explicit Journal( const TQString& tz, KCal::Journal* journal = 0 ); + virtual ~Journal(); + + virtual TQString type() const { return "Journal"; } + + void saveTo( KCal::Journal* journal ); + + virtual void setSummary( const TQString& summary ); + virtual TQString summary() const; + + virtual void setStartDate( const TQDateTime& startDate ); + virtual TQDateTime startDate() const; + + virtual void setEndDate( const TQDateTime& endDate ); + virtual TQDateTime endDate() const; + + // Load the attributes of this class + virtual bool loadAttribute( TQDomElement& ); + + // Save the attributes of this class + virtual bool saveAttributes( TQDomElement& ) const; + + // Load this journal by reading the XML file + virtual bool loadXML( const TQDomDocument& xml ); + + // Serialize this journal to an XML string + virtual TQString saveXML() const; + +protected: + // Read all known fields from this ical journal + void setFields( const KCal::Journal* ); + + TQString productID() const; + + TQString mSummary; + TQDateTime mStartDate; + TQDateTime mEndDate; +}; + +} + +#endif // KOLAB_JOURNAL_H diff --git a/tderesources/kolab/kcal/kolab.desktop b/tderesources/kolab/kcal/kolab.desktop new file mode 100644 index 000000000..40bcacb00 --- /dev/null +++ b/tderesources/kolab/kcal/kolab.desktop @@ -0,0 +1,52 @@ +[Desktop Entry] +Name=Calendar on IMAP Server via KMail +Name[af]=Kalender op IMAP bediener via KMail +Name[bg]=Календар на Ñървър IMAP през KMail +Name[br]=Deiziadur en ur servijer IMAP gant KMail +Name[ca]=Calendari sobre servidor IMAP mitjançant KMail +Name[cs]=Kalendář na IMAP serveru pÅ™es KMail +Name[da]=Kalender pÃ¥ IMAP-server via KMail +Name[de]=Kalender auf einem IMAP-Server via KMail +Name[el]=ΗμεÏολόγιο σε εξυπηÏετητή IMAP μÎσω του KMail +Name[es]=Calendario en servidor IMAP por medio de KMail +Name[et]=Kalender IMAP-serveris (KMaili vahendusel) +Name[eu]=Egutegia IMAP zerbitzarian KMail-en bidez +Name[fa]=تقویم روی کارساز IMAP از طریق KMail +Name[fi]=Kalenteri IMAP-palvelimella KMailin avulla +Name[fr]=Agenda sur serveur IMAP (via KMail) +Name[fy]=Aginda op IMAP-tsjinner fia KMail +Name[ga]=Féilire ar Fhreastalaà IMAP via KMail +Name[gl]=Calendario no servidor IMAP mediante KMail +Name[hu]=IMAP-kiszolgálón tárolt naptár a KMailen keresztül +Name[is]=Dagatal á IMAP þjóni gegnum KMail +Name[it]=Calendario su server IMAP via KMail +Name[ja]=KMail 経由 IMAP サーãƒã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ +Name[kk]=KMail арқылы IMAP Ñерверіндегі күнтізбе +Name[km]=ប្រážáž·áž‘ិន​លើ​ម៉ាស៊ីន​បម្រើ IMAP ážáž¶áž˜â€‹ážšáž™áŸˆ KMail +Name[lt]=Kalendorius IMAP serveryje per KMail +Name[mk]=Календар на IMAP-Ñервер преку КПошта +Name[ms]=Kalendar pada pelayan IMAP melalui KMail +Name[nb]=Kalender pÃ¥ IMAP-tjener via KMail +Name[nds]=Kalenner op IMAP-Server över KMail +Name[ne]=केडीई मेल मारà¥à¤«à¤¤ IMAP सरà¥à¤à¤°à¤®à¤¾ कà¥à¤¯à¤¾à¤²à¥‡à¤¨à¥à¤¡à¤° +Name[nl]=Agenda op IMAP-server via KMail +Name[nn]=Kalender pÃ¥ IMAP-tenar via KMail +Name[pl]=Kalendarz na serwerze IMAP za poÅ›rednictwem KMail +Name[pt]=Calendário em Servidor IMAP via KMail +Name[pt_BR]=Calendário em Servidor IMAP via KMail +Name[ru]=Календарь на Ñервере IMAP через KMail +Name[sk]=Kalendár na IMAP serveri pomocou KMail +Name[sl]=Koledar na strežniku IMAP preko KMaila +Name[sr]=Календар на IMAP Ñерверу преко KMail-а +Name[sr@Latn]=Kalendar na IMAP serveru preko KMail-a +Name[sv]=Kalender pÃ¥ IMAP-server via Kmail +Name[ta]=IMAP சேவையக வழியாக கேஅஞà¯à®šà®²à®¿à®²à¯ நாடà¯à®•à®¾à®Ÿà¯à®Ÿà®¿ +Name[tr]=KMail Aracılığı ile IMAP Sunucusunda Takvim +Name[uk]=Календар на Ñервері IMAP через KMail +Name[zh_CN]=通过 KMail 访问 IMAP æœåŠ¡å™¨ä¸Šçš„日历 +Name[zh_TW]=é€éŽ KMail å–å¾— IMAP 伺æœå™¨ä¸Šçš„行事曆 +X-TDE-Library=kcal_kolab +Type=Service +ServiceTypes=KResources/Plugin +X-TDE-ResourceFamily=calendar +X-TDE-ResourceType=imap diff --git a/tderesources/kolab/kcal/resourcekolab.cpp b/tderesources/kolab/kcal/resourcekolab.cpp new file mode 100644 index 000000000..44c13fd2b --- /dev/null +++ b/tderesources/kolab/kcal/resourcekolab.cpp @@ -0,0 +1,1342 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + 2004 Till Adam <till@klaralvdalens-datakonsult.se> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "resourcekolab.h" +#include "event.h" +#include "task.h" +#include "journal.h" + +#include <kio/observer.h> +#include <kio/uiserver_stub.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <libtdepim/kincidencechooser.h> +#include <kabc/locknull.h> +#include <kmainwindow.h> +#include <klocale.h> +#include <kinputdialog.h> +#include <ktempfile.h> +#include <kmdcodec.h> + +#include <tqfile.h> +#include <tqobject.h> +#include <tqtimer.h> +#include <tqapplication.h> + +#include <assert.h> + +using namespace KCal; +using namespace Kolab; + +static const char* kmailCalendarContentsType = "Calendar"; +static const char* kmailTodoContentsType = "Task"; +static const char* kmailJournalContentsType = "Journal"; +static const char* eventAttachmentMimeType = "application/x-vnd.kolab.event"; +static const char* todoAttachmentMimeType = "application/x-vnd.kolab.task"; +static const char* journalAttachmentMimeType = "application/x-vnd.kolab.journal"; +static const char* incidenceInlineMimeType = "text/calendar"; + + +ResourceKolab::ResourceKolab( const TDEConfig *config ) + : ResourceCalendar( config ), ResourceKolabBase( "ResourceKolab-libkcal" ), + mCalendar( TQString::fromLatin1("UTC") ), mOpen( false ),mResourceChangedTimer( 0, + "mResourceChangedTimer" ), mBatchAddingInProgress( false ) +{ + if ( !config ) { + setResourceName( i18n( "Kolab Server" ) ); + } + setType( "imap" ); + connect( &mResourceChangedTimer, TQT_SIGNAL( timeout() ), + this, TQT_SLOT( slotEmitResourceChanged() ) ); +} + +ResourceKolab::~ResourceKolab() +{ + // The resource is deleted on exit (StdAddressBook's KStaticDeleter), + // and it wasn't closed before that, so close here to save the config. + if ( mOpen ) { + close(); + } +} + +void ResourceKolab::loadSubResourceConfig( TDEConfig& config, + const TQString& name, + const TQString& label, + bool writable, + bool alarmRelevant, + ResourceMap& subResource ) +{ + TDEConfigGroup group( &config, name ); + bool active = group.readBoolEntry( "Active", true ); + subResource.insert( name, Kolab::SubResource( active, writable, + alarmRelevant, label ) ); +} + +bool ResourceKolab::openResource( TDEConfig& config, const char* contentType, + ResourceMap& map ) +{ + // Read the subresource entries from KMail + TQValueList<KMailICalIface::SubResource> subResources; + if ( !kmailSubresources( subResources, contentType ) ) + return false; + map.clear(); + TQValueList<KMailICalIface::SubResource>::ConstIterator it; + for ( it = subResources.begin(); it != subResources.end(); ++it ) + loadSubResourceConfig( config, (*it).location, (*it).label, (*it).writable, + (*it).alarmRelevant, map ); + return true; +} + +bool ResourceKolab::doOpen() +{ + if ( mOpen ) + // Already open + return true; + mOpen = true; + + TDEConfig config( configFile() ); + config.setGroup( "General" ); + mProgressDialogIncidenceLimit = config.readNumEntry("ProgressDialogIncidenceLimit", 200); + + return openResource( config, kmailCalendarContentsType, mEventSubResources ) + && openResource( config, kmailTodoContentsType, mTodoSubResources ) + && openResource( config, kmailJournalContentsType, mJournalSubResources ); +} + +static void writeResourceConfig( TDEConfig& config, ResourceMap& map ) +{ + ResourceMap::ConstIterator it; + for ( it = map.begin(); it != map.end(); ++it ) { + config.setGroup( it.key() ); + config.writeEntry( "Active", it.data().active() ); + } +} + +void ResourceKolab::doClose() +{ + if ( !mOpen ) + // Not open + return; + mOpen = false; + + writeConfig(); +} + +bool ResourceKolab::loadSubResource( const TQString& subResource, + const char* mimetype ) +{ + int count = 0; + if ( !kmailIncidencesCount( count, mimetype, subResource ) ) { + kdError(5650) << "Communication problem in ResourceKolab::load()\n"; + return false; + } + + if ( !count ) + return true; + + const int nbMessages = 200; // read 200 mails at a time (see kabc resource) + + const TQString labelTxt = !strcmp(mimetype, "application/x-vnd.kolab.task") ? i18n( "Loading tasks..." ) + : !strcmp(mimetype, "application/x-vnd.kolab.journal") ? i18n( "Loading journals..." ) + : i18n( "Loading events..." ); + const bool useProgress = tqApp && tqApp->type() != TQApplication::Tty && count > mProgressDialogIncidenceLimit; + if ( useProgress ) + (void)::Observer::self(); // ensure kio_uiserver is running + UIServer_stub uiserver( "kio_uiserver", "UIServer" ); + int progressId = 0; + if ( useProgress ) { + progressId = uiserver.newJob( kapp->dcopClient()->appId(), true ); + uiserver.totalFiles( progressId, count ); + uiserver.infoMessage( progressId, labelTxt ); + uiserver.transferring( progressId, labelTxt ); + } + + for ( int startIndex = 0; startIndex < count; startIndex += nbMessages ) { + TQMap<TQ_UINT32, TQString> lst; + if ( !kmailIncidences( lst, mimetype, subResource, startIndex, nbMessages ) ) { + kdError(5650) << "Communication problem in ResourceKolab::load()\n"; + if ( progressId ) + uiserver.jobFinished( progressId ); + return false; + } + + { // for RAII scoping below + TemporarySilencer t( this ); + for( TQMap<TQ_UINT32, TQString>::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { + addIncidence( mimetype, it.data(), subResource, it.key() ); + } + } + if ( progressId ) { + uiserver.processedFiles( progressId, startIndex ); + uiserver.percent( progressId, 100 * startIndex / count ); + } + +// if ( progress.wasCanceled() ) { +// uiserver.jobFinished( progressId ); +// return false; +// } + } + + if ( progressId ) + uiserver.jobFinished( progressId ); + return true; +} + +bool ResourceKolab::doLoad() +{ + if (!mUidMap.isEmpty() ) { + emit resourceLoaded( this ); + return true; + } + mUidMap.clear(); + + bool result = loadAllEvents() & loadAllTodos() & loadAllJournals(); + if ( result ) { + emit resourceLoaded( this ); + } else { + // FIXME: anyone know if the resource correctly calls loadError() + // if it has one? + } + + return result; +} + +bool ResourceKolab::doLoadAll( ResourceMap& map, const char* mimetype ) +{ + bool rc = true; + for ( ResourceMap::ConstIterator it = map.begin(); it != map.end(); ++it ) { + if ( !it.data().active() ) + // This resource is disabled + continue; + + rc &= loadSubResource( it.key(), mimetype ); + } + return rc; +} + +bool ResourceKolab::loadAllEvents() +{ + removeIncidences( "Event" ); + mCalendar.deleteAllEvents(); + bool kolabStyle = doLoadAll( mEventSubResources, eventAttachmentMimeType ); + bool icalStyle = doLoadAll( mEventSubResources, incidenceInlineMimeType ); + return kolabStyle && icalStyle; +} + +bool ResourceKolab::loadAllTodos() +{ + removeIncidences( "Todo" ); + mCalendar.deleteAllTodos(); + bool kolabStyle = doLoadAll( mTodoSubResources, todoAttachmentMimeType ); + bool icalStyle = doLoadAll( mTodoSubResources, incidenceInlineMimeType ); + + return kolabStyle && icalStyle; +} + +bool ResourceKolab::loadAllJournals() +{ + removeIncidences( "Journal" ); + mCalendar.deleteAllJournals(); + bool kolabStyle = doLoadAll( mJournalSubResources, journalAttachmentMimeType ); + bool icalStyle = doLoadAll( mJournalSubResources, incidenceInlineMimeType ); + + return kolabStyle && icalStyle; +} + +void ResourceKolab::removeIncidences( const TQCString& incidenceType ) +{ + Kolab::UidMap::Iterator mapIt = mUidMap.begin(); + while ( mapIt != mUidMap.end() ) + { + Kolab::UidMap::Iterator it = mapIt++; + // Check the type of this uid: event, todo or journal. + // Need to look up in mCalendar for that. Given the implementation of incidence(uid), + // better call event(uid), todo(uid) etc. directly. + + // A faster but hackish way would probably be to check the type of the resource, + // like mEventSubResources.find( it.data().resource() ) != mEventSubResources.end() ? + const TQString& uid = it.key(); + if ( incidenceType == "Event" && mCalendar.event( uid ) ) + mUidMap.remove( it ); + else if ( incidenceType == "Todo" && mCalendar.todo( uid ) ) + mUidMap.remove( it ); + else if ( incidenceType == "Journal" && mCalendar.journal( uid ) ) + mUidMap.remove( it ); + } +} + +bool ResourceKolab::doSave() +{ + return true; + /* + return kmailTriggerSync( kmailCalendarContentsType ) + && kmailTriggerSync( kmailTodoContentsType ) + && kmailTriggerSync( kmailJournalContentsType ); + */ +} +void ResourceKolab::incidenceUpdatedSilent( KCal::IncidenceBase* incidencebase ) +{ + const TQString uid = incidencebase->uid(); + //kdDebug() << k_funcinfo << uid << endl; + + if ( mUidsPendingUpdate.contains( uid ) || mUidsPendingAdding.contains( uid ) ) { + /* We are currently processing this event ( removing and readding or + * adding it ). If so, ignore this update. Keep the last of these around + * and process once we hear back from KMail on this event. */ + mPendingUpdates.remove( uid ); + mPendingUpdates.insert( uid, incidencebase ); + return; + } + + { // start optimization + /** + KOrganizer and libkcal like calling two Incidence::updated() + for only one user change. That's because after a change, + IncidenceChanger calls incidence->setRevision( rev++ ); + which also calls Incidence::updated(). + + Lets ignore the first updated() and only send to kmail + the second. This makes things faster. + */ + + //IncidenceBase doesn't have revision(), downcast needed. + Incidence *i = dynamic_cast<Incidence*>( incidencebase ); + + if ( i ) { + bool ignoreThisUpdate = false; + + if ( !mLastKnownRevisions.contains( uid ) ) { + mLastKnownRevisions[uid] = i->revision(); + } + + // update the last known revision + if ( mLastKnownRevisions[uid] < i->revision() ) { + mLastKnownRevisions[uid] = i->revision(); + } else { + ignoreThisUpdate = true; + } + + if ( ignoreThisUpdate ) { + return; + } + } + } // end optimization + + TQString subResource; + TQ_UINT32 sernum = 0; + if ( mUidMap.contains( uid ) ) { + subResource = mUidMap[ uid ].resource(); + sernum = mUidMap[ uid ].serialNumber(); + mUidsPendingUpdate.append( uid ); + } + + sendKMailUpdate( incidencebase, subResource, sernum ); +} +void ResourceKolab::incidenceUpdated( KCal::IncidenceBase* incidencebase ) +{ + if ( incidencebase->isReadOnly() ) { + return; + } + + incidencebase->setSyncStatusSilent( KCal::Event::SYNCMOD ); + incidencebase->setLastModified( TQDateTime::currentDateTime() ); + + // we should probably update the revision number here, + // or internally in the Event itself when certain things change. + // need to verify with ical documentation. + incidenceUpdatedSilent( incidencebase ); +} + +void ResourceKolab::resolveConflict( KCal::Incidence* inc, const TQString& subresource, TQ_UINT32 sernum ) +{ + if ( !inc ) { + return; + } + + if ( !mResolveConflict ) { + // we should do no conflict resolution + delete inc; + return; + } + const TQString origUid = inc->uid(); + Incidence* local = mCalendar.incidence( origUid ); + Incidence* localIncidence = 0; + Incidence* addedIncidence = 0; + Incidence* result = 0; + if ( local ) { + if ( *local == *inc ) { + // real duplicate, remove the second one + result = local; + } else { + KIncidenceChooser* ch = new KIncidenceChooser(); + ch->setIncidence( local ,inc ); + if ( KIncidenceChooser::chooseMode == KIncidenceChooser::ask ) { + connect ( this, TQT_SIGNAL( useGlobalMode() ), ch, TQT_SLOT ( useGlobalMode() ) ); + if ( ch->exec() ) { + if ( KIncidenceChooser::chooseMode != KIncidenceChooser::ask ) { + emit useGlobalMode() ; + } + } + } + result = ch->getIncidence(); + delete ch; + } + } else { + // nothing there locally, just take the new one. Can't Happen (TM) + result = inc; + } + if ( result == local ) { + delete inc; + localIncidence = local; + } else if ( result == inc ) { + addedIncidence = inc; + } else if ( result == 0 ) { // take both + addedIncidence = inc; + addedIncidence->setSummary( i18n("Copy of: %1").arg( addedIncidence->summary() ) ); + addedIncidence->setUid( CalFormat::createUniqueId() ); + localIncidence = local; + } + const bool silent = mSilent; + mSilent = false; + if ( !localIncidence ) { + deleteIncidence( local ); // remove local from kmail + } + mUidsPendingDeletion.append( origUid ); + if ( addedIncidence ) { + sendKMailUpdate( addedIncidence, subresource, sernum ); + } else { + kmailDeleteIncidence( subresource, sernum );// remove new from kmail + } + mSilent = silent; +} +void ResourceKolab::addIncidence( const char* mimetype, const TQString& data, + const TQString& subResource, TQ_UINT32 sernum ) +{ + // This uses pointer comparison, so it only works if we use the static + // objects defined in the top of the file + if ( mimetype == eventAttachmentMimeType ) + addEvent( data, subResource, sernum ); + else if ( mimetype == todoAttachmentMimeType ) + addTodo( data, subResource, sernum ); + else if ( mimetype == journalAttachmentMimeType ) + addJournal( data, subResource, sernum ); + else if ( mimetype == incidenceInlineMimeType ) { + Incidence *inc = mFormat.fromString( data ); + addIncidence( inc, subResource, sernum ); + } +} + + +bool ResourceKolab::sendKMailUpdate( KCal::IncidenceBase* incidencebase, const TQString& subresource, + TQ_UINT32 sernum ) +{ + const TQString& type = incidencebase->type(); + const char* mimetype = 0; + TQString data; + bool isXMLStorageFormat = kmailStorageFormat( subresource ) == KMailICalIface::StorageXML; + if ( type == "Event" ) { + if( isXMLStorageFormat ) { + mimetype = eventAttachmentMimeType; + data = Kolab::Event::eventToXML( static_cast<KCal::Event *>(incidencebase), + mCalendar.timeZoneId() ); + } else { + mimetype = incidenceInlineMimeType; + data = mFormat.createScheduleMessage( static_cast<KCal::Event *>(incidencebase), + Scheduler::Request ); + } + } else if ( type == "Todo" ) { + if( isXMLStorageFormat ) { + mimetype = todoAttachmentMimeType; + data = Kolab::Task::taskToXML( static_cast<KCal::Todo *>(incidencebase), + mCalendar.timeZoneId() ); + } else { + mimetype = incidenceInlineMimeType; + data = mFormat.createScheduleMessage( static_cast<KCal::Todo *>(incidencebase), + Scheduler::Request ); + } + } else if ( type == "Journal" ) { + if( isXMLStorageFormat ) { + mimetype = journalAttachmentMimeType; + data = Kolab::Journal::journalToXML( static_cast<KCal::Journal *>(incidencebase ), + mCalendar.timeZoneId() ); + } else { + mimetype = incidenceInlineMimeType; + data = mFormat.createScheduleMessage( static_cast<KCal::Journal *>(incidencebase), + Scheduler::Request ); + } + } else { + kdWarning(5006) << "Can't happen: unhandled type=" << type << endl; + } + +// kdDebug() << k_funcinfo << "Data string:\n" << data << endl; + + KCal::Incidence* incidence = static_cast<KCal::Incidence *>( incidencebase ); + + KCal::Attachment::List atts = incidence->attachments(); + TQStringList attURLs, attMimeTypes, attNames; + TQValueList<KTempFile*> tmpFiles; + for ( KCal::Attachment::List::ConstIterator it = atts.constBegin(); it != atts.constEnd(); ++it ) { + if ( (*it)->isUri() ) { + continue; + } + KTempFile *tempFile = new KTempFile; + if ( tempFile->status() == 0 ) { // open ok + const TQByteArray decoded = (*it)->decodedData() ; + + tempFile->file()->writeBlock( decoded.data(), decoded.count() ); + KURL url; + url.setPath( tempFile->name() ); + attURLs.append( url.url() ); + attMimeTypes.append( (*it)->mimeType() ); + attNames.append( (*it)->label() ); + tempFile->close(); + tmpFiles.append( tempFile ); + } else { + kdWarning(5006) << "Cannot open temporary file for attachment"; + } + } + TQStringList deletedAtts; + if ( kmailListAttachments( deletedAtts, subresource, sernum ) ) { + for ( TQStringList::ConstIterator it = attNames.constBegin(); it != attNames.constEnd(); ++it ) { + deletedAtts.remove( *it ); + } + } + CustomHeaderMap customHeaders; + if ( incidence->schedulingID() != incidence->uid() ) { + customHeaders.insert( "X-Kolab-SchedulingID", incidence->schedulingID() ); + } + + TQString subject = incidencebase->uid(); + if ( !isXMLStorageFormat ) subject.prepend( "iCal " ); // conform to the old style + + // behold, sernum is an in-parameter + const bool rc = kmailUpdate( subresource, sernum, data, mimetype, subject, customHeaders, attURLs, attMimeTypes, attNames, deletedAtts ); + // update the serial number + if ( mUidMap.contains( incidencebase->uid() ) ) { + mUidMap[ incidencebase->uid() ].setSerialNumber( sernum ); + } + + for( TQValueList<KTempFile *>::Iterator it = tmpFiles.begin(); it != tmpFiles.end(); ++it ) { + (*it)->setAutoDelete( true ); + delete (*it); + } + + return rc; +} + +bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _subresource, + TQ_UINT32 sernum ) +{ + Q_ASSERT( incidence ); + if ( !incidence ) { + return false; + } + + kdDebug() << "Resourcekolab, adding incidence " + << incidence->summary() + << "; subresource = " << _subresource + << "; sernum = " << sernum + << "; mBatchAddingInProgress = " << mBatchAddingInProgress + << endl; + + TQString uid = incidence->uid(); + TQString subResource = _subresource; + + Kolab::ResourceMap *map = &mEventSubResources; // don't use a ref here! + + const TQString& type = incidence->type(); + if ( type == "Event" ) { + map = &mEventSubResources; + } else if ( type == "Todo" ) { + map = &mTodoSubResources; + } else if ( type == "Journal" ) { + map = &mJournalSubResources; + } else { + kdWarning() << "unknown type " << type << endl; + } + + if ( !mSilent ) { /* We got this one from the user, tell KMail. */ + // Find out if this event was previously stored in KMail + bool newIncidence = _subresource.isEmpty(); + if ( newIncidence ) { + ResourceType type = Incidences; + // Add a description of the incidence + TQString text = "<b><font size=\"+1\">"; + if ( incidence->type() == "Event" ) { + type = Events; + text += i18n( "Choose the folder where you want to store this event" ); + } else if ( incidence->type() == "Todo" ) { + type = Tasks; + text += i18n( "Choose the folder where you want to store this task" ); + } else { + text += i18n( "Choose the folder where you want to store this incidence" ); + } + text += "<font></b><br>"; + if ( !incidence->summary().isEmpty() ) + text += i18n( "<b>Summary:</b> %1" ).arg( incidence->summary() ) + "<br>"; + if ( !incidence->location().isEmpty() ) + text += i18n( "<b>Location:</b> %1" ).arg( incidence->location() ); + text += "<br>"; + if ( !incidence->doesFloat() ) + text += i18n( "<b>Start:</b> %1, %2" ) + .arg( incidence->dtStartDateStr(), incidence->dtStartTimeStr() ); + else + text += i18n( "<b>Start:</b> %1" ).arg( incidence->dtStartDateStr() ); + text += "<br>"; + if ( incidence->type() == "Event" ) { + Event* event = static_cast<Event*>( incidence ); + if ( event->hasEndDate() ) { + if ( !event->doesFloat() ) { + text += i18n( "<b>End:</b> %1, %2" ) + .arg( event->dtEndDateStr(), event->dtEndTimeStr() ); + } else { + text += i18n( "<b>End:</b> %1" ).arg( event->dtEndDateStr() ); + } + } + text += "<br>"; + } + + // Lets not warn the user 100 times that there's no writable resource + // and not ask 100 times which resource to use + if ( !mBatchAddingInProgress || !mLastUsedResources.contains( type ) ) { + subResource = findWritableResource( type, *map, text ); + mLastUsedResources[type] = subResource; + } else { + subResource = mLastUsedResources[type]; + } + + if ( subResource.isEmpty() ) { + switch( mErrorCode ) { + case NoWritableFound: + setException( new ErrorFormat( ErrorFormat::NoWritableFound ) ); + break; + case UserCancel: + setException( new ErrorFormat( ErrorFormat::UserCancel ) ); + break; + case NoError: + break; + } + } + } + + if ( subResource.isEmpty() ) { + endAddingIncidences(); // cleanup + kdDebug(5650) << "ResourceKolab: subResource is empty" << endl; + return false; + } + + mNewIncidencesMap.insert( uid, subResource ); + + if ( !sendKMailUpdate( incidence, subResource, sernum ) ) { + kdError(5650) << "Communication problem in ResourceKolab::addIncidence()\n"; + endAddingIncidences(); // cleanup + return false; + } else { + // KMail is doing it's best to add the event now, put a sticker on it, + // so we know it's one of our transient ones + mUidsPendingAdding.append( uid ); + + /* Add to the cache immediately if this is a new event coming from + * KOrganizer. It relies on the incidence being in the calendar when + * addIncidence returns. */ + if ( newIncidence || sernum == 0 ) { + mCalendar.addIncidence( incidence ); + incidence->registerObserver( this ); + } + } + } else { /* KMail told us */ + const bool ourOwnUpdate = mUidsPendingUpdate.contains( uid ); + kdDebug( 5650 ) << "addIncidence: ourOwnUpdate " << ourOwnUpdate << endl; + /* Check if we updated this one, which means kmail deleted and added it. + * We know the new state, so lets just not do much at all. The old incidence + * in the calendar remains valid, but the serial number changed, so we need to + * update that */ + if ( ourOwnUpdate ) { + mUidsPendingUpdate.remove( uid ); + mUidMap.remove( uid ); + mUidMap[ uid ] = StorageReference( subResource, sernum ); + } else { + /* This is a real add, from KMail, we didn't trigger this ourselves. + * If this uid already exists in this folder, do conflict resolution, + * unless the folder is read-only, in which case the user should not be + * offered a means of putting mails in a folder she'll later be unable to + * upload. Skip the incidence, in this case. */ + if ( mUidMap.contains( uid ) ) { + if ( mUidMap[ uid ].resource() == subResource ) { + if ( (*map)[ subResource ].writable() ) { + kdDebug( 5650 ) << "lets resolve the conflict " << endl; + resolveConflict( incidence, subResource, sernum ); + } else { + kdWarning( 5650 ) << "Duplicate event in a read-only folder detected! " + "Please inform the owner of the folder. " << endl; + } + return true; + } else { + // duplicate uid in a different folder, do the internal-uid tango + incidence->setSchedulingID( uid ); + + incidence->setUid( CalFormat::createUniqueId( ) ); + uid = incidence->uid(); + + /* Will be needed when kmail triggers a delete, so we don't delete the inocent + * incidence that's sharing the uid with this one */ + mOriginalUID2fakeUID[tqMakePair( incidence->schedulingID(), subResource )] = uid; + } + } + /* Add to the cache if the add didn't come from KOrganizer, in which case + * we've already added it, and listen to updates from KOrganizer for it. */ + if ( !mUidsPendingAdding.contains( uid ) ) { + mCalendar.addIncidence( incidence ); + incidence->registerObserver( this ); + } + if ( !subResource.isEmpty() && sernum != 0 ) { + mUidMap[ uid ] = StorageReference( subResource, sernum ); + incidence->setReadOnly( !(*map)[ subResource ].writable() ); + } + } + /* Check if there are updates for this uid pending and if so process them. */ + if ( KCal::IncidenceBase *update = mPendingUpdates.find( uid ) ) { + mSilent = false; // we do want to tell KMail + mPendingUpdates.remove( uid ); + incidenceUpdated( update ); + } else { + /* If the uid was added by KMail, KOrganizer needs to be told, so + * schedule emitting of the resourceChanged signal. */ + if ( !mUidsPendingAdding.contains( uid ) ) { + if ( !ourOwnUpdate ) mResourceChangedTimer.changeInterval( 100 ); + } else { + mUidsPendingAdding.remove( uid ); + } + } + + mNewIncidencesMap.remove( uid ); + } + return true; +} + +bool ResourceKolab::addEvent( KCal::Event *event ) +{ + return addEvent( event, TQString() ); +} + +bool ResourceKolab::addEvent( KCal::Event *event, const TQString &subResource ) +{ + if ( mUidMap.contains( event->uid() ) ) { + return true; //noop + } else { + return addIncidence( event, subResource, 0 ); + } +} + +void ResourceKolab::addEvent( const TQString& xml, const TQString& subresource, + TQ_UINT32 sernum ) +{ + KCal::Event* event = Kolab::Event::xmlToEvent( xml, mCalendar.timeZoneId(), this, subresource, sernum ); + Q_ASSERT( event ); + if ( event ) { + addIncidence( event, subresource, sernum ); + } +} + +bool ResourceKolab::deleteIncidence( KCal::Incidence* incidence ) +{ + if ( incidence->isReadOnly() ) { + return false; + } + + const TQString uid = incidence->uid(); + if( !mUidMap.contains( uid ) ) return false; // Odd + /* The user told us to delete, tell KMail */ + if ( !mSilent ) { + kmailDeleteIncidence( mUidMap[ uid ].resource(), + mUidMap[ uid ].serialNumber() ); + mUidsPendingDeletion.append( uid ); + incidence->unRegisterObserver( this ); + mCalendar.deleteIncidence( incidence ); + mUidMap.remove( uid ); + } else { + assert( false ); // If this still happens, something is very wrong + } + return true; +} + +bool ResourceKolab::deleteEvent( KCal::Event* event ) +{ + return deleteIncidence( event ); +} + +KCal::Event* ResourceKolab::event( const TQString& uid ) +{ + return mCalendar.event(uid); +} + +KCal::Event::List ResourceKolab::rawEvents( EventSortField sortField, SortDirection sortDirection ) +{ + return mCalendar.rawEvents( sortField, sortDirection ); +} + +KCal::Event::List ResourceKolab::rawEventsForDate( const TQDate& date, + EventSortField sortField, + SortDirection sortDirection ) +{ + return mCalendar.rawEventsForDate( date, sortField, sortDirection ); +} + +KCal::Event::List ResourceKolab::rawEventsForDate( const TQDateTime& qdt ) +{ + return mCalendar.rawEventsForDate( qdt ); +} + +KCal::Event::List ResourceKolab::rawEvents( const TQDate& start, + const TQDate& end, + bool inclusive ) +{ + return mCalendar.rawEvents( start, end, inclusive ); +} + +bool ResourceKolab::addTodo( KCal::Todo *todo ) +{ + return addTodo( todo, TQString() ); +} + +bool ResourceKolab::addTodo( KCal::Todo *todo, const TQString &subResource ) +{ + if ( mUidMap.contains( todo->uid() ) ) { + return true; //noop + } else { + return addIncidence( todo, subResource, 0 ); + } +} + +void ResourceKolab::addTodo( const TQString& xml, const TQString& subresource, + TQ_UINT32 sernum ) +{ + KCal::Todo* todo = Kolab::Task::xmlToTask( xml, mCalendar.timeZoneId(), this, subresource, sernum ); + Q_ASSERT( todo ); + if ( todo ) { + addIncidence( todo, subresource, sernum ); + } +} + +bool ResourceKolab::deleteTodo( KCal::Todo* todo ) +{ + return deleteIncidence( todo ); +} + +KCal::Todo* ResourceKolab::todo( const TQString& uid ) +{ + return mCalendar.todo( uid ); +} + +KCal::Todo::List ResourceKolab::rawTodos( TodoSortField sortField, SortDirection sortDirection ) +{ + return mCalendar.rawTodos( sortField, sortDirection ); +} + +KCal::Todo::List ResourceKolab::rawTodosForDate( const TQDate& date ) +{ + return mCalendar.rawTodosForDate( date ); +} + +bool ResourceKolab::addJournal( KCal::Journal *journal ) +{ + return addJournal( journal, TQString() ); +} + +bool ResourceKolab::addJournal( KCal::Journal *journal, const TQString &subResource ) +{ + if ( mUidMap.contains( journal->uid() ) ) + return true; //noop + else + return addIncidence( journal, subResource, 0 ); +} + +void ResourceKolab::addJournal( const TQString& xml, const TQString& subresource, + TQ_UINT32 sernum ) +{ + KCal::Journal* journal = + Kolab::Journal::xmlToJournal( xml, mCalendar.timeZoneId() ); + Q_ASSERT( journal ); + if( journal ) { + addIncidence( journal, subresource, sernum ); + } +} + +bool ResourceKolab::deleteJournal( KCal::Journal* journal ) +{ + return deleteIncidence( journal ); +} + +KCal::Journal* ResourceKolab::journal( const TQString& uid ) +{ + return mCalendar.journal(uid); +} + +KCal::Journal::List ResourceKolab::rawJournals( JournalSortField sortField, SortDirection sortDirection ) +{ + return mCalendar.rawJournals( sortField, sortDirection ); +} + +KCal::Journal::List ResourceKolab::rawJournalsForDate( const TQDate &date ) +{ + return mCalendar.rawJournalsForDate( date ); +} + +KCal::Alarm::List ResourceKolab::relevantAlarms( const KCal::Alarm::List &alarms ) +{ + KCal::Alarm::List relevantAlarms; + KCal::Alarm::List::ConstIterator it( alarms.begin() ); + while ( it != alarms.end() ) { + KCal::Alarm *a = (*it); + ++it; + const TQString &uid = a->parent()->uid(); + if ( mUidMap.contains( uid ) ) { + const TQString &sr = mUidMap[ uid ].resource(); + Kolab::SubResource *subResource = 0; + if ( mEventSubResources.contains( sr ) ) + subResource = &( mEventSubResources[ sr ] ); + else if ( mTodoSubResources.contains( sr ) ) + subResource = &( mTodoSubResources[ sr ] ); + assert( subResource ); + if ( subResource->alarmRelevant() ) + relevantAlarms.append ( a ); + else { + kdDebug(5650) << "Alarm skipped, not relevant." << endl; + } + } + } + return relevantAlarms; +} + + + +KCal::Alarm::List ResourceKolab::alarms( const TQDateTime& from, + const TQDateTime& to ) +{ + return relevantAlarms( mCalendar.alarms( from, to ) ); +} + +KCal::Alarm::List ResourceKolab::alarmsTo( const TQDateTime& to ) +{ + return relevantAlarms( mCalendar.alarmsTo(to) ); +} + +void ResourceKolab::setTimeZoneId( const TQString& tzid ) +{ + mCalendar.setTimeZoneId( tzid ); + mFormat.setTimeZone( mCalendar.timeZoneId(), !mCalendar.isLocalTime() ); +} + +bool ResourceKolab::fromKMailAddIncidence( const TQString& type, + const TQString& subResource, + TQ_UINT32 sernum, + int format, + const TQString& data ) +{ + bool rc = true; + TemporarySilencer t( this ); // RAII + if ( type != kmailCalendarContentsType && type != kmailTodoContentsType + && type != kmailJournalContentsType ) { + // Not ours + return false; + } + + if ( !subresourceActive( subResource ) ) { + return true; + } + + if ( format == KMailICalIface::StorageXML ) { + // If this data file is one of ours, load it here + if ( type == kmailCalendarContentsType ) { + addEvent( data, subResource, sernum ); + } else if ( type == kmailTodoContentsType ) { + addTodo( data, subResource, sernum ); + } else if ( type == kmailJournalContentsType ) { + addJournal( data, subResource, sernum ); + } else { + rc = false; + } + } else { + Incidence *inc = mFormat.fromString( data ); + if ( inc ) { + addIncidence( inc, subResource, sernum ); + } else { + rc = false; + } + } + return rc; +} + +void ResourceKolab::fromKMailDelIncidence( const TQString& type, + const TQString& subResource, + const TQString& uid ) +{ + if ( type != kmailCalendarContentsType && type != kmailTodoContentsType + && type != kmailJournalContentsType ) + // Not ours + return; + if ( !subresourceActive( subResource ) ) return; + + // Can't be in both, by contract + if ( mUidsPendingDeletion.find( uid ) != mUidsPendingDeletion.end() ) { + mUidsPendingDeletion.remove( mUidsPendingDeletion.find( uid ) ); + } else if ( mUidsPendingUpdate.contains( uid ) ) { + // It's good to know if was deleted, but we are waiting on a new one to + // replace it, so let's just sit tight. + } else { + TQString uidToUse; + + TQPair<TQString, TQString> p( uid, subResource ); + if ( mOriginalUID2fakeUID.contains( p ) ) { + // Incidence with the same uid in a different folder... + // use the UID that addIncidence(...) generated + uidToUse = mOriginalUID2fakeUID[p]; + } else { + uidToUse = uid; + } + + // We didn't trigger this, so KMail did, remove the reference to the uid + KCal::Incidence* incidence = mCalendar.incidence( uidToUse ); + if( incidence ) { + incidence->unRegisterObserver( this ); + mCalendar.deleteIncidence( incidence ); + } + mUidMap.remove( uidToUse ); + mOriginalUID2fakeUID.remove( p ); + mResourceChangedTimer.changeInterval( 100 ); + } +} + +void ResourceKolab::fromKMailRefresh( const TQString& type, + const TQString& /*subResource*/ ) +{ + // TODO: Only load the specified subResource + if ( type == "Calendar" ) + loadAllEvents(); + else if ( type == "Task" ) + loadAllTodos(); + else if ( type == "Journal" ) + loadAllJournals(); + else + kdWarning(5006) << "KCal Kolab resource: fromKMailRefresh: unknown type " << type << endl; + mResourceChangedTimer.changeInterval( 100 ); +} + +void ResourceKolab::fromKMailAddSubresource( const TQString& type, + const TQString& subResource, + const TQString& label, + bool writable, bool alarmRelevant ) +{ + ResourceMap* map = 0; + const char* mimetype = 0; + if ( type == kmailCalendarContentsType ) { + map = &mEventSubResources; + mimetype = eventAttachmentMimeType; + } else if ( type == kmailTodoContentsType ) { + map = &mTodoSubResources; + mimetype = todoAttachmentMimeType; + } else if ( type == kmailJournalContentsType ) { + map = &mJournalSubResources; + mimetype = journalAttachmentMimeType; + } else + // Not ours + return; + + if ( map->contains( subResource ) ) + // Already registered + return; + + TDEConfig config( configFile() ); + config.setGroup( subResource ); + + bool active = config.readBoolEntry( subResource, true ); + (*map)[ subResource ] = Kolab::SubResource( active, writable, + alarmRelevant, label ); + loadSubResource( subResource, mimetype ); + emit signalSubresourceAdded( this, type, subResource, label ); +} + +void ResourceKolab::fromKMailDelSubresource( const TQString& type, + const TQString& subResource ) +{ + ResourceMap* map = subResourceMap( type ); + if ( !map ) // not ours + return; + if ( map->contains( subResource ) ) + map->erase( subResource ); + else + // Not registered + return; + + // Delete from the config file + TDEConfig config( configFile() ); + config.deleteGroup( subResource ); + config.sync(); + + unloadSubResource( subResource ); + + emit signalSubresourceRemoved( this, type, subResource ); +} + +TQStringList ResourceKolab::subresources() const +{ + // Workaround: The ResourceView in KOrganizer wants to know this + // before it opens the resource :-( Make sure we are open + const_cast<ResourceKolab*>( this )->doOpen(); + return ( mEventSubResources.keys() + + mTodoSubResources.keys() + + mJournalSubResources.keys() ); +} + +const TQString +ResourceKolab::labelForSubresource( const TQString& subresource ) const +{ + if ( mEventSubResources.contains( subresource ) ) + return mEventSubResources[ subresource ].label(); + if ( mTodoSubResources.contains( subresource ) ) + return mTodoSubResources[ subresource ].label(); + if ( mJournalSubResources.contains( subresource ) ) + return mJournalSubResources[ subresource ].label(); + return subresource; +} + +void ResourceKolab::fromKMailAsyncLoadResult( const TQMap<TQ_UINT32, TQString>& map, + const TQString& type, + const TQString& folder ) +{ + TemporarySilencer t( this ); + for( TQMap<TQ_UINT32, TQString>::ConstIterator it = map.begin(); it != map.end(); ++it ) + addIncidence( type.latin1(), it.data(), folder, it.key() ); +} + +bool ResourceKolab::subresourceActive( const TQString& subresource ) const +{ + // Workaround: The ResourceView in KOrganizer wants to know this + // before it opens the resource :-( Make sure we are open + const_cast<ResourceKolab*>( this )->doOpen(); + + if ( mEventSubResources.contains( subresource ) ) + return mEventSubResources[ subresource ].active(); + if ( mTodoSubResources.contains( subresource ) ) + return mTodoSubResources[ subresource ].active(); + if ( mJournalSubResources.contains( subresource ) ) + return mJournalSubResources[ subresource ].active(); + + // Safe default bet: + kdDebug(5650) << "subresourceActive( " << subresource << " ): Safe bet\n"; + + return true; +} + +void ResourceKolab::setSubresourceActive( const TQString &subresource, bool v ) +{ + ResourceMap *map = 0; + const char* mimeType = 0; + if ( mEventSubResources.contains( subresource ) ) { + map = &mEventSubResources; + mimeType = eventAttachmentMimeType; + } + if ( mTodoSubResources.contains( subresource ) ) { + map = &mTodoSubResources; + mimeType = todoAttachmentMimeType; + } + if ( mJournalSubResources.contains( subresource ) ) { + map = &mJournalSubResources; + mimeType = journalAttachmentMimeType; + } + + if ( map && ( ( *map )[ subresource ].active() != v ) ) { + ( *map )[ subresource ].setActive( v ); + if ( v ) { + loadSubResource( subresource, mimeType ); + } else { + unloadSubResource( subresource ); + } + mResourceChangedTimer.changeInterval( 100 ); + } + TQTimer::singleShot( 0, this, TQT_SLOT(writeConfig()) ); +} + +bool ResourceKolab::subresourceWritable( const TQString& subresource ) const +{ + // Workaround: The ResourceView in KOrganizer wants to know this + // before it opens the resource :-( Make sure we are open + const_cast<ResourceKolab*>( this )->doOpen(); + + if ( mEventSubResources.contains( subresource ) ) + return mEventSubResources[ subresource ].writable(); + if ( mTodoSubResources.contains( subresource ) ) + return mTodoSubResources[ subresource ].writable(); + if ( mJournalSubResources.contains( subresource ) ) + return mJournalSubResources[ subresource ].writable(); + + return false; //better a safe default +} + +void ResourceKolab::slotEmitResourceChanged() +{ + kdDebug(5650) << "KCal Kolab resource: emitting resource changed " << endl; + mResourceChangedTimer.stop(); + emit resourceChanged( this ); +} + +KABC::Lock* ResourceKolab::lock() +{ + return new KABC::LockNull( true ); +} + +Kolab::ResourceMap* ResourceKolab::subResourceMap( const TQString& contentsType ) +{ + if ( contentsType == kmailCalendarContentsType ) { + return &mEventSubResources; + } else if ( contentsType == kmailTodoContentsType ) { + return &mTodoSubResources; + } else if ( contentsType == kmailJournalContentsType ) { + return &mJournalSubResources; + } + // Not ours + return 0; +} + + +/*virtual*/ +bool ResourceKolab::addSubresource( const TQString& resource, const TQString& parent ) +{ + kdDebug(5650) << "KCal Kolab resource - adding subresource: " << resource << endl; + TQString contentsType = kmailCalendarContentsType; + if ( !parent.isEmpty() ) { + if ( mEventSubResources.contains( parent ) ) + contentsType = kmailCalendarContentsType; + else if ( mTodoSubResources.contains( parent ) ) + contentsType = kmailTodoContentsType; + else if ( mJournalSubResources.contains( parent ) ) + contentsType = kmailJournalContentsType; + } else { + TQStringList contentTypeChoices; + contentTypeChoices << i18n("Calendar") << i18n("Tasks") << i18n("Journals"); + const TQString caption = i18n("Which kind of subresource should this be?"); + const TQString choice = KInputDialog::getItem( caption, TQString(), contentTypeChoices ); + if ( choice == contentTypeChoices[0] ) + contentsType = kmailCalendarContentsType; + else if ( choice == contentTypeChoices[1] ) + contentsType = kmailTodoContentsType; + else if ( choice == contentTypeChoices[2] ) + contentsType = kmailJournalContentsType; + } + + return kmailAddSubresource( resource, parent, contentsType ); +} + +/*virtual*/ +bool ResourceKolab::removeSubresource( const TQString& resource ) +{ + kdDebug(5650) << "KCal Kolab resource - removing subresource: " << resource << endl; + return kmailRemoveSubresource( resource ); +} + +/*virtual*/ +TQString ResourceKolab::subresourceIdentifier( Incidence *incidence ) +{ + TQString uid = incidence->uid(); + if ( mUidMap.contains( uid ) ) + return mUidMap[ uid ].resource(); + else + if ( mNewIncidencesMap.contains( uid ) ) + return mNewIncidencesMap[ uid ]; + else + return TQString(); +} + + +bool ResourceKolab::unloadSubResource( const TQString& subResource ) +{ + const bool silent = mSilent; + mSilent = true; + Kolab::UidMap::Iterator mapIt = mUidMap.begin(); + TQPtrList<KCal::Incidence> incidences; + while ( mapIt != mUidMap.end() ) + { + Kolab::UidMap::Iterator it = mapIt++; + const StorageReference ref = it.data(); + if ( ref.resource() != subResource ) continue; + // FIXME incidence() is expensive + KCal::Incidence* incidence = mCalendar.incidence( it.key() ); + if( incidence ) { + // register all observers first before actually deleting them + // in case of inter-incidence relations the other part will get + // the change notification otherwise + incidence->unRegisterObserver( this ); + incidences.append( incidence ); + } + mUidMap.remove( it ); + } + TQPtrListIterator<KCal::Incidence> it( incidences ); + for ( ; it.current(); ++it ) { + mCalendar.deleteIncidence( it.current() ); + } + mSilent = silent; + return true; +} + +TQString ResourceKolab::subresourceType( const TQString &resource ) +{ + if ( mEventSubResources.contains( resource ) ) + return "event"; + if ( mTodoSubResources.contains( resource ) ) + return "todo"; + if ( mJournalSubResources.contains( resource ) ) + return "journal"; + return TQString(); +} + +void ResourceKolab::writeConfig() +{ + TDEConfig config( configFile() ); + writeResourceConfig( config, mEventSubResources ); + writeResourceConfig( config, mTodoSubResources ); + writeResourceConfig( config, mJournalSubResources ); +} + +void ResourceKolab::beginAddingIncidences() +{ + mBatchAddingInProgress = true; +} + +void ResourceKolab::endAddingIncidences() +{ + mBatchAddingInProgress = false; + mLastUsedResources.clear(); +} + +#include "resourcekolab.moc" diff --git a/tderesources/kolab/kcal/resourcekolab.h b/tderesources/kolab/kcal/resourcekolab.h new file mode 100644 index 000000000..fdf952cc3 --- /dev/null +++ b/tderesources/kolab/kcal/resourcekolab.h @@ -0,0 +1,284 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + 2004 Till Adam <till@klaralvdalens-datakonsult.se> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef KCAL_RESOURCEKOLAB_H +#define KCAL_RESOURCEKOLAB_H + +#include <tqtimer.h> + +#include <tdepimmacros.h> +#include <libkcal/calendarlocal.h> +#include <libkcal/icalformat.h> +#include <libkcal/resourcecalendar.h> +#include "../shared/resourcekolabbase.h" + +namespace KCal { + +struct TemporarySilencer; + +class KDE_EXPORT ResourceKolab : public KCal::ResourceCalendar, + public KCal::IncidenceBase::Observer, + public Kolab::ResourceKolabBase +{ + Q_OBJECT + + friend struct TemporarySilencer; + +public: + ResourceKolab( const TDEConfig* ); + virtual ~ResourceKolab(); + + /// Load resource data. + bool doLoad(); + + /// Save resource data. + bool doSave(); + + /// Open the notes resource. + bool doOpen(); + /// Close the notes resource. + void doClose(); + + // The libkcal functions. See the resource for descriptions + KDE_DEPRECATED bool addEvent( KCal::Event *event ); + bool addEvent( KCal::Event *event, const TQString &subResource ); + bool deleteEvent( KCal::Event * ); + KCal::Event* event( const TQString &UniqueStr ); + KCal::Event::List rawEvents( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); + KCal::Event::List rawEventsForDate( + const TQDate& date, + EventSortField sortField=EventSortUnsorted, + SortDirection sortDirection=SortDirectionAscending ); + KCal::Event::List rawEventsForDate( const TQDateTime& qdt ); + KCal::Event::List rawEvents( const TQDate& start, const TQDate& end, + bool inclusive = false ); + + KDE_DEPRECATED bool addTodo( KCal::Todo * todo ); + bool addTodo( KCal::Todo *todo, const TQString &subResource ); + bool deleteTodo( KCal::Todo * ); + KCal::Todo* todo( const TQString &uid ); + KCal::Todo::List rawTodos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); + KCal::Todo::List rawTodosForDate( const TQDate& date ); + + KDE_DEPRECATED bool addJournal( KCal::Journal * ); + bool addJournal( KCal::Journal *, const TQString &subResource ); + bool deleteJournal( KCal::Journal * ); + KCal::Journal* journal( const TQString &uid ); + KCal::Journal::List rawJournals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); + KCal::Journal::List rawJournalsForDate( const TQDate &date ); + + KCal::Alarm::List alarms( const TQDateTime& from, const TQDateTime& to ); + KCal::Alarm::List alarmsTo( const TQDateTime& to ); + + void setTimeZoneId( const TQString& tzid ); + + bool deleteIncidence( KCal::Incidence* i ); + + /// The ResourceKolabBase methods called by KMail + bool fromKMailAddIncidence( const TQString& type, const TQString& subResource, + TQ_UINT32 sernum, int format, const TQString& data ); + void fromKMailDelIncidence( const TQString& type, const TQString& subResource, + const TQString& uid ); + void fromKMailRefresh( const TQString& type, const TQString& subResource ); + + /// Listen to KMail changes in the amount of sub resources + void fromKMailAddSubresource( const TQString& type, const TQString& subResource, + const TQString& label, bool writable, + bool alarmRelevant ); + void fromKMailDelSubresource( const TQString& type, const TQString& subResource ); + + void fromKMailAsyncLoadResult( const TQMap<TQ_UINT32, TQString>& map, + const TQString& type, + const TQString& folder ); + + /** Return the list of subresources. */ + TQStringList subresources() const; + + bool canHaveSubresources() const { return true; } + + /** Is this subresource active? */ + bool subresourceActive( const TQString& ) const; + /** (De)activate the subresource */ + virtual void setSubresourceActive( const TQString &, bool ); + + /** Is this subresource writable? */ + bool subresourceWritable( const TQString& ) const; + + /** What is the label for this subresource? */ + virtual const TQString labelForSubresource( const TQString& resource ) const; + + virtual TQString subresourceIdentifier( Incidence *incidence ); + + virtual bool addSubresource( const TQString& resource, const TQString& parent ); + virtual bool removeSubresource( const TQString& resource ); + + virtual TQString subresourceType( const TQString &resource ); + + KABC::Lock* lock(); + + void beginAddingIncidences(); + + void endAddingIncidences(); + +signals: + void useGlobalMode(); +protected slots: + void slotEmitResourceChanged(); + void writeConfig(); + +protected: + /** + * Return list of alarms which are relevant for the current user. These + * are the ones coming from folders which the user has "Administer" rights + * for, as per ACL */ + KCal::Alarm::List relevantAlarms( const KCal::Alarm::List &alarms ); + +private: + void removeIncidences( const TQCString& incidenceType ); + void resolveConflict( KCal::Incidence*, const TQString& subresource, TQ_UINT32 sernum ); + void addIncidence( const char* mimetype, const TQString& xml, + const TQString& subResource, TQ_UINT32 sernum ); + + + /** + Caller guarantees i is not null. + */ + bool addIncidence( KCal::Incidence *i, const TQString& subresource, + TQ_UINT32 sernum ); + + void addEvent( const TQString& xml, const TQString& subresource, + TQ_UINT32 sernum ); + void addTodo( const TQString& xml, const TQString& subresource, + TQ_UINT32 sernum ); + void addJournal( const TQString& xml, const TQString& subresource, + TQ_UINT32 sernum ); + + + bool loadAllEvents(); + bool loadAllTodos(); + bool loadAllJournals(); + + bool doLoadAll( Kolab::ResourceMap& map, const char* mimetype ); + + /// Reimplemented from IncidenceBase::Observer to know when an incidence was changed + void incidenceUpdated( KCal::IncidenceBase* ); + void incidenceUpdatedSilent( KCal::IncidenceBase* incidencebase); + + bool openResource( TDEConfig& config, const char* contentType, + Kolab::ResourceMap& map ); + void loadSubResourceConfig( TDEConfig& config, const TQString& name, + const TQString& label, bool writable, + bool alarmRelevant, Kolab::ResourceMap& subResource ); + bool loadSubResource( const TQString& subResource, const char* mimetype ); + bool unloadSubResource( const TQString& subResource ); + + TQString configFile() const { + return ResourceKolabBase::configFile( "kcal" ); + } + + Kolab::ResourceMap* subResourceMap( const TQString& contentsType ); + + bool sendKMailUpdate( KCal::IncidenceBase* incidence, const TQString& _subresource, + TQ_UINT32 sernum ); + + + KCal::CalendarLocal mCalendar; + + // The list of subresources + Kolab::ResourceMap mEventSubResources, mTodoSubResources, mJournalSubResources; + + bool mOpen; // If the resource is open, this is true + TQDict<KCal::IncidenceBase> mPendingUpdates; + TQTimer mResourceChangedTimer; + ICalFormat mFormat; + + /** + This map contains the association between a new added incidence + and the subresource it belongs to. + That's needed to return the correct mapping in subresourceIdentifier(). + + We can't trust on mUidMap here, because it contains only non-pending uids. + */ + TQMap<TQString, TQString> mNewIncidencesMap; + int mProgressDialogIncidenceLimit; + + /** + * If a user has a subresource for viewing another user's folder then it can happen + * that addIncidence(...) adds an incidence with an already existing UID. + * + * When this happens, addIncidence(...) sets a new random UID and stores the + * original UID using incidence->setSchedulingID(uid) because KCal doesn't + * allow two incidences to have the same UID. + * + * This map keeps track of the generated UIDs (which are local) so we can delete the + * right incidence inside fromKMailDelIncidence(...) whenever we sync. + * + * The key is originalUID,subResource and the value is the fake UID. + */ + TQMap< TQPair<TQString, TQString>, TQString > mOriginalUID2fakeUID; + + bool mBatchAddingInProgress; + TQMap<Kolab::ResourceType,TQString> mLastUsedResources; + + /** + Indexed by uid, it holds the last known revision of an incidence. + If we receive an update where the incidence still has the same + revision as the last known, we ignore it and don't send it to kmail, + because shortly after, IncidenceChanger will increment the revision + and that will trigger another update. + + If we didn't discard the first update, kmail would have been updated twice. + */ + TQMap<TQString,int> mLastKnownRevisions; + +}; + +struct TemporarySilencer { + TemporarySilencer( ResourceKolab *_resource ) + { + resource = _resource; + oldValue = resource->mSilent; + resource->mSilent = true; + } + ~TemporarySilencer() + { + resource->mSilent = oldValue; + } + ResourceKolab *resource; + bool oldValue; +}; + +} + +#endif // KCAL_RESOURCEKOLAB_H diff --git a/tderesources/kolab/kcal/resourcekolab_plugin.cpp b/tderesources/kolab/kcal/resourcekolab_plugin.cpp new file mode 100644 index 000000000..eb56897ae --- /dev/null +++ b/tderesources/kolab/kcal/resourcekolab_plugin.cpp @@ -0,0 +1,49 @@ +/* + This file is part of libkabc and/or kaddressbook. + Copyright (c) 2002 - 2004 Klarälvdalens Datakonsult AB + <info@klaralvdalens-datakonsult.se> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "resourcekolab.h" + +class KolabFactory : public KRES::PluginFactoryBase +{ +public: + KRES::Resource *resource( const TDEConfig *config ) + { + return new KCal::ResourceKolab( config ); + } + + KRES::ConfigWidget *configWidget( TQWidget* ) + { + return 0; + } +}; + +K_EXPORT_COMPONENT_FACTORY(kcal_kolab,KolabFactory) diff --git a/tderesources/kolab/kcal/task.cpp b/tderesources/kolab/kcal/task.cpp new file mode 100644 index 000000000..fe75845db --- /dev/null +++ b/tderesources/kolab/kcal/task.cpp @@ -0,0 +1,461 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "task.h" + +#include <libkcal/todo.h> +#include <kdebug.h> + +using namespace Kolab; + +// Kolab Storage Specification: +// "The priority can be a number between 1 and 5, with 1 being the highest priority." +// iCalendar (RFC 2445): +// "The priority is specified as an integer in the range +// zero to nine. A value of zero specifies an +// undefined priority. A value of one is the +// highest priority. A value of nine is the lowest +// priority." + +static int kcalPriorityToKolab( const int kcalPriority ) +{ + if ( kcalPriority >= 0 && kcalPriority <= 9 ) { + // We'll map undefined (0) to 3 (default) + // 0 1 2 3 4 5 6 7 8 9 + static const int priorityMap[10] = { 3, 1, 1, 2, 2, 3, 3, 4, 4, 5 }; + return priorityMap[kcalPriority]; + } + else { + kdWarning() << "kcalPriorityToKolab(): Got invalid priority " << kcalPriority << endl; + return 3; + } +} + +static int kolabPrioritytoKCal( const int kolabPriority ) +{ + if ( kolabPriority >= 1 && kolabPriority <= 5 ) { + // 1 2 3 4 5 + static const int priorityMap[5] = { 1, 3, 5, 7, 9 }; + return priorityMap[kolabPriority - 1]; + } + else { + kdWarning() << "kolabPrioritytoKCal(): Got invalid priority " << kolabPriority << endl; + return 5; + } +} + +KCal::Todo* Task::xmlToTask( const TQString& xml, const TQString& tz, KCal::ResourceKolab *res, + const TQString& subResource, TQ_UINT32 sernum ) +{ + Task task( res, subResource, sernum, tz ); + task.load( xml ); + KCal::Todo* todo = new KCal::Todo(); + task.saveTo( todo ); + return todo; +} + +TQString Task::taskToXML( KCal::Todo* todo, const TQString& tz ) +{ + Task task( 0, TQString(), 0, tz, todo ); + return task.saveXML(); +} + +Task::Task( KCal::ResourceKolab *res, const TQString &subResource, TQ_UINT32 sernum, + const TQString& tz, KCal::Todo* task ) + : Incidence( res, subResource, sernum, tz ), + mPriority( 5 ), mPercentCompleted( 0 ), + mStatus( KCal::Incidence::StatusNone ), + mHasStartDate( false ), mHasDueDate( false ), + mHasCompletedDate( false ) +{ + if ( task ) + setFields( task ); +} + +Task::~Task() +{ +} + +void Task::setPriority( int priority ) +{ + mPriority = priority; +} + +int Task::priority() const +{ + return mPriority; +} + +void Task::setPercentCompleted( int percent ) +{ + mPercentCompleted = percent; +} + +int Task::percentCompleted() const +{ + return mPercentCompleted; +} + +void Task::setStatus( KCal::Incidence::Status status ) +{ + mStatus = status; +} + +KCal::Incidence::Status Task::status() const +{ + return mStatus; +} + +void Task::setParent( const TQString& parentUid ) +{ + mParent = parentUid; +} + +TQString Task::parent() const +{ + return mParent; +} + +void Task::setDueDate( const TQDateTime& date ) +{ + mDueDate = date; + mHasDueDate = true; + mFloatingStatus = HasTime; +} + +void Task::setDueDate( const TQDate &date ) +{ + mDueDate = date; + mHasDueDate = true; + mFloatingStatus = AllDay; +} + + +void Task::setDueDate( const TQString &date ) +{ + if ( date.length() > 10 ) { + // This is a date + time + setDueDate( stringToDateTime( date ) ); + } else { + // This is only a date + setDueDate( stringToDate( date ) ); + } +} + +TQDateTime Task::dueDate() const +{ + return mDueDate; +} + +void Task::setHasStartDate( bool v ) +{ + mHasStartDate = v; +} + +bool Task::hasStartDate() const +{ + return mHasStartDate; +} + +bool Task::hasDueDate() const +{ + return mHasDueDate; +} + +void Task::setCompletedDate( const TQDateTime& date ) +{ + mCompletedDate = date; + mHasCompletedDate = true; +} + +TQDateTime Task::completedDate() const +{ + return mCompletedDate; +} + +bool Task::hasCompletedDate() const +{ + return mHasCompletedDate; +} + +bool Task::loadAttribute( TQDomElement& element ) +{ + TQString tagName = element.tagName(); + + if ( tagName == "priority" ) { + bool ok; + mKolabPriorityFromDom = element.text().toInt( &ok ); + if ( !ok || mKolabPriorityFromDom < 1 || mKolabPriorityFromDom > 5 ) { + kdWarning() << "loadAttribute(): Invalid \"priority\" value: " << element.text() << endl; + mKolabPriorityFromDom = -1; + } + } else if ( tagName == "x-kcal-priority" ) { + bool ok; + mKCalPriorityFromDom = element.text().toInt( &ok ); + if ( !ok || mKCalPriorityFromDom < 0 || mKCalPriorityFromDom > 9 ) { + kdWarning() << "loadAttribute(): Invalid \"x-kcal-priority\" value: " << element.text() << endl; + mKCalPriorityFromDom = -1; + } + } else if ( tagName == "completed" ) { + bool ok; + int percent = element.text().toInt( &ok ); + if ( !ok || percent < 0 || percent > 100 ) + percent = 0; + setPercentCompleted( percent ); + } else if ( tagName == "status" ) { + if ( element.text() == "in-progress" ) + setStatus( KCal::Incidence::StatusInProcess ); + else if ( element.text() == "completed" ) + setStatus( KCal::Incidence::StatusCompleted ); + else if ( element.text() == "waiting-on-someone-else" ) + setStatus( KCal::Incidence::StatusNeedsAction ); + else if ( element.text() == "deferred" ) + // Guessing a status here + setStatus( KCal::Incidence::StatusCanceled ); + else + // Default + setStatus( KCal::Incidence::StatusNone ); + } else if ( tagName == "due-date" ) { + setDueDate( element.text() ); + } else if ( tagName == "parent" ) { + setParent( element.text() ); + } else if ( tagName == "x-completed-date" ) { + setCompletedDate( stringToDateTime( element.text() ) ); + } else if ( tagName == "start-date" ) { + setHasStartDate( true ); + setStartDate( element.text() ); + } else + return Incidence::loadAttribute( element ); + + // We handled this + return true; +} + +bool Task::saveAttributes( TQDomElement& element ) const +{ + // Save the base class elements + Incidence::saveAttributes( element ); + + // We need to save x-kcal-priority as well, since the Kolab priority can only save values from + // 1 to 5, but we have values from 0 to 9, and do not want to loose them + writeString( element, "priority", TQString::number( kcalPriorityToKolab( priority() ) ) ); + writeString( element, "x-kcal-priority", TQString::number( priority() ) ); + + writeString( element, "completed", TQString::number( percentCompleted() ) ); + + switch( status() ) { + case KCal::Incidence::StatusInProcess: + writeString( element, "status", "in-progress" ); + break; + case KCal::Incidence::StatusCompleted: + writeString( element, "status", "completed" ); + break; + case KCal::Incidence::StatusNeedsAction: + writeString( element, "status", "waiting-on-someone-else" ); + break; + case KCal::Incidence::StatusCanceled: + writeString( element, "status", "deferred" ); + break; + case KCal::Incidence::StatusNone: + writeString( element, "status", "not-started" ); + break; + case KCal::Incidence::StatusTentative: + case KCal::Incidence::StatusConfirmed: + case KCal::Incidence::StatusDraft: + case KCal::Incidence::StatusFinal: + case KCal::Incidence::StatusX: + // All of these are saved as StatusNone. + writeString( element, "status", "not-started" ); + break; + } + + if ( hasDueDate() ) { + if ( mFloatingStatus == HasTime ) { + writeString( element, "due-date", dateTimeToString( dueDate() ) ); + } else { + writeString( element, "due-date", dateToString( dueDate().date() ) ); + } + } + + if ( !parent().isNull() ) { + writeString( element, "parent", parent() ); + } + + if ( hasCompletedDate() && percentCompleted() == 100 ) { + writeString( element, "x-completed-date", dateTimeToString( completedDate() ) ); + } + + return true; +} + + +bool Task::loadXML( const TQDomDocument& document ) +{ + mKolabPriorityFromDom = -1; + mKCalPriorityFromDom = -1; + + TQDomElement top = document.documentElement(); + + if ( top.tagName() != "task" ) { + tqWarning( "XML error: Top tag was %s instead of the expected task", + top.tagName().ascii() ); + return false; + } + setHasStartDate( false ); // todo's don't necessarily have one + + for ( TQDomNode n = top.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + if ( !loadAttribute( e ) ) + // TODO: Unhandled tag - save for later storage + kdDebug() << "Warning: Unhandled tag " << e.tagName() << endl; + } else + kdDebug() << "Node is not a comment or an element???" << endl; + } + + loadAttachments(); + decideAndSetPriority(); + return true; +} + +TQString Task::saveXML() const +{ + TQDomDocument document = domTree(); + TQDomElement element = document.createElement( "task" ); + element.setAttribute( "version", "1.0" ); + saveAttributes( element ); + if ( !hasStartDate() && startDate().isValid() ) { + // events and journals always have a start date, but tasks don't. + // Remove the entry done by the inherited save above, because we + // don't have one. + TQDomNodeList l = element.elementsByTagName( "start-date" ); + Q_ASSERT( l.count() == 1 ); + element.removeChild( l.item( 0 ) ); + } + document.appendChild( element ); + return document.toString(); +} + +void Task::setFields( const KCal::Todo* task ) +{ + Incidence::setFields( task ); + + setPriority( task->priority() ); + setPercentCompleted( task->percentComplete() ); + setStatus( task->status() ); + setHasStartDate( task->hasStartDate() ); + + if ( task->hasDueDate() ) { + setDueDate( localToUTC( task->dtDue() ) ); + if ( task->doesFloat() ) { + // This is a floating task. Don't timezone move this one + mFloatingStatus = AllDay; + setDueDate( task->dtDue().date() ); + } else { + mFloatingStatus = HasTime; + setDueDate( localToUTC( task->dtDue() ) ); + } + } else { + mHasDueDate = false; + } + + if ( task->relatedTo() ) { + setParent( task->relatedTo()->uid() ); + } else if ( !task->relatedToUid().isEmpty() ) { + setParent( task->relatedToUid( ) ); + } else { + setParent( TQString() ); + } + + if ( task->hasCompletedDate() && task->percentComplete() == 100 ) { + setCompletedDate( localToUTC( task->completed() ) ); + } else { + mHasCompletedDate = false; + } +} + +void Task::decideAndSetPriority() +{ + // If we have both Kolab and KCal values in the XML, we prefer the KCal value, but only if the + // values are still in sync + if ( mKolabPriorityFromDom != -1 && mKCalPriorityFromDom != -1 ) { + const bool inSync = ( kcalPriorityToKolab( mKCalPriorityFromDom ) == mKolabPriorityFromDom ); + if ( inSync ) { + setPriority( mKCalPriorityFromDom ); + } + else { + // Out of sync, some other client changed the Kolab priority, so we have to ignore our + // KCal priority + setPriority( kolabPrioritytoKCal( mKolabPriorityFromDom ) ); + } + } + + // Only KCal priority set, use that. + else if ( mKolabPriorityFromDom == -1 && mKCalPriorityFromDom != -1 ) { + kdWarning() << "decideAndSetPriority(): No Kolab priority found, only the KCal priority!" << endl; + setPriority( mKCalPriorityFromDom ); + } + + // Only Kolab priority set, use that + else if ( mKolabPriorityFromDom != -1 && mKCalPriorityFromDom == -1 ) { + setPriority( kolabPrioritytoKCal( mKolabPriorityFromDom ) ); + } + + // No priority set, use the default + else { + // According the RFC 2445, we should use 0 here, for undefined priority, but AFAIK KOrganizer + // doesn't support that, so we'll use 5. + setPriority( 5 ); + } +} + +void Task::saveTo( KCal::Todo* task ) +{ + Incidence::saveTo( task ); + + task->setPriority( priority() ); + task->setPercentComplete( percentCompleted() ); + task->setStatus( status() ); + task->setHasStartDate( hasStartDate() ); + task->setHasDueDate( hasDueDate() ); + if ( hasDueDate() ) + task->setDtDue( utcToLocal( dueDate() ) ); + + if ( !parent().isNull() ) + task->setRelatedToUid( parent() ); + + if ( hasCompletedDate() && task->percentComplete() == 100 ) + task->setCompleted( utcToLocal( mCompletedDate ) ); +} diff --git a/tderesources/kolab/kcal/task.h b/tderesources/kolab/kcal/task.h new file mode 100644 index 000000000..38a12a70e --- /dev/null +++ b/tderesources/kolab/kcal/task.h @@ -0,0 +1,143 @@ +/* + This file is part of the kolab resource - the implementation of the + Kolab storage format. See www.kolab.org for documentation on this. + + Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef KOLAB_TASK_H +#define KOLAB_TASK_H + +#include <incidence.h> + +#include <libkcal/incidence.h> + +class TQDomElement; + +namespace KCal { + class Todo; + class ResourceKolab; +} + +namespace Kolab { + +/** + * This class represents a task, and knows how to load/save it + * from/to XML, and from/to a KCal::Todo. + * The instances of this class are temporary, only used to convert + * one to the other. + */ +class Task : public Incidence { +public: + /// Use this to parse an xml string to a task entry + /// The caller is responsible for deleting the returned task + static KCal::Todo* xmlToTask( const TQString& xml, const TQString& tz, KCal::ResourceKolab *res = 0, + const TQString& subResource = TQString(), TQ_UINT32 sernum = 0 ); + + /// Use this to get an xml string describing this task entry + static TQString taskToXML( KCal::Todo*, const TQString& tz ); + + explicit Task( KCal::ResourceKolab *res, const TQString& subResource, TQ_UINT32 sernum, + const TQString& tz, KCal::Todo* todo = 0 ); + virtual ~Task(); + + virtual TQString type() const { return "Task"; } + + void saveTo( KCal::Todo* todo ); + + virtual void setPriority( int priority ); + virtual int priority() const; + + virtual void setPercentCompleted( int percent ); + virtual int percentCompleted() const; + + virtual void setStatus( KCal::Incidence::Status status ); + virtual KCal::Incidence::Status status() const; + + virtual void setParent( const TQString& parentUid ); + virtual TQString parent() const; + + virtual void setHasStartDate( bool ); + virtual bool hasStartDate() const; + + virtual void setDueDate( const TQDateTime &date ); + virtual void setDueDate( const TQString &date ); + virtual void setDueDate( const TQDate &date ); + virtual TQDateTime dueDate() const; + virtual bool hasDueDate() const; + + virtual void setCompletedDate( const TQDateTime& date ); + virtual TQDateTime completedDate() const; + virtual bool hasCompletedDate() const; + + // Load the attributes of this class + virtual bool loadAttribute( TQDomElement& ); + + // Save the attributes of this class + virtual bool saveAttributes( TQDomElement& ) const; + + // Load this task by reading the XML file + virtual bool loadXML( const TQDomDocument& xml ); + + // Serialize this task to an XML string + virtual TQString saveXML() const; + +protected: + // Read all known fields from this ical todo + void setFields( const KCal::Todo* ); + + // This sets the priority of this task by looking at mKolabPriorityFromDom and + // mKCalPriorityFromDom. + void decideAndSetPriority(); + + // This is the KCal priority, not the Kolab priority. + // See kcalPriorityToKolab() and kolabPrioritytoKCal(). + int mPriority; + + // Those priority values are the raw values read by loadAttribute(). + // They will be converted later in decideAndSetPriority(). + int mKolabPriorityFromDom; + int mKCalPriorityFromDom; + + int mPercentCompleted; + KCal::Incidence::Status mStatus; + TQString mParent; + + bool mHasStartDate; + + bool mHasDueDate; + TQDateTime mDueDate; + + bool mHasCompletedDate; + TQDateTime mCompletedDate; +}; + +} + +#endif // KOLAB_TASK_H |