diff --git a/outboxinterface/CMakeLists.txt b/outboxinterface/CMakeLists.txt index c431e58ac..dbcd88c04 100644 --- a/outboxinterface/CMakeLists.txt +++ b/outboxinterface/CMakeLists.txt @@ -1,45 +1,50 @@ #include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES} ${KDEPIMLIBS_INCLUDE_DIRS}) # TODO: 5324 is mailtransport. we need one of our own! add_definitions( -DKDE_DEFAULT_DEBUG_AREA=5324 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) add_subdirectory( tests ) set(outboxinterface_lib_srcs + dispatcherinterface.cpp localfolders.cpp messagequeuejob.cpp addressattribute.cpp dispatchmodeattribute.cpp errorattribute.cpp sentcollectionattribute.cpp transportattribute.cpp resourcetester/resourcesynchronizationjob.cpp ) +qt4_add_dbus_interface( outboxinterface_lib_srcs interfaces/org.kde.Akonadi.MailDispatcher.xml mdainterface ) +install( FILES interfaces/org.kde.Akonadi.MailDispatcher.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) + kde4_add_kcfg_files(outboxinterface_lib_srcs settings.kcfgc) install(FILES outboxinterface.kcfg DESTINATION ${KCFG_INSTALL_DIR}) kde4_add_library(outboxinterface SHARED ${outboxinterface_lib_srcs}) target_link_libraries(outboxinterface ${KDE4_KIO_LIBS} akonadi-kde kmime mailtransport ) set_target_properties(outboxinterface PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install(TARGETS outboxinterface ${INSTALL_TARGETS_DEFAULT_ARGS}) install( FILES outboxinterface_export.h + dispatcherinterface.h localfolders.h messagequeuejob.h addressattribute.h dispatchmodeattribute.h errorattribute.h sentcollectionattribute.h transportattribute.h DESTINATION ${INCLUDE_INSTALL_DIR}/outboxinterface COMPONENT Devel) diff --git a/outboxinterface/dispatcherinterface.cpp b/outboxinterface/dispatcherinterface.cpp new file mode 100644 index 000000000..aa1451c0b --- /dev/null +++ b/outboxinterface/dispatcherinterface.cpp @@ -0,0 +1,230 @@ +/* + Copyright (c) 2009 Constantin Berzan + + 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. +*/ + +#include "dispatcherinterface.h" + +#include "localfolders.h" +#include "addressattribute.h" +#include "dispatchmodeattribute.h" +#include "sentcollectionattribute.h" +#include "transportattribute.h" + +#include "mdainterface.h" + +#include +#include +#include + +#include + +using namespace Akonadi; +using namespace OutboxInterface; + + +/** + * Private class that helps to provide binary compatibility between releases. + * @internal + */ +class OutboxInterface::DispatcherInterfacePrivate +{ + public: + DispatcherInterfacePrivate(); + ~DispatcherInterfacePrivate(); + + DispatcherInterface *instance; + + bool connected; + org::kde::Akonadi::MailDispatcher *iface; + AgentInstance agent; + + // slots + void connectToAgent(); + void dbusServiceOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner ); + void agentInstanceRemoved( const AgentInstance &a ); + void agentInstanceChanged( const AgentInstance &a ); + +}; + +K_GLOBAL_STATIC( DispatcherInterfacePrivate, sInstance ) + +DispatcherInterfacePrivate::DispatcherInterfacePrivate() + : instance( new DispatcherInterface( this ) ) + , iface( 0 ) +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QObject::connect( bus.interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), + instance, SLOT(dbusServiceOwnerChanged(QString,QString,QString)) ); + // AgentInstance objects are not updated automatically, so we need to watch + // for AgentManager's signals: + QObject::connect( AgentManager::self(), SIGNAL(instanceOnline(Akonadi::AgentInstance,bool)), + instance, SLOT(agentInstanceChanged(Akonadi::AgentInstance)) ); + QObject::connect( AgentManager::self(), SIGNAL(instanceProgressChanged(Akonadi::AgentInstance)), + instance, SLOT(agentInstanceChanged(Akonadi::AgentInstance)) ); + QObject::connect( AgentManager::self(), SIGNAL(instanceStatusChanged(Akonadi::AgentInstance)), + instance, SLOT(agentInstanceChanged(Akonadi::AgentInstance)) ); + QObject::connect( AgentManager::self(), SIGNAL(instanceRemoved(Akonadi::AgentInstance)), + instance, SLOT(agentInstanceRemoved(Akonadi::AgentInstance)) ); + connected = false; + connectToAgent(); +} + +DispatcherInterfacePrivate::~DispatcherInterfacePrivate() +{ + delete instance; +} + +void DispatcherInterfacePrivate::connectToAgent() +{ + if( connected ) { + kDebug() << "Already connected to MDA."; + return; + } + + delete iface; + iface = new org::kde::Akonadi::MailDispatcher( QLatin1String( "org.freedesktop.Akonadi.Agent.akonadi_maildispatcher_agent" ), + QLatin1String( "/" ), QDBusConnection::sessionBus(), instance ); + if( !iface->isValid() ) { + kDebug() << "Couldn't get D-Bus interface of MDA. Retrying in 1s."; + QTimer::singleShot( 1000, instance, SLOT(connectToAgent()) ); + return; + } + + agent = AgentManager::self()->instance( QLatin1String( "akonadi_maildispatcher_agent" ) ); + if( !agent.isValid() ) { + kDebug() << "Could not get agent instance of MDA. Retrying in 1s."; + QTimer::singleShot( 1000, instance, SLOT(connectToAgent()) ); + return; + } + + kDebug() << "Connected to the MDA."; + connected = true; +} + +void DispatcherInterfacePrivate::dbusServiceOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner ) +{ + Q_UNUSED( oldOwner ); + if( name == QLatin1String( "org.freedesktop.Akonadi.Agent.akonad_maildispatcher_agent" ) ) { + if( newOwner.isEmpty() ) { + kDebug() << "MDA disappeared from D-Bus."; + connected = false; + QTimer::singleShot( 0, instance, SLOT(connectToAgent()) ); + } + } +} + +void DispatcherInterfacePrivate::agentInstanceRemoved( const AgentInstance &a ) +{ + if( agent == a ) { + kDebug() << "MDA agent disappeared."; + connected = false; + QTimer::singleShot( 0, instance, SLOT(connectToAgent()) ); + } +} + +void DispatcherInterfacePrivate::agentInstanceChanged( const AgentInstance &a ) +{ + if( agent == a ) { + kDebug() << "Updating instance."; + agent = a; + // This is not as weird as it looks :) operator== checks the id only, but + // operator= copies everything (like status, progress etc.) + } +} + + + +DispatcherInterface::DispatcherInterface( DispatcherInterfacePrivate *dd ) + : QObject() + , d( dd ) +{ +} + +DispatcherInterface *DispatcherInterface::self() +{ + return sInstance->instance; +} + +bool DispatcherInterface::isReady() const +{ + return d->connected; +} + +bool DispatcherInterface::dispatcherOnline() const +{ + if( !d->connected ) { + kWarning() << "Not connected to the MDA."; + return false; + } + + return d->agent.isOnline(); +} + +AgentInstance::Status DispatcherInterface::dispatcherStatus() const +{ + if( !d->connected ) { + kWarning() << "Not connected to the MDA."; + return AgentInstance::Broken; + } + + return d->agent.status(); +} + +int DispatcherInterface::dispatcherProgress() const +{ + if( !d->connected ) { + kWarning() << "Not connected to the MDA."; + return -1; + } + + return d->agent.progress(); +} + +void DispatcherInterface::abortDispatching() +{ + if( !d->connected ) { + kWarning() << "Not connected to the MDA."; + return; + } + + d->iface->abort(); +} + +void DispatcherInterface::dispatchManually() +{ + if( !d->connected ) { + kWarning() << "Not connected to the MDA."; + return; + } + + kDebug() << "implement me"; //TODO +} + +void DispatcherInterface::retryDispatching() +{ + if( !d->connected ) { + kWarning() << "Not connected to the MDA."; + return; + } + + kDebug() << "implement me"; //TODO +} + + + +#include "dispatcherinterface.moc" diff --git a/outboxinterface/dispatcherinterface.h b/outboxinterface/dispatcherinterface.h new file mode 100644 index 000000000..93bf49753 --- /dev/null +++ b/outboxinterface/dispatcherinterface.h @@ -0,0 +1,97 @@ +/* + Copyright (c) 2009 Constantin Berzan + + 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. +*/ + +#ifndef OUTBOXINTERFACE_DISPATCHERINTERFACE_H +#define OUTBOXINTERFACE_DISPATCHERINTERFACE_H + +#include + +#include + +#include + +namespace OutboxInterface { + +class DispatcherInterfacePrivate; + +/** + An interface for apps to interact with the MDA. + It connects to the MDA via D-Bus and provides info about queued messages + and progress, and methods such as abort or retry sending. + + TODO should this class provide a progress widget? + + TODO dispatchManually and retryDispatching functions should be offered on a + per-item basis as well, I imagine when the user will have a messageView of + the outbox. Then do we need global ones here (i.e. for all items in the + outbox)? +*/ +class OUTBOXINTERFACE_EXPORT DispatcherInterface : public QObject +{ + Q_OBJECT + + public: + /** + Returns the DispatcherInterface instance. + */ + static DispatcherInterface *self(); + + bool isReady() const; + bool dispatcherOnline() const; + Akonadi::AgentInstance::Status dispatcherStatus() const; + int dispatcherProgress() const; + + /** + Aborts sending the current message, and marks all messages in the queue + as DispatchMode::Never. + */ + void abortDispatching(); + + /** + Looks for messages in the outbox with DispatchMode::Never and marks them + DispatchMode::Immediately for sending. + */ + void dispatchManually(); + + /** + Looks for messages in the outbox with ErrorAttribute, and clears them and + queues them again for sending. + */ + void retryDispatching(); + + + private: + friend class DispatcherInterfacePrivate; + DispatcherInterfacePrivate *const d; + + // singleton class; the only instance resides in sInstance->instance + DispatcherInterface( DispatcherInterfacePrivate *dd ); + + Q_PRIVATE_SLOT( d, void connectToAgent() ) + Q_PRIVATE_SLOT( d, void dbusServiceOwnerChanged( const QString&, const QString&, const QString& ) ) + Q_PRIVATE_SLOT( d, void agentInstanceRemoved( const Akonadi::AgentInstance& ) ) + Q_PRIVATE_SLOT( d, void agentInstanceChanged( const Akonadi::AgentInstance& ) ) + +}; + + +} + + +#endif diff --git a/outboxinterface/interfaces/org.kde.Akonadi.MailDispatcher.xml b/outboxinterface/interfaces/org.kde.Akonadi.MailDispatcher.xml new file mode 100644 index 000000000..7c91ccc86 --- /dev/null +++ b/outboxinterface/interfaces/org.kde.Akonadi.MailDispatcher.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/outboxinterface/localfolders.cpp b/outboxinterface/localfolders.cpp index 37d640107..468181969 100644 --- a/outboxinterface/localfolders.cpp +++ b/outboxinterface/localfolders.cpp @@ -1,445 +1,445 @@ /* Copyright (c) 2009 Constantin Berzan 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. */ #include "localfolders.h" #include "settings.h" #include "addressattribute.h" #include "dispatchmodeattribute.h" #include "errorattribute.h" #include "sentcollectionattribute.h" #include "transportattribute.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // svn:external from playground/pim/akonaditest #define DBUS_SERVICE_NAME QLatin1String( "org.kde.pim.LocalFolders" ) using namespace Akonadi; using namespace OutboxInterface; /** * Private class that helps to provide binary compatibility between releases. * @internal */ class OutboxInterface::LocalFoldersPrivate { public: LocalFoldersPrivate(); ~LocalFoldersPrivate(); LocalFolders *instance; bool ready; bool preparing; bool scheduled; bool isMainInstance; Collection outbox; Collection sentMail; Collection rootMaildir; KJob *outboxJob; KJob *sentMailJob; Monitor *monitor; /** If this is the main instance, attempts to create the resource and collections if necessary, then fetches them. If this is not the main instance, waits for them to be created by the main instance, and then fetches them. Will emit foldersReady() when done */ void prepare(); /** Schedules a prepare() in 1 second. Called when this is not the main instance and we need to wait, or when something disappeared and needs to be recreated. */ void schedulePrepare(); // slot /** Creates the maildir resource, if it is not found. */ void createResourceIfNeeded(); /** Creates the outbox and sent-mail collections, if they are not present. */ void createCollectionsIfNeeded(); /** Creates a Monitor to watch the resource and connects to its signals. This is used to watch for evil users deleting the resource / outbox / etc. */ void connectMonitor(); /** Fetches the collections of the maildir resource. There is one root collection, which contains the outbox and sent-mail collections. */ void fetchCollections(); void resourceCreateResult( KJob *job ); void resourceSyncResult( KJob *job ); void collectionCreateResult( KJob *job ); void collectionFetchResult( KJob *job ); }; K_GLOBAL_STATIC( LocalFoldersPrivate, sInstance ) LocalFolders::LocalFolders( LocalFoldersPrivate *dd ) : QObject() , d( dd ) { // register attributes AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); } LocalFolders *LocalFolders::self() { return sInstance->instance; } void LocalFolders::fetch() { d->prepare(); } bool LocalFolders::isReady() const { return d->ready; } Collection LocalFolders::outbox() const { Q_ASSERT( d->ready ); return d->outbox; } Collection LocalFolders::sentMail() const { Q_ASSERT( d->ready ); return d->sentMail; } LocalFoldersPrivate::LocalFoldersPrivate() : instance( new LocalFolders(this) ) { isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); ready = false; // prepare() expects these preparing = false; outboxJob = 0; sentMailJob = 0; monitor = 0; prepare(); } LocalFoldersPrivate::~LocalFoldersPrivate() { delete instance; } void LocalFoldersPrivate::prepare() { if( ready ) { - kDebug() << "Already ready. Emitting foldersReady()."; + //kDebug() << "Already ready. Emitting foldersReady()."; emit instance->foldersReady(); return; } if( preparing ) { kDebug() << "Already preparing."; return; } kDebug() << "Preparing. isMainInstance" << isMainInstance; preparing = true; scheduled = false; Q_ASSERT( outboxJob == 0 ); Q_ASSERT( sentMailJob == 0 ); Q_ASSERT( monitor == 0); rootMaildir = Collection( -1 ); outbox = Collection( -1 ); sentMail = Collection( -1 ); createResourceIfNeeded(); } void LocalFoldersPrivate::schedulePrepare() { if( scheduled ) { kDebug() << "Prepare already scheduled."; return; } kDebug() << "Scheduling prepare."; if( monitor ) { monitor->disconnect( instance ); monitor->deleteLater(); monitor = 0; } ready = false; preparing = false; scheduled = true; QTimer::singleShot( 1000, instance, SLOT( prepare() ) ); } void LocalFoldersPrivate::createResourceIfNeeded() { Q_ASSERT( preparing ); // Another instance might have created the resource and updated the config. Settings::self()->readConfig(); kDebug() << "Resource from config:" << Settings::resourceId(); // check that the maildir resource exists AgentInstance resource = AgentManager::self()->instance( Settings::resourceId() ); if( !resource.isValid() ) { // Try to grab main instance status (if previous main instance quit). if( !isMainInstance ) { isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); if( isMainInstance ) { kDebug() << "I have become the main instance."; } } // Create resource if main instance. if( !isMainInstance ) { kDebug() << "Waiting for the main instance to create the resource."; schedulePrepare(); } else { kDebug() << "Creating maildir resource."; AgentType type = AgentManager::self()->type( "akonadi_maildir_resource" ); AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type ); QObject::connect( job, SIGNAL( result( KJob * ) ), instance, SLOT( resourceCreateResult( KJob * ) ) ); // this is not an Akonadi::Job, so we must start it ourselves job->start(); } } else { connectMonitor(); } } void LocalFoldersPrivate::createCollectionsIfNeeded() { Q_ASSERT( preparing ); // but I may not be the main instance Q_ASSERT( rootMaildir.isValid() ); if( !outbox.isValid() ) { kDebug() << "Creating outbox collection."; Collection col; col.setParent( rootMaildir ); col.setName( "outbox" ); col.setContentMimeTypes( QStringList( "message/rfc822" ) ); Q_ASSERT( outboxJob == 0 ); outboxJob = new CollectionCreateJob( col ); QObject::connect( outboxJob, SIGNAL( result( KJob * ) ), instance, SLOT( collectionCreateResult( KJob * ) ) ); } if( !sentMail.isValid() ) { kDebug() << "Creating sent-mail collection."; Collection col; col.setParent( rootMaildir ); col.setName( "sent-mail" ); col.setContentMimeTypes( QStringList( "message/rfc822" ) ); Q_ASSERT( sentMailJob == 0 ); sentMailJob = new CollectionCreateJob( col ); QObject::connect( sentMailJob, SIGNAL( result( KJob * ) ), instance, SLOT( collectionCreateResult( KJob * ) ) ); } if( outboxJob == 0 && sentMailJob == 0 ) { // Everything is ready (created and fetched). kDebug() << "Local folders ready. resourceId" << Settings::resourceId() << "outbox id" << outbox.id() << "sentMail id" << sentMail.id(); Q_ASSERT( !ready ); ready = true; preparing = false; Settings::self()->writeConfig(); emit instance->foldersReady(); } } void LocalFoldersPrivate::connectMonitor() { Q_ASSERT( preparing ); // but I may not be the main instance Q_ASSERT( monitor == 0 ); monitor = new Monitor( instance ); monitor->setResourceMonitored( Settings::resourceId().toAscii() ); QObject::connect( monitor, SIGNAL( collectionRemoved( Akonadi::Collection ) ), instance, SLOT( schedulePrepare() ) ); kDebug() << "Connected monitor."; fetchCollections(); } void LocalFoldersPrivate::fetchCollections() { Q_ASSERT( preparing ); // but I may not be the main instance kDebug() << "Fetching collections in maildir resource."; CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); job->setResource( Settings::resourceId() ); // limit search QObject::connect( job, SIGNAL( result( KJob * ) ), instance, SLOT( collectionFetchResult( KJob * ) ) ); } void LocalFoldersPrivate::resourceCreateResult( KJob *job ) { Q_ASSERT( isMainInstance ); Q_ASSERT( preparing ); if( job->error() ) { kFatal() << "AgentInstanceCreateJob failed to make a maildir resource for us."; } AgentInstanceCreateJob *createJob = static_cast( job ); AgentInstance agent = createJob->instance(); Settings::setResourceId( agent.identifier() ); kDebug() << "Created maildir resource with id" << Settings::resourceId(); // configure the resource agent.setName( i18n( "Local Mail Folders" ) ); QDBusInterface conf( "org.freedesktop.Akonadi.Resource." + Settings::resourceId(), "/Settings", "org.kde.Akonadi.Maildir.Settings" ); QDBusReply reply = conf.call( "setPath", KGlobal::dirs()->localxdgdatadir() + "mail" ); if( !reply.isValid() ) { kFatal() << "Failed to set the root maildir."; } agent.reconfigure(); // sync the resource ResourceSynchronizationJob *sjob = new ResourceSynchronizationJob( agent ); QObject::connect( sjob, SIGNAL( result( KJob* ) ), instance, SLOT( resourceSyncResult( KJob* ) ) ); sjob->start(); // non-Akonadi } void LocalFoldersPrivate::resourceSyncResult( KJob *job ) { Q_ASSERT( isMainInstance ); Q_ASSERT( preparing ); if( job->error() ) { kFatal() << "ResourceSynchronizationJob failed."; } connectMonitor(); } void LocalFoldersPrivate::collectionCreateResult( KJob *job ) { Q_ASSERT( isMainInstance ); if( job->error() ) { kFatal() << "CollectionCreateJob failed to make a collection for us."; } CollectionCreateJob *createJob = static_cast( job ); if( job == outboxJob ) { outboxJob = 0; outbox = createJob->collection(); kDebug() << "Created outbox collection with id" << outbox.id(); } else if( job == sentMailJob ) { sentMailJob = 0; sentMail = createJob->collection(); kDebug() << "Created sent-mail collection with id" << sentMail.id(); } else { kFatal() << "Got a result for a job I don't know about."; } if( outboxJob == 0 && sentMailJob == 0 ) { // Done creating. Refetch everything. fetchCollections(); } } void LocalFoldersPrivate::collectionFetchResult( KJob *job ) { Q_ASSERT( preparing ); // but I may not be the main instance CollectionFetchJob *fetchJob = static_cast( job ); Collection::List cols = fetchJob->collections(); kDebug() << "CollectionFetchJob fetched" << cols.count() << "collections."; outbox = Collection( -1 ); sentMail = Collection( -1 ); Q_FOREACH( const Collection &col, cols ) { if( col.parent() == Collection::root().id() ) { rootMaildir = col; kDebug() << "Fetched root maildir collection."; } else if( col.name() == "outbox" ) { Q_ASSERT( outbox.id() == -1 ); outbox = col; kDebug() << "Fetched outbox collection."; } else if( col.name() == "sent-mail" ) { Q_ASSERT( sentMail.id() == -1 ); sentMail = col; kDebug() << "Fetched sent-mail collection."; } else { kWarning() << "Extraneous collection" << col.name() << "with id" << col.id() << "found."; } } if( !rootMaildir.isValid() ) { kFatal() << "Failed to fetch root maildir collection."; } createCollectionsIfNeeded(); } #include "localfolders.moc"