/* 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 2004 Till Adam 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 subResources; if ( !kmailSubresources( subResources, contentType ) ) return false; map.clear(); TQValueList::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 tdeio_uiserver is running UIServer_stub uiserver( "tdeio_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 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::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( 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(incidencebase), mCalendar.timeZoneId() ); } else { mimetype = incidenceInlineMimeType; data = mFormat.createScheduleMessage( static_cast(incidencebase), Scheduler::Request ); } } else if ( type == "Todo" ) { if( isXMLStorageFormat ) { mimetype = todoAttachmentMimeType; data = Kolab::Task::taskToXML( static_cast(incidencebase), mCalendar.timeZoneId() ); } else { mimetype = incidenceInlineMimeType; data = mFormat.createScheduleMessage( static_cast(incidencebase), Scheduler::Request ); } } else if ( type == "Journal" ) { if( isXMLStorageFormat ) { mimetype = journalAttachmentMimeType; data = Kolab::Journal::journalToXML( static_cast(incidencebase ), mCalendar.timeZoneId() ); } else { mimetype = incidenceInlineMimeType; data = mFormat.createScheduleMessage( static_cast(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( incidencebase ); KCal::Attachment::List atts = incidence->attachments(); TQStringList attURLs, attMimeTypes, attNames; TQValueList 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::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 = ""; 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 += "
"; if ( !incidence->summary().isEmpty() ) text += i18n( "Summary: %1" ).arg( incidence->summary() ) + "
"; if ( !incidence->location().isEmpty() ) text += i18n( "Location: %1" ).arg( incidence->location() ); text += "
"; if ( !incidence->doesFloat() ) text += i18n( "Start: %1, %2" ) .arg( incidence->dtStartDateStr(), incidence->dtStartTimeStr() ); else text += i18n( "Start: %1" ).arg( incidence->dtStartDateStr() ); text += "
"; if ( incidence->type() == "Event" ) { Event* event = static_cast( incidence ); if ( event->hasEndDate() ) { if ( !event->doesFloat() ) { text += i18n( "End: %1, %2" ) .arg( event->dtEndDateStr(), event->dtEndTimeStr() ); } else { text += i18n( "End: %1" ).arg( event->dtEndDateStr() ); } } text += "
"; } // 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 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( 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& map, const TQString& type, const TQString& folder ) { TemporarySilencer t( this ); for( TQMap::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( 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( 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 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 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"