kde-playground/kdepim-runtime/resources/kcal/kcalresource.cpp
2015-04-14 22:08:21 +00:00

695 lines
21 KiB
C++

/*
Copyright (c) 2008-2009 Kevin Krammer <kevin.krammer@gmx.at>
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 <kcal/assignmentvisitor.h>
#include <kcal/calformat.h>
#include <kcal/resourcecalendar.h>
#include <kresources/configdialog.h>
#include <akonadi/kcal/incidencemimetypevisitor.h>
#include <akonadi/changerecorder.h>
#include <akonadi/entitydisplayattribute.h>
#include <akonadi/itemfetchscope.h>
#include <kconfig.h>
#include <kinputdialog.h>
#include <kwindowsystem.h>
#include <QTimer>
#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<KCal::Incidence> 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<KCal::ResourceCalendar*>( 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<EntityDisplayAttribute>( 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<EntityDisplayAttribute>( 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>( IncidencePtr( incidence->clone() ) );
items << item;
}
}
itemsRetrieved( items );
}
bool KCalResource::retrieveItem( const Akonadi::Item &item, const QSet<QByteArray> &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>( 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 incidencePtr = item.payload<IncidencePtr>();
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>( 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<QByteArray>& 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 incidencePtr = item.payload<IncidencePtr>();
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;