/* Copyright (c) 2008-2009 Kevin Krammer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcalresource.h" #include "resources/kabc/kresourceassistant.h" #include #include #include #include #include #include #include #include #include #include #include #include #include typedef boost::shared_ptr IncidencePtr; using namespace Akonadi; static const char progName[] = "KCalResource"; static const char progVersion[] = "0.9"; KCalResource::KCalResource( const QString &id ) : ResourceBase( id ), mManager( new KCal::CalendarResourceManager( QLatin1String( "calendar" ) ) ), mResource( 0 ), mMimeVisitor( new IncidenceMimeTypeVisitor() ), mFullItemRetrieve( false ), mDelayedSaveTimer( new QTimer( this ) ), mIncidenceAssigner( new KCal::AssignmentVisitor() ) { KGlobal::locale()->insertCatalog( QLatin1String( "akonadi_kresourceassistant" ) ); // setup for UID generation const QString prodId = QLatin1String( "-//Akonadi//NONSGML KDE Compatibility Resource %1//EN" ); KCal::CalFormat::setApplication( QLatin1String( progName ), prodId.arg( progVersion ) ); connect( this, SIGNAL(reloadConfiguration()), SLOT(reloadConfig()) ); connect( mDelayedSaveTimer, SIGNAL(timeout()), this, SLOT(delayedSaveCalendar()) ); changeRecorder()->itemFetchScope().fetchFullPayload(); changeRecorder()->fetchCollection( true ); mDelayedSaveTimer->setSingleShot( true ); } KCalResource::~KCalResource() { delete mMimeVisitor; delete mIncidenceAssigner; } void KCalResource::configure( WId windowId ) { if ( mResource != 0 ) { emit status( Running, i18nc( "@info:status", "Changing calendar plugin configuration" ) ); KRES::ConfigDialog dlg( 0, QLatin1String( "calendar" ), mResource ); if ( windowId ) KWindowSystem::setMainWindow( &dlg, windowId ); if ( dlg.exec() ) { setName( mResource->resourceName() ); mManager->writeConfig( KGlobal::config().data() ); emit configurationDialogAccepted(); } else { emit configurationDialogRejected(); } emit status( Idle, QString() ); // TODO: need to react on name changes, but do we need to react on data changes? // as a workaround lets sync the collection tree synchronizeCollectionTree(); return; } emit status( Running, i18nc( "@info:status", "Acquiring calendar plugin configuration" ) ); KResourceAssistant kresAssistant( QLatin1String( "Calendar" ) ); KWindowSystem::setMainWindow( &kresAssistant, windowId ); connect( &kresAssistant, SIGNAL(error(QString)), this, SIGNAL(error(QString)) ); if ( kresAssistant.exec() != QDialog::Accepted ) { emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); emit configurationDialogRejected(); return; } emit configurationDialogAccepted(); mResource = dynamic_cast( kresAssistant.resource() ); Q_ASSERT( mResource != 0 ); mManager->add( mResource ); mManager->writeConfig( KGlobal::config().data() ); if ( !openConfiguration() ) { const QString message = i18nc( "@info:status", "Initialization based on newly created configuration failed." ); emit error( message ); emit status( NotConfigured, message ); return; } emit status( Running, i18nc( "@info:status", "Loading calendar" ) ); // signals emitted by initialLoadingFinished() or loadingError() if ( !mResource->load() ) { kError() << "Resource::load() failed"; } } void KCalResource::retrieveCollections() { kDebug(); if ( mResource == 0 ) { kError() << "No KCal resource"; const QString message = i18nc( "@info:status", "No KDE calendar plugin configured yet" ); emit error( message ); emit status( NotConfigured, message ); return; } Collection topLevelCollection; topLevelCollection.setParentCollection( Collection::root() ); topLevelCollection.setRemoteId( mResource->identifier() ); topLevelCollection.setName( mResource->resourceName() ); EntityDisplayAttribute* attr = topLevelCollection.attribute( Collection::AddIfMissing ); attr->setDisplayName( mResource->resourceName() ); attr->setIconName( QLatin1String( "office-calendar" ) ); QStringList mimeTypes; mimeTypes << QLatin1String( "text/calendar" ); mimeTypes += mMimeVisitor->allMimeTypes(); Collection::Rights readOnlyRights; Collection::Rights readWriteRights; readWriteRights |= Collection::CanCreateItem; readWriteRights |= Collection::CanChangeItem; readWriteRights |= Collection::CanDeleteItem; if ( mResource->canHaveSubresources() ) { mimeTypes << Collection::mimeType(); readWriteRights |= Collection::CanCreateCollection; readWriteRights |= Collection::CanChangeCollection; readWriteRights |= Collection::CanDeleteCollection; } topLevelCollection.setContentMimeTypes( mimeTypes ); topLevelCollection.setRights( mResource->readOnly() ? readOnlyRights : readWriteRights ); Collection::List list; list << topLevelCollection; const QStringList subResources = mResource->subresources(); kDebug() << "subResources" << subResources; foreach ( const QString &subResource, subResources ) { // TODO check with KOrganizer how it handles subresources Collection childCollection; childCollection.setParentCollection( topLevelCollection ); childCollection.setRemoteId( subResource ); childCollection.setName( mResource->labelForSubresource( subResource ) ); attr = childCollection.attribute( Collection::AddIfMissing ); attr->setDisplayName( childCollection.name() ); const QString type = mResource->subresourceType( subResource ); if ( !type.isEmpty() ) { QStringList childMimeTypes( Collection::mimeType() ); childMimeTypes << QLatin1String( "application/x-vnd.akonadi.calendar." ) + type; childCollection.setContentMimeTypes( childMimeTypes ); if ( type == QLatin1String( "journal" ) ) attr->setIconName( QLatin1String( "view-pim-journal" ) ); else if ( type == QLatin1String( "todo" ) ) attr->setIconName( QLatin1String( "view-pim-tasks" ) ); else attr->setIconName( QLatin1String( "office-calendar" ) ); } else { attr->setIconName( QLatin1String( "office-calendar" ) ); childCollection.setContentMimeTypes( mimeTypes ); } // TODO we have no API for adding incidences to specific sub resources, so we should // not tell Akonadi adding that items is allowed childCollection.setRights( mResource->readOnly() ? readOnlyRights : readWriteRights ); list << childCollection; } collectionsRetrieved( list ); } void KCalResource::retrieveItems( const Akonadi::Collection &collection ) { kDebug(); if ( mResource == 0 ) { kError() << "No KCal resource"; const QString message = i18nc( "@info:status", "No KDE calendar plugin configured yet" ); emit error( message ); emit status( NotConfigured, message ); return; } Item::List items; const KCal::Incidence::List incidenceList = mResource->rawIncidences(); foreach ( KCal::Incidence *incidence, incidenceList ) { const QString subResource = mResource->subresourceIdentifier( incidence ); if ( subResource == collection.remoteId() || ( subResource.isEmpty() && collection.parentCollection() == Collection::root() ) ) { Item item( mMimeVisitor->mimeType( incidence ) ); item.setRemoteId( incidence->uid() ); if ( mFullItemRetrieve ) item.setPayload( IncidencePtr( incidence->clone() ) ); items << item; } } itemsRetrieved( items ); } bool KCalResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) { kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() << "mimeType=" << item.mimeType() << "parts=" << parts; if ( mResource == 0 ) { kError() << "No KCal resource"; const QString message = i18nc( "@info:status", "No KDE calendar plugin configured yet" ); emit error( message ); emit status( NotConfigured, message ); return false; } const QString rid = item.remoteId(); KCal::Incidence *incidence = mResource->incidence( rid ); if ( incidence == 0 ) { kError() << "No incidence with uid" << rid; emit error( i18nc( "@info:status", "Request for data of a specific calendar entry failed " "because there is no such entry" ) ); return false; } Item i( item ); i.setMimeType( mMimeVisitor->mimeType( incidence ) ); i.setPayload( IncidencePtr( incidence->clone() ) ); itemRetrieved( i ); return true; } void KCalResource::aboutToQuit() { mManager->writeConfig( KGlobal::config().data() ); if ( mResource ) mResource->save(); } void KCalResource::doSetOnline( bool online ) { kDebug() << "online=" << online << ", isOnline=" << isOnline(); if ( online ) reloadConfig(); else closeConfiguration(); ResourceBase::doSetOnline( online ); } void KCalResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& collection ) { kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() << "mimeType=" << item.mimeType(); if ( mResource == 0 ) { kError() << "Resource not fully operational yet"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } Q_UNUSED( collection ); if ( item.hasPayload() ) { IncidencePtr incidencePtr = item.payload(); if ( incidencePtr->uid().isEmpty() ) { const QString uid = KCal::CalFormat::createUniqueId(); incidencePtr->setUid( uid ); } KCal::Incidence *incidence = incidencePtr->clone(); if ( mResource->addIncidence( incidence ) ) { Item newItem( item ); newItem.setRemoteId( incidencePtr->uid() ); newItem.setPayload( incidencePtr ); if ( !mResource->save( incidence ) ) { kError() << "Failed to save incidence" << incidence->uid(); // resource error emitted by savingError() } changeCommitted( newItem ); return; } kError() << "Failed to add incidence to resource"; } changeProcessed(); } void KCalResource::itemChanged( const Akonadi::Item &item, const QSet& parts ) { kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() << "mimeType=" << item.mimeType() << "parts=" << parts; if ( mResource == 0 ) { kError() << "Resource not fully operational yet"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } if ( item.hasPayload() ) { IncidencePtr incidencePtr = item.payload(); KCal::Incidence *incidence = mResource->incidence( incidencePtr->uid() ); if ( incidence == 0 ) { incidence = incidencePtr->clone(); if ( mResource->addIncidence( incidence ) ) { if ( !mResource->save( incidence ) ) { kError() << "Failed to save incidence" << incidence->uid(); // resource error emitted by savingError() } changeCommitted( item ); return; } kError() << "Failed to add incidence to resource"; } else { // make sure any observer the resource might have installed gets properly notified incidence->startUpdates(); bool assignResult = mIncidenceAssigner->assign( incidence, incidencePtr.get() ); if ( assignResult ) incidence->updated(); incidence->endUpdates(); if ( !assignResult ) { kWarning() << "Item changed incidence type. Replacing it."; mResource->deleteIncidence( incidence ); mResource->addIncidence( incidencePtr->clone() ); scheduleSaveCalendar(); } else { if ( !mResource->save( incidence ) ) { kError() << "Failed to save incidence" << incidence->uid(); // resource error emitted by savingError() } } changeCommitted( item ); return; } } changeProcessed(); } void KCalResource::itemRemoved( const Akonadi::Item &item ) { kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId(); if ( mResource == 0 ) { kError() << "Resource not fully operational yet"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } KCal::Incidence *incidence = mResource->incidence( item.remoteId() ); if ( incidence != 0 && mResource->deleteIncidence( incidence ) ) { if ( !mResource->save( incidence ) ) { kError() << "Failed to save incidence" << incidence->uid(); // resource error emitted by savingError() } changeCommitted( item ); return; } changeProcessed(); } void KCalResource::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) { kDebug() << "collection id=" << collection.id() << ", remoteId=" << collection.remoteId() << ", name=" << collection.name() << ", parent id=" << parent.id() << ", remoteId=" << parent.remoteId(); if ( mResource == 0 ) { kError() << "Resource not fully operational yet"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } if ( mResource->addSubresource( collection.name(), parent.remoteId() ) ) { // result delivered by signalSubresourceAdded(). how do we best relate this? changeCommitted( collection ); return; } changeProcessed(); } void KCalResource::collectionChanged( const Akonadi::Collection &collection ) { kDebug() << "collection id=" << collection.id() << ", remoteId=" << collection.remoteId(); if ( mResource == 0 ) { kError() << "Resource not fully operational yet"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } // currently only changing the top level collection's name supported if ( collection.parentCollection() == Collection::root() ) { const QString newName = collection.displayName(); if ( newName != mResource->resourceName() ) { mResource->setResourceName( newName ); setName( newName ); changeCommitted( collection ); // TODO save config return; } } changeProcessed(); } void KCalResource::collectionRemoved( const Akonadi::Collection &collection ) { kDebug() << "collection id=" << collection.id() << ", remoteId=" << collection.remoteId(); if ( mResource == 0 ) { kError() << "Resource not fully operational yet"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } // currently only removing sub resource supported const QStringList subResources = mResource->subresources(); if ( subResources.contains( collection.remoteId() ) ) { if ( mResource->removeSubresource( collection.remoteId() ) ) { changeCommitted( collection ); scheduleSaveCalendar(); return; } } changeProcessed(); } bool KCalResource::openConfiguration() { if ( mResource != 0 ) { if ( !mResource->isOpen() ) { if ( !mResource->open() ) { kError() << "Opening resource" << mResource->identifier() << "failed"; return false; } } connect( mResource, SIGNAL(resourceLoaded(ResourceCalendar*)), this, SLOT(initialLoadingFinished(ResourceCalendar*)) ); connect( mResource, SIGNAL(resourceLoadError(ResourceCalendar*,QString)), this, SLOT(loadingError(ResourceCalendar*,QString)) ); setName( mResource->resourceName() ); } return true; } void KCalResource::closeConfiguration() { mDelayedSaveTimer->stop(); if ( mResource != 0 ) { if ( mResource->isOpen() ) mResource->close(); disconnect( mResource, SIGNAL(resourceLoadError(ResourceCalendar*,QString)), this, SLOT(loadingError(ResourceCalendar*,QString)) ); disconnect( mResource, SIGNAL(resourceSaveError(ResourceCalendar*,QString)), this, SLOT(savingError(ResourceCalendar*,QString)) ); disconnect( mResource, SIGNAL(resourceChanged(ResourceCalendar*)), this, SLOT(resourceChanged(ResourceCalendar*)) ); } } bool KCalResource::saveCalendar() { kDebug(); mDelayedSaveTimer->stop(); if ( !mResource || mResource->readOnly() ) return false; if ( !mResource->save() ) { kError() << "Saving failed"; // resource error emitted by savingError() return false; } kDebug() << "Saving succeeded"; return true; } bool KCalResource::scheduleSaveCalendar() { if ( !mResource || mResource->readOnly() ) return false; if ( !mDelayedSaveTimer->isActive() ) mDelayedSaveTimer->start( 5000 ); return true; } void KCalResource::reloadConfig() { kDebug() << "resource=" << (void*) mResource; if ( mResource != 0 ) { if ( !mResource->save() ) { kError() << "Saving of calendar failed"; } } closeConfiguration(); KGlobal::config()->reparseConfiguration(); if ( KGlobal::config()->groupList().isEmpty() ) { emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } Q_ASSERT( KGlobal::config().data() != 0); mManager->readConfig( KGlobal::config().data() ); mResource = mManager->standardResource(); if ( mResource != 0 ) { if ( mResource->type().toLower() == QLatin1String( "akonadi" ) ) { kError() << "Resource config points to an Akonadi bridge resource"; emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } } if ( mResource == 0 ) { emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); return; } if ( !isOnline() ) return; if ( !openConfiguration() ) { kError() << "openConfiguration() failed"; const QString message = i18nc( "@info:status", "Initialization based on stored configuration failed." ); emit error( message ); emit status( NotConfigured, message ); return; } emit status( Running, i18nc( "@info:status", "Loading calendar" ) ); // signals emitted by initialLoadingFinished() or loadingError() if ( !mResource->load() ) { kError() << "Resource::load() failed"; } } void KCalResource::initialLoadingFinished( ResourceCalendar *resource ) { Q_UNUSED( resource ); Q_ASSERT( resource == mResource ); kDebug(); disconnect( mResource, SIGNAL(resourceLoaded(ResourceCalendar*)), this, SLOT(initialLoadingFinished(ResourceCalendar*)) ); connect( mResource, SIGNAL(resourceChanged(ResourceCalendar*)), this, SLOT(resourceChanged(ResourceCalendar*)) ); emit status( Idle, QString() ); mFullItemRetrieve = false; synchronize(); } void KCalResource::resourceChanged( ResourceCalendar *resource ) { Q_UNUSED( resource ); Q_ASSERT( resource == mResource ); kDebug(); if ( mDelayedSaveTimer->isActive() ) { // TODO should record changes for delayed saving kError() << "Delayed saving scheduled when resource changed. We might have lost changes"; mDelayedSaveTimer->stop(); } mFullItemRetrieve = true; synchronize(); } void KCalResource::loadingError( ResourceCalendar *resource, const QString &message ) { Q_UNUSED( resource ); Q_ASSERT( resource == mResource ); kError() << "Loading error: " << message; const QString statusMessage = i18nc( "@info:status", "Loading of calendar failed: %1", message ); emit error( statusMessage ); emit status( Broken, statusMessage ); } void KCalResource::savingError( ResourceCalendar *resource, const QString &message ) { Q_UNUSED( resource ); Q_ASSERT( resource == mResource ); kError() << "Saving error: " << message; const QString statusMessage = i18nc( "@info:status", "Saving of calendar failed: %1", message ); emit error( statusMessage ); emit status( Broken, statusMessage ); } void KCalResource::delayedSaveCalendar() { if ( !saveCalendar() ) { kError() << "Saving failed, rescheduling delayed save"; if ( !scheduleSaveCalendar() ) { kError() << "Scheduling failed as well, giving up"; } } } AKONADI_RESOURCE_MAIN( KCalResource ) // kate: space-indent on; indent-width 2; replace-tabs on;