diff --git a/akonadi/agentbase.cpp b/akonadi/agentbase.cpp index 47461f842..7ed317e28 100644 --- a/akonadi/agentbase.cpp +++ b/akonadi/agentbase.cpp @@ -1,555 +1,598 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause Copyright (c) 2007 Bruno Virlet Copyright (c) 2008 Kevin Krammer 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 "agentbase.h" #include "agentbase_p.h" #include "controladaptor.h" #include "statusadaptor.h" #include "monitor_p.h" #include "xdgbasedirs_p.h" #include "session.h" #include "session_p.h" #include "changerecorder.h" #include "itemfetchjob.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; static AgentBase *sAgentBase = 0; AgentBase::Observer::Observer() { } AgentBase::Observer::~Observer() { } void AgentBase::Observer::itemAdded( const Item &item, const Collection &collection ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( item ); Q_UNUSED( collection ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::itemChanged( const Item &item, const QSet &partIdentifiers ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( item ); Q_UNUSED( partIdentifiers ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::itemRemoved( const Item &item ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( item ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( collection ); Q_UNUSED( parent ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::collectionChanged( const Collection &collection ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( collection ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::collectionRemoved( const Collection &collection ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( collection ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } //@cond PRIVATE AgentBasePrivate::AgentBasePrivate( AgentBase *parent ) : q_ptr( parent ), mStatusCode( AgentBase::Idle ), mProgress( 0 ), mNeedsNetwork( false ), mOnline( false ), mSettings( 0 ), mObserver( 0 ) { } AgentBasePrivate::~AgentBasePrivate() { mMonitor->setConfig( 0 ); delete mSettings; } void AgentBasePrivate::init() { Q_Q( AgentBase ); /** * Create a default session for this process. */ SessionPrivate::createDefaultSession( mId.toLatin1() ); mTracer = new org::freedesktop::Akonadi::Tracer( QLatin1String( "org.freedesktop.Akonadi" ), QLatin1String( "/tracing" ), QDBusConnection::sessionBus(), q ); new ControlAdaptor( q ); new StatusAdaptor( q ); if ( !QDBusConnection::sessionBus().registerObject( QLatin1String( "/" ), q, QDBusConnection::ExportAdaptors ) ) q->error( QString::fromLatin1( "Unable to register object at dbus: %1" ).arg( QDBusConnection::sessionBus().lastError().message() ) ); mSettings = new QSettings( QString::fromLatin1( "%1/agent_config_%2" ).arg( XdgBaseDirs::saveDir( "config", QLatin1String( "akonadi" ) ), mId ), QSettings::IniFormat ); mMonitor = new ChangeRecorder( q ); mMonitor->ignoreSession( Session::defaultSession() ); mMonitor->itemFetchScope().setCacheOnly( true ); mMonitor->setConfig( mSettings ); mOnline = mSettings->value( QLatin1String( "Agent/Online" ), true ).toBool(); + mName = mSettings->value( QLatin1String( "Agent/Name" ) ).toString(); + if (mName.isEmpty()) { + mName = mSettings->value( QLatin1String( "Resource/Name" ) ).toString(); + if (!mName.isEmpty()) { + mSettings->remove( QLatin1String( "Resource/Name" ) ); + mSettings->setValue( QLatin1String( "Agent/Name" ), mName ); + } + } + + connect( mMonitor, SIGNAL( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ), SLOT( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ) ); connect( mMonitor, SIGNAL( itemChanged( const Akonadi::Item&, const QSet& ) ), SLOT( itemChanged( const Akonadi::Item&, const QSet& ) ) ); connect( mMonitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) ); connect( mMonitor, SIGNAL( itemRemoved( const Akonadi::Item& ) ), SLOT( itemRemoved( const Akonadi::Item& ) ) ); connect( mMonitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), SLOT(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); connect( mMonitor, SIGNAL( collectionChanged( const Akonadi::Collection& ) ), SLOT( collectionChanged( const Akonadi::Collection& ) ) ); connect( mMonitor, SIGNAL( collectionRemoved( const Akonadi::Collection& ) ), SLOT( collectionRemoved( const Akonadi::Collection& ) ) ); connect( q, SIGNAL( status( int, QString ) ), q, SLOT( slotStatus( int, QString ) ) ); connect( q, SIGNAL( percent( int ) ), q, SLOT( slotPercent( int ) ) ); connect( q, SIGNAL( warning( QString ) ), q, SLOT( slotWarning( QString ) ) ); connect( q, SIGNAL( error( QString ) ), q, SLOT( slotError( QString ) ) ); // Use reference counting to allow agents to finish internal jobs when the // agent is stopped. KGlobal::ref(); KGlobal::setAllowQuit(true); QTimer::singleShot( 0, q, SLOT( delayedInit() ) ); } void AgentBasePrivate::delayedInit() { Q_Q( AgentBase ); if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.freedesktop.Akonadi.Agent." ) + mId ) ) kFatal() << "Unable to register service at dbus:" << QDBusConnection::sessionBus().lastError().message(); q->setOnline( mOnline ); } void AgentBasePrivate::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->itemAdded( item, collection ); } void AgentBasePrivate::itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->itemChanged( item, partIdentifiers ); } void AgentBasePrivate::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver ) { // inter-resource moves, requires we know which resources the source and destination are in though if ( !source.resource().isEmpty() && !dest.resource().isEmpty() ) { if ( source.resource() != dest.resource() ) { if ( source.resource() == q_ptr->identifier() ) // moved away from us mObserver->itemRemoved( item ); else if ( dest.resource() == q_ptr->identifier() ) // moved to us mObserver->itemAdded( item, dest ); else // not for us, not sure if we should get here at all changeProcessed(); return; } } // either incomplete information or intra-resource move // ### we cannot just call itemRemoved here as this will already trigger changeProcessed() // so, just itemAdded() is good enough as no resource can have implemented intra-resource moves anyway // without using Observer2 mObserver->itemAdded( item, dest ); // mObserver->itemRemoved( item ); } } void AgentBasePrivate::itemRemoved( const Akonadi::Item &item ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->itemRemoved( item ); } void AgentBasePrivate::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->collectionAdded( collection, parent ); } void AgentBasePrivate::collectionChanged( const Akonadi::Collection &collection ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->collectionChanged( collection ); } void AgentBasePrivate::collectionRemoved( const Akonadi::Collection &collection ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->collectionRemoved( collection ); } void AgentBasePrivate::changeProcessed() { mMonitor->changeProcessed(); QTimer::singleShot( 0, mMonitor, SLOT( replayNext() ) ); } void AgentBasePrivate::slotStatus( int status, const QString &message ) { mStatusMessage = message; mStatusCode = 0; switch ( status ) { case AgentBase::Idle: if ( mStatusMessage.isEmpty() ) mStatusMessage = defaultReadyMessage(); mStatusCode = 0; break; case AgentBase::Running: if ( mStatusMessage.isEmpty() ) mStatusMessage = defaultSyncingMessage(); mStatusCode = 1; break; case AgentBase::Broken: if ( mStatusMessage.isEmpty() ) mStatusMessage = defaultErrorMessage(); mStatusCode = 2; break; default: Q_ASSERT( !"Unknown status passed" ); break; } } void AgentBasePrivate::slotPercent( int progress ) { mProgress = progress; } void AgentBasePrivate::slotWarning( const QString& message ) { mTracer->warning( QString::fromLatin1( "AgentBase(%1)" ).arg( mId ), message ); } void AgentBasePrivate::slotError( const QString& message ) { mTracer->error( QString::fromLatin1( "AgentBase(%1)" ).arg( mId ), message ); } void AgentBasePrivate::slotNetworkStatusChange( Solid::Networking::Status stat ) { Q_Q( AgentBase ); q->setOnline( stat == Solid::Networking::Connected ); } AgentBase::AgentBase( const QString & id ) : d_ptr( new AgentBasePrivate( this ) ) { sAgentBase = this; d_ptr->mId = id; d_ptr->init(); if ( KApplication::kApplication() ) KApplication::kApplication()->disableSessionManagement(); } AgentBase::AgentBase( AgentBasePrivate* d, const QString &id ) : d_ptr( d ) { sAgentBase = this; d_ptr->mId = id; d_ptr->init(); } AgentBase::~AgentBase() { delete d_ptr; } QString AgentBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug( 5250 ) << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug( 5250 ) << "Identifier argument missing"; exit( 1 ); } QByteArray catalog; char *p = strrchr( argv[0], '/' ); if ( p ) catalog = QByteArray( p + 1 ); else catalog = QByteArray( argv[0] ); KCmdLineArgs::init( argc, argv, identifier.toLatin1(), catalog, ki18n("Akonadi Agent"),"0.1" , ki18n("Akonadi Agent") ); KCmdLineOptions options; options.add("identifier ", ki18n("Agent identifier")); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } // @endcond int AgentBase::init( AgentBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); KGlobal::locale()->insertCatalog( QLatin1String("libakonadi") ); int rv = kapp->exec(); delete r; return rv; } int AgentBase::status() const { Q_D( const AgentBase ); return d->mStatusCode; } QString AgentBase::statusMessage() const { Q_D( const AgentBase ); return d->mStatusMessage; } int AgentBase::progress() const { Q_D( const AgentBase ); return d->mProgress; } QString AgentBase::progressMessage() const { Q_D( const AgentBase ); return d->mProgressMessage; } bool AgentBase::isOnline() const { Q_D( const AgentBase ); return d->mOnline; } void AgentBase::setNeedsNetwork( bool needsNetwork ) { Q_D( AgentBase ); d->mNeedsNetwork = needsNetwork; if ( d->mNeedsNetwork ) { connect( Solid::Networking::notifier() , SIGNAL( statusChanged( Solid::Networking::Status ) ) , this, SLOT( slotNetworkStatusChange( Solid::Networking::Status ) ) ); } else { disconnect( Solid::Networking::notifier(), 0, 0, 0 ); setOnline( true ); } } void AgentBase::setOnline( bool state ) { Q_D( AgentBase ); d->mOnline = state; d->mSettings->setValue( QLatin1String( "Agent/Online" ), state ); doSetOnline( state ); emit onlineChanged( state ); } void AgentBase::doSetOnline( bool online ) { Q_UNUSED( online ); } void AgentBase::configure( WId windowId ) { Q_UNUSED( windowId ); } #ifdef Q_OS_WIN //krazy:exclude=cpp void AgentBase::configure( qlonglong windowId ) { configure( reinterpret_cast( windowId ) ); } #endif WId AgentBase::winIdForDialogs() const { bool registered = QDBusConnection::sessionBus().interface()->isServiceRegistered( QLatin1String("org.freedesktop.akonaditray") ); if ( !registered ) return 0; QDBusInterface dbus( QLatin1String("org.freedesktop.akonaditray"), QLatin1String("/Actions"), QLatin1String("org.freedesktop.Akonadi.Tray") ); QDBusMessage reply = dbus.call( QLatin1String("getWinId") ); if ( reply.type() == QDBusMessage::ErrorMessage ) return 0; WId winid = (WId)reply.arguments().at( 0 ).toLongLong(); return winid; } void AgentBase::quit() { Q_D( AgentBase ); aboutToQuit(); if ( d->mSettings ) { d->mMonitor->setConfig( 0 ); d->mSettings->sync(); } KGlobal::deref(); } void AgentBase::aboutToQuit() { } void AgentBase::cleanup() { Q_D( AgentBase ); + // prevent the monitor from picking up deletion signals for our own data if we are a resource + // and thus avoid that we kill our own data as last act before our own death + d->mMonitor->blockSignals( true ); + aboutToQuit(); const QString fileName = d->mSettings->fileName(); /* * First destroy the settings object... */ d->mMonitor->setConfig( 0 ); delete d->mSettings; d->mSettings = 0; /* * ... then remove the file from hd. */ QFile::remove( fileName ); /* * ... and also remove the agent configuration file if there is one. */ QString configFile = KStandardDirs::locateLocal( "config", KGlobal::config()->name() ); QFile::remove( configFile ); KGlobal::deref(); } void AgentBase::registerObserver( Observer *observer ) { kDebug() << "observer=" << (void*) observer << "this=" << (void*) this; d_ptr->mObserver = observer; } QString AgentBase::identifier() const { return d_ptr->mId; } +void AgentBase::setAgentName( const QString &name ) +{ + Q_D( AgentBase ); + if ( name == d->mName ) + return; + + // TODO: rename collection + d->mName = name; + + if ( d->mName.isEmpty() || d->mName == d->mId ) { + d->mSettings->remove( QLatin1String( "Resource/Name" ) ); + d->mSettings->remove( QLatin1String( "Agent/Name" ) ); + } else + d->mSettings->setValue( QLatin1String( "Agent/Name" ), d->mName ); + + d->mSettings->sync(); + + emit agentNameChanged( d->mName ); +} + +QString AgentBase::agentName() const +{ + Q_D( const AgentBase ); + if ( d->mName.isEmpty() ) + return d->mId; + else + return d->mName; +} + void AgentBase::changeProcessed() { Q_D( AgentBase ); d->changeProcessed(); } ChangeRecorder * AgentBase::changeRecorder() const { return d_ptr->mMonitor; } void AgentBase::reconfigure() { emit reloadConfiguration(); } #include "agentbase.moc" #include "agentbase_p.moc" diff --git a/akonadi/agentbase.h b/akonadi/agentbase.h index 9480791db..07285ce2b 100644 --- a/akonadi/agentbase.h +++ b/akonadi/agentbase.h @@ -1,474 +1,492 @@ /* This file is part of akonadiresources. Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause Copyright (c) 2008 Kevin Krammer 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 AKONADI_AGENTBASE_H #define AKONADI_AGENTBASE_H #include "akonadi_export.h" #include #include class ControlAdaptor; class StatusAdaptor; namespace Akonadi { class AgentBasePrivate; class ChangeRecorder; class Collection; class Item; class Session; /** * @short The base class for all Akonadi agents and resources. * * This class is a base class for all Akonadi agents, which covers the real * agent processes and all resources. * * It provides: * - lifetime management * - change monitoring and recording * - configuration interface * - problem reporting * * @author Till Adam , Volker Krause */ class AKONADI_EXPORT AgentBase : public QObject, protected QDBusContext { Q_OBJECT public: /** * @short The interface for reacting on monitored or replayed changes. * * The Observer provides an interface to react on monitored or replayed changes. * * Since the this base class does only tell the change recorder that the change * has been processed, an AgentBase subclass which wants to actually process * the change needs to subclass Observer and reimplement the methods it is * interested in. * * Such an agent specific Observer implementation can either be done * stand-alone, i.e. as a separate object, or by inheriting both AgentBase * and AgentBase::Observer. * * The observer implementation then has registered with the agent, so it * can forward the incoming changes to the observer. * * @note In the multiple inheritance approach the init() method automatically * registers itself as the observer. * * Example for stand-alone observer: * @code * class ExampleAgent : public AgentBase * { * public: * ExampleAgent( const QString &id ); * * ~ExampleAgent(); * * private: * AgentBase::Observer *mObserver; * }; * * class ExampleObserver : public AgentBase::Observer * { * protected: * void itemChanged( const Item &item ); * }; * * ExampleAgent::ExampleAgent( const QString &id ) * : AgentBase( id ), mObserver( 0 ) * { * mObserver = new ExampleObserver(); * registerObserver( mObserver ); * } * * ExampleAgent::~ExampleAgent() * { * delete mObserver; * } * * void ExampleObserver::itemChanged( const Item &item ) * { * // do something with item * kDebug() << "Item id=" << item.id(); * * // let base implementation tell the change recorder that we * // have processed the change * AgentBase::Observer::itemChanged( item ); * } * @endcode * * Example for observer through multiple inheritance: * @code * class ExampleAgent : public AgentBase, public AgentBase::Observer * { * public: * ExampleAgent( const QString &id ); * * protected: * void itemChanged( const Item &item ); * }; * * ExampleAgent::ExampleAgent( const QString &id ) * : AgentBase( id ) * { * // no need to create or register observer since * // we are the observer and registration happens automatically * // in init() * } * * void ExampleAgent::itemChanged( const Item &item ) * { * // do something with item * kDebug() << "Item id=" << item.id(); * * // let base implementation tell the change recorder that we * // have processed the change * AgentBase::Observer::itemChanged( item ); * } * @endcode * * @author Kevin Krammer */ class AKONADI_EXPORT Observer // krazy:exclude=dpointer { public: /** * Creates an observer instance. */ Observer(); /** * Destroys the observer instance. */ virtual ~Observer(); /** * Reimplement to handle adding of new items. * @param item The newly added item. * @param collection The collection @p item got added to. */ virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * Reimplement to handle changes to existing items. * @param item The changed item. * @param partIdentifiers The identifiers of the item parts that has been changed. */ virtual void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); /** * Reimplement to handle deletion of items. * @param item The deleted item. */ virtual void itemRemoved( const Akonadi::Item &item ); /** * Reimplement to handle adding of new collections. * @param collection The newly added collection. * @param parent The parent collection. */ virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); /** * Reimplement to handle changes to existing collections. * @param collection The changed collection. */ virtual void collectionChanged( const Akonadi::Collection &collection ); /** * Reimplement to handle deletion of collections. * @param collection The deleted collection. */ virtual void collectionRemoved( const Akonadi::Collection &collection ); }; /** * This enum describes the different states the * agent can be in. */ enum Status { Idle = 0, ///< The agent does currently nothing. Running, ///< The agent is working on something. Broken ///< The agent encountered an error state. }; /** * Use this method in the main function of your agent * application to initialize your agent subclass. * This method also takes care of creating a KApplication * object and parsing command line arguments. * * @note In case the given class is also derived from AgentBase::Observer * it gets registered as its own observer (see AgentBase::Observer), e.g. * agentInstance->registerObserver( agentInstance ); * * @code * * class MyAgent : public AgentBase * { * ... * }; * * AKONADI_AGENT_MAIN( MyAgent ) * * @endcode */ template static int init( int argc, char **argv ) { const QString id = parseArguments( argc, argv ); KApplication app; T* r = new T( id ); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Observer *observer = dynamic_cast( r ); if ( observer != 0 ) r->registerObserver( observer ); return init( r ); } /** * This method returns the current status code of the agent. * * The following return values are possible: * * - 0 - Idle * - 1 - Running * - 2 - Broken */ virtual int status() const; /** * This method returns an i18n'ed description of the current status code. */ virtual QString statusMessage() const; /** * This method returns the current progress of the agent in percentage. */ virtual int progress() const; /** * This method returns an i18n'ed description of the current progress. */ virtual QString progressMessage() const; /** * This method is called whenever the agent shall show its configuration dialog * to the user. It will be automatically called when the agent is started for * the first time. * @param windowId The parent window id. */ virtual void configure( WId windowId ); /** * This method returns the windows id, which should be used for dialogs. */ WId winIdForDialogs() const; #ifdef Q_OS_WIN /** * Overload of @ref configure needed because WId cannot be automatically casted * to qlonglong on Windows. */ void configure( qlonglong windowId ); #endif /** * Returns the instance identifier of this agent. */ QString identifier() const; /** * This method is called when the agent is removed from * the system, so it can do some cleanup stuff. */ virtual void cleanup(); /** * Registers the given observer for reacting on monitored or recorded changes. * * @param observer The change handler to register. No ownership transfer, i.e. * the caller stays owner of the pointer and can reset * the registration by calling this method with @c 0 */ void registerObserver( Observer *observer ); + /** + * This method is used to set the name of the agent. + */ + //FIXME_API: make sure location is renamed to this by agentbase + void setAgentName( const QString &name ); + + /** + * Returns the name of the agent. + */ + QString agentName() const; + Q_SIGNALS: + /** + * This signal is emitted whenever the name of the agent has changed. + * + * @param name The new name of the agent. + */ + void agentNameChanged( const QString &name ); + /** * This signal should be emitted whenever the status of the agent has been changed. * @param status The new Status code. * @param message A i18n'ed description of the new status. */ void status( int status, const QString &message = QString() ); /** * This signal should be emitted whenever the progress of an action in the agent * (e.g. data transfer, connection establishment to remote server etc.) has changed. * * @param progress The progress of the action in percent. */ void percent( int progress ); /** * This signal shall be used to report warnings. * * @param message The i18n'ed warning message. */ void warning( const QString& message ); /** * This signal shall be used to report errors. * * @param message The i18n'ed error message. */ void error( const QString& message ); /** * Emitted if another application has changed the agents configuration remotely * and called AgentInstance::reconfigure(). * * @since 4.2 */ void reloadConfiguration(); /** * Emitted when the online state changed. * @param state The online state. * @since 4.2 */ void onlineChanged( bool b ); protected: /** * Creates an agent base. * * @param id The instance id of the agent. */ AgentBase( const QString & id ); /** * Destroys the agent base. */ ~AgentBase(); /** * This method is called whenever the agent application is about to * quit. * * Reimplement this method to do session cleanup (e.g. disconnecting * from groupware server). */ virtual void aboutToQuit(); /** * Returns the Akonadi::ChangeRecorder object used for monitoring. * Use this to configure which parts you want to monitor. */ ChangeRecorder* changeRecorder() const; /** * Marks the current change as processes and replays the next change if change * recording is enabled (noop otherwise). This method is called * from the default implementation of the change notification slots. While not * required when not using change recording, it is nevertheless recommended * to call this method when done with processing a change notification. */ void changeProcessed(); /** * Returns whether the agent is currently online. */ bool isOnline() const; /** * Sets whether the agent needs network or not. * * @since 4.2 * @todo use this in combination with Solid::Networking::Notifier to change * the onLine status of the agent. */ void setNeedsNetwork( bool needsNetwork ); /** * Sets whether the agent shall be online or not. */ void setOnline( bool state ); protected: //@cond PRIVATE AgentBasePrivate *d_ptr; explicit AgentBase( AgentBasePrivate* d, const QString &id ); //@endcond /** * This method is called whenever the @p online status has changed. * Reimplement this method to react on online status changes. */ virtual void doSetOnline( bool online ); private: //@cond PRIVATE static QString parseArguments( int, char** ); static int init( AgentBase *r ); // D-Bus interface stuff void reconfigure(); void quit(); // dbus agent interface friend class ::StatusAdaptor; friend class ::ControlAdaptor; Q_DECLARE_PRIVATE( AgentBase ) Q_PRIVATE_SLOT( d_func(), void delayedInit() ) Q_PRIVATE_SLOT( d_func(), void slotStatus( int, const QString& ) ) Q_PRIVATE_SLOT( d_func(), void slotPercent( int ) ) Q_PRIVATE_SLOT( d_func(), void slotWarning( const QString& ) ) Q_PRIVATE_SLOT( d_func(), void slotError( const QString& ) ) Q_PRIVATE_SLOT( d_func(), void slotNetworkStatusChange( Solid::Networking::Status ) ) //@endcond }; } #ifndef AKONADI_AGENT_MAIN /** * Convenience Macro for the most common main() function for Akonadi agents. */ #define AKONADI_AGENT_MAIN( agentClass ) \ int main( int argc, char **argv ) \ { \ return Akonadi::AgentBase::init( argc, argv ); \ } #endif #endif diff --git a/akonadi/agentbase_p.h b/akonadi/agentbase_p.h index 8b864f72a..3d45fd3bc 100644 --- a/akonadi/agentbase_p.h +++ b/akonadi/agentbase_p.h @@ -1,106 +1,107 @@ /* Copyright (c) 2007 Volker Krause Copyright (c) 2008 Kevin Krammer 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 AKONADI_AGENTBASE_P_H #define AKONADI_AGENTBASE_P_H #include "agentbase.h" #include "tracerinterface.h" #include #include class QSettings; namespace Akonadi { /** * @internal */ class AgentBasePrivate : public QObject { Q_OBJECT public: AgentBasePrivate( AgentBase *parent ); virtual ~AgentBasePrivate(); void init(); virtual void delayedInit(); void slotStatus( int status, const QString &message ); void slotPercent( int progress ); void slotWarning( const QString& message ); void slotError( const QString& message ); void slotNetworkStatusChange( Solid::Networking::Status ); virtual void changeProcessed(); QString defaultReadyMessage() const { if ( mOnline ) return i18nc( "@info:status, application ready for work", "Ready" ); return i18nc( "@info:status", "Offline" ); } QString defaultSyncingMessage() const { return i18nc( "@info:status", "Syncing..." ); } QString defaultErrorMessage() const { return i18nc( "@info:status", "Error." ); } AgentBase *q_ptr; Q_DECLARE_PUBLIC( AgentBase ) QString mId; + QString mName; int mStatusCode; QString mStatusMessage; uint mProgress; QString mProgressMessage; bool mNeedsNetwork; bool mOnline; QSettings *mSettings; ChangeRecorder *mMonitor; org::freedesktop::Akonadi::Tracer *mTracer; AgentBase::Observer *mObserver; protected Q_SLOTS: void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); void itemMoved( const Akonadi::Item &, const Akonadi::Collection &source, const Akonadi::Collection &destination ); void itemRemoved( const Akonadi::Item &item ); void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); void collectionChanged( const Akonadi::Collection &collection ); void collectionRemoved( const Akonadi::Collection &collection ); }; } #endif diff --git a/akonadi/collectiondeletejob.cpp b/akonadi/collectiondeletejob.cpp index 0690b9aba..e6b400dd9 100644 --- a/akonadi/collectiondeletejob.cpp +++ b/akonadi/collectiondeletejob.cpp @@ -1,57 +1,69 @@ /* Copyright (c) 2006 Volker Krause 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 "collectiondeletejob.h" #include "collection.h" #include "job_p.h" +#include + using namespace Akonadi; class Akonadi::CollectionDeleteJobPrivate : public JobPrivate { public: CollectionDeleteJobPrivate( CollectionDeleteJob *parent ) : JobPrivate( parent ) { } Collection mCollection; }; CollectionDeleteJob::CollectionDeleteJob(const Collection &collection, QObject * parent) : Job( new CollectionDeleteJobPrivate( this ), parent ) { Q_D( CollectionDeleteJob ); d->mCollection = collection; } CollectionDeleteJob::~CollectionDeleteJob() { } void CollectionDeleteJob::doStart() { Q_D( CollectionDeleteJob ); - d->writeData( d->newTag() + " DELETE " + QByteArray::number( d->mCollection.id() ) + '\n' ); + if ( !d->mCollection.isValid() && d->mCollection.remoteId().isEmpty() ) { + setError( Unknown ); + setErrorText( QLatin1String("Invalid collection") ); + emitResult(); + return; + } + + if ( d->mCollection.isValid() ) + d->writeData( d->newTag() + " DELETE " + QByteArray::number( d->mCollection.id() ) + '\n' ); + else + d->writeData( d->newTag() + " RID DELETE " + ImapParser::quote( d->mCollection.remoteId().toUtf8() ) + '\n' ); } #include "collectiondeletejob.moc" diff --git a/akonadi/collectiondeletejob.h b/akonadi/collectiondeletejob.h index 01a6c32cd..9850fa46e 100644 --- a/akonadi/collectiondeletejob.h +++ b/akonadi/collectiondeletejob.h @@ -1,77 +1,76 @@ /* Copyright (c) 2006 Volker Krause 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 AKONADI_COLLECTIONDELETEJOB_H #define AKONADI_COLLECTIONDELETEJOB_H #include namespace Akonadi { class Collection; class CollectionDeleteJobPrivate; /** * @short Job that deletes a collection in the Akonadi storage. * * This job deletes a collection and all its sub-collections as well as all associated content. * * @code * * Akonadi::Collection collection = ... * * Akonadi::CollectionDeleteJob *job = new Akonadi::CollectionDeleteJob( collection ); - * - * if ( job->exec() ) - * qDebug() << "Deleted successfully"; - * else - * qDebug() << "Error occurred"; + * connect( job, SIGNAL(result(KJob*)), this, SLOT(deletionResult(KJob*)) ); * * @endcode * * @author Volker Krause */ class AKONADI_EXPORT CollectionDeleteJob : public Job { Q_OBJECT public: /** - * Creates a new collection delete job. + * Creates a new collection delete job. The collection needs to either have a unique + * identifier or a remote identifier set. Note that using a remote identifier only works + * in a resource context (that is from within ResourceBase), as remote identifiers + * are not guaranteed to be globally unique. * * @param collection The collection to delete. * @param parent The parent object. */ explicit CollectionDeleteJob( const Collection &collection, QObject *parent = 0 ); /** * Destroys the collection delete job. */ ~CollectionDeleteJob(); protected: virtual void doStart(); private: Q_DECLARE_PRIVATE( CollectionDeleteJob ) }; } #endif diff --git a/akonadi/resourcebase.cpp b/akonadi/resourcebase.cpp index ebef609c1..be798b66e 100644 --- a/akonadi/resourcebase.cpp +++ b/akonadi/resourcebase.cpp @@ -1,627 +1,604 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause 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 "resourcebase.h" #include "agentbase_p.h" #include "resourceadaptor.h" #include "collectiondeletejob.h" #include "collectionsync_p.h" #include "itemsync.h" #include "resourcescheduler_p.h" #include "tracerinterface.h" #include "xdgbasedirs_p.h" #include "changerecorder.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "session.h" #include "resourceselectjob_p.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; class Akonadi::ResourceBasePrivate : public AgentBasePrivate { public: ResourceBasePrivate( ResourceBase *parent ) : AgentBasePrivate( parent ), scheduler( 0 ), mItemSyncer( 0 ), mCollectionSyncer( 0 ) { mStatusMessage = defaultReadyMessage(); } Q_DECLARE_PUBLIC( ResourceBase ) void delayedInit() { if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + mId ) ) kFatal() << "Unable to register service at D-Bus: " << QDBusConnection::sessionBus().lastError().message(); AgentBasePrivate::delayedInit(); } virtual void changeProcessed() { mMonitor->changeProcessed(); if ( !mMonitor->isEmpty() ) scheduler->scheduleChangeReplay(); scheduler->taskDone(); } void slotDeliveryDone( KJob* job ); void slotCollectionSyncDone( KJob *job ); void slotLocalListDone( KJob *job ); void slotSynchronizeCollection( const Collection &col ); void slotCollectionListDone( KJob *job ); void slotItemSyncDone( KJob *job ); void slotPercent( KJob* job, unsigned long percent ); void slotDeleteResourceCollection(); void slotDeleteResourceCollectionDone( KJob *job ); void slotCollectionDeletionDone( KJob *job ); - QString mName; - // synchronize states Collection currentCollection; ResourceScheduler *scheduler; ItemSync *mItemSyncer; CollectionSync *mCollectionSyncer; }; ResourceBase::ResourceBase( const QString & id ) : AgentBase( new ResourceBasePrivate( this ), id ) { Q_D( ResourceBase ); new ResourceAdaptor( this ); - const QString name = d->mSettings->value( QLatin1String( "Resource/Name" ) ).toString(); - if ( !name.isEmpty() ) - d->mName = name; - d->scheduler = new ResourceScheduler( this ); d->mMonitor->setChangeRecordingEnabled( true ); connect( d->mMonitor, SIGNAL(changesAdded()), d->scheduler, SLOT(scheduleChangeReplay()) ); d->mMonitor->setResourceMonitored( d->mId.toLatin1() ); connect( d->scheduler, SIGNAL(executeFullSync()), SLOT(retrieveCollections()) ); connect( d->scheduler, SIGNAL(executeCollectionTreeSync()), SLOT(retrieveCollections()) ); connect( d->scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection)), SLOT(slotSynchronizeCollection(Akonadi::Collection)) ); connect( d->scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet)), SLOT(retrieveItem(Akonadi::Item,QSet)) ); connect( d->scheduler, SIGNAL(executeResourceCollectionDeletion()), SLOT(slotDeleteResourceCollection()) ); connect( d->scheduler, SIGNAL( status( int, QString ) ), SIGNAL( status( int, QString ) ) ); connect( d->scheduler, SIGNAL(executeChangeReplay()), d->mMonitor, SLOT(replayNext()) ); connect( d->scheduler, SIGNAL(fullSyncComplete()), SIGNAL(synchronized()) ); connect( d->mMonitor, SIGNAL(nothingToReplay()), d->scheduler, SLOT(taskDone()) ); connect( this, SIGNAL(synchronized()), d->scheduler, SLOT(taskDone()) ); + connect( this, SIGNAL(agentNameChanged(QString)), this, SIGNAL(nameChanged(QString))); d->scheduler->setOnline( d->mOnline ); if ( !d->mMonitor->isEmpty() ) d->scheduler->scheduleChangeReplay(); new ResourceSelectJob( identifier() ); } ResourceBase::~ResourceBase() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName( const QString &name ) { - Q_D( ResourceBase ); - if ( name == d->mName ) - return; - - // TODO: rename collection - d->mName = name; - - if ( d->mName.isEmpty() || d->mName == d->mId ) - d->mSettings->remove( QLatin1String( "Resource/Name" ) ); - else - d->mSettings->setValue( QLatin1String( "Resource/Name" ), d->mName ); - - d->mSettings->sync(); - - emit nameChanged( d->mName ); + AgentBase::setAgentName(name); } QString ResourceBase::name() const { - Q_D( const ResourceBase ); - if ( d->mName.isEmpty() ) - return d->mId; - else - return d->mName; + return AgentBase::agentName(); } QString ResourceBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug( 5250 ) << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug( 5250 ) << "Identifier argument missing"; exit( 1 ); } QByteArray catalog; char *p = strrchr( argv[0], '/' ); if ( p ) catalog = QByteArray( p + 1 ); else catalog = QByteArray( argv[0] ); KCmdLineArgs::init( argc, argv, identifier.toLatin1(), catalog, ki18nc("@title, application name", "Akonadi Resource"), "0.1", ki18nc("@title, application description", "Akonadi Resource") ); KCmdLineOptions options; options.add("identifier ", ki18nc("@label, commandline option", "Resource identifier")); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } int ResourceBase::init( ResourceBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); int rv = kapp->exec(); delete r; return rv; } void ResourceBase::itemRetrieved( const Item &item ) { Q_D( ResourceBase ); Q_ASSERT( d->scheduler->currentTask().type == ResourceScheduler::FetchItem ); if ( !item.isValid() ) { QDBusMessage reply( d->scheduler->currentTask().dbusMsg ); reply << false; QDBusConnection::sessionBus().send( reply ); d->scheduler->taskDone(); return; } Item i( item ); QSet requestedParts = d->scheduler->currentTask().itemParts; foreach ( const QByteArray &part, requestedParts ) { if ( !item.loadedPayloadParts().contains( part ) ) { kWarning( 5250 ) << "Item does not provide part" << part; } } ItemModifyJob *job = new ItemModifyJob( i ); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->disableRevisionCheck(); connect( job, SIGNAL(result(KJob*)), SLOT(slotDeliveryDone(KJob*)) ); } void ResourceBasePrivate::slotDeliveryDone(KJob * job) { Q_Q( ResourceBase ); Q_ASSERT( scheduler->currentTask().type == ResourceScheduler::FetchItem ); QDBusMessage reply( scheduler->currentTask().dbusMsg ); if ( job->error() ) { emit q->error( QLatin1String( "Error while creating item: " ) + job->errorString() ); reply << false; } else { reply << true; } QDBusConnection::sessionBus().send( reply ); scheduler->taskDone(); } void ResourceBasePrivate::slotDeleteResourceCollection() { Q_Q( ResourceBase ); CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->setResource( q->identifier() ); connect( job, SIGNAL(result(KJob*)), q, SLOT(slotDeleteResourceCollectionDone(KJob*)) ); } void ResourceBasePrivate::slotDeleteResourceCollectionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); scheduler->taskDone(); } else { const CollectionFetchJob *fetchJob = static_cast( job ); if ( !fetchJob->collections().isEmpty() ) { CollectionDeleteJob *job = new CollectionDeleteJob( fetchJob->collections().first() ); connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotCollectionDeletionDone( KJob* ) ) ); } else { // there is no resource collection, so just ignore the request scheduler->taskDone(); } } } void ResourceBasePrivate::slotCollectionDeletionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBase::changeCommitted(const Item& item) { Q_D( ResourceBase ); ItemModifyJob *job = new ItemModifyJob( item ); job->d_func()->setClean(); job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error? job->ignorePayload(); // we only want to reset the dirty flag and update the remote id d->changeProcessed(); } void ResourceBase::changeCommitted( const Collection &collection ) { Q_D( ResourceBase ); CollectionModifyJob *job = new CollectionModifyJob( collection ); Q_UNUSED( job ); //TODO: error checking d->changeProcessed(); } bool ResourceBase::requestItemDelivery( qint64 uid, const QString & remoteId, const QString &mimeType, const QStringList &_parts ) { Q_D( ResourceBase ); if ( !isOnline() ) { emit error( i18nc( "@info", "Cannot fetch item in offline mode." ) ); return false; } setDelayedReply( true ); // FIXME: we need at least the revision number too Item item( uid ); item.setMimeType( mimeType ); item.setRemoteId( remoteId ); QSet parts; Q_FOREACH( const QString &str, _parts ) parts.insert( str.toLatin1() ); d->scheduler->scheduleItemFetch( item, parts, message().createReply() ); return true; } void ResourceBase::collectionsRetrieved(const Collection::List & collections) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrieved()", "Calling collectionsRetrieved() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); } d->mCollectionSyncer->setRemoteCollections( collections ); } void ResourceBase::collectionsRetrievedIncremental(const Collection::List & changedCollections, const Collection::List & removedCollections) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievedIncremental()", "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); } d->mCollectionSyncer->setRemoteCollections( changedCollections, removedCollections ); } void ResourceBase::setCollectionStreamingEnabled(bool enable) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::setCollectionStreamingEnabled()", "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); } d->mCollectionSyncer->setStreamingEnabled( enable ); } void ResourceBase::collectionsRetrievalDone() { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievalDone()", "Calling collectionsRetrievalDone() although no collection retrieval is in progress" ); // streaming enabled, so finalize the sync if ( d->mCollectionSyncer ) { d->mCollectionSyncer->retrievalDone(); } // user did the sync himself, we are done now else { d->scheduler->taskDone(); } } void ResourceBasePrivate::slotCollectionSyncDone(KJob * job) { Q_Q( ResourceBase ); mCollectionSyncer = 0; if ( job->error() ) { emit q->error( job->errorString() ); } else { if ( scheduler->currentTask().type == ResourceScheduler::SyncAll ) { CollectionFetchJob *list = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); list->setResource( mId ); q->connect( list, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*)) ); return; } } scheduler->taskDone(); } void ResourceBasePrivate::slotLocalListDone(KJob * job) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } else { Collection::List cols = static_cast( job )->collections(); foreach ( const Collection &col, cols ) { scheduler->scheduleSync( col ); } scheduler->scheduleFullSyncCompletion(); } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection( const Collection &col ) { Q_Q( ResourceBase ); currentCollection = col; // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentMimeTypes(); contentTypes.removeAll( Collection::mimeType() ); if ( !contentTypes.isEmpty() ) { emit q->status( AgentBase::Running, i18nc( "@info:status", "Syncing collection '%1'", currentCollection.name() ) ); q->retrieveItems( currentCollection ); return; } scheduler->taskDone(); } void ResourceBase::itemsRetrievalDone() { Q_D( ResourceBase ); // streaming enabled, so finalize the sync if ( d->mItemSyncer ) { d->mItemSyncer->deliveryDone(); } // user did the sync himself, we are done now else { d->scheduler->taskDone(); } } void ResourceBase::clearCache() { Q_D( ResourceBase ); d->scheduler->scheduleResourceCollectionDeletion(); } Collection ResourceBase::currentCollection() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection , "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress" ); return d->currentCollection; } Item ResourceBase::currentItem() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::FetchItem , "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress" ); return d->scheduler->currentTask().item; } void ResourceBase::synchronizeCollectionTree() { d_func()->scheduler->scheduleCollectionTreeSync(); } void ResourceBase::cancelTask() { Q_D( ResourceBase ); switch ( d->scheduler->currentTask().type ) { case ResourceScheduler::FetchItem: itemRetrieved( Item() ); // sends the error reply and break; case ResourceScheduler::ChangeReplay: d->changeProcessed(); break; default: d->scheduler->taskDone(); } } void ResourceBase::cancelTask( const QString &msg ) { cancelTask(); emit error( msg ); } void ResourceBase::deferTask() { Q_D( ResourceBase ); d->scheduler->deferTask(); } void ResourceBase::doSetOnline( bool state ) { d_func()->scheduler->setOnline( state ); } void ResourceBase::synchronizeCollection(qint64 collectionId ) { CollectionFetchJob* job = new CollectionFetchJob( Collection(collectionId), CollectionFetchJob::Base ); job->setResource( identifier() ); connect( job, SIGNAL(result(KJob*)), SLOT(slotCollectionListDone(KJob*)) ); } void ResourceBasePrivate::slotCollectionListDone( KJob *job ) { if ( !job->error() ) { Collection::List list = static_cast( job )->collections(); if ( !list.isEmpty() ) { Collection col = list.first(); scheduler->scheduleSync( col ); } } // TODO: error handling } void ResourceBase::setTotalItems( int amount ) { kDebug() << amount; Q_D( ResourceBase ); setItemStreamingEnabled( true ); d->mItemSyncer->setTotalItems( amount ); } void ResourceBase::setItemStreamingEnabled( bool enable ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::setItemStreamingEnabled()", "Calling setItemStreamingEnabled() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mItemSyncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); } d->mItemSyncer->setStreamingEnabled( enable ); } void ResourceBase::itemsRetrieved( const Item::List &items ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrieved()", "Calling itemsRetrieved() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mItemSyncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); } d->mItemSyncer->setFullSyncItems( items ); } void ResourceBase::itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrievedIncremental()", "Calling itemsRetrievedIncremental() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mItemSyncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); } d->mItemSyncer->setIncrementalSyncItems( changedItems, removedItems ); } void ResourceBasePrivate::slotItemSyncDone( KJob *job ) { mItemSyncer = 0; Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBasePrivate::slotPercent( KJob *job, unsigned long percent ) { Q_Q( ResourceBase ); Q_UNUSED( job ); emit q->percent( percent ); } #include "resourcebase.moc" diff --git a/akonadi/session_p.h b/akonadi/session_p.h index 4b667b5f6..18c0d208a 100644 --- a/akonadi/session_p.h +++ b/akonadi/session_p.h @@ -1,115 +1,115 @@ /* Copyright (c) 2007 Volker Krause 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 AKONADI_SESSION_P_H #define AKONADI_SESSION_P_H #include "session.h" #include "imapparser_p.h" #include #include #include #include class QLocalSocket; namespace Akonadi { /** * @internal */ class SessionPrivate { public: SessionPrivate( Session *parent ) : mParent( parent ), mConnectionSettings( 0 ), protocolVersion( 0 ) { parser = new ImapParser(); } ~SessionPrivate() { delete parser; delete mConnectionSettings; } void startNext(); void reconnect(); void socketDisconnected(); void socketError( QLocalSocket::LocalSocketError error ); void dataReceived(); void doStartNext(); void startJob( Job* job ); void jobDone( KJob* job ); void jobWriteFinished( Akonadi::Job* job ); void jobDestroyed( QObject *job ); bool canPipelineNext(); /** * Creates a new default session for this thread with * the given @p sessionId. The session can be accessed * later by defaultSession(). * * You only need to call this method if you want that the * default session has a special custom id, otherwise a random unique * id is used automatically. */ static void createDefaultSession( const QByteArray &sessionId ); /** Associates the given Job object with this session. */ void addJob( Job* job ); /** Returns the next IMAP tag. */ int nextTag(); /** Sends the given raw data. */ void writeData( const QByteArray &data ); - static int minimumProtocolVersion() { return 14; } + static int minimumProtocolVersion() { return 15; } Session *mParent; QByteArray sessionId; QSettings *mConnectionSettings; QLocalSocket* socket; bool connected; int theNextTag; int protocolVersion; // job management QQueue queue; QQueue pipeline; Job* currentJob; bool jobRunning; // parser stuff ImapParser *parser; }; } #endif diff --git a/akonadi/tests/collectionjobtest.cpp b/akonadi/tests/collectionjobtest.cpp index 6ea68cf39..add714360 100644 --- a/akonadi/tests/collectionjobtest.cpp +++ b/akonadi/tests/collectionjobtest.cpp @@ -1,602 +1,604 @@ /* Copyright (c) 2006 Volker Krause 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 #include "collectionjobtest.h" #include #include "test_utils.h" #include "testattribute.h" #include "agentmanager.h" #include "agentinstance.h" #include "attributefactory.h" #include "cachepolicy.h" #include "collection.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "collectionselectjob_p.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "collectionpathresolver_p.h" #include "collectionutils_p.h" #include "control.h" #include "item.h" #include "kmime/messageparts.h" #include "resourceselectjob_p.h" #include #include using namespace Akonadi; QTEST_AKONADIMAIN( CollectionJobTest, NoGUI ) void CollectionJobTest::initTestCase() { qRegisterMetaType(); AttributeFactory::registerAttribute(); Control::start(); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } static Collection findCol( const Collection::List &list, const QString &name ) { foreach ( const Collection &col, list ) if ( col.name() == name ) return col; return Collection(); } // list compare which ignores the order template static void compareLists( const QList &l1, const QList &l2 ) { QCOMPARE( l1.count(), l2.count() ); foreach ( const T entry, l1 ) { QVERIFY( l2.contains( entry ) ); } } template static T* extractAttribute( QList attrs ) { T dummy; foreach ( Attribute* attr, attrs ) { if ( attr->type() == dummy.type() ) return dynamic_cast( attr ); } return 0; } static Collection::Id res1ColId = 6; // -1; static Collection::Id res2ColId = 7; //-1; static Collection::Id res3ColId = -1; static Collection::Id searchColId = -1; void CollectionJobTest::testTopLevelList( ) { // non-recursive top-level list CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); // check if everything is there and has the correct types and attributes QCOMPARE( list.count(), 4 ); Collection col; col = findCol( list, "res1" ); QVERIFY( col.isValid() ); res1ColId = col.id(); // for the next test QVERIFY( res1ColId > 0 ); QVERIFY( CollectionUtils::isResource( col ) ); QCOMPARE( col.parent(), Collection::root().id() ); QCOMPARE( col.resource(), QLatin1String("akonadi_knut_resource_0") ); QVERIFY( findCol( list, "res2" ).isValid() ); res2ColId = findCol( list, "res2" ).id(); QVERIFY( res2ColId > 0 ); QVERIFY( findCol( list, "res3" ).isValid() ); res3ColId = findCol( list, "res3" ).id(); QVERIFY( res3ColId > 0 ); col = findCol( list, "Search" ); searchColId = col.id(); QVERIFY( col.isValid() ); QVERIFY( CollectionUtils::isVirtualParent( col ) ); QCOMPARE( col.resource(), QLatin1String("akonadi_search_resource") ); } void CollectionJobTest::testFolderList( ) { // recursive list of physical folders CollectionFetchJob *job = new CollectionFetchJob( Collection( res1ColId ), CollectionFetchJob::Recursive ); QSignalSpy spy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); QVERIFY( spy.isValid() ); QVERIFY( job->exec() ); Collection::List list = job->collections(); int count = 0; for ( int i = 0; i < spy.count(); ++i ) { Collection::List l = spy[i][0].value(); for ( int j = 0; j < l.count(); ++j ) { QVERIFY( list.count() > count + j ); QCOMPARE( list[count + j].id(), l[j].id() ); } count += l.count(); } QCOMPARE( count, list.count() ); // check if everything is there QCOMPARE( list.count(), 4 ); Collection col; QStringList contentTypes; col = findCol( list, "foo" ); QVERIFY( col.isValid() ); QCOMPARE( col.parent(), res1ColId ); QVERIFY( CollectionUtils::isFolder( col ) ); contentTypes << "message/rfc822" << "text/calendar" << "text/directory" << "application/octet-stream" << "inode/directory"; compareLists( col.contentMimeTypes(), contentTypes ); QVERIFY( findCol( list, "bar" ).isValid() ); QCOMPARE( findCol( list, "bar" ).parent(), col.id() ); QVERIFY( findCol( list, "bla" ).isValid() ); } void CollectionJobTest::testNonRecursiveFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( res1ColId ), CollectionFetchJob::Base ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 1 ); QVERIFY( findCol( list, "res1" ).isValid() ); } void CollectionJobTest::testEmptyFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testSearchFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( searchColId ), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testResourceFolderList() { // non-existing resource CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->setResource( "i_dont_exist" ); QVERIFY( !job->exec() ); // recursive listing of all collections of an existing resource job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); job->setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 5 ); QVERIFY( findCol( list, "res1" ).isValid() ); QVERIFY( findCol( list, "foo" ).isValid() ); QVERIFY( findCol( list, "bar" ).isValid() ); QVERIFY( findCol( list, "bla" ).isValid() ); int fooId = findCol( list, "foo" ).id(); // limited listing of a resource job = new CollectionFetchJob( Collection( fooId ), CollectionFetchJob::Recursive ); job->setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); list = job->collections(); QCOMPARE( list.count(), 3 ); QVERIFY( findCol( list, "bar" ).isValid() ); QVERIFY( findCol( list, "bla" ).isValid() ); } void CollectionJobTest::testCreateDeleteFolder_data() { QTest::addColumn("collection"); QTest::addColumn("creatable"); Collection col; QTest::newRow("empty") << col << false; col.setName( "new folder" ); col.setParent( res3ColId ); QTest::newRow("simple") << col << true; col.setParent( res3ColId ); col.setName( "foo" ); QTest::newRow( "existing in different resource" ) << col << true; col.setName( "mail folder" ); QStringList mimeTypes; mimeTypes << "inode/directory" << "message/rfc822"; col.setContentMimeTypes( mimeTypes ); col.setRemoteId( "remote id" ); CachePolicy policy; policy.setInheritFromParent( false ); policy.setIntervalCheckTime( 60 ); policy.setLocalParts( QStringList( MessagePart::Envelope ) ); policy.setSyncOnDemand( true ); policy.setCacheTimeout( 120 ); col.setCachePolicy( policy ); QTest::newRow( "complex" ) << col << true; col = Collection(); col.setName( "New Folder" ); col.setParent( searchColId ); QTest::newRow( "search folder" ) << col << false; col.setParent( res2ColId ); col.setName( "foo2" ); QTest::newRow( "already existing" ) << col << false; col.setName( "Bla" ); col.setParent( 2 ); QTest::newRow( "already existing with different case" ) << col << true; CollectionPathResolver *resolver = new CollectionPathResolver( "res2/foo2", this ); QVERIFY( resolver->exec() ); col.setParent( resolver->collection() ); col.setName( "new folder" ); QTest::newRow( "parent noinferior" ) << col << false; col.setParent( INT_MAX ); QTest::newRow( "missing parent" ) << col << false; col = Collection(); col.setName( "rid parent" ); col.setParentRemoteId( "8" ); QTest::newRow( "rid parent" ) << col << false; // missing resource context } void CollectionJobTest::testCreateDeleteFolder() { QFETCH( Collection, collection ); QFETCH( bool, creatable ); CollectionCreateJob *createJob = new CollectionCreateJob( collection, this ); QCOMPARE( createJob->exec(), creatable ); if ( !creatable ) return; Collection createdCol = createJob->collection(); QVERIFY( createdCol.isValid() ); QCOMPARE( createdCol.name(), collection.name() ); QCOMPARE( createdCol.parent(), collection.parent() ); QCOMPARE( createdCol.remoteId(), collection.remoteId() ); QCOMPARE( createdCol.cachePolicy(), collection.cachePolicy() ); CollectionFetchJob *listJob = new CollectionFetchJob( Collection( collection.parent() ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); Collection listedCol = findCol( listJob->collections(), collection.name() ); QCOMPARE( listedCol, createdCol ); QCOMPARE( listedCol.remoteId(), collection.remoteId() ); QCOMPARE( listedCol.cachePolicy(), collection.cachePolicy() ); // fetch parent to compare inherited collection properties Collection parentCol = Collection::root(); if ( collection.parent() > 0 ) { CollectionFetchJob *listJob = new CollectionFetchJob( Collection( collection.parent() ), CollectionFetchJob::Base, this ); AKVERIFYEXEC( listJob ); QCOMPARE( listJob->collections().count(), 1 ); parentCol = listJob->collections().first(); } if ( collection.contentMimeTypes().isEmpty() ) compareLists( listedCol.contentMimeTypes(), parentCol.contentMimeTypes() ); else compareLists( listedCol.contentMimeTypes(), collection.contentMimeTypes() ); if ( collection.resource().isEmpty() ) QCOMPARE( listedCol.resource(), parentCol.resource() ); else QCOMPARE( listedCol.resource(), collection.resource() ); CollectionDeleteJob *delJob = new CollectionDeleteJob( createdCol, this ); AKVERIFYEXEC( delJob ); listJob = new CollectionFetchJob( Collection( collection.parent() ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); QVERIFY( !findCol( listJob->collections(), collection.name() ).isValid() ); } void CollectionJobTest::testIllegalDeleteFolder() { // non-existing folder CollectionDeleteJob *del = new CollectionDeleteJob( Collection( INT_MAX ), this ); QVERIFY( !del->exec() ); // root del = new CollectionDeleteJob( Collection::root(), this ); QVERIFY( !del->exec() ); } void CollectionJobTest::testStatistics() { // empty folder CollectionStatisticsJob *statistics = new CollectionStatisticsJob( Collection( res1ColId ), this ); QVERIFY( statistics->exec() ); CollectionStatistics s = statistics->statistics(); QCOMPARE( s.count(), 0ll ); QCOMPARE( s.unreadCount(), 0ll ); // folder with attributes and content CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this );; QVERIFY( resolver->exec() ); statistics = new CollectionStatisticsJob( Collection( resolver->collection() ), this ); QVERIFY( statistics->exec() ); s = statistics->statistics(); QCOMPARE( s.count(), 15ll ); QCOMPARE( s.unreadCount(), 14ll ); } void CollectionJobTest::testModify_data() { QTest::addColumn( "uid" ); QTest::addColumn( "rid" ); QTest::newRow( "uid" ) << collectionIdFromPath( "res1/foo" ) << QString(); QTest::newRow( "rid" ) << -1ll << QString( "10" ); } #define RESET_COLLECTION_ID \ col.setId( uid ); \ if ( !rid.isEmpty() ) col.setRemoteId( rid ) void CollectionJobTest::testModify() { QFETCH( qint64, uid ); QFETCH( QString, rid ); if ( !rid.isEmpty() ) { ResourceSelectJob *rjob = new ResourceSelectJob( "akonadi_knut_resource_0" ); AKVERIFYEXEC( rjob ); } QStringList reference; reference << "text/calendar" << "text/directory" << "message/rfc822" << "application/octet-stream" << "inode/directory"; Collection col; RESET_COLLECTION_ID; // test noop modify CollectionModifyJob *mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); CollectionFetchJob* ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); compareLists( col.contentMimeTypes(), reference ); // test clearing content types RESET_COLLECTION_ID; col.setContentMimeTypes( QStringList() ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); QVERIFY( col.contentMimeTypes().isEmpty() ); // test setting contnet types RESET_COLLECTION_ID; col.setContentMimeTypes( reference ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); compareLists( col.contentMimeTypes(), reference ); // add attribute RESET_COLLECTION_ID; col.attribute( Collection::AddIfMissing )->data = "new"; mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QVERIFY( ljob->collections().first().hasAttribute() ); QCOMPARE( ljob->collections().first().attribute()->data, QByteArray( "new" ) ); // modify existing attribute RESET_COLLECTION_ID; col.attribute()->data = "modified"; mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QVERIFY( ljob->collections().first().hasAttribute() ); QCOMPARE( ljob->collections().first().attribute()->data, QByteArray( "modified" ) ); // renaming RESET_COLLECTION_ID; col.setName( "foo (renamed)" ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); QCOMPARE( col.name(), QString( "foo (renamed)" ) ); RESET_COLLECTION_ID; col.setName( "foo" ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); } #undef RESET_COLLECTION_ID void CollectionJobTest::testIllegalModify() { // non-existing collection Collection col( INT_MAX ); col.setParent( res1ColId ); CollectionModifyJob *mod = new CollectionModifyJob( col, this ); QVERIFY( !mod->exec() ); // rename to already existing name col = Collection( res1ColId ); col.setName( "res2" ); mod = new CollectionModifyJob( col, this ); QVERIFY( !mod->exec() ); } void CollectionJobTest::testUtf8CollectionName() { QString folderName = QString::fromUtf8( "ä" ); // create collection Collection col; col.setParent( res3ColId ); col.setName( folderName ); CollectionCreateJob *create = new CollectionCreateJob( col, this ); QVERIFY( create->exec() ); col = create->collection(); QVERIFY( col.isValid() ); // list parent CollectionFetchJob *list = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::Recursive, this ); QVERIFY( list->exec() ); QCOMPARE( list->collections().count(), 1 ); QCOMPARE( col, list->collections().first() ); QCOMPARE( col.name(), folderName ); // modify collection col.setContentMimeTypes( QStringList( "message/rfc822'" ) ); CollectionModifyJob *modify = new CollectionModifyJob( col, this ); QVERIFY( modify->exec() ); // collection statistics CollectionStatisticsJob *statistics = new CollectionStatisticsJob( col, this ); QVERIFY( statistics->exec() ); CollectionStatistics s = statistics->statistics(); QCOMPARE( s.count(), 0ll ); QCOMPARE( s.unreadCount(), 0ll ); // delete collection CollectionDeleteJob *del = new CollectionDeleteJob( col, this ); QVERIFY( del->exec() ); } void CollectionJobTest::testMultiList() { Collection::List req; req << Collection( res1ColId ) << Collection( res2ColId ); CollectionFetchJob* job = new CollectionFetchJob( req, this ); QVERIFY( job->exec() ); Collection::List res; res = job->collections(); compareLists( res, req ); } void CollectionJobTest::testSelect() { CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this );; QVERIFY( resolver->exec() ); Collection col( resolver->collection() ); CollectionSelectJob *job = new CollectionSelectJob( col, this ); QVERIFY( job->exec() ); QCOMPARE( job->unseen(), -1 ); job = new CollectionSelectJob( col, this ); job->setRetrieveStatus( true ); QVERIFY( job->exec() ); QVERIFY( job->unseen() > -1 ); job = new CollectionSelectJob( Collection::root(), this ); QVERIFY( job->exec() ); job = new CollectionSelectJob( Collection( INT_MAX ), this ); QVERIFY( !job->exec() ); } void CollectionJobTest::testRidFetch() { Collection col; col.setRemoteId( "10" ); CollectionFetchJob *job = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); job->setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); QCOMPARE( job->collections().count(), 1 ); col = job->collections().first(); QVERIFY( col.isValid() ); QCOMPARE( col.remoteId(), QString::fromLatin1( "10" ) ); } -void CollectionJobTest::testRidCreate() +void CollectionJobTest::testRidCreateDelete() { Collection collection; collection.setName( "rid create" ); collection.setParentRemoteId( "8" ); + collection.setRemoteId( "MY REMOTE ID" ); ResourceSelectJob *resSel = new ResourceSelectJob( "akonadi_knut_resource_2" ); AKVERIFYEXEC( resSel ); CollectionCreateJob *createJob = new CollectionCreateJob( collection, this ); AKVERIFYEXEC( createJob ); Collection createdCol = createJob->collection(); QVERIFY( createdCol.isValid() ); QCOMPARE( createdCol.name(), collection.name() ); CollectionFetchJob *listJob = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); Collection listedCol = findCol( listJob->collections(), collection.name() ); QCOMPARE( listedCol, createdCol ); QCOMPARE( listedCol.name(), collection.name() ); - CollectionDeleteJob *delJob = new CollectionDeleteJob( createdCol, this ); + QVERIFY( !collection.isValid() ); + CollectionDeleteJob *delJob = new CollectionDeleteJob( collection, this ); AKVERIFYEXEC( delJob ); listJob = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); QVERIFY( !findCol( listJob->collections(), collection.name() ).isValid() ); } #include "collectionjobtest.moc" diff --git a/akonadi/tests/collectionjobtest.h b/akonadi/tests/collectionjobtest.h index 8def92d2d..3ecc3d6db 100644 --- a/akonadi/tests/collectionjobtest.h +++ b/akonadi/tests/collectionjobtest.h @@ -1,51 +1,51 @@ /* Copyright (c) 2006 Volker Krause 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 COLLECTIONJOBTEST_H #define COLLECTIONJOBTEST_H #include class CollectionJobTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testTopLevelList(); void testFolderList(); void testNonRecursiveFolderList(); void testEmptyFolderList(); void testSearchFolderList(); void testResourceFolderList(); void testCreateDeleteFolder_data(); void testCreateDeleteFolder(); void testIllegalDeleteFolder(); void testStatistics(); void testModify_data(); void testModify(); void testIllegalModify(); void testUtf8CollectionName(); void testMultiList(); void testSelect(); void testRidFetch(); - void testRidCreate(); + void testRidCreateDelete(); }; #endif diff --git a/kabc/plugins/dir/dir.desktop b/kabc/plugins/dir/dir.desktop index b53082700..75c57b057 100644 --- a/kabc/plugins/dir/dir.desktop +++ b/kabc/plugins/dir/dir.desktop @@ -1,49 +1,53 @@ [Desktop Entry] Name=Folder Name[ca]=Carpeta Name[da]=Mappe Name[de]=Ordner Name[el]=Φάκελος Name[es]=Carpeta Name[et]=Kataloog +Name[fr]=Dossier Name[ga]=Fillteán Name[gl]=Cartafol +Name[it]=Cartella Name[km]=ថត Name[lv]=Mape Name[nb]=Mappe Name[nds]=Orner Name[nn]=Mappe Name[pt]=Pasta Name[pt_BR]=Pasta Name[sv]=Katalog Name[tr]=Dizin Name[uk]=Тека Name[x-test]=xxFolderxx Name[zh_CN]=文件夹 Name[zh_TW]=資料夾 Comment=Provides access to contacts, each stored in a single file, in a given folder. Supports standard VCard file and other formats depending on availability of plugins. Comment[ca]=Proporciona l'accés als contactes, cada un emmagatzemat en un fitxer individual, en una carpeta donada. Accepta els fitxers estàndard VCard i altres formats depenent de la disponibilitat dels connectors. Comment[da]=Giver adgang til kontakter, hver lagret i en enkelt fil, i en given mappe. Understøtter standard VCard-fil og andre formater afhængigt af tilgængeligheden af plugins. Comment[de]=Ermöglicht Zugriff auf Kontakte, die in einzelnen Dateien in einem vorgegebenen Ordner gespeichert sind. Unterstützt Standard-VCard-Dateien und andere Formate abhängig von den verfügbaren Modulen. Comment[el]=Προσφέρει πρόσβαση σε επαφές, αποθηκευμένες σε ξεχωριστά αρχεία, σε ένα 
δοσμένο φάκελο. Υποστηρίζει τυπικά αρχεία VCard και άλλες μορφές αρχείων ανάλογα με τη διαθεσιμότητα των πρόσθετων. Comment[es]=Provee acceso a los contactos, cada uno almacenado en un archivo diferente, dentro de un directorio determinado. Soporta archivos VCard estándar y otros formatos dependiendo de la disponibilidad de los complementos Comment[et]=Võimaldab kasutada eraldi failidesse salvestatud kontakte määratud kataloogis. Toetab standardseid VCard-faile ja teisi vorminguid sõltuvalt pluginate olemasolust. +Comment[fr]=Fourni l'accès aux contacts stockés chacun dans un fichier dans le dossier indiqué. Le format VCard et d'autres formats sont pris en charge en fonction des modules externes disponibles Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha, gach ceann stóráilte i gcomhad aonair, i bhfillteán sonraithe. Tacaítear le comhaid v-Chárta agus le formáidí eile, ag brath ar na breiseáin atá ar fáil. Comment[gl]=Dá acceso aos contactos, cada un gardado nun ficheiro nun cartafol dado. Admite ficheiros vCard estándar e outros formatos, en función da dispoñibilidade de extensións. +Comment[it]=Fornisce accesso a contatti, ciascuno dei quali memorizzato in un singolo file in una cartella data. Gestisce file in standard VCard e altri formati in base ai plugin disponibili. Comment[km]=ផ្ដល់​ការ​ចូលដំណើរការ​ទៅ​កាន់​ទំនាក់ទំនង ដែល​ទំនាក់ទំនង​នីមួយៗ​ត្រូវ​បានផ្ទុកនៅ​ក្នុង​ឯកសារ​តែ​មួយ នៅ​ក្នុង​ថត​ដែល​បានផ្ដល់​ឲ្យ ។ គាំទ្រ​ឯកសារ VCard និង​ទ្រង់ទ្រាយ​ផ្សេងៗ​ ដោយ​អាស្រ័យ​លើ​កម្មវិធី​ជំនួយ​ដែល​មាន ។ Comment[lv]=Nodrošina pieeju kontaktiem, kas katrs saglabāts individuālā failā norādītajā mapē. Atbalsta standarta VCard failus un citus formātus, atkarībā no pieejamajiem spraudņiem. Comment[nb]=Gir tilgang til kontakter, lagret hver for seg i en enkelt fil, i en gitt mappe. Støtter standard vCard og andre formater avhengig av tilgjengelige programtillegg. Comment[nds]=Stellt Togriep op Kontakten praat, elkeen binnen een Datei binnen en angeven Orner. Ünnerstütt VCard-Dateien un anner Formaten na de verföögboren Modulen. Comment[nn]=Gjev tilgang til kontaktar i ei viss mappe, der dei er lagra i kvar sine filer. Standard vCard-filer er støtta, i tillegg til andre format, avhengig av kva programtillegg som er i bruk. Comment[pt]=Oferece o acesso aos contactos, estando cada um guardado num único ficheiro de uma dada pasta. Suporta os ficheiros VCard normais, assim como outros formatos, dependendo da disponibilidade dos 'plugins'. Comment[pt_BR]=Fornece acesso aos contatos, cada um armazenado em um único arquivo, em uma pasta informada. Suporta o formato de arquivos VCard padrão e outros formatos, dependendo da disponibilidade de plug-ins. Comment[sv]=Ger tillgång till kontakter, var och en lagrad i en enstaka fil, i en given katalog. Stöder vCard-standardfiler och andra format, beroende på tillgängliga insticksprogram. Comment[uk]=Надає доступ до контактів, кожен з яких зберігається у окремому файлі у вказаній теці. Підтримує стандартні файли VCard та файли у інших форматах, залежно від наявності відповідних додатків. Comment[x-test]=xxProvides access to contacts, each stored in a single file, in a given folder. Supports standard VCard file and other formats depending on availability of plugins.xx Comment[zh_CN]=提供对被存储在指定目录下的单独一个文件中的联系人的访问支持。支持标准 VCard 文件和其它插件所允许的格式。 Comment[zh_TW]=提供存取特定目錄下,個別儲存在單一檔案中的聯絡人的功能。支援標準的 VCard 檔,以及外掛程式所提供的其它格式檔案。 X-KDE-Library=kabc_directory Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=dir diff --git a/kabc/plugins/file/file.desktop b/kabc/plugins/file/file.desktop index b40a82369..376233baf 100644 --- a/kabc/plugins/file/file.desktop +++ b/kabc/plugins/file/file.desktop @@ -1,69 +1,71 @@ [Desktop Entry] Name=File Name[be]=Файл Name[ca]=Fitxer Name[cs]=Soubor Name[da]=Fil Name[de]=Datei Name[el]=Αρχείο Name[es]=Archivo Name[et]=Fail Name[fr]=Fichier Name[ga]=Comhad Name[gl]=Ficheiro Name[hne]=फाइल Name[hu]=Fájl Name[ja]=ファイル Name[km]=ឯកសារ Name[lt]=Failas Name[lv]=Fails Name[nb]=Fil Name[nds]=Datei Name[nl]=Bestand Name[nn]=Fil Name[oc]=Fichièr Name[pa]=ਫਾਇਲ Name[pl]=Plik Name[pt]=Ficheiro Name[pt_BR]=Arquivo Name[ro]=Fișier Name[ru]=Файл Name[se]=Fiila Name[sk]=Súbor Name[sl]=Datoteka Name[sr]=Фајл Name[sr@latin]=Fajl Name[sv]=Fil Name[th]=แฟ้ม Name[tr]=Dosya Name[uk]=Файл Name[x-test]=xxFilexx Name[zh_CN]=文件 Name[zh_TW]=檔案 Comment=Provides access to contacts stored in a single local file. Supports standard VCard files and other formats depending on available plugins. Comment[ca]=Proporciona l'accés als contactes emmagatzemats en un fitxer individual. Accepta els fitxers estàndard VCard i altres formats, depenent dels connectors disponibles. Comment[da]=Giver adgang til kontakter, hver lagret i en enkelt fil. Understøtter standard VCard-fil og andre formater afhængigt af tilgængelige af plugins. Comment[de]=Ermöglicht Zugriff auf Kontakte, die in einer einzigen Dateien lokal gespeichert sind. Unterstützt Standard-VCard-Dateien und andere Formate abhängig von den verfügbaren Modulen. Comment[el]=Προσφέρει πρόσβαση σε επαφές, αποθηκευμένες σε ένα τοπικό αρχείο. Υποστηρίζει τυπικά αρχεία VCard και άλλες μορφές αρχείων ανάλογα με τη διαθεσιμότητα των πρόσθετων. Comment[es]=Provee acceso a los contactos almacenados en un único archivo local. Soporta archivos VCard estándar y otros formatos dependiendo de la disponibilidad de los componentes. Comment[et]=Võimaldab kasutada kohalikku faili salvestatud kontakte. Toetab standardseid VCard-faile ja teisi vorminguid sõltuvalt pluginate olemasolust. +Comment[fr]=Fourni l'accès aux contacts stockés dans un seul fichier local. Le format VCard et d'autres formats sont pris en charge en fonction des modules externes disponibles. Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha, stóráilte i gcomhad aonair. Tacaítear le comhaid v-Chárta agus formáidí eile, ag brath ar na breiseáin atá ar fáil. Comment[gl]=Dá acceso a contactos gardados nun único ficheiro local. Admite ficheiros vCard estándar e outros formatos en función das extensións dispoñíbeis. +Comment[it]=Fornisce accesso a contatti memorizzati in un singolo file locale. Gestisce file in standard VCard e altri formati in base ai plugin disponibili. Comment[ja]=単一のローカルファイルに保存されている連絡先へのアクセスを提供します。標準の VCard ファイルと、利用可能なプラグインに応じたフォーマットをサポートします。 Comment[km]=ផ្ដល់​កា​រចូលដំណើរការ​ទៅ​ទំនាក់ទំនង​​នៅ​ក្នុង​ឯកសារ​មូលដ្ឋាន​តែ​មួយ ។ គាំទ្រ​ឯកសារ VCard ស្តង់ដារ និង​ទ្រង់ទ្រាយ​ផ្សេងៗ​ ដោយ​អាស្រ័យ​លើ​កម្មវិធី​ជំនួយ​ដែល​មាន ។ Comment[lv]=Nodrošina piekļuvi kontaktiem, kas glabājas vienā lokālā failā. Atbalsta standarta VCard failus un citus formātus, atkarībā no pieejamajiem spraudņiem. Comment[nb]=Gir tilgang til kontakter, lagret i en enkelt lokal fil. Støtter standard vCard og andre formater avhengig av tilgjengelige programtillegg. Comment[nds]=Stellt Togriep op Kontakten praat, de in een enkel lokaal Datei wohrt warrt. Ünnerstütt VCard-Dateien un anner Formaten na de 'verföögboren Modulen. Comment[nn]=Gjev tilgang til kontaktar lagra i ei einskild lokal fil. Standard vCard-filer er støtta, i tillegg til andre format, avhengig av kva programtillegg som er i bruk. Comment[pt]=Oferece o acesso aos contactos, estando todos guardados num único ficheiro local. Suporta os ficheiros VCard normais, assim como outros formatos, dependendo da disponibilidade dos 'plugins'. Comment[pt_BR]=Fornece acesso aos contatos armazenados em um único arquivo local. Suporta o formato de arquivos VCard padrão e outros formatos, dependendo da disponibilidade de plug-ins. Comment[sv]=Ger tillgång till kontakter lagrade i en enda lokal fil. Stöder vCard-standardfiler och andra format, beroende på tillgängliga insticksprogram. Comment[uk]=Надає доступ до контактів, які зберігаються у окремому локальному файлі. Підтримує стандартні файли VCard та файли у інших форматах, залежно від наявності відповідних додатків. Comment[x-test]=xxProvides access to contacts stored in a single local file. Supports standard VCard files and other formats depending on available plugins.xx Comment[zh_CN]=提供对被存储在单独的本地文件中的联系人的访问支持。支持标准 VCard 文件和其它插件所允许的格式。 Comment[zh_TW]=提供存取儲存在單一檔案中的聯絡人的功能。支援標準的 VCard 檔,以及外掛程式所提供的其它格式檔案。 X-KDE-Library=kabc_file Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=file diff --git a/kabc/plugins/net/net.desktop b/kabc/plugins/net/net.desktop index dee85a987..56b73203d 100644 --- a/kabc/plugins/net/net.desktop +++ b/kabc/plugins/net/net.desktop @@ -1,71 +1,73 @@ [Desktop Entry] Name=Network Name[be]=Сетка Name[ca]=Xarxa Name[cs]=Síť Name[da]=Netværk Name[de]=Netzwerk Name[el]=Δίκτυο Name[es]=Red Name[et]=Võrk Name[fr]=Réseau Name[ga]=Líonra Name[gl]=Rede Name[hne]=नेटवर्क Name[hu]=Hálózat Name[it]=Rete Name[ja]=ネットワーク Name[km]=បណ្តាញ Name[lt]=Tinklas Name[lv]=Tīkls Name[nb]=Nettverk Name[nds]=Nettwark Name[nl]=Netwerk Name[nn]=Nettverk Name[oc]=Ret Name[pa]=ਨੈੱਟਵਰਕ Name[pl]=Sieć Name[pt]=Rede Name[pt_BR]=Rede Name[ro]=Rețea Name[ru]=Сетевой файл Name[se]=Fierbmi Name[sk]=Sieť Name[sl]=Omrežje Name[sr]=Мрежа Name[sr@latin]=Mreža Name[sv]=Nätverk Name[th]=ระบบเครือข่าย Name[tr]=Ağ Name[uk]=Мережа Name[x-test]=xxNetworkxx Name[zh_CN]=网络 Name[zh_TW]=網路 Comment=Provides access to contacts in remote files using KDE's network framework KIO. Supports standard VCard files and other formats depending on available plugins. Comment[ca]=Proporciona l'accés als contactes en fitxers remots usant la infraestructura KIO de xarxa del KDE. Accepta els fitxers estàndard VCard i altres formats, depenent dels connectors disponibles. Comment[da]=Giver adgang til kontakter i eksterne filer med brug af KDE's netværks-framework KIO. Understøtter standard VCard-filer og andre formater afhængigt af tilgængelige plugins. Comment[de]=Ermöglicht Zugriff auf Kontakte in entfernten Dateien durch das KIO-Netzwerksystem von KDE. Unterstützt Standard-VCard-Dateien und andere Formate abhängig von den verfügbaren Modulen. Comment[el]=Προσφέρει πρόσβαση σε επαφές σε απομακρυσμένα αρχεία με τη χρήση του συστήματος KIO του KDE. Υποστηρίζει τυπικά αρχεία VCard και άλλες μορφές αρχείων ανάλογα με τη διαθεσιμότητα των πρόσθετων. Comment[es]=Provee acceso a los contactos en un archivo remoto utilizando la infraestructura de red KIO de KDE. Soporta archivos VCard estándar y otros formatos dependiendo en la disponibilidad de los complementos. Comment[et]=Võimaldab kasutada võrgufaile KDE võrguraamistiku KIO abil. Toetab standardseid VCard-faile ja teisi vorminguid sõltuvalt pluginate olemasolust. +Comment[fr]=Fourni l'accès aux contacts stockés dans des fichiers distants en utilisant le mécanisme réseau KIO de KDE. Le format VCard et d'autres formats sont pris en charge en fonction des modules externes disponibles. Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha i gcianchomhaid tríd an gcreatlach líonra KIO atá cuid de KDE. Tacaítear le comhaid v-Chárta agus formáidí eile, ag brath ar na breiseáin atá ar fáil. Comment[gl]=Dá acceso a contactos gardados en ficheiros remotos mediante a infraestrutura de rede KIO, de KDE. Admite ficheiros vCard estándar e outros formatos en función das extensións dispoñíbeis. +Comment[it]=Fornisce accesso a contatti su file remoti usando KIO, l'infrastruttura di rete di KDE. Gestisce file in standard VCard e altri formati in base ai plugin disponibili. Comment[ja]=KDE のネットワークフレームワーク KIO を使って、リモートファイルに保存されている連絡先へのアクセスを提供します。標準の VCard ファイルと、利用可能なプラグインに応じたフォーマットをサポートします。 Comment[km]=ផ្ដល់​កា​រចូលដំណើរការ​ទៅកាន់​ទំនាក់ទំនង​ក្នុង​​ឯកសារ​ពី​ចម្ងាយ ដោយ​ប្រើ KIO ​គ្រោងការណ៍​បណ្ដាញ​របស់ KDE ។ គាំទ្រ​ឯកសារ VCard ស្តង់ដារ និង​ទ្រង់ទ្រាយ​ផ្សេងៗ​ទៀត ដោយ​អាស្រ័យ​លើ​កម្មវិធី​ជំនួយ​ដែល​លមាន ។ Comment[lv]=Nodrošina piekļuvi kontaktiem attālinātos failus, izmantojot KDE tīklošanas ietvaru KIO. Atbalsta standarta VCard failus un citus formātus, atkarībā no pieejamajiem spraudņiem. Comment[nb]=Gir tilgang til kontakter i nettverksfiler, ved bruk av KDEs rammeverk KIO for nettverk. Støtter standard vCard og andre formater avhengig av tilgjengelige programtillegg. Comment[nds]=Stellt Togriep op Kontakten binnen feern Dateien praat, bruukt KDE sien Nettwark-Rahmenwark KIO. Ünnerstütt VCard-Dateien un anner Formaten na de verföögboren Modulen. Comment[nn]=Gjev tilgang til kontaktar i nettverksfiler ved å bruka KIO, nettverksrammeverket frå KDE. Standard vCard-filer er støtta, i tillegg til andre format, avhengig av kva programtillegg som er i bruk. Comment[pt]=Oferece o acesso aos contactos em ficheiros remotos, disponíveis através de um KIO da plataforma de rede do KDE. Suporta os ficheiros VCard normais, assim como outros formatos, dependendo da disponibilidade dos 'plugins'. Comment[pt_BR]=Fornece acesso aos contatos em arquivos remotos usando o KIO framework da rede do KDE. O suporte à arquivos VCard padrão e outros formatos dependem da disponibilidade de plug-ins. Comment[sv]=Ger tillgång till kontakter i fjärrfiler med användning av KDE:s I/O-ramverk. Stöder vCard-standardfiler och andra format, beroende på tillgängliga insticksprogram. Comment[tr]=KDE'nin ağ iskeleti KIO'yu kullanarak uzak dosyalardaki kişilere erişim sağlar. Mevcut eklentilere bağlı olarak standart VCard dosyalarını ve diğer dosya biçimleri destekler. Comment[uk]=Надає доступ до контактів, які зберігаються у віддалених файлах, за допомогою мережевих засобів KIO KDE. Підтримує стандартні файли VCard та файли у інших форматах, залежно від наявності відповідних додатків. Comment[x-test]=xxProvides access to contacts in remote files using KDE's network framework KIO. Supports standard VCard files and other formats depending on available plugins.xx Comment[zh_CN]=通过 KDE 的网络透明框架 KIO,提供对被存储在远程文件中的联系人的访问支持。支持标准 VCard 文件和其它插件所允许的格式。 Comment[zh_TW]=提供存取使用 KDE 網路架構 KIO 儲存在遠端檔案中的聯絡人。支援標準的 VCard 檔,以及外掛程式所提供的其它格式檔案。 X-KDE-Library=kabc_net Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=net diff --git a/kimap/sessionthread.cpp b/kimap/sessionthread.cpp index 34a832eed..6477b45f9 100644 --- a/kimap/sessionthread.cpp +++ b/kimap/sessionthread.cpp @@ -1,208 +1,209 @@ /* Copyright (c) 2009 Kevin Ottens 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 "sessionthread_p.h" #include #include #include #include "imapstreamparser.h" #include "message_p.h" #include "session.h" using namespace KIMAP; Q_DECLARE_METATYPE(KTcpSocket::Error) Q_DECLARE_METATYPE(KSslErrorUiData) static const int _kimap_socketErrorTypeId = qRegisterMetaType(); static const int _kimap_sslErrorUiData = qRegisterMetaType(); SessionThread::SessionThread( const QString &hostName, quint16 port, Session *parent ) : QThread(), m_hostName(hostName), m_port(port), m_session(parent), m_socket(0), m_stream(0), m_encryptedMode(false) { // Yeah, sounds weird, but QThread object is linked to the parent // thread not to itself, and I'm too lazy to introduce yet another // internal QObject moveToThread(this); } SessionThread::~SessionThread() { - quit(); + // don't call quit() directly, this will deadlock in wait() if exec() hasn't run yet + QMetaObject::invokeMethod( this, "quit" ); wait(); } void SessionThread::sendData( const QByteArray &payload ) { QMutexLocker locker(&m_mutex); m_dataQueue.enqueue( payload ); QTimer::singleShot( 0, this, SLOT( writeDataQueue() ) ); } void SessionThread::writeDataQueue() { QMutexLocker locker(&m_mutex); while ( !m_dataQueue.isEmpty() ) { m_socket->write( m_dataQueue.dequeue() ); } } void SessionThread::readMessage() { QMutexLocker locker(&m_mutex); if ( m_stream->availableDataSize()==0 ) { return; } Message message; QList *payload = &message.content; try { while ( !m_stream->atCommandEnd() ) { if ( m_stream->hasString() ) { *payload << Message::Part(m_stream->readString()); } else if ( m_stream->hasList() ) { *payload << Message::Part(m_stream->readParenthesizedList()); } else if ( m_stream->hasResponseCode() ) { payload = &message.responseCode; } else if ( m_stream->atResponseCodeEnd() ) { payload = &message.content; } else if ( m_stream->hasLiteral() ) { QByteArray literal; while ( !m_stream->atLiteralEnd() ) { literal+= m_stream->readLiteralPart(); } *payload << Message::Part(literal); } } emit responseReceived(message); } catch (KIMAP::ImapParserException e) { qWarning() << "The stream parser raised an exception:" << e.what(); } if ( m_stream->availableDataSize()>1 ) { QTimer::singleShot( 0, this, SLOT( readMessage() ) ); } } void SessionThread::closeSocket() { QMutexLocker locker(&m_mutex); m_encryptedMode = false; QMetaObject::invokeMethod( m_socket, "close" ); } void SessionThread::reconnect() { QMutexLocker locker(&m_mutex); if ( m_socket->state() != SessionSocket::ConnectedState && m_socket->state() != SessionSocket::ConnectingState ) { if (m_encryptedMode) { m_socket->connectToHostEncrypted(m_hostName, m_port); } else { m_socket->connectToHost(m_hostName, m_port); } } } void SessionThread::run() { m_socket = new SessionSocket; m_stream = new ImapStreamParser( m_socket ); connect( m_socket, SIGNAL(readyRead()), this, SLOT(readMessage()), Qt::QueuedConnection ); connect( m_socket, SIGNAL(disconnected()), m_session, SLOT(socketDisconnected()) ); connect( m_socket, SIGNAL(connected()), m_session, SLOT(socketConnected()) ); connect( m_socket, SIGNAL(error(KTcpSocket::Error)), m_session, SLOT(socketError()) ); connect( this, SIGNAL(responseReceived(KIMAP::Message)), m_session, SLOT(responseReceived(KIMAP::Message)) ); QTimer::singleShot( 0, this, SLOT( reconnect() ) ); exec(); delete m_stream; delete m_socket; } void SessionThread::startSsl(const KTcpSocket::SslVersion &version) { QMutexLocker locker(&m_mutex); m_socket->setAdvertisedSslVersion(version); m_socket->ignoreSslErrors(); connect(m_socket, SIGNAL(encrypted()), this, SLOT(sslConnected())); m_socket->startClientEncryption(); } void SessionThread::sslConnected() { QMutexLocker locker(&m_mutex); KSslCipher cipher = m_socket->sessionCipher(); if ( m_socket->sslErrors().count() > 0 || m_socket->encryptionMode() != KTcpSocket::SslClientMode || cipher.isNull() || cipher.usedBits() == 0) { kDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslErrors().count() << "items."; KSslErrorUiData errorData(m_socket); emit sslError(errorData); } else { kDebug() << "TLS negotiation done."; m_encryptedMode = true; emit encryptionNegotiationResult(true); } } void SessionThread::sslErrorHandlerResponse(bool response) { QMutexLocker locker(&m_mutex); if (response) { m_encryptedMode = true; emit encryptionNegotiationResult(true); } else { m_encryptedMode = false; //reconnect in unencrypted mode, so new commands can be issued m_socket->disconnectFromHost(); m_socket->waitForDisconnected(); m_socket->connectToHost(m_hostName, m_port); emit encryptionNegotiationResult(false); } } #include "sessionthread_p.moc"