diff --git a/akonadi/changerecorder.cpp b/akonadi/changerecorder.cpp index 4eb55fad3..84cb9f8ef 100644 --- a/akonadi/changerecorder.cpp +++ b/akonadi/changerecorder.cpp @@ -1,193 +1,194 @@ /* 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 "changerecorder.h" #include "monitor_p.h" #include #include using namespace Akonadi; class Akonadi::ChangeRecorderPrivate : public MonitorPrivate { public: ChangeRecorderPrivate( ChangeRecorder* parent ) : MonitorPrivate( parent ), settings( 0 ), enableChangeRecording( true ) { } Q_DECLARE_PUBLIC( ChangeRecorder ) NotificationMessage::List pendingNotifications; QSettings *settings; bool enableChangeRecording; virtual void slotNotify( const NotificationMessage::List &msgs ) { if ( !enableChangeRecording ) { foreach( const NotificationMessage &msg, msgs ) processNotification( msg ); return; } Q_Q( ChangeRecorder ); int oldChanges = pendingNotifications.count(); foreach ( const NotificationMessage &msg, msgs ) { if ( acceptNotification( msg ) ) NotificationMessage::appendAndCompress( pendingNotifications, msg ); } if ( pendingNotifications.count() != oldChanges ) { saveNotifications(); emit q->changesAdded(); } } void loadNotifications() { pendingNotifications.clear(); QStringList list; settings->beginGroup( QLatin1String( "ChangeRecorder" ) ); int size = settings->beginReadArray( QLatin1String( "change" ) ); for ( int i = 0; i < size; ++i ) { settings->setArrayIndex( i ); NotificationMessage msg; msg.setSessionId( settings->value( QLatin1String( "sessionId" ) ).toByteArray() ); msg.setType( (NotificationMessage::Type)settings->value( QLatin1String( "type" ) ).toInt() ); msg.setOperation( (NotificationMessage::Operation)settings->value( QLatin1String( "op" ) ).toInt() ); msg.setUid( settings->value( QLatin1String( "uid" ) ).toLongLong() ); msg.setRemoteId( settings->value( QLatin1String( "rid" ) ).toString() ); msg.setResource( settings->value( QLatin1String( "resource" ) ).toByteArray() ); msg.setParentCollection( settings->value( QLatin1String( "parentCol" ) ).toLongLong() ); msg.setParentDestCollection( settings->value( QLatin1String( "parentDestCol" ) ).toLongLong() ); msg.setMimeType( settings->value( QLatin1String( "mimeType" ) ).toString() ); list = settings->value( QLatin1String( "itemParts" ) ).toStringList(); QSet itemParts; Q_FOREACH( const QString &entry, list ) itemParts.insert( entry.toLatin1() ); msg.setItemParts( itemParts ); pendingNotifications << msg; } settings->endArray(); settings->endGroup(); } void saveNotifications() { if ( !settings ) return; settings->beginGroup( QLatin1String( "ChangeRecorder" ) ); settings->beginWriteArray( QLatin1String( "change" ), pendingNotifications.count() ); for ( int i = 0; i < pendingNotifications.count(); ++i ) { settings->setArrayIndex( i ); NotificationMessage msg = pendingNotifications.at( i ); settings->setValue( QLatin1String( "sessionId" ), msg.sessionId() ); settings->setValue( QLatin1String( "type" ), msg.type() ); settings->setValue( QLatin1String( "op" ), msg.operation() ); settings->setValue( QLatin1String( "uid" ), msg.uid() ); settings->setValue( QLatin1String( "rid" ), msg.remoteId() ); settings->setValue( QLatin1String( "resource" ), msg.resource() ); settings->setValue( QLatin1String( "parentCol" ), msg.parentCollection() ); settings->setValue( QLatin1String( "parentDestCol" ), msg.parentDestCollection() ); settings->setValue( QLatin1String( "mimeType" ), msg.mimeType() ); QStringList list; const QSet itemParts = msg.itemParts(); QSetIterator it( itemParts ); while ( it.hasNext() ) list.append( QString::fromLatin1( it.next() ) ); settings->setValue( QLatin1String( "itemParts" ), list ); } settings->endArray(); settings->endGroup(); } }; ChangeRecorder::ChangeRecorder(QObject * parent) : Monitor( new ChangeRecorderPrivate( this ), parent ) { Q_D( ChangeRecorder ); + d->init(); d->connectToNotificationManager(); } ChangeRecorder::~ ChangeRecorder() { Q_D( ChangeRecorder ); d->saveNotifications(); } void ChangeRecorder::setConfig(QSettings * settings) { Q_D( ChangeRecorder ); if ( settings ) { d->settings = settings; Q_ASSERT( d->pendingNotifications.isEmpty() ); d->loadNotifications(); } else if ( d->settings ) { d->saveNotifications(); d->settings = settings; } } void ChangeRecorder::replayNext() { bool nothing = true; Q_D( ChangeRecorder ); while( !d->pendingNotifications.isEmpty() ) { const NotificationMessage msg = d->pendingNotifications.first(); if ( d->processNotification( msg ) ) { nothing = false; break; } d->pendingNotifications.takeFirst(); } if( nothing ) { // This is necessary when none of the notifications were accepted / processed // above, and so there is no one to call changeProcessed() and the ChangeReplay task // will be stuck forever in the ResourceScheduler. emit nothingToReplay(); } d->saveNotifications(); } bool ChangeRecorder::isEmpty() const { Q_D( const ChangeRecorder ); return d->pendingNotifications.isEmpty(); } void ChangeRecorder::changeProcessed() { Q_D( ChangeRecorder ); if ( !d->pendingNotifications.isEmpty() ) d->pendingNotifications.removeFirst(); d->saveNotifications(); } void ChangeRecorder::setChangeRecordingEnabled( bool enable ) { Q_D( ChangeRecorder ); d->enableChangeRecording = enable; Q_ASSERT( enable || d->pendingNotifications.isEmpty() ); } #include "changerecorder.moc" diff --git a/akonadi/collectioncreatejob.cpp b/akonadi/collectioncreatejob.cpp index 9f94c9087..2baf48efc 100644 --- a/akonadi/collectioncreatejob.cpp +++ b/akonadi/collectioncreatejob.cpp @@ -1,113 +1,113 @@ /* 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 "collectioncreatejob.h" #include "imapparser_p.h" #include "protocolhelper_p.h" #include "job_p.h" #include using namespace Akonadi; class Akonadi::CollectionCreateJobPrivate : public JobPrivate { public: CollectionCreateJobPrivate( CollectionCreateJob *parent ) : JobPrivate( parent ) { } Collection mCollection; }; CollectionCreateJob::CollectionCreateJob( const Collection &collection, QObject * parent ) : Job( new CollectionCreateJobPrivate( this ), parent ) { Q_D( CollectionCreateJob ); d->mCollection = collection; } CollectionCreateJob::~CollectionCreateJob( ) { } void CollectionCreateJob::doStart( ) { Q_D( CollectionCreateJob ); - if ( d->mCollection.parent() < 0 && d->mCollection.parentRemoteId().isEmpty() ) { + if ( d->mCollection.parentCollection().id() < 0 && d->mCollection.parentCollection().remoteId().isEmpty() ) { setError( Unknown ); setErrorText( QLatin1String("Invalid parent") ); emitResult(); return; } QByteArray command = d->newTag(); - if ( d->mCollection.parent() < 0 ) + if ( d->mCollection.parentCollection().id() < 0 ) command += " RID"; command += " CREATE \"" + d->mCollection.name().toUtf8() + "\" "; - if ( d->mCollection.parent() >= 0 ) - command += QByteArray::number( d->mCollection.parent() ); + if ( d->mCollection.parentCollection().id() >= 0 ) + command += QByteArray::number( d->mCollection.parentCollection().id() ); else - command += ImapParser::quote( d->mCollection.parentRemoteId().toUtf8() ); + command += ImapParser::quote( d->mCollection.parentCollection().remoteId().toUtf8() ); command += " ("; if ( !d->mCollection.contentMimeTypes().isEmpty() ) { QList cList; foreach( const QString &s, d->mCollection.contentMimeTypes() ) cList << s.toLatin1(); command += "MIMETYPE (" + ImapParser::join( cList, QByteArray(" ") ) + ')'; } command += " REMOTEID \"" + d->mCollection.remoteId().toUtf8() + '"'; foreach ( Attribute* attr, d->mCollection.attributes() ) command += ' ' + attr->type() + ' ' + ImapParser::quote( attr->serialized() ); command += ' ' + ProtocolHelper::cachePolicyToByteArray( d->mCollection.cachePolicy() ); command += ")\n"; d->writeData( command ); emitWriteFinished(); } Collection CollectionCreateJob::collection() const { Q_D( const CollectionCreateJob ); return d->mCollection; } void CollectionCreateJob::doHandleResponse(const QByteArray & tag, const QByteArray & data) { Q_D( CollectionCreateJob ); if ( tag == "*" ) { Collection col; ProtocolHelper::parseCollection( data, col ); if ( !col.isValid() ) return; - col.setParent( d->mCollection.parent() ); + col.setParentCollection( d->mCollection.parentCollection() ); col.setName( d->mCollection.name() ); col.setRemoteId( d->mCollection.remoteId() ); d->mCollection = col; } else { Job::doHandleResponse( tag, data ); } } #include "collectioncreatejob.moc" diff --git a/akonadi/entitycache_p.h b/akonadi/entitycache_p.h index 961168473..eda286102 100644 --- a/akonadi/entitycache_p.h +++ b/akonadi/entitycache_p.h @@ -1,213 +1,231 @@ /* Copyright (c) 2009 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_ENTITYCACHE_P_H #define AKONADI_ENTITYCACHE_P_H #include #include #include #include #include #include #include #include #include #include class KJob; namespace Akonadi { /** @internal QObject part of EntityCache. */ class EntityCacheBase : public QObject { Q_OBJECT public: explicit EntityCacheBase (QObject * parent = 0); signals: void dataAvailable(); private slots: virtual void fetchResult( KJob* job ) = 0; }; template struct EntityCacheNode { EntityCacheNode() : pending( false ), invalid( false ) {} EntityCacheNode( typename T::Id id ) : entity( T( id ) ), pending( true ), invalid( false ) {} T entity; bool pending; bool invalid; }; -} - -Q_DECLARE_METATYPE( Akonadi::EntityCacheNode* ) -Q_DECLARE_METATYPE( Akonadi::EntityCacheNode* ) - -namespace Akonadi { - /** * @internal * A in-memory FIFO cache for a small amount of Entity objects. */ template class EntityCache : public EntityCacheBase { public: explicit EntityCache( int maxCapacity, QObject *parent = 0 ) : EntityCacheBase( parent ), mCapacity( maxCapacity ) {} ~EntityCache() { qDeleteAll( mCache ); } /** Object is available in the cache and can be retrieved. */ bool isCached( typename T::Id id ) const { EntityCacheNode* node = cacheNodeForId( id ); return node && !node->pending; } /** Object has been requested but is not yet loaded into the cache or is already available. */ bool isRequested( typename T::Id id ) const { return cacheNodeForId( id ); } + /** Returns the cached object if available, an empty instance otherwise. */ T retrieve( typename T::Id id ) const { - Q_ASSERT( isCached( id ) ); EntityCacheNode* node = cacheNodeForId( id ); - if ( !node->invalid ) + if ( node && !node->pending && !node->invalid ) return node->entity; return T(); } + /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate( typename T::Id id ) { EntityCacheNode* node = cacheNodeForId( id ); if ( node ) node->invalid = true; } + /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ + void update( typename T::Id id, const FetchScope &scope ) + { + EntityCacheNode* node = cacheNodeForId( id ); + if ( node ) + mCache.removeAll( node ); + if ( !node || node->pending ) + request( id, scope ); + delete node; + } + + /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ + bool ensureCached( typename T::Id id, const FetchScope &scope ) + { + EntityCacheNode* node = cacheNodeForId( id ); + if ( !node ) { + request( id, scope ); + return false; + } + return !node->pending; + } + /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ void request( typename T::Id id, const FetchScope &scope ) { Q_ASSERT( !isRequested( id ) ); shrinkCache(); EntityCacheNode *node = new EntityCacheNode( id ); FetchJob* job = createFetchJob( id ); job->setFetchScope( scope ); - job->setProperty( "EntityCacheNode", QVariant::fromValue*>( node ) ); + job->setProperty( "EntityCacheNode", QVariant::fromValue( id ) ); connect( job, SIGNAL(result(KJob*)), SLOT(fetchResult(KJob*)) ); mCache.enqueue( node ); } private: EntityCacheNode* cacheNodeForId( typename T::Id id ) const { for ( typename QQueue*>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it ) { if ( (*it)->entity.id() == id ) return *it; } return 0; } void fetchResult( KJob* job ) { - EntityCacheNode *node = job->property( "EntityCacheNode" ).value*>(); - Q_ASSERT( node ); - typename T::Id id = node->entity.id(); + typename T::Id id = job->property( "EntityCacheNode" ).template value(); + EntityCacheNode *node = cacheNodeForId( id ); + if ( !node ) + return; // got replaced in the meantime + node->pending = false; extractResult( node, job ); if ( node->entity.id() != id ) { // make sure we find this node again if something went wrong here... kWarning() << "Something went very wrong..."; node->entity.setId( id ); node->invalid = true; } emit dataAvailable(); } void extractResult( EntityCacheNode* node, KJob* job ) const; inline FetchJob* createFetchJob( typename T::Id id ) { return new FetchJob( T( id ), this ); } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while ( mCache.size() >= mCapacity && !mCache.first()->pending ) delete mCache.dequeue(); } private: QQueue*> mCache; int mCapacity; }; template<> inline void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) const { CollectionFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->collections().isEmpty() ) node->entity = Collection(); else node->entity = fetch->collections().first(); } template<> inline void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) const { ItemFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->items().isEmpty() ) node->entity = Item(); else node->entity = fetch->items().first(); } template<> inline CollectionFetchJob* EntityCache::createFetchJob( Collection::Id id ) { return new CollectionFetchJob( Collection( id ), CollectionFetchJob::Base, this ); } typedef EntityCache CollectionCache; typedef EntityCache ItemCache; } #endif diff --git a/akonadi/monitor.cpp b/akonadi/monitor.cpp index 1396b4f44..147742217 100644 --- a/akonadi/monitor.cpp +++ b/akonadi/monitor.cpp @@ -1,170 +1,171 @@ /* Copyright (c) 2006 - 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 "monitor.h" #include "monitor_p.h" #include "itemfetchjob.h" #include "notificationmessage_p.h" #include "session.h" #include #include #include #include #include #include "collectionfetchscope.h" using namespace Akonadi; #define d d_ptr Monitor::Monitor( QObject *parent ) : QObject( parent ), d_ptr( new MonitorPrivate( this ) ) { + d_ptr->init(); d_ptr->connectToNotificationManager(); } //@cond PRIVATE Monitor::Monitor(MonitorPrivate * d, QObject *parent) : QObject( parent ), d_ptr( d ) { } //@endcond Monitor::~Monitor() { delete d; } void Monitor::setCollectionMonitored( const Collection &collection, bool monitored ) { if ( monitored ) d->collections << collection; else d->collections.removeAll( collection ); emit collectionMonitored( collection, monitored ); } void Monitor::setItemMonitored( const Item & item, bool monitored ) { if ( monitored ) d->items.insert( item.id() ); else d->items.remove( item.id() ); emit itemMonitored( item, monitored ); } void Monitor::setResourceMonitored( const QByteArray & resource, bool monitored ) { if ( monitored ) d->resources.insert( resource ); else d->resources.remove( resource ); emit resourceMonitored( resource, monitored ); } void Monitor::setMimeTypeMonitored( const QString & mimetype, bool monitored ) { if ( monitored ) d->mimetypes.insert( mimetype ); else d->mimetypes.remove( mimetype ); emit mimeTypeMonitored( mimetype, monitored ); } void Akonadi::Monitor::setAllMonitored( bool monitored ) { d->monitorAll = monitored; emit allMonitored( monitored ); } void Monitor::ignoreSession(Session * session) { d->sessions << session->sessionId(); connect( session, SIGNAL(destroyed(QObject*)), this, SLOT(slotSessionDestroyed(QObject*)) ); } void Monitor::fetchCollection(bool enable) { d->fetchCollection = enable; } void Monitor::fetchCollectionStatistics(bool enable) { d->fetchCollectionStatistics = enable; } void Monitor::setItemFetchScope( const ItemFetchScope &fetchScope ) { d->mItemFetchScope = fetchScope; } ItemFetchScope &Monitor::itemFetchScope() { return d->mItemFetchScope; } void Monitor::setCollectionFetchScope( const CollectionFetchScope &fetchScope ) { d->mCollectionFetchScope = fetchScope; } CollectionFetchScope& Monitor::collectionFetchScope() { return d->mCollectionFetchScope; } Collection::List Monitor::collectionsMonitored() const { return d->collections; } QList Monitor::itemsMonitored() const { return d->items.toList(); } QStringList Monitor::mimeTypesMonitored() const { return d->mimetypes.toList(); } QList Monitor::resourcesMonitored() const { return d->resources.toList(); } bool Monitor::isAllMonitored() const { return d->monitorAll; } #undef d #include "monitor.moc" diff --git a/akonadi/monitor.h b/akonadi/monitor.h index 45d045328..0bdbc0b76 100644 --- a/akonadi/monitor.h +++ b/akonadi/monitor.h @@ -1,378 +1,379 @@ /* Copyright (c) 2006 - 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_MONITOR_H #define AKONADI_MONITOR_H #include #include #include namespace Akonadi { class CollectionFetchScope; class CollectionStatistics; class Item; class ItemFetchScope; class MonitorPrivate; class Session; /** * @short Monitors an item or collection for changes. * * The Monitor emits signals if some of these objects are changed or * removed or new ones are added to the Akonadi storage. * * Optionally, the changed objects can be fetched automatically from the server. * To enable this, see itemFetchScope() and collectionFetchScope(). * * @todo: distinguish between monitoring collection properties and collection content. * @todo: special case for collection content counts changed * * @author Volker Krause */ class AKONADI_EXPORT Monitor : public QObject { Q_OBJECT public: /** * Creates a new monitor. * * @param parent The parent object. */ explicit Monitor( QObject *parent = 0 ); /** * Destroys the monitor. */ virtual ~Monitor(); /** * Sets whether the specified collection shall be monitored for changes. * * @param collection The collection to monitor. * If this collection is Collection::root(), all collections * in the Akonadi storage will be monitored. */ void setCollectionMonitored( const Collection &collection, bool monitored = true ); /** * Sets whether the specified item shall be monitored for changes. * * @param item The item to monitor. */ void setItemMonitored( const Item &item, bool monitored = true ); /** * Sets whether the specified resource shall be monitored for changes. * * @param resource The resource identifier. */ void setResourceMonitored( const QByteArray &resource, bool monitored = true ); /** * Sets whether objects of the specified mime type shall be monitored for changes. * * @param mimetype The mime type to monitor. */ void setMimeTypeMonitored( const QString &mimetype, bool monitored = true ); /** * Sets whether all items shall be monitored. */ void setAllMonitored( bool monitored = true ); /** * Ignores all change notifications caused by the given session. * * @param session The session you want to ignore. */ void ignoreSession( Session *session ); /** * Enables automatic fetching of changed collections from the Akonadi storage. * * @param enable @c true enables automatic fetching, @c false disables automatic fetching. */ void fetchCollection( bool enable ); /** * Enables automatic fetching of changed collection statistics information from * the Akonadi storage. * * @param enable @c true to enables automatic fetching, @c false disables automatic fetching. */ void fetchCollectionStatistics( bool enable ); /** * Sets the item fetch scope. * * Controls how much of an item's data is fetched from the server, e.g. * whether to fetch the full item payload or only meta data. * * @param fetchScope The new scope for item fetch operations. * * @see itemFetchScope() */ void setItemFetchScope( const ItemFetchScope &fetchScope ); /** * Returns the item fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the ItemFetchScope documentation * for an example. * * @return a reference to the current item fetch scope * * @see setItemFetchScope() for replacing the current item fetch scope */ ItemFetchScope &itemFetchScope(); /** * Sets the collection fetch scope. * * Controls which collections are monitored and how much of a collection's data * is fetched from the server. * * @param fetchScope The new scope for collection fetch operations. * * @see collectionFetchScope() * @since 4.4 */ void setCollectionFetchScope( const CollectionFetchScope &fetchScope ); /** * Returns the collection fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the CollectionFetchScope documentation * for an example. * * @return a reference to the current collection fetch scope * * @see setCollectionFetchScope() for replacing the current collection fetch scope * @since 4.4 */ CollectionFetchScope &collectionFetchScope(); /** * Returns the list of collections being monitored. * * @since 4.3 */ Collection::List collectionsMonitored() const; /** * Returns the set of items being monitored. * * @since 4.3 */ QList itemsMonitored() const; /** * Returns the set of mimetypes being monitored. * * @since 4.3 */ QStringList mimeTypesMonitored() const; /** * Returns the set of identifiers for resources being monitored. * * @since 4.3 */ QList resourcesMonitored() const; /** * Returns true if everything is being monitored. * * @since 4.3 */ bool isAllMonitored() const; Q_SIGNALS: /** * This signal is emitted if a monitored item has changed, e.g. item parts have been modified. * * @param item The changed item. * @param partIdentifiers The identifiers of the item parts that has been changed. */ void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); /** * This signal is emitted if a monitored item has been moved between two collections * * @param item The moved item. * @param collectionSource The collection the item has been moved from. * @param collectionDestination The collection the item has been moved to. */ void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination ); /** * This signal is emitted if an item has been added to a monitored collection in the Akonadi storage. * * @param item The new item. * @param collection The collection the item has been added to. */ void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if * - a monitored item has been removed from the Akonadi storage * or * - a item has been removed from a monitored collection. * * @param item The removed item. */ void itemRemoved( const Akonadi::Item &item ); //TODO remove if we are sure nobody uses it any longer void itemRemoved( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if a reference to an item is added to a virtual collection. * @param item The linked item. * @param collection The collection the item is linked to. * * @since 4.2 */ void itemLinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if a reference to an item is removed from a virtual collection. * @param item The unlinked item. * @param collection The collection the item is unlinked from. * * @since 4.2 */ void itemUnlinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if a new collection has been added to a monitored collection in the Akonadi storage. * * @param collection The new collection. * @param parent The parent collection. */ void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); /** * This signal is emitted if a monitored collection has been changed (properties or content). * * @param collection The changed collection. */ void collectionChanged( const Akonadi::Collection &collection ); /** * This signals is emitted if a monitored collection has been moved. * * @param collection The moved collection. * @param source The previous parent collection. * @param distination The new parent collection. */ void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination ); /** * This signal is emitted if a monitored collection has been removed from the Akonadi storage. * * @param collection The removed collection. */ void collectionRemoved( const Akonadi::Collection &collection ); /** * This signal is emitted if the statistics information of a monitored collection * has changed. * * @param id The collection identifier of the changed collection. * @param statistics The updated collection statistics, invalid if automatic * fetching of statistics changes is disabled. */ void collectionStatisticsChanged( Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics ); /** * This signal is emitted if the Monitor starts or stops monitoring @p collection explicitly. * @param collection The collection * @param monitored Whether the collection is now being monitored or not. * * @since 4.3 */ void collectionMonitored( const Akonadi::Collection &collection, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring @p item explicitly. * @param item The item * @param monitored Whether the item is now being monitored or not. * * @since 4.3 */ void itemMonitored( const Akonadi::Item &item, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring the resource with the identifier @p identifier explicitly. * @param identifier The identifier of the resource. * @param monitored Whether the resource is now being monitored or not. * * @since 4.3 */ void resourceMonitored( const QByteArray &identifier, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring @p mimeType explicitly. * @param mimeType The mimeType. * @param monitored Whether the mimeType is now being monitored or not. * * @since 4.3 */ void mimeTypeMonitored( const QString &mimeType, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring everything. * @param monitored Whether everything is now being monitored or not. * * @since 4.3 */ void allMonitored( bool monitored ); protected: //@cond PRIVATE MonitorPrivate *d_ptr; explicit Monitor( MonitorPrivate *d, QObject *parent = 0 ); //@endcond private: Q_DECLARE_PRIVATE( Monitor ) //@cond PRIVATE Q_PRIVATE_SLOT( d_ptr, void slotSessionDestroyed( QObject* ) ) Q_PRIVATE_SLOT( d_ptr, void slotStatisticsChangedFinished( KJob* ) ) Q_PRIVATE_SLOT( d_ptr, void slotFlushRecentlyChangedCollections() ) Q_PRIVATE_SLOT( d_ptr, void slotNotify( const Akonadi::NotificationMessage::List& ) ) Q_PRIVATE_SLOT( d_ptr, void slotItemJobFinished( KJob* ) ) Q_PRIVATE_SLOT( d_ptr, void slotCollectionJobFinished( KJob* ) ) + Q_PRIVATE_SLOT( d_ptr, void dataAvailable() ) //@endcond }; } #endif diff --git a/akonadi/monitor_p.cpp b/akonadi/monitor_p.cpp index 232cc7188..4f9606519 100644 --- a/akonadi/monitor_p.cpp +++ b/akonadi/monitor_p.cpp @@ -1,394 +1,505 @@ /* Copyright (c) 2007 Tobias Koenig 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. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmessage_p.h" #include "session.h" #include using namespace Akonadi; +static const int PipelineSize = 5; + MonitorPrivate::MonitorPrivate(Monitor * parent) : q_ptr( parent ), nm( 0 ), monitorAll( false ), + collectionCache( 3*PipelineSize ), // needs to be at least 3x pipeline size for the collection move case + itemCache( PipelineSize ), // needs to be at least 1x pipeline size fetchCollection( false ), fetchCollectionStatistics( false ) { } +void MonitorPrivate::init() +{ + QObject::connect( &collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); + QObject::connect( &itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); +} + bool MonitorPrivate::connectToNotificationManager() { NotificationMessage::registerDBusTypes(); if ( !nm ) nm = new org::freedesktop::Akonadi::NotificationManager( QLatin1String( "org.freedesktop.Akonadi" ), QLatin1String( "/notifications" ), QDBusConnection::sessionBus(), q_ptr ); else return true; if ( !nm ) { kWarning() << "Unable to connect to notification manager"; } else { QObject::connect( nm, SIGNAL(notify(Akonadi::NotificationMessage::List)), q_ptr, SLOT(slotNotify(Akonadi::NotificationMessage::List)) ); return true; } return false; } bool MonitorPrivate::acceptNotification(const NotificationMessage & msg) { if ( isSessionIgnored( msg.sessionId() ) ) return false; switch ( msg.type() ) { case NotificationMessage::InvalidType: kWarning() << "Received invalid change notification!"; return false; case NotificationMessage::Item: return isItemMonitored( msg.uid(), msg.parentCollection(), msg.parentDestCollection(), msg.mimeType(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); case NotificationMessage::Collection: return isCollectionMonitored( msg.uid(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); } Q_ASSERT( false ); return false; } +void MonitorPrivate::dispatchNotifications() +{ + while ( pipeline.size() < PipelineSize && !pendingNotifications.isEmpty() ) { + const NotificationMessage msg = pendingNotifications.dequeue(); + if ( ensureDataAvailable( msg ) && pipeline.isEmpty() ) + emitNotification( msg ); + else + pipeline.enqueue( msg ); + } +} + +bool MonitorPrivate::ensureDataAvailable( const NotificationMessage &msg ) +{ + bool allCached = true; + if ( fetchCollection ) { + if ( !collectionCache.ensureCached( msg.parentCollection(), mCollectionFetchScope ) ) + allCached = false; + if ( msg.operation() == NotificationMessage::Move && !collectionCache.ensureCached( msg.parentDestCollection(), mCollectionFetchScope ) ) + allCached = false; + } + if ( msg.operation() == NotificationMessage::Remove ) + return allCached; // the actual object is gone already, nothing to fetch there + + if ( msg.type() == NotificationMessage::Item && !mItemFetchScope.isEmpty() ) { + if ( !itemCache.ensureCached( msg.uid(), mItemFetchScope ) ) + allCached = false; + } else if ( msg.type() == NotificationMessage::Collection && fetchCollection ) { + if ( !collectionCache.ensureCached( msg.uid(), mCollectionFetchScope ) ) + allCached = false; + } + return allCached; +} + +void MonitorPrivate::emitNotification( const NotificationMessage &msg ) +{ + const Collection parent = collectionCache.retrieve( msg.parentCollection() ); + Collection destParent; + if ( msg.operation() == NotificationMessage::Move ) + destParent = collectionCache.retrieve( msg.parentDestCollection() ); + + if ( msg.type() == NotificationMessage::Collection ) { + const Collection col = collectionCache.retrieve( msg.uid() ); + emitCollectionNotification( msg, col, parent, destParent ); + } else if ( msg.type() == NotificationMessage::Item ) { + const Item item = itemCache.retrieve( msg.uid() ); + emitItemNotification( msg, item, parent, destParent ); + } +} + +void MonitorPrivate::dataAvailable() +{ + while ( !pipeline.isEmpty() ) { + const NotificationMessage msg = pipeline.head(); + if ( ensureDataAvailable( msg ) ) { + emitNotification( msg ); + pipeline.dequeue(); + } else { + break; + } + } + dispatchNotifications(); +} + +void MonitorPrivate::updatePendingStatistics( const NotificationMessage &msg ) +{ + if ( msg.type() == NotificationMessage::Item ) { + notifyCollectionStatisticsWatchers( msg.parentCollection(), msg.resource() ); + } else if ( msg.type() == NotificationMessage::Collection && msg.operation() == NotificationMessage::Remove ) { + // no need for statistics updates anymore + recentlyChangedCollections.remove( msg.uid() ); + } +} + bool MonitorPrivate::processNotification(const NotificationMessage & msg) { if ( !acceptNotification( msg ) ) return false; if ( msg.type() == NotificationMessage::Item ) { notifyCollectionStatisticsWatchers( msg.parentCollection(), msg.resource() ); if ( !mItemFetchScope.isEmpty() && ( msg.operation() == NotificationMessage::Add || msg.operation() == NotificationMessage::Move || msg.operation() == NotificationMessage::Link ) ) { Item item( msg.uid() ); item.setRemoteId( msg.remoteId() ); ItemCollectionFetchJob *job = new ItemCollectionFetchJob( item, msg.parentCollection(), msg.parentDestCollection(), q_ptr ); job->setFetchScope( mItemFetchScope ); pendingJobs.insert( job, msg ); QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotItemJobFinished(KJob*)) ); return true; } if ( !mItemFetchScope.isEmpty() && msg.operation() == NotificationMessage::Modify ) { Item item( msg.uid() ); item.setRemoteId( msg.remoteId() ); ItemFetchJob *job = new ItemFetchJob( item, q_ptr ); job->setFetchScope( mItemFetchScope ); pendingJobs.insert( job, msg ); QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotItemJobFinished(KJob*)) ); return true; } emitItemNotification( msg ); return true; } else if ( msg.type() == NotificationMessage::Collection ) { if ( msg.operation() != NotificationMessage::Remove && fetchCollection ) { Collection::List list; list << Collection( msg.uid() ); if ( msg.operation() == NotificationMessage::Add || msg.operation() == NotificationMessage::Move ) list << Collection( msg.parentCollection() ); if ( msg.operation() == NotificationMessage::Move ) list << Collection( msg.parentDestCollection() ); CollectionFetchJob *job = new CollectionFetchJob( list, q_ptr ); pendingJobs.insert( job, msg ); QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotCollectionJobFinished(KJob*)) ); return true; } if ( msg.operation() == NotificationMessage::Remove ) { // no need for statistics updates anymore recentlyChangedCollections.remove( msg.uid() ); } emitCollectionNotification( msg ); return true; } else { kWarning() << "Received unknown change notification!"; } return false; } void MonitorPrivate::slotSessionDestroyed( QObject * object ) { Session* session = qobject_cast( object ); if ( session ) sessions.removeAll( session->sessionId() ); } void MonitorPrivate::slotStatisticsChangedFinished( KJob* job ) { if ( job->error() ) { kWarning() << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast( job ); emit q_ptr->collectionStatisticsChanged( statisticsJob->collection().id(), statisticsJob->statistics() ); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { foreach( Collection::Id collection, recentlyChangedCollections ) { if ( fetchCollectionStatistics ) { fetchStatistics( collection ); } else { static const CollectionStatistics dummyStatistics; emit q_ptr->collectionStatisticsChanged( collection, dummyStatistics ); } } recentlyChangedCollections.clear(); } void MonitorPrivate::slotNotify( const NotificationMessage::List &msgs ) { - foreach ( const NotificationMessage &msg, msgs ) - processNotification( msg ); + foreach ( const NotificationMessage &msg, msgs ) { + invalidateCaches( msg ); + if ( acceptNotification( msg ) ) { + updatePendingStatistics( msg ); + NotificationMessage::appendAndCompress( pendingNotifications, msg ); + } + } + + dispatchNotifications(); } void MonitorPrivate::emitItemNotification( const NotificationMessage &msg, const Item &item, const Collection &collection, const Collection &collectionDest ) { Q_ASSERT( msg.type() == NotificationMessage::Item ); Collection col = collection; Collection colDest = collectionDest; if ( !col.isValid() ) { col = Collection( msg.parentCollection() ); col.setResource( QString::fromUtf8( msg.resource() ) ); } if ( !colDest.isValid() ) { colDest = Collection( msg.parentDestCollection() ); // FIXME setResource here required ? } Item it = item; if ( !it.isValid() ) { it = Item( msg.uid() ); it.setRemoteId( msg.remoteId() ); it.setMimeType( msg.mimeType() ); } switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->itemAdded( it, col ); break; case NotificationMessage::Modify: emit q_ptr->itemChanged( it, msg.itemParts() ); break; case NotificationMessage::Move: emit q_ptr->itemMoved( it, col, colDest ); break; case NotificationMessage::Remove: emit q_ptr->itemRemoved( it ); emit q_ptr->itemRemoved( it, col ); break; case NotificationMessage::Link: emit q_ptr->itemLinked( it, col ); break; case NotificationMessage::Unlink: emit q_ptr->itemUnlinked( it, col ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in item change notification"; break; } } void MonitorPrivate::emitCollectionNotification( const NotificationMessage &msg, const Collection &col, const Collection &par, const Collection &dest ) { Q_ASSERT( msg.type() == NotificationMessage::Collection ); Collection collection = col; if ( !collection.isValid() ) { collection = Collection( msg.uid() ); collection.setParentCollection( Collection( msg.parentCollection() ) ); collection.setResource( QString::fromUtf8( msg.resource() ) ); collection.setRemoteId( msg.remoteId() ); } Collection parent = par; if ( !parent.isValid() ) parent = Collection( msg.parentCollection() ); Collection destination = dest; if ( !destination.isValid() ) destination = Collection( msg.parentDestCollection() ); switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->collectionAdded( collection, parent ); break; case NotificationMessage::Modify: emit q_ptr->collectionChanged( collection ); break; case NotificationMessage::Move: emit q_ptr->collectionMoved( collection, parent, destination ); break; case NotificationMessage::Remove: emit q_ptr->collectionRemoved( collection ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in collection change notification"; } } void MonitorPrivate::slotItemJobFinished( KJob* job ) { if ( !pendingJobs.contains( job ) ) { kWarning() << "Unknown job - wtf is going on here?"; return; } NotificationMessage msg = pendingJobs.take( job ); Item item; Collection col; Collection destCol; if ( job->error() ) { kWarning() << "Error on fetching item:" << job->errorText(); } else { ItemFetchJob *fetchJob = qobject_cast( job ); if ( fetchJob && fetchJob->items().count() > 0 ) item = fetchJob->items().first(); ItemCollectionFetchJob *cfjob = qobject_cast( job ); if ( cfjob ) { item = cfjob->item(); col = cfjob->collection(); destCol = cfjob->destCollection(); } } emitItemNotification( msg, item, col, destCol ); } void MonitorPrivate::slotCollectionJobFinished( KJob* job ) { if ( !pendingJobs.contains( job ) ) { kWarning() << "Unknown job - wtf is going on here?"; return; } NotificationMessage msg = pendingJobs.take( job ); if ( job->error() ) { kWarning() << "Error on fetching collection:" << job->errorText(); } else { CollectionFetchJob *listJob = qobject_cast( job ); QHash cols; foreach ( const Collection &c, listJob->collections() ) cols.insert( c.id(), c ); emitCollectionNotification( msg, cols.value( msg.uid() ), cols.value( msg.parentCollection() ), cols.value( msg.parentDestCollection() ) ); } } +void MonitorPrivate::invalidateCaches( const NotificationMessage &msg ) +{ + // remove invalidates + if ( msg.operation() == NotificationMessage::Remove ) { + if ( msg.type() == NotificationMessage::Collection ) { + collectionCache.invalidate( msg.uid() ); + } else if ( msg.type() == NotificationMessage::Item ) { + itemCache.invalidate( msg.uid() ); + } + } + + // modify removes the cache entry, as we need to re-fetch + if ( msg.operation() == NotificationMessage::Modify ) { + if ( msg.type() == NotificationMessage::Collection ) { + collectionCache.update( msg.uid(), mCollectionFetchScope ); + } else if ( msg.type() == NotificationMessage::Item ) { + itemCache.update( msg.uid(), mItemFetchScope ); + } + } +} + ItemCollectionFetchJob::ItemCollectionFetchJob( const Item &item, Collection::Id collectionId, Collection::Id destCollectionId, QObject *parent ) : Job( parent ), mReferenceItem( item ), mCollectionId( collectionId ), mDestCollectionId( destCollectionId ) { } ItemCollectionFetchJob::~ItemCollectionFetchJob() { } Item ItemCollectionFetchJob::item() const { return mItem; } Collection ItemCollectionFetchJob::collection() const { return mCollection; } Collection ItemCollectionFetchJob::destCollection() const { return mDestCollection; } void ItemCollectionFetchJob::setFetchScope( const ItemFetchScope &fetchScope ) { mFetchScope = fetchScope; } void ItemCollectionFetchJob::doStart() { CollectionFetchJob *listJob = new CollectionFetchJob( Collection( mCollectionId ), CollectionFetchJob::Base, this ); connect( listJob, SIGNAL( result( KJob* ) ), SLOT( collectionJobDone( KJob* ) ) ); addSubjob( listJob ); if ( mDestCollectionId > 0 ) { CollectionFetchJob *destListJob = new CollectionFetchJob( Collection( mDestCollectionId ), CollectionFetchJob::Base, this ); connect( destListJob, SIGNAL( result( KJob* ) ), SLOT( destCollectionJobDone( KJob* ) ) ); addSubjob( destListJob ); } ItemFetchJob *fetchJob = new ItemFetchJob( mReferenceItem, this ); fetchJob->setFetchScope( mFetchScope ); connect( fetchJob, SIGNAL( result( KJob* ) ), SLOT( itemJobDone( KJob* ) ) ); addSubjob( fetchJob ); } void ItemCollectionFetchJob::collectionJobDone( KJob* job ) { if ( !job->error() ) { CollectionFetchJob *listJob = qobject_cast( job ); if ( listJob->collections().isEmpty() ) { setError( 1 ); setErrorText( QLatin1String( "No collection found" ) ); } else mCollection = listJob->collections().first(); } } void ItemCollectionFetchJob::destCollectionJobDone( KJob* job ) { if ( !job->error() ) { CollectionFetchJob *listJob = qobject_cast( job ); if ( listJob->collections().isEmpty() ) { setError( 1 ); setErrorText( QLatin1String( "No collection found" ) ); } else mDestCollection = listJob->collections().first(); } } void ItemCollectionFetchJob::itemJobDone( KJob* job ) { if ( !job->error() ) { ItemFetchJob *fetchJob = qobject_cast( job ); if ( fetchJob->items().isEmpty() ) { setError( 2 ); setErrorText( QLatin1String( "No item found" ) ); } else mItem = fetchJob->items().first(); emitResult(); } } // @endcond #include "monitor_p.moc" diff --git a/akonadi/monitor_p.h b/akonadi/monitor_p.h index 3dfd9c47a..039dd361a 100644 --- a/akonadi/monitor_p.h +++ b/akonadi/monitor_p.h @@ -1,194 +1,206 @@ /* Copyright (c) 2007 Tobias Koenig 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_MONITOR_P_H #define AKONADI_MONITOR_P_H #include "monitor.h" #include "collection.h" #include "collectionstatisticsjob.h" #include "collectionfetchscope.h" #include "item.h" #include "itemfetchscope.h" #include "job.h" #include #include "notificationmanagerinterface.h" +#include "entitycache_p.h" #include #include #include namespace Akonadi { class Monitor; /** * @internal */ class MonitorPrivate { public: MonitorPrivate( Monitor *parent ); virtual ~MonitorPrivate() {} + void init(); Monitor *q_ptr; Q_DECLARE_PUBLIC( Monitor ) org::freedesktop::Akonadi::NotificationManager *nm; Collection::List collections; QSet resources; QSet items; QSet mimetypes; bool monitorAll; QList sessions; ItemFetchScope mItemFetchScope; CollectionFetchScope mCollectionFetchScope; QHash pendingJobs; + CollectionCache collectionCache; + ItemCache itemCache; + QQueue pendingNotifications; + QQueue pipeline; + bool fetchCollection; + bool fetchCollectionStatistics; bool isCollectionMonitored( Collection::Id collection, const QByteArray &resource ) const { if ( monitorAll || isCollectionMonitored( collection ) || resources.contains( resource ) ) return true; return false; } bool isItemMonitored( Item::Id item, Collection::Id collection, Collection::Id collectionDest, const QString &mimetype, const QByteArray &resource ) const { if ( monitorAll || isCollectionMonitored( collection ) || isCollectionMonitored( collectionDest ) ||items.contains( item ) || resources.contains( resource ) || isMimeTypeMonitored( mimetype ) ) return true; return false; } bool isSessionIgnored( const QByteArray &sessionId ) const { return sessions.contains( sessionId ); } bool connectToNotificationManager(); bool acceptNotification( const NotificationMessage &msg ); + void dispatchNotifications(); + bool ensureDataAvailable( const NotificationMessage &msg ); + void emitNotification( const NotificationMessage &msg ); + void updatePendingStatistics( const NotificationMessage &msg ); bool processNotification( const NotificationMessage &msg ); + void invalidateCaches( const NotificationMessage &msg ); + // private slots + void dataAvailable(); void slotSessionDestroyed( QObject* ); void slotStatisticsChangedFinished( KJob* ); void slotFlushRecentlyChangedCollections(); virtual void slotNotify( const NotificationMessage::List &msgs ); void slotItemJobFinished( KJob* job ); void slotCollectionJobFinished( KJob *job ); void emitItemNotification( const NotificationMessage &msg, const Item &item = Item(), const Collection &collection = Collection(), const Collection &collectionDest = Collection() ); void emitCollectionNotification( const NotificationMessage &msg, const Collection &col = Collection(), const Collection &par = Collection(), const Collection &dest = Collection() ); - bool fetchCollection; - bool fetchCollectionStatistics; - private: // collections that need a statistics update QSet recentlyChangedCollections; bool isCollectionMonitored( Collection::Id collection ) const { if ( collections.contains( Collection( collection ) ) ) return true; if ( collections.contains( Collection::root() ) ) return true; return false; } bool isMimeTypeMonitored( const QString& mimetype ) const { if ( mimetypes.contains( mimetype ) ) return true; KMimeType::Ptr mimeType = KMimeType::mimeType( mimetype, KMimeType::ResolveAliases ); if ( mimeType.isNull() ) return false; foreach ( const QString &mt, mimetypes ) { if ( mimeType->is( mt ) ) return true; } return false; } void fetchStatistics( Collection::Id colId ) { CollectionStatisticsJob *job = new CollectionStatisticsJob( Collection( colId ), q_ptr ); QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotStatisticsChangedFinished(KJob*)) ); } void notifyCollectionStatisticsWatchers( Collection::Id collection, const QByteArray &resource ) { if ( isCollectionMonitored( collection, resource ) ) { if (recentlyChangedCollections.empty() ) QTimer::singleShot( 500, q_ptr, SLOT(slotFlushRecentlyChangedCollections()) ); recentlyChangedCollections.insert( collection ); } } }; /** * @internal * * A job which fetches an item and a collection. */ class AKONADI_EXPORT ItemCollectionFetchJob : public Job { Q_OBJECT public: explicit ItemCollectionFetchJob( const Item &item, Collection::Id collectionId, Collection::Id destCollectionId, QObject *parent = 0 ); ~ItemCollectionFetchJob(); Item item() const; Collection collection() const; Collection destCollection() const; void setFetchScope( const ItemFetchScope &fetchScope ); protected: virtual void doStart(); private Q_SLOTS: void collectionJobDone( KJob* job ); void destCollectionJobDone( KJob* job ); void itemJobDone( KJob* job ); private: Item mReferenceItem; Collection::Id mCollectionId; Collection::Id mDestCollectionId; Item mItem; Collection mCollection; Collection mDestCollection; ItemFetchScope mFetchScope; }; } #endif diff --git a/akonadi/tests/entitycachetest.cpp b/akonadi/tests/entitycachetest.cpp index 9b2ee026f..a862d0354 100644 --- a/akonadi/tests/entitycachetest.cpp +++ b/akonadi/tests/entitycachetest.cpp @@ -1,98 +1,131 @@ /* Copyright (c) 2009 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 "entitycache.cpp" #include #include using namespace Akonadi; class EntityCacheTest : public QObject { Q_OBJECT private: template void testCache() { EntityCache cache( 2 ); QSignalSpy spy( &cache, SIGNAL(dataAvailable()) ); QVERIFY( spy.isValid() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( !cache.isRequested( 1 ) ); + QVERIFY( !cache.retrieve( 1 ).isValid() ); cache.request( 1, FetchScope() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); + QVERIFY( !cache.retrieve( 1 ).isValid() ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 1 ); QVERIFY( cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); const T e1 = cache.retrieve( 1 ); QCOMPARE( e1.id(), 1ll ); spy.clear(); cache.request( 2, FetchScope() ); cache.request( 3, FetchScope() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( !cache.isRequested( 1 ) ); QVERIFY( cache.isRequested( 2 ) ); QVERIFY( cache.isRequested( 3 ) ); cache.invalidate( 2 ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 2 ); QVERIFY( cache.isCached( 2 ) ); QVERIFY( cache.isCached( 3 ) ); const T e2 = cache.retrieve( 2 ); const T e3a = cache.retrieve( 3 ); QCOMPARE( e3a.id(), 3ll ); QVERIFY( !e2.isValid() ); cache.invalidate( 3 ); const T e3b = cache.retrieve( 3 ); QVERIFY( !e3b.isValid() ); + + spy.clear(); + cache.update( 3, FetchScope() ); + cache.update( 3, FetchScope() ); + QVERIFY( !cache.isCached( 3 ) ); + QVERIFY( cache.isRequested( 3 ) ); + QVERIFY( !cache.retrieve( 3 ).isValid() ); + + QTest::qWait( 1000 ); + QCOMPARE( spy.count(), 1 ); + QVERIFY( cache.isCached( 3 ) ); + QVERIFY( cache.retrieve( 3 ).isValid() ); } private slots: void testCacheGeneric_data() { QTest::addColumn( "collection" ); QTest::newRow( "collection" ) << true; QTest::newRow( "item" ) << false; } void testCacheGeneric() { QFETCH( bool, collection ); if ( collection ) testCache(); else testCache(); } + + void testItemCache() + { + ItemCache cache( 1 ); + QSignalSpy spy( &cache, SIGNAL(dataAvailable()) ); + QVERIFY( spy.isValid() ); + + ItemFetchScope scope; + scope.fetchFullPayload( true ); + cache.request( 1, scope ); + + QTest::qWait( 1000 ); + QCOMPARE( spy.count(), 1 ); + QVERIFY( cache.isCached( 1 ) ); + QVERIFY( cache.isRequested( 1 ) ); + const Item item = cache.retrieve( 1 ); + QCOMPARE( item.id(), 1ll ); + QVERIFY( item.hasPayload() ); + } }; QTEST_AKONADIMAIN( EntityCacheTest, NoGUI ) #include "entitycachetest.moc" diff --git a/akonadi/tests/monitortest.cpp b/akonadi/tests/monitortest.cpp index 68215d947..182b2f9cf 100644 --- a/akonadi/tests/monitortest.cpp +++ b/akonadi/tests/monitortest.cpp @@ -1,358 +1,359 @@ /* 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 "monitortest.h" #include "test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; QTEST_AKONADIMAIN( MonitorTest, NoGUI ) static Collection res3; Q_DECLARE_METATYPE(Akonadi::Collection::Id) Q_DECLARE_METATYPE(QSet) void MonitorTest::initTestCase() { Control::start(); res3 = Collection( collectionIdFromPath( "res3" ) ); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void MonitorTest::testMonitor_data() { QTest::addColumn( "fetchCol" ); QTest::newRow( "with collection fetching" ) << true; QTest::newRow( "without collection fetching" ) << false; } void MonitorTest::testMonitor() { QFETCH( bool, fetchCol ); Monitor *monitor = new Monitor( this ); monitor->setCollectionMonitored( Collection::root() ); monitor->fetchCollection( fetchCol ); monitor->itemFetchScope().fetchFullPayload(); // monitor signals qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); QSignalSpy caddspy( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); QSignalSpy cmodspy( monitor, SIGNAL(collectionChanged(const Akonadi::Collection&)) ); QSignalSpy cmvspy( monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)) ); QSignalSpy crmspy( monitor, SIGNAL(collectionRemoved(const Akonadi::Collection&)) ); QSignalSpy cstatspy( monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)) ); QSignalSpy iaddspy( monitor, SIGNAL(itemAdded(const Akonadi::Item&, const Akonadi::Collection&)) ); QSignalSpy imodspy( monitor, SIGNAL(itemChanged(const Akonadi::Item&, const QSet&)) ); QSignalSpy imvspy( monitor, SIGNAL(itemMoved(const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection&)) ); QSignalSpy irmspy( monitor, SIGNAL(itemRemoved(const Akonadi::Item&)) ); QVERIFY( caddspy.isValid() ); QVERIFY( cmodspy.isValid() ); QVERIFY( cmvspy.isValid() ); QVERIFY( crmspy.isValid() ); QVERIFY( cstatspy.isValid() ); QVERIFY( iaddspy.isValid() ); QVERIFY( imodspy.isValid() ); QVERIFY( imvspy.isValid() ); QVERIFY( irmspy.isValid() ); // create a collection Collection monitorCol; monitorCol.setParentCollection( res3 ); monitorCol.setName( "monitor" ); CollectionCreateJob *create = new CollectionCreateJob( monitorCol, this ); QVERIFY( create->exec() ); monitorCol = create->collection(); QVERIFY( monitorCol.isValid() ); QTest::qWait(1000); // make sure the DBus signal has been processed QCOMPARE( caddspy.count(), 1 ); QList arg = caddspy.takeFirst(); Collection col = arg.at(0).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), QString("monitor") ); Collection parent = arg.at(1).value(); QCOMPARE( parent, res3 ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // add an item Item newItem; newItem.setMimeType( "application/octet-stream" ); ItemCreateJob *append = new ItemCreateJob( newItem, monitorCol, this ); QVERIFY( append->exec() ); Item monitorRef = append->item(); QVERIFY( monitorRef.isValid() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); /* qRegisterMetaType() registers the type with a name of "qlonglong". Doing qRegisterMetaType( "Akonadi::Collection::Id" ) doesn't help. The problem here is that Akonadi::Collection::Id is a typedef to qlonglong, and qlonglong is already a registered meta type. So the signal spy will give us a QVariant of type Akonadi::Collection::Id, but calling .value() on that variant will in fact end up calling qvariant_cast. From the point of view of QMetaType, Akonadi::Collection::Id and qlonglong are different types, so QVariant can't convert, and returns a default-constructed qlonglong, zero. When connecting to a real slot (without QSignalSpy), this problem is avoided, because the casting is done differently (via a lot of void pointers). The docs say nothing about qRegisterMetaType -ing a typedef, so I'm not sure if this is a bug or not. (cberzan) */ QCOMPARE( iaddspy.count(), 1 ); arg = iaddspy.takeFirst(); Item item = arg.at( 0 ).value(); QCOMPARE( item, monitorRef ); QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); Collection collection = arg.at( 1 ).value(); QCOMPARE( collection.id(), monitorCol.id() ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // modify an item item.setPayload( "some new content" ); ItemModifyJob *store = new ItemModifyJob( item, this ); QVERIFY( store->exec() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); QCOMPARE( imodspy.count(), 1 ); arg = imodspy.takeFirst(); item = arg.at( 0 ).value(); QCOMPARE( monitorRef, item ); + QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray( "some new content" ) ); QSet parts = arg.at( 1 ).value >(); QCOMPARE( parts, QSet() << "PLD:RFC822" ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // move an item ItemMoveJob *move = new ItemMoveJob( item, res3 ); QVERIFY( move->exec() ); QTest::qWait( 1000 ); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); QCOMPARE( imvspy.count(), 1 ); arg = imvspy.takeFirst(); item = arg.at( 0 ).value(); // the item QCOMPARE( monitorRef, item ); col = arg.at( 1 ).value(); // the source collection QCOMPARE( col.id(), monitorCol.id() ); col = arg.at( 2 ).value(); // the destination collection QCOMPARE( col.id(), res3.id() ); QCOMPARE( cmodspy.count(), 2 ); arg = cmodspy.takeFirst(); Collection col1 = arg.at( 0 ).value(); arg = cmodspy.takeFirst(); Collection col2 = arg.at( 0 ).value(); // source and dest collections, in any order QVERIFY( ( col1.id() == monitorCol.id() && col2.id() == res3.id() ) || ( col2.id() == monitorCol.id() && col1.id() == res3.id() ) ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // delete an item ItemDeleteJob *del = new ItemDeleteJob( monitorRef, this ); QVERIFY( del->exec() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); cmodspy.clear(); QCOMPARE( irmspy.count(), 1 ); arg = irmspy.takeFirst(); Item ref = qvariant_cast( arg.at(0) ); QCOMPARE( monitorRef, ref ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); imvspy.clear(); // modify a collection monitorCol.setName( "changed name" ); CollectionModifyJob *mod = new CollectionModifyJob( monitorCol, this ); AKVERIFYEXEC( mod ); QTest::qWait(1000); QCOMPARE( cmodspy.count(), 1 ); arg = cmodspy.takeFirst(); col = arg.at(0).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), QString("changed name") ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // move a collection Collection dest = Collection( collectionIdFromPath( "res1/foo" ) ); CollectionMoveJob *cmove = new CollectionMoveJob( monitorCol, dest, this ); AKVERIFYEXEC( cmove ); QTest::qWait( 1000 ); QCOMPARE( cmvspy.count(), 1 ); arg = cmvspy.takeFirst(); col = arg.at( 0 ).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), monitorCol.name() ); col = arg.at( 1 ).value(); QCOMPARE( col, res3 ); col = arg.at( 2 ).value(); QCOMPARE( col, dest ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // delete a collection CollectionDeleteJob *cdel = new CollectionDeleteJob( monitorCol, this ); QVERIFY( cdel->exec() ); QTest::qWait(1000); QCOMPARE( crmspy.count(), 1 ); arg = crmspy.takeFirst(); QCOMPARE( arg.at(0).value().id(), monitorCol.id() ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); } void MonitorTest::testVirtualCollectionsMonitoring() { Monitor *monitor = new Monitor( this ); monitor->setCollectionMonitored( Collection( 1 ) ); // top-level 'Search' collection QSignalSpy caddspy( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); QVERIFY( caddspy.isValid() ); SearchCreateJob *job = new SearchCreateJob( "Test search collection", "test-search-query", this ); AKVERIFYEXEC( job ); QTest::qWait( 1000 ); QCOMPARE( caddspy.count(), 1 ); } #include "monitortest.moc" diff --git a/kioslave/pop3/pop3.cpp b/kioslave/pop3/pop3.cpp index 94dce4858..e14460ab3 100644 --- a/kioslave/pop3/pop3.cpp +++ b/kioslave/pop3/pop3.cpp @@ -1,1147 +1,1147 @@ /* * Copyright (c) 1999-2001 Alex Zepeda * Copyright (c) 2001-2002 Michael Haeckel * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include "pop3.h" #include "common.h" #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #define GREETING_BUF_LEN 1024 #define MAX_RESPONSE_LEN 512 #define MAX_COMMANDS 10 extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } using namespace KIO; static sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, NULL, NULL }, { SASL_CB_NOECHOPROMPT, NULL, NULL }, { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; int kdemain(int argc, char **argv) { if (argc != 4) { kDebug(7105) << "Usage: kio_pop3 protocol domain-socket1 domain-socket2"; return -1; } QCoreApplication app( argc, argv ); // needed for QSocketNotifier KComponentData componentData("kio_pop3"); if (!initSASL()) return -1; // Are we looking to use SSL? POP3Protocol *slave; if (strcasecmp(argv[1], "pop3s") == 0) { slave = new POP3Protocol(argv[2], argv[3], true); } else { slave = new POP3Protocol(argv[2], argv[3], false); } slave->dispatchLoop(); delete slave; sasl_done(); return 0; } POP3Protocol::POP3Protocol(const QByteArray & pool, const QByteArray & app, bool isSSL) : TCPSlaveBase((isSSL ? "pop3s" : "pop3"), pool, app, isSSL) { kDebug(7105); m_cmd = CMD_NONE; m_iOldPort = 0; m_tTimeout.tv_sec = 10; m_tTimeout.tv_usec = 0; supports_apop = false; m_try_apop = true; m_try_sasl = true; opened = false; readBufferLen = 0; } POP3Protocol::~POP3Protocol() { kDebug(7105); closeConnection(); } void POP3Protocol::setHost(const QString & _host, quint16 _port, const QString & _user, const QString & _pass) { m_sServer = _host; m_iPort = _port; m_sUser = _user; m_sPass = _pass; } ssize_t POP3Protocol::myRead(void *data, ssize_t len) { if (readBufferLen) { ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; memcpy(data, readBuffer, copyLen); readBufferLen -= copyLen; if (readBufferLen) memcpy(readBuffer, &readBuffer[copyLen], readBufferLen); return copyLen; } waitForResponse(600); return read((char*)data, len); } ssize_t POP3Protocol::myReadLine(char *data, ssize_t len) { ssize_t copyLen = 0, readLen = 0; while (true) { while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') copyLen++; if (copyLen < readBufferLen || copyLen == len) { copyLen++; memcpy(data, readBuffer, copyLen); data[copyLen] = '\0'; readBufferLen -= copyLen; if (readBufferLen) memcpy(readBuffer, &readBuffer[copyLen], readBufferLen); return copyLen; } waitForResponse(600); readLen = read(&readBuffer[readBufferLen], len - readBufferLen); readBufferLen += readLen; if (readLen <= 0) { data[0] = '\0'; return 0; } } } POP3Protocol::Resp POP3Protocol::getResponse(char *r_buf, unsigned int r_len) { char *buf = 0; unsigned int recv_len = 0; // fd_set FDs; // Give the buffer the appropriate size r_len = r_len ? r_len : MAX_RESPONSE_LEN; buf = new char[r_len]; // Clear out the buffer memset(buf, 0, r_len); myReadLine(buf, r_len - 1); kDebug(7105) << "S:" << buf; // This is really a funky crash waiting to happen if something isn't // null terminated. recv_len = strlen(buf); /* * From rfc1939: * * Responses in the POP3 consist of a status indicator and a keyword * possibly followed by additional information. All responses are * terminated by a CRLF pair. Responses may be up to 512 characters * long, including the terminating CRLF. There are currently two status * indicators: positive ("+OK") and negative ("-ERR"). Servers MUST * send the "+OK" and "-ERR" in upper case. */ if (strncmp(buf, "+OK", 3) == 0) { if (r_buf && r_len) { memcpy(r_buf, (buf[3] == ' ' ? buf + 4 : buf + 3), qMin(r_len, (buf[3] == ' ' ? recv_len - 4 : recv_len - 3))); } delete[]buf; return Ok; } else if (strncmp(buf, "-ERR", 4) == 0) { if (r_buf && r_len) { memcpy(r_buf, (buf[4] == ' ' ? buf + 5 : buf + 4), qMin(r_len, (buf[4] == ' ' ? recv_len - 5 : recv_len - 4))); } QString serverMsg = QString::fromLatin1(buf).mid(5).trimmed(); m_sError = i18n("The server said: \"%1\"", serverMsg); delete[]buf; return Err; } else if (strncmp(buf, "+ ", 2) == 0) { if (r_buf && r_len) { memcpy(r_buf, buf + 2, qMin(r_len, recv_len - 4)); r_buf[qMin(r_len - 1, recv_len - 4)] = '\0'; } delete[]buf; return Cont; } else { kDebug(7105) << "Invalid POP3 response received!"; if (r_buf && r_len) { memcpy(r_buf, buf, qMin(r_len, recv_len)); } if (!*buf) { m_sError = i18n("The server terminated the connection."); } else { m_sError = i18n("Invalid response from server:\n\"%1\"", buf); } delete[]buf; return Invalid; } } bool POP3Protocol::sendCommand(const QByteArray &cmd) { /* * From rfc1939: * * Commands in the POP3 consist of a case-insensitive keyword, possibly * followed by one or more arguments. All commands are terminated by a * CRLF pair. Keywords and arguments consist of printable ASCII * characters. Keywords and arguments are each separated by a single * SPACE character. Keywords are three or four characters long. Each * argument may be up to 40 characters long. */ if (!isConnected()) return false; QByteArray cmdrn = cmd + "\r\n"; // Show the command line the client sends, but make sure the password // doesn't show up in the debug output QByteArray debugCommand = cmd; if (!m_sPass.isEmpty()) debugCommand.replace(m_sPass.toAscii(),""); kDebug(7105) << "C:" << debugCommand; // Now actually write the command to the socket if (write(cmdrn.data(), cmdrn.size()) != static_cast < ssize_t > (cmdrn.size())) { m_sError = i18n("Could not send to server.\n"); return false; } return true; } POP3Protocol::Resp POP3Protocol::command(const QByteArray &cmd, char *recv_buf, unsigned int len) { sendCommand(cmd); return getResponse(recv_buf, len); } void POP3Protocol::openConnection() { m_try_apop = !hasMetaData("auth") || metaData("auth") == "APOP"; m_try_sasl = !hasMetaData("auth") || metaData("auth") == "SASL"; if (!pop3_open()) { kDebug(7105) << "pop3_open failed"; } else { connected(); } } void POP3Protocol::closeConnection() { // If the file pointer exists, we can assume the socket is valid, // and to make sure that the server doesn't magically undo any of // our deletions and so-on, we should send a QUIT and wait for a // response. We don't care if it's positive or negative. Also // flush out any semblance of a persistant connection, i.e.: the // old username and password are now invalid. if (!opened) { return; } command("QUIT"); disconnectFromHost(); readBufferLen = 0; m_sOldUser = m_sOldPass = m_sOldServer = ""; opened = false; } int POP3Protocol::loginAPOP( char *challenge, KIO::AuthInfo &ai ) { char buf[512]; QString apop_string = QString::fromLatin1("APOP "); if (m_sUser.isEmpty() || m_sPass.isEmpty()) { // Prompt for usernames if (!openPasswordDialog(ai)) { error(ERR_ABORTED, i18n("No authentication details supplied.")); closeConnection(); return -1; } else { m_sUser = ai.username; m_sPass = ai.password; } } m_sOldUser = m_sUser; m_sOldPass = m_sPass; apop_string.append(m_sUser); memset(buf, 0, sizeof(buf)); KMD5 ctx; kDebug(7105) << "APOP challenge: " << challenge; // Generate digest ctx.update(challenge, strlen(challenge)); ctx.update(m_sPass.toLatin1() ); // Genenerate APOP command apop_string.append(" "); apop_string.append(ctx.hexDigest()); if (command(apop_string.toLocal8Bit(), buf, sizeof(buf)) == Ok) { return 0; } kDebug(7105) << "Could not login via APOP. Falling back to USER/PASS"; closeConnection(); if (metaData("auth") == "APOP") { error(ERR_COULD_NOT_LOGIN, i18n ("Login via APOP failed. The server %1 may not support APOP, although it claims to support it, or the password may be wrong.\n\n%2", m_sServer, m_sError)); return -1; } return 1; } bool POP3Protocol::saslInteract( void *in, AuthInfo &ai ) { kDebug(7105); sasl_interact_t *interact = ( sasl_interact_t * ) in; //some mechanisms do not require username && pass, so don't need a popup //window for getting this info for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { if ( interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS ) { if (m_sUser.isEmpty() || m_sPass.isEmpty()) { if (!openPasswordDialog(ai)) { error(ERR_ABORTED, i18n("No authentication details supplied.")); return false; } m_sUser = ai.username; m_sPass = ai.password; } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { kDebug(7105) << "SASL_INTERACT id: " << interact->id; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kDebug(7105) << "SASL_CB_[USER|AUTHNAME]: " << m_sUser; interact->result = strdup( m_sUser.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kDebug(7105) << "SASL_CB_PASS: [hidden] "; interact->result = strdup( m_sPass.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = NULL; interact->len = 0; break; } interact++; } return true; } #define SASLERROR closeConnection(); \ error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occurred during authentication: %1", \ QString::fromUtf8( sasl_errdetail( conn ) ))); \ int POP3Protocol::loginSASL( KIO::AuthInfo &ai ) { char buf[512]; QString sasl_buffer = QString::fromLatin1("AUTH"); int result; sasl_conn_t *conn = NULL; sasl_interact_t *client_interact = NULL; const char *out = NULL; uint outlen; const char *mechusing = NULL; Resp resp; result = sasl_client_new( "pop", m_sServer.toLatin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { kDebug(7105) << "sasl_client_new failed with: " << result; SASLERROR return false; } // We need to check what methods the server supports... // This is based on RFC 1734's wisdom if ( hasMetaData("sasl") || command(sasl_buffer.toLocal8Bit()) == Ok ) { QStringList sasl_list; if (hasMetaData("sasl")) { sasl_list.append(metaData("sasl").toLatin1()); } else while (true /* !AtEOF() */ ) { memset(buf, 0, sizeof(buf)); myReadLine(buf, sizeof(buf) - 1); // HACK: This assumes fread stops at the first \n and not \r - if (strcmp(buf, ".\r\n") == 0) { + if ( (buf[0] == 0) || (strcmp(buf, ".\r\n") == 0) ) { break; // End of data } // sanders, changed -2 to -1 below buf[strlen(buf) - 2] = '\0'; sasl_list.append(buf); } do { result = sasl_client_start(conn, sasl_list.join(" ").toLatin1(), &client_interact, &out, &outlen, &mechusing); if (result == SASL_INTERACT) if ( !saslInteract( client_interact, ai ) ) { closeConnection(); sasl_dispose( &conn ); return -1; }; } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug(7105) << "sasl_client_start failed with: " << result; SASLERROR sasl_dispose( &conn ); return -1; } kDebug(7105) << "Preferred authentication method is " << mechusing << "."; QByteArray msg,tmp; QString firstCommand = "AUTH " + QString::fromLatin1( mechusing ); msg = QByteArray::fromRawData( out, outlen ).toBase64(); if ( !msg.isEmpty() ) { firstCommand += ' '; firstCommand += QString::fromLatin1( msg.data(), msg.size() ); } tmp.resize( 2049 ); resp = command( firstCommand.toLatin1(), tmp.data(), 2049 ); while( resp == Cont ) { tmp.resize(tmp.indexOf((char)0)); msg = QByteArray::fromBase64( tmp ); do { result = sasl_client_step(conn, msg.isEmpty() ? 0 : msg.data(), msg.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) if ( !saslInteract( client_interact, ai ) ) { closeConnection(); sasl_dispose( &conn ); return -1; }; } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug(7105) << "sasl_client_step failed with: " << result; SASLERROR sasl_dispose( &conn ); return -1; } msg = QByteArray::fromRawData( out, outlen ).toBase64(); tmp.resize(2049); resp = command( msg, tmp.data(), 2049 ); } sasl_dispose( &conn ); if ( resp == Ok ) { kDebug(7105) << "SASL authenticated"; m_sOldUser = m_sUser; m_sOldPass = m_sPass; return 0; } if (metaData("auth") == "SASL") { closeConnection(); error(ERR_COULD_NOT_LOGIN, i18n ("Login via SASL (%1) failed. The server may not support %2, or the password may be wrong.\n\n%3", mechusing, mechusing, m_sError)); return -1; } } if (metaData("auth") == "SASL") { closeConnection(); error(ERR_COULD_NOT_LOGIN, i18n("Your POP3 server does not support SASL.\n" "Choose a different authentication method.")); return -1; } return 1; } bool POP3Protocol::loginPASS( KIO::AuthInfo &ai ) { char buf[512]; if (m_sUser.isEmpty() || m_sPass.isEmpty()) { // Prompt for usernames if (!openPasswordDialog(ai)) { error(ERR_ABORTED, i18n("No authentication details supplied.")); closeConnection(); return false; } else { m_sUser = ai.username; m_sPass = ai.password; } } m_sOldUser = m_sUser; m_sOldPass = m_sPass; QString one_string = QString::fromLatin1("USER "); one_string.append( m_sUser ); if ( command(one_string.toLocal8Bit(), buf, sizeof(buf)) != Ok ) { kDebug(7105) << "Could not login. Bad username Sorry"; m_sError = i18n("Could not login to %1.\n\n", m_sServer) + m_sError; error(ERR_COULD_NOT_LOGIN, m_sError); closeConnection(); return false; } one_string = QString::fromLatin1("PASS "); one_string.append(m_sPass); if ( command(one_string.toLocal8Bit(), buf, sizeof(buf)) != Ok ) { kDebug(7105) << "Could not login. Bad password Sorry."; m_sError = i18n ("Could not login to %1. The password may be wrong.\n\n%2", m_sServer, m_sError); error(ERR_COULD_NOT_LOGIN, m_sError); closeConnection(); return false; } kDebug(7105) << "USER/PASS login succeeded"; return true; } bool POP3Protocol::pop3_open() { kDebug(7105); char *greeting_buf; if ((m_iOldPort == m_iPort) && (m_sOldServer == m_sServer) && (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { kDebug(7105) << "Reusing old connection"; return true; } do { closeConnection(); if (!connectToHost((isAutoSsl() ? "pop3s" : "pop3"), m_sServer.toLatin1(), m_iPort)) { // error(ERR_COULD_NOT_CONNECT, m_sServer); // ConnectToHost has already send an error message. return false; } opened = true; greeting_buf = new char[GREETING_BUF_LEN]; memset(greeting_buf, 0, GREETING_BUF_LEN); // If the server doesn't respond with a greeting if (getResponse(greeting_buf, GREETING_BUF_LEN) != Ok) { m_sError = i18n("Could not login to %1.\n\n", m_sServer) + ((!greeting_buf || !*greeting_buf) ? i18n("The server terminated the connection immediately.") : i18n("Server does not respond properly:\n%1\n", greeting_buf)); error(ERR_COULD_NOT_LOGIN, m_sError); delete[]greeting_buf; closeConnection(); return false; // we've got major problems, and possibly the // wrong port } QString greeting(greeting_buf); delete[]greeting_buf; if (greeting.length() > 0) { greeting.truncate(greeting.length() - 2); } // Does the server support APOP? QString apop_cmd; QRegExp re("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$", Qt::CaseInsensitive); kDebug(7105) << "greeting: " << greeting; int apop_pos = greeting.indexOf(re); supports_apop = (bool) (apop_pos != -1); if (metaData("nologin") == "on") return true; if (metaData("auth") == "APOP" && !supports_apop) { error(ERR_COULD_NOT_LOGIN, i18n("Your POP3 server does not support APOP.\n" "Choose a different authentication method.")); closeConnection(); return false; } m_iOldPort = m_iPort; m_sOldServer = m_sServer; // Try to go into TLS mode if ((metaData("tls") == "on" /*### || (canUseTLS() && metaData("tls") != "off")*/) && command("STLS") == Ok ) { if (startSsl()) { kDebug(7105) << "TLS mode has been enabled."; } else { kDebug(7105) << "TLS mode setup has failed. Aborting." << endl; error(ERR_COULD_NOT_CONNECT, i18n("Your POP3 server claims to " "support TLS but negotiation " "was unsuccessful. You can " "disable TLS in KDE using the " "crypto settings module.")); closeConnection(); return false; } } else if (metaData("tls") == "on") { error(ERR_COULD_NOT_CONNECT, i18n("Your POP3 server does not support TLS. Disable " "TLS, if you want to connect without encryption.")); closeConnection(); return false; } KIO::AuthInfo authInfo; authInfo.username = m_sUser; authInfo.password = m_sPass; authInfo.prompt = i18n("Username and password for your POP3 account:"); if ( supports_apop && m_try_apop ) { kDebug(7105) << "Trying APOP"; int retval = loginAPOP( greeting.toLatin1().data() + apop_pos, authInfo ); switch ( retval ) { case 0: return true; case -1: return false; default: m_try_apop = false; } } else if ( m_try_sasl ) { kDebug(7105) << "Trying SASL"; int retval = loginSASL( authInfo ); switch ( retval ) { case 0: return true; case -1: return false; default: m_try_sasl = false; } } else { // Fall back to conventional USER/PASS scheme kDebug(7105) << "Trying USER/PASS"; return loginPASS( authInfo ); } } while ( true ); } size_t POP3Protocol::realGetSize(unsigned int msg_num) { char *buf; QByteArray cmd; size_t ret = 0; buf = new char[MAX_RESPONSE_LEN]; memset(buf, 0, MAX_RESPONSE_LEN); cmd = "LIST " + QByteArray::number( msg_num ); if ( command(cmd, buf, MAX_RESPONSE_LEN) != Ok ) { delete[]buf; return 0; } else { cmd = buf; cmd.remove(0, cmd.indexOf(" ")); ret = cmd.toLong(); } delete[]buf; return ret; } void POP3Protocol::get(const KUrl & url) { // List of supported commands // // URI Command Result // pop3://user:pass@domain/index LIST List message sizes // pop3://user:pass@domain/uidl UIDL List message UIDs // pop3://user:pass@domain/remove/#1 DELE #1 Mark a message for deletion // pop3://user:pass@domain/download/#1 RETR #1 Get message header and body // pop3://user:pass@domain/list/#1 LIST #1 Get size of a message // pop3://user:pass@domain/uid/#1 UIDL #1 Get UID of a message // pop3://user:pass@domain/commit QUIT Delete marked messages // pop3://user:pass@domain/headers/#1 TOP #1 Get header of message // // Notes: // Sizes are in bytes. // No support for the STAT command has been implemented. // commit closes the connection to the server after issuing the QUIT command. bool ok = true; char buf[MAX_PACKET_LEN]; char destbuf[MAX_PACKET_LEN]; QString cmd, path = url.path(); int maxCommands = (metaData("pipelining") == "on") ? MAX_COMMANDS : 1; if (path.at(0) == '/') path.remove(0, 1); if (path.isEmpty()) { kDebug(7105) << "We should be a dir!!"; error(ERR_IS_DIRECTORY, url.url()); m_cmd = CMD_NONE; return; } if (((path.indexOf('/') == -1) && (path != "index") && (path != "uidl") && (path != "commit"))) { error(ERR_MALFORMED_URL, url.url()); m_cmd = CMD_NONE; return; } cmd = path.left(path.indexOf('/')); path.remove(0, path.indexOf('/') + 1); if (!pop3_open()) { kDebug(7105) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } if ((cmd == "index") || (cmd == "uidl")) { unsigned long size = 0; bool result; if (cmd == "index") { result = ( command("LIST") == Ok ); } else { result = ( command("UIDL") == Ok ); } /* LIST +OK Mailbox scan listing follows 1 2979 2 1348 . */ if (result) { while (true /* !AtEOF() */ ) { memset(buf, 0, sizeof(buf)); myReadLine(buf, sizeof(buf) - 1); // HACK: This assumes fread stops at the first \n and not \r - if (strcmp(buf, ".\r\n") == 0) { + if ( (buf[0] == 0) || (strcmp(buf, ".\r\n") == 0) ) { break; // End of data } // sanders, changed -2 to -1 below int bufStrLen = strlen(buf); buf[bufStrLen - 2] = '\0'; size += bufStrLen; data(QByteArray::fromRawData(buf, bufStrLen)); totalSize(size); } } kDebug(7105) << "Finishing up list"; data(QByteArray()); finished(); } else if (cmd == "remove") { const QStringList waitingCommands = path.split(','); int activeCommands = 0; QStringList::ConstIterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(("DELE " + *it).toLatin1()); activeCommands++; it++; } getResponse(buf, sizeof(buf) - 1); activeCommands--; } finished(); m_cmd = CMD_NONE; } else if (cmd == "download" || cmd == "headers") { const QStringList waitingCommands = path.split(',', QString::SkipEmptyParts); bool noProgress = (metaData("progress") == "off" || waitingCommands.count() > 1); int p_size = 0; unsigned int msg_len = 0; QString list_cmd("LIST "); list_cmd += path; memset(buf, 0, sizeof(buf)); if ( !noProgress ) { if ( command(list_cmd.toLatin1(), buf, sizeof(buf) - 1) == Ok ) { list_cmd = buf; // We need a space, otherwise we got an invalid reply if (!list_cmd.indexOf(" ")) { kDebug(7105) << "List command needs a space? " << list_cmd; closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } list_cmd.remove(0, list_cmd.indexOf(" ") + 1); msg_len = list_cmd.toUInt(&ok); if (!ok) { kDebug(7105) << "LIST command needs to return a number? :" << list_cmd << ":"; closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else { closeConnection(); error(ERR_COULD_NOT_READ, m_sError); return; } } int activeCommands = 0; QStringList::ConstIterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(((cmd == "headers") ? "TOP " + *it + " 0" : "RETR " + *it).toLatin1()); activeCommands++; it++; } if ( getResponse(buf, sizeof(buf) - 1) == Ok ) { activeCommands--; mimeType("message/rfc822"); totalSize(msg_len); memset(buf, 0, sizeof(buf)); char ending = '\n'; bool endOfMail = false; bool eat = false; while (true /* !AtEOF() */ ) { ssize_t readlen = myRead(buf, sizeof(buf) - 1); if (readlen <= 0) { if (isConnected()) error(ERR_SERVER_TIMEOUT, m_sServer); else error(ERR_CONNECTION_BROKEN, m_sServer); closeConnection(); return; } if (ending == '.' && readlen > 1 && buf[0] == '\r' && buf[1] == '\n') { readBufferLen = readlen - 2; memcpy(readBuffer, &buf[2], readBufferLen); break; } bool newline = (ending == '\n'); if (buf[readlen - 1] == '\n') ending = '\n'; else if (buf[readlen - 1] == '.' && ((readlen > 1) ? buf[readlen - 2] == '\n' : ending == '\n')) ending = '.'; else ending = ' '; char *buf1 = buf, *buf2 = destbuf; // ".." at start of a line means only "." // "." means end of data for (ssize_t i = 0; i < readlen; i++) { if (*buf1 == '\r' && eat) { endOfMail = true; if (i == readlen - 1 /* && !AtEOF() */ ) myRead(buf, 1); else if (i < readlen - 2) { readBufferLen = readlen - i - 2; memcpy(readBuffer, &buf[i + 2], readBufferLen); } break; } else if (*buf1 == '\n') { newline = true; eat = false; } else if (*buf1 == '.' && newline) { newline = false; eat = true; } else { newline = false; eat = false; } if (!eat) { *buf2 = *buf1; buf2++; } buf1++; } if (buf2 > destbuf) { data(QByteArray::fromRawData(destbuf, buf2-destbuf)); } if (endOfMail) break; if (!noProgress) { p_size += readlen; processedSize(p_size); } } infoMessage("message complete"); } else { kDebug(7105) << "Could not login. Bad RETR Sorry"; closeConnection(); error(ERR_COULD_NOT_READ, m_sError); return; } } kDebug(7105) << "Finishing up"; data(QByteArray()); finished(); } else if ((cmd == "uid") || (cmd == "list")) { QString qbuf; (void) path.toInt(&ok); if (!ok) { return; // We fscking need a number! } if (cmd == "uid") { path.prepend("UIDL "); } else { path.prepend("LIST "); } memset(buf, 0, sizeof(buf)); if ( command(path.toAscii(), buf, sizeof(buf) - 1) == Ok ) { const int len = strlen(buf); mimeType("text/plain"); totalSize(len); data(QByteArray::fromRawData(buf, len)); processedSize(len); kDebug(7105) << buf; kDebug(7105) << "Finishing up uid"; data(QByteArray()); finished(); } else { closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else if (cmd == "commit") { kDebug(7105) << "Issued QUIT"; closeConnection(); finished(); m_cmd = CMD_NONE; return; } } void POP3Protocol::listDir(const KUrl &) { bool isINT; int num_messages = 0; QByteArray q_buf(MAX_RESPONSE_LEN, 0); // Try and open a connection if (!pop3_open()) { kDebug(7105) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } // Check how many messages we have. STAT is by law required to // at least return +OK num_messages total_size if ( command("STAT", q_buf.data(), MAX_RESPONSE_LEN) != Ok ) { error(ERR_INTERNAL, i18n("The POP3 command 'STAT' failed")); return; } kDebug(7105) << "The stat buf is :" << q_buf << ":"; if (q_buf.indexOf(" ") == -1) { error(ERR_INTERNAL, i18n("Invalid POP3 response, should have at least one space.")); closeConnection(); return; } q_buf.remove(q_buf.indexOf(" "), q_buf.length()); num_messages = q_buf.toUInt(&isINT); if (!isINT) { error(ERR_INTERNAL, i18n("Invalid POP3 STAT response.")); closeConnection(); return; } UDSEntry entry; QString fname; for (int i = 0; i < num_messages; i++) { fname = "Message %1"; entry.insert(KIO::UDSEntry::UDS_NAME, fname.arg(i + 1)); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/plain")); KUrl uds_url; if (isAutoSsl()) { uds_url.setProtocol("pop3s"); } else { uds_url.setProtocol("pop3"); } uds_url.setUser(m_sUser); uds_url.setPass(m_sPass); uds_url.setHost(m_sServer); uds_url.setPath(QString::fromLatin1("/download/%1").arg(i + 1)); entry.insert(KIO::UDSEntry::UDS_URL, uds_url.url()); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.insert(KIO::UDSEntry::UDS_SIZE, realGetSize(i + 1)); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IWUSR); listEntry(entry, false); entry.clear(); } listEntry(entry, true); // ready finished(); } void POP3Protocol::stat(const KUrl & url) { QString _path = url.path(); if (_path.at(0) == '/') _path.remove(0, 1); UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, _path); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/rfc822")); // TODO: maybe get the size of the message? statEntry(entry); finished(); } void POP3Protocol::del(const KUrl & url, bool /*isfile */ ) { QString invalidURI; bool isInt; if (!pop3_open()) { kDebug(7105) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } QString _path = url.path(); if (_path.at(0) == '/') { _path.remove(0, 1); } _path.toUInt(&isInt); if (!isInt) { invalidURI = _path; } else { _path.prepend("DELE "); if ( command(_path.toAscii()) != Ok ) { invalidURI = _path; } } kDebug(7105) << "Path:" << _path; finished(); }