diff --git a/akonadi/entitytreemodel.cpp b/akonadi/entitytreemodel.cpp index 3155e0e65..65c32a701 100644 --- a/akonadi/entitytreemodel.cpp +++ b/akonadi/entitytreemodel.cpp @@ -1,946 +1,947 @@ /* Copyright (c) 2008 Stephen Kelly 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 "entitytreemodel.h" #include "entitytreemodel_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionutils_p.h" #include "kdebug.h" using namespace Akonadi; EntityTreeModel::EntityTreeModel( Session *session, Monitor *monitor, QObject *parent ) : QAbstractItemModel( parent ), d_ptr( new EntityTreeModelPrivate( this ) ) { Q_D( EntityTreeModel ); d->m_monitor = monitor; d->m_session = session; d->m_includeStatistics = true; d->m_monitor->fetchCollectionStatistics( true ); d->m_mimeChecker.setWantedMimeTypes( d->m_monitor->mimeTypesMonitored() ); connect( monitor, SIGNAL( mimeTypeMonitored( const QString&, bool ) ), SLOT( monitoredMimeTypeChanged( const QString&, bool ) ) ); // monitor collection changes connect( monitor, SIGNAL( collectionChanged( const Akonadi::Collection& ) ), SLOT( monitoredCollectionChanged( const Akonadi::Collection& ) ) ); connect( monitor, SIGNAL( collectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ), SLOT( monitoredCollectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ) ); connect( monitor, SIGNAL( collectionRemoved( const Akonadi::Collection& ) ), SLOT( monitoredCollectionRemoved( const Akonadi::Collection&) ) ); // connect( monitor, // SIGNAL( collectionMoved( const Akonadi::Collection &, const Akonadi::Collection &, const Akonadi::Collection & ) ), // SLOT( monitoredCollectionMoved( const Akonadi::Collection &, const Akonadi::Collection &, const Akonadi::Collection & ) ) ); //TODO: Figure out if the monitor emits these signals even without an item fetch scope. // Wrap them in an if() if so. // Don't want to be adding items to a model if NoItemPopulation is set. // If LazyPopulation is set, then we'll have to add items to collections which // have already been lazily populated. // Monitor item changes. connect( monitor, SIGNAL( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ), SLOT( monitoredItemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ) ); connect( monitor, SIGNAL( itemChanged( const Akonadi::Item&, const QSet& ) ), SLOT( monitoredItemChanged( const Akonadi::Item&, const QSet& ) ) ); connect( monitor, SIGNAL( itemRemoved( const Akonadi::Item& ) ), SLOT( monitoredItemRemoved( const Akonadi::Item& ) ) ); //connect( monitor, SIGNAL( itemMoved( const Akonadi::Item, const Akonadi::Collection, const Akonadi::Collection ) ), // SLOT( monitoredItemMoved( const Akonadi::Item, const Akonadi::Collection, const Akonadi::Collection ) ) ); connect( monitor, SIGNAL( collectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ) ), SLOT(monitoredCollectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ) ) ); connect( monitor, SIGNAL( itemLinked( const Akonadi::Item&, const Akonadi::Collection& )), SLOT( monitoredItemLinked( const Akonadi::Item&, const Akonadi::Collection& ))); connect( monitor, SIGNAL( itemUnlinked( const Akonadi::Item&, const Akonadi::Collection& )), SLOT( monitoredItemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ))); // connect( q, SIGNAL( modelReset() ), q, SLOT( slotModelReset() ) ); d->m_rootCollection = Collection::root(); d->m_rootCollectionDisplayName = QLatin1String( "[*]" ); // Initializes the model cleanly. clearAndReset(); } EntityTreeModel::~EntityTreeModel() { Q_D( EntityTreeModel ); foreach( QList list, d->m_childEntities ) { qDeleteAll(list); list.clear(); } delete d_ptr; } void EntityTreeModel::clearAndReset() { Q_D( EntityTreeModel ); d->m_collections.clear(); d->m_items.clear(); d->m_childEntities.clear(); reset(); QTimer::singleShot( 0, this, SLOT( startFirstListJob() ) ); } Collection EntityTreeModel::collectionForId( Collection::Id id ) const { Q_D( const EntityTreeModel ); return d->m_collections.value( id ); } Item EntityTreeModel::itemForId( Item::Id id ) const { Q_D( const EntityTreeModel ); return d->m_items.value( id ); } int EntityTreeModel::columnCount( const QModelIndex & parent ) const { // TODO: Statistics? if ( parent.isValid() && parent.column() != 0 ) return 0; return 1; } QVariant EntityTreeModel::getData( const Item &item, int column, int role ) const { if ( column == 0 ) { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: if ( item.hasAttribute() && !item.attribute()->displayName().isEmpty() ) { return item.attribute()->displayName(); } else { return item.remoteId(); } break; case Qt::DecorationRole: if ( item.hasAttribute() && !item.attribute()->iconName().isEmpty() ) return item.attribute()->icon(); break; case MimeTypeRole: return item.mimeType(); break; case RemoteIdRole: return item.remoteId(); break; case ItemRole: return QVariant::fromValue( item ); break; case ItemIdRole: return item.id(); break; default: break; } } return QVariant(); } QVariant EntityTreeModel::getData( const Collection &collection, int column, int role ) const { Q_D(const EntityTreeModel); if ( column > 0 ) return QString(); if ( collection == Collection::root() ) { // Only display the root collection. It may not be edited. if ( role == Qt::DisplayRole ) return d->m_rootCollectionDisplayName; if ( role == Qt::EditRole ) return QVariant(); } if ( column == 0 && (role == Qt::DisplayRole || role == Qt::EditRole) ) { if ( collection.hasAttribute() && !collection.attribute()->displayName().isEmpty() ) return collection.attribute()->displayName(); return collection.name(); } switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: if ( column == 0 ) { if ( collection.hasAttribute() && !collection.attribute()->displayName().isEmpty() ) { return collection.attribute()->displayName(); } return collection.name(); } break; case Qt::DecorationRole: if ( collection.hasAttribute() && !collection.attribute()->iconName().isEmpty() ) { return collection.attribute()->icon(); } return KIcon( CollectionUtils::defaultIconName( collection ) ); break; case MimeTypeRole: return collection.mimeType(); break; case RemoteIdRole: return collection.remoteId(); break; case CollectionIdRole: return collection.id(); break; case CollectionRole: { return QVariant::fromValue( collection ); break; } default: break; } return QVariant(); } QVariant EntityTreeModel::data( const QModelIndex & index, int role ) const { const int headerSet = (role / TerminalUserRole); role %= TerminalUserRole; if ( !index.isValid() ) { if (ColumnCountRole != role) return QVariant(); return getColumnCount(headerSet); } if (ColumnCountRole == role) return getColumnCount(headerSet); Q_D( const EntityTreeModel ); const Node *node = reinterpret_cast( index.internalPointer() ); if (ParentCollectionRole == role) { const Collection parentCollection = d->m_collections.value( node->parent ); Q_ASSERT(parentCollection.isValid()); return QVariant::fromValue(parentCollection); } if ( Node::Collection == node->type ) { const Collection collection = d->m_collections.value( node->id ); if ( !collection.isValid() ) return QVariant(); return getData( collection, index.column(), role ); } else if ( Node::Item == node->type ) { const Item item = d->m_items.value( node->id ); if ( !item.isValid() ) return QVariant(); return getData( item, index.column(), role ); } return QVariant(); } Qt::ItemFlags EntityTreeModel::flags( const QModelIndex & index ) const { Q_D( const EntityTreeModel ); // Pass modeltest. // http://labs.trolltech.com/forums/topic/79 if ( !index.isValid() ) return 0; Qt::ItemFlags flags = QAbstractItemModel::flags( index ); // Only show and enable items in columns other than 0. if ( index.column() != 0 ) return flags; const Node *node = reinterpret_cast(index.internalPointer()); if ( Node::Collection == node->type ) { const Collection collection = d->m_collections.value( node->id ); if ( collection.isValid() ) { if ( collection == Collection::root() ) { // Selectable and displayable only. return flags; } const int rights = collection.rights(); if ( rights & Collection::CanChangeCollection ) { flags |= Qt::ItemIsEditable; // Changing the collection includes changing the metadata (child entityordering). // Need to allow this by drag and drop. flags |= Qt::ItemIsDropEnabled; } if ( rights & Collection::CanDeleteCollection ) { // If this collection is moved, it will need to be deleted flags |= Qt::ItemIsDragEnabled; } if ( rights & ( Collection::CanCreateCollection | Collection::CanCreateItem ) ) { // Can we drop new collections and items into this collection? flags |= Qt::ItemIsDropEnabled; } } } else if ( Node::Item == node->type ) { // Rights come from the parent collection. const Node *parentNode = reinterpret_cast( index.parent().internalPointer() ); // TODO: Is this right for the root collection? I think so, but only by chance. // But will it work if m_rootCollection is different from Collection::root? // Should probably rely on index.parent().isValid() for that. const Collection parentCollection = d->m_collections.value( parentNode->id ); if ( parentCollection.isValid() ) { const int rights = parentCollection.rights(); // Can't drop onto items. if ( rights & Collection::CanChangeItem ) { flags = flags | Qt::ItemIsEditable; } if ( rights & Collection::CanDeleteItem ) { // If this item is moved, it will need to be deleted from its parent. flags = flags | Qt::ItemIsDragEnabled; } } } return flags; } Qt::DropActions EntityTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList EntityTreeModel::mimeTypes() const { // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example. return QStringList() << QLatin1String( "text/uri-list" ); } bool EntityTreeModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) { Q_D( EntityTreeModel ); // TODO Use action and collection rights and return false if necessary // if row and column are -1, then the drop was on parent directly. // data should then be appended on the end of the items of the collections as appropriate. // That will mean begin insert rows etc. // Otherwise it was a sibling of the row^th item of parent. // That will need to be handled by a proxy model. This one can't handle ordering. // if parent is invalid the drop occurred somewhere on the view that is no model, and corresponds to the root. kDebug() << "ismove" << ( action == Qt::MoveAction ); if ( action == Qt::IgnoreAction ) return true; // Shouldn't do this. Need to be able to drop vcards for example. // if (!data->hasFormat("text/uri-list")) // return false; // TODO This is probably wrong and unnecessary. if ( column > 0 ) return false; const Node *node = reinterpret_cast( parent.internalId() ); if ( Node::Item == node->type ) { // Can't drop data onto an item, although we can drop data between items. return false; // TODO: Maybe if it's a drop on an item I should drop below the item instead? // Find out what others do. } if ( Node::Collection == node->type ) { const Collection destCollection = d->m_collections.value( node->id ); // Applications can't create new collections in root. Only resources can. if ( destCollection == Collection::root() ) return false; if ( data->hasFormat( QLatin1String( "text/uri-list" ) ) ) { MimeTypeChecker mimeChecker; mimeChecker.setWantedMimeTypes( destCollection.contentMimeTypes() ); TransactionSequence *transaction = new TransactionSequence( d->m_session ); const KUrl::List urls = KUrl::List::fromMimeData( data ); foreach ( const KUrl &url, urls ) { const Collection collection = d->m_collections.value( Collection::fromUrl( url ).id() ); if ( collection.isValid() ) { if ( !mimeChecker.isWantedCollection( collection ) ) return false; if ( Qt::MoveAction == action ) { // new CollectionMoveJob(col, destCol, transaction); } else if ( Qt::CopyAction == action ) { CollectionCopyJob *collectionCopyJob = new CollectionCopyJob( collection, destCollection, transaction ); connect( collectionCopyJob, SIGNAL( result( KJob* ) ), SLOT( copyJobDone( KJob* ) ) ); } } else { const Item item = d->m_items.value( Item::fromUrl( url ).id() ); if ( item.isValid() ) { if ( Qt::MoveAction == action ) { ItemMoveJob *itemMoveJob = new ItemMoveJob( item, destCollection, transaction ); connect( itemMoveJob, SIGNAL( result( KJob* ) ), SLOT( moveJobDone( KJob* ) ) ); } else if ( Qt::CopyAction == action ) { ItemCopyJob *itemCopyJob = new ItemCopyJob( item, destCollection, transaction); connect( itemCopyJob, SIGNAL( result( KJob* ) ), SLOT( copyJobDone( KJob* ) ) ); } } else { // A uri, but not an akonadi url. What to do? // Should handle known mimetypes like vcards first. // That should make any remaining uris meaningless at this point. } } } return false; // ### Return false so that the view does not update with the dropped // in place where they were dropped. That will be done when the monitor notifies the model // through collectionsReceived that the move was successful. } else { // not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do // fromMimeData for them. Hmm, put it in the same transaction with the above? // TODO: This should be handled first, not last. } } return false; } QModelIndex EntityTreeModel::index( int row, int column, const QModelIndex & parent ) const { Q_D( const EntityTreeModel ); if ( parent.column() > 0 ) { return QModelIndex(); } //TODO: don't use column count here? Use some d-> func. if ( column >= columnCount() || column < 0 ) return QModelIndex(); QList childEntities; const Node *parentNode = reinterpret_cast( parent.internalPointer() ); if ( !parentNode || !parent.isValid() ) { if ( d->m_showRootCollection ) childEntities << d->m_childEntities.value( -1 ); else childEntities = d->m_childEntities.value( d->m_rootCollection.id() ); } else { if ( parentNode->id >= 0 ) childEntities = d->m_childEntities.value( parentNode->id ); } const int size = childEntities.size(); if ( row < 0 || row >= size ) return QModelIndex(); Node *node = childEntities.at( row ); return createIndex( row, column, reinterpret_cast( node ) ); } QModelIndex EntityTreeModel::parent( const QModelIndex & index ) const { Q_D( const EntityTreeModel ); if ( !index.isValid() ) return QModelIndex(); const Node *node = reinterpret_cast( index.internalPointer() ); if ( !node ) return QModelIndex(); const Collection collection = d->m_collections.value( node->parent ); if ( !collection.isValid() ) return QModelIndex(); if ( collection.id() == d->m_rootCollection.id() ) { if ( !d->m_showRootCollection ) return QModelIndex(); else return createIndex( 0, 0, reinterpret_cast( d->m_rootNode ) ); } const int row = d->indexOf( d->m_childEntities.value( collection.parentCollection().id()), collection.id() ); Node *parentNode = d->m_childEntities.value( collection.parentCollection().id() ).at( row ); return createIndex( row, 0, reinterpret_cast( parentNode ) ); } int EntityTreeModel::rowCount( const QModelIndex & parent ) const { Q_D( const EntityTreeModel ); const Node *node = reinterpret_cast( parent.internalPointer() ); qint64 id; if ( !parent.isValid() ) { // If we're showing the root collection then it will be the only child of the root. if ( d->m_showRootCollection ) return d->m_childEntities.value( -1 ).size(); id = d->m_rootCollection.id(); } else { if ( !node ) return 0; if ( Node::Item == node->type ) return 0; id = node->id; } if ( parent.column() <= 0 ) return d->m_childEntities.value( id ).size(); return 0; } int EntityTreeModel::getColumnCount(int headerSet) const { // Not needed in this model. Q_UNUSED(headerSet); return columnCount(); } QVariant EntityTreeModel::getHeaderData( int section, Qt::Orientation orientation, int role, int headerSet) const { // Not needed in this model. Q_UNUSED(headerSet); if ( section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole ) return i18nc( "@title:column, name of a thing", "Name" ); return QAbstractItemModel::headerData( section, orientation, role ); } QVariant EntityTreeModel::headerData( int section, Qt::Orientation orientation, int role ) const { const int headerSet = (role / TerminalUserRole); role %= TerminalUserRole; return getHeaderData( section, orientation, role, headerSet ); } QMimeData *EntityTreeModel::mimeData( const QModelIndexList &indexes ) const { Q_D( const EntityTreeModel ); QMimeData *data = new QMimeData(); KUrl::List urls; foreach( const QModelIndex &index, indexes ) { if ( index.column() != 0 ) continue; if (!index.isValid()) continue; const Node *node = reinterpret_cast( index.internalPointer() ); if ( Node::Collection == node->type ) urls << d->m_collections.value(node->id).url(); else if ( Node::Item == node->type ) urls << d->m_items.value( node->id ).url( Item::UrlWithMimeType ); else // if that happens something went horrible wrong Q_ASSERT(false); } urls.populateMimeData( data ); return data; } // Always return false for actions which take place asyncronously, eg via a Job. bool EntityTreeModel::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_D( EntityTreeModel ); const Node *node = reinterpret_cast( index.internalPointer() ); if ( index.column() == 0 && (role & (Qt::EditRole | ItemRole | CollectionRole)) ) { if ( Node::Collection == node->type ) { Collection collection = d->m_collections.value( node->id ); if ( !collection.isValid() || !value.isValid() ) return false; if ( Qt::EditRole == role ) { collection.setName( value.toString() ); if ( collection.hasAttribute() ) { EntityDisplayAttribute *displayAttribute = collection.attribute(); displayAttribute->setDisplayName( value.toString() ); collection.addAttribute( displayAttribute ); } } if ( CollectionRole == role ) collection = value.value(); CollectionModifyJob *job = new CollectionModifyJob( collection, d->m_session ); connect( job, SIGNAL( result( KJob* ) ), SLOT( updateJobDone( KJob* ) ) ); return false; } else if (Node::Item == node->type) { Item item = d->m_items.value( node->id ); if ( !item.isValid() || !value.isValid() ) return false; if ( Qt::EditRole == role ) { if ( item.hasAttribute() ) { EntityDisplayAttribute *displayAttribute = item.attribute( Entity::AddIfMissing ); displayAttribute->setDisplayName( value.toString() ); item.addAttribute( displayAttribute ); } } if ( ItemRole == role ) item = value.value(); ItemModifyJob *itemModifyJob = new ItemModifyJob( item, d->m_session ); connect( itemModifyJob, SIGNAL( result( KJob* ) ), SLOT( updateJobDone( KJob* ) ) ); return false; } } return QAbstractItemModel::setData( index, value, role ); } bool EntityTreeModel::canFetchMore( const QModelIndex & parent ) const { Q_D(const EntityTreeModel); const Item item = parent.data( ItemRole ).value(); if ( item.isValid() ) { // items can't have more rows. // TODO: Should I use this for fetching more of an item, ie more payload parts? return false; } else { // but collections can... const Collection::Id colId = parent.data( CollectionIdRole ).toULongLong(); // But the root collection can't... if ( Collection::root().id() == colId ) { return false; } foreach (Node *node, d->m_childEntities.value( colId ) ) { if ( Node::Item == node->type ) { // Only try to fetch more from a collection if we don't already have items in it. // Otherwise we'd spend all the time listing items in collections. // This means that collections which don't contain items get a lot of item fetch jobs started on them. // Will fix that later. return false; } } return true; } // TODO: It might be possible to get akonadi to tell us if a collection is empty // or not and use that information instead of assuming all collections are not empty. // Using Collection statistics? } void EntityTreeModel::fetchMore( const QModelIndex & parent ) { Q_D( EntityTreeModel ); if (!canFetchMore(parent)) return; if ( d->m_itemPopulation == ImmediatePopulation ) // Nothing to do. The items are already in the model. return; else if ( d->m_itemPopulation == LazyPopulation ) { const Collection collection = parent.data( CollectionRole ).value(); if ( !collection.isValid() ) return; d->fetchItems( collection ); } } bool EntityTreeModel::hasChildren( const QModelIndex &parent ) const { Q_D( const EntityTreeModel ); // TODO: Empty collections right now will return true and get a little + to expand. // There is probably no way to tell if a collection // has child items in akonadi without first attempting an itemFetchJob... // Figure out a way to fix this. (Statistics) return ((rowCount(parent) > 0) || (canFetchMore( parent ) && d->m_itemPopulation == LazyPopulation)); } bool EntityTreeModel::match(const Item &item, const QVariant &value, Qt::MatchFlags flags) const { Q_UNUSED(item); Q_UNUSED(value); Q_UNUSED(flags); return false; } bool EntityTreeModel::match(const Collection &collection, const QVariant &value, Qt::MatchFlags flags) const { Q_UNUSED(collection); Q_UNUSED(value); Q_UNUSED(flags); return false; } QModelIndexList EntityTreeModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const { if (role != AmazingCompletionRole) return QAbstractItemModel::match(start, role, value, hits, flags); // Try to match names, and email addresses. QModelIndexList list; if (role < 0 || !start.isValid() || !value.isValid()) return list; const int column = 0; int row = start.row(); QModelIndex parentIdx = start.parent(); int parentRowCount = rowCount(parentIdx); while (row < parentRowCount && (hits == -1 || list.size() < hits)) { QModelIndex idx = index(row, column, parentIdx); Item item = idx.data(ItemRole).value(); if (!item.isValid()) { Collection col = idx.data(CollectionRole).value(); if (!col.isValid()) { continue; } if (match(col, value, flags)) list << idx; } else { if (match(item, value, flags)) { list << idx; } } ++row; } return list; } bool EntityTreeModel::insertRows( int, int, const QModelIndex& ) { return false; } bool EntityTreeModel::insertColumns( int, int, const QModelIndex& ) { return false; } bool EntityTreeModel::removeRows( int start, int end, const QModelIndex &parent ) { /* beginRemoveRows(start, end, parent); // TODO: Implement me. endRemoveRows(start, end, parent); */ return false; } bool EntityTreeModel::removeColumns( int, int, const QModelIndex& ) { return false; } void EntityTreeModel::setRootCollection( const Collection &collection ) { Q_D(EntityTreeModel); Q_ASSERT( collection.isValid() ); d->m_rootCollection = collection; clearAndReset(); } Collection EntityTreeModel::rootCollection() const { Q_D(const EntityTreeModel); return d->m_rootCollection; } QModelIndex EntityTreeModel::indexForCollection( const Collection &collection ) const { Q_D(const EntityTreeModel); - // TODO: will this work for collection::root while showing it? - - const int row = d->indexOf( d->m_childEntities.value( collection.parentCollection().id() ), collection.id() ); + // The id of the parent of Collection::root is not guaranteed to be -1 as assumed by startFirstListJob, + // we ensure that we use -1 for the invalid Collection. + const Collection::Id parentId = collection.parentCollection().isValid() ? collection.parentCollection().id() : -1; + const int row = d->indexOf( d->m_childEntities.value( parentId ), collection.id() ); if ( row < 0 ) return QModelIndex(); - Node *node = d->m_childEntities.value( collection.parentCollection().id() ).at( row ); + Node *node = d->m_childEntities.value( parentId ).at( row ); return createIndex( row, 0, reinterpret_cast( node ) ); } QModelIndexList EntityTreeModel::indexesForItem( const Item &item ) const { Q_D(const EntityTreeModel); QModelIndexList indexes; const Collection::List collections = d->getParentCollections( item ); const qint64 id = item.id(); foreach ( const Collection &collection, collections ) { const int row = d->indexOf( d->m_childEntities.value( collection.id() ), id ); Node *node = d->m_childEntities.value( collection.id() ).at( row ); indexes << createIndex( row, 0, reinterpret_cast( node ) ); } return indexes; } void EntityTreeModel::setItemPopulationStrategy( ItemPopulationStrategy strategy ) { Q_D(EntityTreeModel); d->m_itemPopulation = strategy; clearAndReset(); } EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const { Q_D(const EntityTreeModel); return d->m_itemPopulation; } void EntityTreeModel::setIncludeRootCollection( bool include ) { Q_D(EntityTreeModel); d->m_showRootCollection = include; clearAndReset(); } bool EntityTreeModel::includeRootCollection() const { Q_D(const EntityTreeModel); return d->m_showRootCollection; } void EntityTreeModel::setRootCollectionDisplayName( const QString &displayName ) { Q_D(EntityTreeModel); d->m_rootCollectionDisplayName = displayName; // TODO: Emit datachanged if it is being shown. } QString EntityTreeModel::rootCollectionDisplayName() const { Q_D( const EntityTreeModel); return d->m_rootCollectionDisplayName; } void EntityTreeModel::setCollectionFetchStrategy( CollectionFetchStrategy strategy ) { Q_D( EntityTreeModel); d->m_collectionFetchStrategy = strategy; clearAndReset(); } EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const { Q_D( const EntityTreeModel); return d->m_collectionFetchStrategy; } #include "entitytreemodel.moc" diff --git a/akonadi/entitytreemodel_p.cpp b/akonadi/entitytreemodel_p.cpp index ae3f3d7f2..82f395d19 100644 --- a/akonadi/entitytreemodel_p.cpp +++ b/akonadi/entitytreemodel_p.cpp @@ -1,789 +1,784 @@ /* Copyright (c) 2008 Stephen Kelly 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 "entitytreemodel_p.h" #include "entitytreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; EntityTreeModelPrivate::EntityTreeModelPrivate( EntityTreeModel *parent ) : q_ptr( parent ), m_collectionFetchStrategy( EntityTreeModel::FetchCollectionsRecursive ), m_itemPopulation( EntityTreeModel::ImmediatePopulation ), m_includeUnsubscribed( true ), m_includeStatistics( false ), m_showRootCollection( false ) { } int EntityTreeModelPrivate::indexOf( const QList &nodes, Entity::Id id ) const { int i = 0; foreach ( const Node *node, nodes ) { if ( node->id == id ) return i; i++; } return -1; } ItemFetchJob* EntityTreeModelPrivate::getItemFetchJob( const Collection &parent, ItemFetchScope scope ) const { ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( parent, m_session ); itemJob->setFetchScope( scope ); return itemJob; } ItemFetchJob* EntityTreeModelPrivate::getItemFetchJob( const Item &item, ItemFetchScope scope ) const { ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( item, m_session ); itemJob->setFetchScope( scope ); return itemJob; } void EntityTreeModelPrivate::runItemFetchJob(ItemFetchJob *itemFetchJob, const Collection &parent) const { Q_Q( const EntityTreeModel ); // TODO: This hack is probably not needed anymore. Remove it. // ### HACK: itemsReceivedFromJob needs to know which collection items were added to. // That is not provided by akonadi, so we attach it in a property. itemFetchJob->setProperty( ItemFetchCollectionId(), QVariant( parent.id() ) ); q->connect( itemFetchJob, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ), q, SLOT( itemsFetched( const Akonadi::Item::List& ) ) ); q->connect( itemFetchJob, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::fetchItems( const Collection &parent ) { Q_Q( EntityTreeModel ); // TODO: Use a more specific fetch scope to get only the envelope for mails etc. ItemFetchJob *itemJob = getItemFetchJob(parent, m_monitor->itemFetchScope() ); runItemFetchJob(itemJob, parent); } void EntityTreeModelPrivate::fetchCollections( const Collection &collection, CollectionFetchJob::Type type ) { Q_Q( EntityTreeModel ); CollectionFetchJob *job = new CollectionFetchJob( collection, type, m_session ); job->fetchScope().setIncludeUnsubscribed( m_includeUnsubscribed ); job->fetchScope().setIncludeStatistics( m_includeStatistics ); job->fetchScope().setContentMimeTypes( m_monitor->mimeTypesMonitored() ); q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ), q, SLOT( collectionsFetched( const Akonadi::Collection::List& ) ) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::collectionsFetched( const Akonadi::Collection::List& collections ) { // TODO: refactor this stuff into separate methods for listing resources in Collection::root, and listing collections within resources. Q_Q( EntityTreeModel ); Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self(); Collection::List _collections = collections; forever { int collectionsSize = _collections.size(); QMutableListIterator it(_collections); while (it.hasNext()) { const Collection col = it.next(); const Collection::Id parentId = col.parentCollection().id(); const Collection::Id colId = col.id(); if ( m_collections.contains( parentId ) ) { insertCollection( col, m_collections.value( parentId ) ); if ( m_pendingChildCollections.contains( colId ) ) { QList pendingParentIds = m_pendingChildCollections.value( colId ); foreach(const Collection::Id &id, pendingParentIds) { Collection pendingCollection = m_pendingCollections.value(id); Q_ASSERT( pendingCollection.isValid() ); insertPendingCollection( pendingCollection, col, it ); m_pendingCollections.remove(id); } if ( !it.findNext(col) && !it.findPrevious(col) ) { Q_ASSERT("Something went very wrong" == "false"); } m_pendingChildCollections.remove( colId ); } it.remove(); } else { m_pendingCollections.insert( colId, col ); if ( !m_pendingChildCollections.value( parentId ).contains( colId ) ) m_pendingChildCollections[ parentId ].append( colId ); } } if ( _collections.isEmpty() ) break; // forever if( _collections.size() == collectionsSize ) { // Didn't process any collections this iteration. // Persist them until the next time collectionsFetched receives collections. kWarning() << "Some collections could not be inserted into the model yet."; break; // forever } } } void EntityTreeModelPrivate::itemsFetched( const Akonadi::Item::List& items ) { Q_Q( EntityTreeModel ); QObject *job = q->sender(); Q_ASSERT( job ); const Collection::Id collectionId = job->property( ItemFetchCollectionId() ).value(); Item::List itemsToInsert; Item::List itemsToUpdate; const Collection collection = m_collections.value( collectionId ); Q_ASSERT( collection.isValid() ); const QList collectionEntities = m_childEntities.value( collectionId ); foreach ( const Item &item, items ) { if ( indexOf( collectionEntities, item.id() ) != -1 ) { itemsToUpdate << item; } else { if ( m_mimeChecker.wantedMimeTypes().isEmpty() || m_mimeChecker.isWantedItem( item ) ) { itemsToInsert << item; } } } if ( itemsToInsert.size() > 0 ) { const int startRow = m_childEntities.value( collectionId ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collectionId ) ); q->beginInsertRows( parentIndex, startRow, startRow + items.size() - 1 ); foreach ( const Item &item, items ) { Item::Id itemId = item.id(); m_items.insert( itemId, item ); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; m_childEntities[ collectionId ].append( node ); } q->endInsertRows(); } } void EntityTreeModelPrivate::monitoredMimeTypeChanged( const QString & mimeType, bool monitored ) { if ( monitored ) m_mimeChecker.addWantedMimeType( mimeType ); else m_mimeChecker.removeWantedMimeType( mimeType ); } void EntityTreeModelPrivate::retrieveAncestors(const Akonadi::Collection& collection) { Q_Q( EntityTreeModel ); // Unlike fetchCollections, this method fetches collections by traversing up, not down. CollectionFetchJob *job = new CollectionFetchJob( collection.parentCollection(), CollectionFetchJob::Base, m_session ); job->fetchScope().setIncludeUnsubscribed( m_includeUnsubscribed ); job->fetchScope().setIncludeStatistics( m_includeStatistics ); q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ), q, SLOT( ancestorsFetched( const Akonadi::Collection::List& ) ) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::ancestorsFetched(const Akonadi::Collection::List& collectionList) { // List is a size of one. foreach(const Collection &collection, collectionList) { // We should find a collection already in the tree before we reach the collection root. // We're looking to bridge a gap here. Q_ASSERT(collection != Collection::root()); // We already checked this either on the previous recursion or in monitoredCollectionAdded. Q_ASSERT(!m_collections.contains(collection.id())); m_ancestors.prepend(collection); if (m_collections.contains(collection.parentCollection().id())) { m_ancestors.prepend( m_collections.value(collection.parentCollection().id()) ); insertAncestors(m_ancestors); } else { retrieveAncestors(collection); } } } void EntityTreeModelPrivate::insertAncestors(const Akonadi::Collection::List& collectionList) { Collection::List::const_iterator it; const Collection::List::const_iterator begin = collectionList.constBegin() + 1; const Collection::List::const_iterator end = collectionList.constEnd(); for (it = begin; it != end; ++it) { insertCollection(*it, *(it-1)); } m_ancestors.clear(); } void EntityTreeModelPrivate::insertCollection( const Akonadi::Collection& collection, const Akonadi::Collection& parent ) { Q_ASSERT(collection.isValid()); Q_ASSERT(parent.isValid()); Q_Q( EntityTreeModel ); // TODO: Use order attribute of parent if available // Otherwise prepend collections and append items. Currently this prepends all collections. // Or I can prepend and append for single signals, then 'change' the parent. // QList childCols = m_childEntities.value( parent.id() ); // int row = childCols.size(); // int numChildCols = childCollections.value(parent.id()).size(); const int row = 0; const QModelIndex parentIndex = q->indexForCollection( parent ); q->beginInsertRows( parentIndex, row, row ); m_collections.insert( collection.id(), collection ); Node *node = new Node; node->id = collection.id(); node->parent = parent.id(); node->type = Node::Collection; m_childEntities[ parent.id() ].prepend( node ); q->endInsertRows(); if ( m_itemPopulation == EntityTreeModel::ImmediatePopulation ) fetchItems( collection ); } void EntityTreeModelPrivate::insertPendingCollection( const Akonadi::Collection& collection, const Akonadi::Collection& parent, QMutableListIterator &colIt ) { insertCollection(collection, parent); m_pendingCollections.remove( collection.id() ); if (colIt.findPrevious(collection) || colIt.findNext(collection)) { colIt.remove(); } Q_ASSERT(m_collections.contains(parent.id())); QList pendingChildCollectionsToInsert = m_pendingChildCollections.value(collection.id()); QList::const_iterator it; const QList::const_iterator begin = pendingChildCollectionsToInsert.constBegin(); const QList::const_iterator end = pendingChildCollectionsToInsert.constEnd(); for ( it = begin; it != end; ++it ) { insertPendingCollection(m_pendingCollections.value( *it ), collection, colIt); } m_pendingChildCollections.remove( parent.id() ); } void EntityTreeModelPrivate::monitoredCollectionAdded( const Akonadi::Collection& collection, const Akonadi::Collection& parent ) { // If the resource is removed while populating the model with it, we might still // get some monitor signals. These stale/out-of-order signals can't be completely eliminated // in the akonadi server due to implementation details, so we also handle such signals in the model silently // in all the monior slots. // Stephen Kelly, 28, July 2009 // This is currently temporarily blocked by a uninitialized value bug in the server. // if ( !m_collections.contains( parent.id() ) ) // { // kWarning() << "Got a stale notification for a collection whose parent was already removed." << collection.id() << collection.remoteId(); // return; // } // Some collection trees contain multiple mimetypes. Even though server side filtering ensures we // only get the ones we're interested in from the job, we have to filter on collections received through signals too. if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedCollection( collection ) ) return; if (!m_collections.contains(parent.id())) { // The collection we're interested in is contained in a collection we're not interested in. // We download the ancestors of the collection we're interested in to complete the tree. m_ancestors.prepend(collection); retrieveAncestors(collection); return; } insertCollection(collection, parent); } void EntityTreeModelPrivate::monitoredCollectionRemoved( const Akonadi::Collection& collection ) { if ( !m_collections.contains( collection.parent() ) ) return; Q_Q( EntityTreeModel ); // This may be a signal for a collection we've already removed by removing its ancestor. if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } const int row = indexOf( m_childEntities.value( collection.parentCollection().id() ), collection.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.parentCollection().id() ) ); q->beginRemoveRows( parentIndex, row, row ); // Delete all descendant collections and items. removeChildEntities(collection.id()); // Remove deleted collection from its parent. m_childEntities[ collection.parentCollection().id() ].removeAt( row ); q->endRemoveRows(); } void EntityTreeModelPrivate::removeChildEntities(Collection::Id colId) { QList::const_iterator it; QList childList = m_childEntities.value(colId); const QList::const_iterator begin = childList.constBegin(); const QList::const_iterator end = childList.constEnd(); for (it = begin; it != end; ++it) { if (Node::Item == (*it)->type) { m_items.remove((*it)->id); } else { removeChildEntities((*it)->id); m_collections.remove((*it)->id); } } m_childEntities.remove(colId); } void EntityTreeModelPrivate::monitoredCollectionMoved( const Akonadi::Collection& collection, const Akonadi::Collection& sourceCollection, const Akonadi::Collection& destCollection ) { if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } Q_Q( EntityTreeModel ); const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), collection.id() ); const QModelIndex srcParentIndex = q->indexForCollection( sourceCollection ); const QModelIndex destParentIndex = q->indexForCollection( destCollection ); const int destRow = 0; // Prepend collections // TODO: Uncomment for Qt4.6 // q->beginMoveRows( srcParentIndex, srcRow, srcRow, destParentIndex, destRow ); // Node *node = m_childEntities[ sourceCollection.id() ].takeAt( srcRow ); // m_childEntities[ destCollection.id() ].prepend( node ); // q->endMoveRows(); } void EntityTreeModelPrivate::monitoredCollectionChanged( const Akonadi::Collection &collection ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } m_collections[ collection.id() ] = collection; const QModelIndex index = q->indexForCollection( collection ); Q_ASSERT( index.isValid() ); q->dataChanged( index, index ); } void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged( Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( id ) ) { kWarning() << "Got statistics response for non-existing collection:" << id; } else { m_collections[ id ].setStatistics( statistics ); const QModelIndex index = q->indexForCollection( m_collections[ id ] ); q->dataChanged( index, index ); } } void EntityTreeModelPrivate::monitoredItemAdded( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem( item ) ) return; const int row = m_childEntities.value( collection.id() ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); m_items.insert( item.id(), item ); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; m_childEntities[ collection.id() ].append( node ); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemRemoved( const Akonadi::Item &item ) { Q_Q( EntityTreeModel ); const Collection::List parents = getParentCollections( item ); if ( parents.isEmpty() ) return; if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } // TODO: Iterate over all (virtual) collections. const Collection collection = parents.first(); Q_ASSERT( m_collections.contains( collection.id() ) ); const int row = indexOf( m_childEntities.value( collection.id() ), item.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginRemoveRows( parentIndex, row, row ); m_items.remove( item.id() ); m_childEntities[ collection.id() ].removeAt( row ); q->endRemoveRows(); } void EntityTreeModelPrivate::monitoredItemChanged( const Akonadi::Item &item, const QSet& ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } m_items[ item.id() ] = item; const QModelIndexList indexes = q->indexesForItem( item ); foreach ( const QModelIndex &index, indexes ) { if( !index.isValid() ) { kWarning() << "item has invalid index:" << item.id() << item.remoteId(); } else { q->dataChanged( index, index ); } } } void EntityTreeModelPrivate::monitoredItemMoved( const Akonadi::Item& item, const Akonadi::Collection& sourceCollection, const Akonadi::Collection& destCollection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( sourceCollection.id() ) ); Q_ASSERT( m_collections.contains( destCollection.id() ) ); const Item::Id itemId = item.id(); const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), itemId ); const QModelIndex srcIndex = q->indexForCollection( sourceCollection ); const QModelIndex destIndex = q->indexForCollection( destCollection ); // Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes? const int destRow = q->rowCount( destIndex ); // TODO: Uncomment for Qt4.6 // q->beginMoveRows( srcIndex, srcRow, srcRow, destIndex, destRow ); // Node *node = m_childEntities[ sourceItem.id() ].takeAt( srcRow ); // m_childEntities[ destItem.id() ].append( node ); // q->endMoveRows(); } void EntityTreeModelPrivate::monitoredItemLinked( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem( item ) ) return; const int row = m_childEntities.value( collection.id() ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; m_childEntities[ collection.id()].append( node ); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemUnlinked( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); const int row = indexOf( m_childEntities.value( collection.id() ), item.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); m_childEntities[ collection.id() ].removeAt( row ); q->endInsertRows(); } void EntityTreeModelPrivate::fetchJobDone( KJob *job ) { Q_ASSERT(m_pendingCollections.isEmpty()); Q_ASSERT(m_pendingChildCollections.isEmpty()); if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::copyJobDone( KJob *job ) { if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::moveJobDone( KJob *job ) { if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::updateJobDone( KJob *job ) { if ( job->error() ) { // TODO: handle job errors kWarning() << "Job error:" << job->errorString(); } else { // TODO: Is this trying to do the job of collectionstatisticschanged? // CollectionStatisticsJob *csjob = static_cast( job ); // Collection result = csjob->collection(); // collectionStatisticsChanged( result.id(), csjob->statistics() ); } } void EntityTreeModelPrivate::startFirstListJob() { Q_Q(EntityTreeModel); if (m_collections.size() > 0) return; Collection rootCollection; // Even if the root collection is the invalid collection, we still need to start // the first list job with Collection::root. if ( m_showRootCollection ) { rootCollection = Collection::root(); // Notify the outside that we're putting collection::root into the model. -// kDebug() << "begin"; q->beginInsertRows( QModelIndex(), 0, 0 ); m_collections.insert( rootCollection.id(), rootCollection ); m_rootNode = new Node; m_rootNode->id = rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_childEntities[ -1 ].append( m_rootNode ); -// kDebug() << "why"; q->endInsertRows(); } else { // Otherwise store it silently because it's not part of the usable model. rootCollection = m_rootCollection; m_rootNode = new Node; m_rootNode->id = rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_collections.insert( rootCollection.id(), rootCollection ); } -// kDebug() << "inserting" << rootCollection.id(); - // Includes recursive trees. Lower levels are fetched in the onRowsInserted slot if // necessary. // HACK: fix this for recursive listing if we filter on mimetypes that only exit deeper // in the hierarchy if ( ( m_collectionFetchStrategy == EntityTreeModel::FetchFirstLevelChildCollections) /*|| ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive )*/ ) { fetchCollections( rootCollection, CollectionFetchJob::FirstLevel ); } if ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive ) fetchCollections( rootCollection, CollectionFetchJob::Recursive ); // If the root collection is not collection::root, then it could have items, and they will need to be // retrieved now. if ( m_itemPopulation != EntityTreeModel::NoItemPopulation ) { -// kDebug() << (rootCollection == Collection::root()); if (rootCollection != Collection::root()) fetchItems( rootCollection ); } } Collection EntityTreeModelPrivate::getParentCollection( Entity::Id id ) const { QHashIterator > iter( m_childEntities ); while ( iter.hasNext() ) { iter.next(); if ( indexOf( iter.value(), id ) != -1 ) { return m_collections.value( iter.key() ); } } return Collection(); } Collection::List EntityTreeModelPrivate::getParentCollections( const Item &item ) const { Collection::List list; QHashIterator > iter( m_childEntities ); while ( iter.hasNext() ) { iter.next(); if ( indexOf( iter.value(), item.id() ) != -1 ) { list << m_collections.value( iter.key() ); } } return list; } Collection EntityTreeModelPrivate::getParentCollection( const Collection &collection ) const { return m_collections.value( collection.parentCollection().id() ); } Entity::Id EntityTreeModelPrivate::childAt( Collection::Id id, int position, bool *ok ) const { const QList list = m_childEntities.value( id ); if ( list.size() <= position ) { *ok = false; return 0; } *ok = true; return list.at( position )->id; } int EntityTreeModelPrivate::indexOf( Collection::Id parent, Collection::Id collectionId ) const { return indexOf( m_childEntities.value( parent ), collectionId ); } Item EntityTreeModelPrivate::getItem( Item::Id id) const { if ( id > 0 ) id *= -1; return m_items.value( id ); } diff --git a/akonadi/itemfetchjob.cpp b/akonadi/itemfetchjob.cpp index 36e6bbe35..b017b94e4 100644 --- a/akonadi/itemfetchjob.cpp +++ b/akonadi/itemfetchjob.cpp @@ -1,341 +1,355 @@ /* 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 "itemfetchjob.h" #include "attributefactory.h" #include "collection.h" #include "collectionselectjob_p.h" #include "imapparser_p.h" #include "itemfetchscope.h" #include "itemserializer_p.h" #include "itemserializerplugin.h" #include "job_p.h" #include "entity_p.h" #include "protocol_p.h" #include "protocolhelper_p.h" #include #include #include #include #include using namespace Akonadi; class Akonadi::ItemFetchJobPrivate : public JobPrivate { public: ItemFetchJobPrivate( ItemFetchJob *parent ) : JobPrivate( parent ) { } void timeout() { Q_Q( ItemFetchJob ); mEmitTimer->stop(); // in case we are called by result() if ( !mPendingItems.isEmpty() ) { emit q->itemsReceived( mPendingItems ); mPendingItems.clear(); } } void startFetchJob(); void selectDone( KJob * job ); Q_DECLARE_PUBLIC( ItemFetchJob ) Collection mCollection; Item mItem; Item::List mItems; ItemFetchScope mFetchScope; Item::List mPendingItems; // items pending for emitting itemsReceived() QTimer* mEmitTimer; }; void ItemFetchJobPrivate::startFetchJob() { QByteArray command = newTag(); if ( mItem.isValid() ) command += " " AKONADI_CMD_UID " " AKONADI_CMD_ITEMFETCH " " + QByteArray::number( mItem.id() ); else if ( !mItem.remoteId().isEmpty() ) command += " " AKONADI_CMD_RID " " AKONADI_CMD_ITEMFETCH " " + mItem.remoteId().toUtf8(); else command += " " AKONADI_CMD_ITEMFETCH " 1:*"; if ( mFetchScope.fullPayload() ) command += " " AKONADI_PARAM_FULLPAYLOAD; if ( mFetchScope.allAttributes() ) command += " " AKONADI_PARAM_ALLATTRIBUTES; if ( mFetchScope.cacheOnly() ) command += " " AKONADI_PARAM_CACHEONLY; + if ( mFetchScope.ancestorRetrieval() != ItemFetchScope::None ) { + switch ( mFetchScope.ancestorRetrieval() ) { + case ItemFetchScope::Parent: + command += " ANCESTORS 1"; + break; + case ItemFetchScope::All: + command += " ANCESTORS INF"; + break; + default: + Q_ASSERT( false ); + } + } //TODO: detect somehow if server supports external payload attribute command += " " AKONADI_PARAM_EXTERNALPAYLOAD; command += " (UID REMOTEID COLLECTIONID FLAGS SIZE DATETIME"; foreach ( const QByteArray &part, mFetchScope.payloadParts() ) command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part ); foreach ( const QByteArray &part, mFetchScope.attributes() ) command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, part ); command += ")\n"; writeData( command ); } void ItemFetchJobPrivate::selectDone( KJob * job ) { if ( !job->error() ) // the collection is now selected, fetch the message(s) startFetchJob(); } ItemFetchJob::ItemFetchJob( const Collection &collection, QObject * parent ) : Job( new ItemFetchJobPrivate( this ), parent ) { Q_D( ItemFetchJob ); d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); d->mCollection = collection; } ItemFetchJob::ItemFetchJob( const Item & item, QObject * parent) : Job( new ItemFetchJobPrivate( this ), parent ) { Q_D( ItemFetchJob ); d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); d->mCollection = Collection::root(); d->mItem = item; } ItemFetchJob::~ItemFetchJob() { } void ItemFetchJob::doStart() { Q_D( ItemFetchJob ); if ( !d->mItem.isValid() ) { // collection content listing if ( d->mCollection == Collection::root() ) { setErrorText( QLatin1String("Cannot list root collection.") ); setError( Unknown ); emitResult(); } CollectionSelectJob *job = new CollectionSelectJob( d->mCollection, this ); connect( job, SIGNAL(result(KJob*)), SLOT(selectDone(KJob*)) ); addSubjob( job ); } else d->startFetchJob(); } void ItemFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) { Q_D( ItemFetchJob ); if ( tag == "*" ) { int begin = data.indexOf( "FETCH" ); if ( begin >= 0 ) { // split fetch response into key/value pairs QList fetchResponse; ImapParser::parseParenthesizedList( data, fetchResponse, begin + 6 ); // create a new item object Item::Id uid = -1; int rev = -1; QString rid; QString mimeType; Entity::Id cid = -1; for ( int i = 0; i < fetchResponse.count() - 1; i += 2 ) { const QByteArray key = fetchResponse.value( i ); const QByteArray value = fetchResponse.value( i + 1 ); if ( key == "UID" ) uid = value.toLongLong(); else if ( key == "REV" ) rev = value.toInt(); else if ( key == "REMOTEID" ) { if ( !value.isEmpty() ) rid = QString::fromUtf8( value ); else rid.clear(); } else if ( key == "COLLECTIONID" ) { cid = value.toInt(); } else if ( key == "MIMETYPE" ) mimeType = QString::fromLatin1( value ); } if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) { kWarning() << "Broken fetch response: UID, RID, REV or MIMETYPE missing!"; return; } Item item( uid ); item.setRemoteId( rid ); item.setRevision( rev ); item.setMimeType( mimeType ); item.setStorageCollectionId( cid ); if ( !item.isValid() ) return; // parse fetch response fields for ( int i = 0; i < fetchResponse.count() - 1; i += 2 ) { const QByteArray key = fetchResponse.value( i ); // skip stuff we dealt with already if ( key == "UID" || key == "REV" || key == "REMOTEID" || key == "MIMETYPE" || key == "COLLECTIONID") continue; // flags if ( key == "FLAGS" ) { QList flags; ImapParser::parseParenthesizedList( fetchResponse[i + 1], flags ); foreach ( const QByteArray &flag, flags ) { item.setFlag( flag ); } } else if ( key == "SIZE" ) { const quint64 size = fetchResponse[i + 1].toLongLong(); item.setSize( size ); } else if ( key == "DATETIME" ) { QDateTime datetime; ImapParser::parseDateTime( fetchResponse[i + 1], datetime ); item.setModificationTime( datetime ); + } else if ( key == "ANCESTORS" ) { + ProtocolHelper::parseAncestors( fetchResponse[i + 1], &item ); } else { int version = 0; QByteArray plainKey( key ); ProtocolHelper::PartNamespace ns; ImapParser::splitVersionedKey( key, plainKey, version ); plainKey = ProtocolHelper::decodePartIdentifier( plainKey, ns ); switch ( ns ) { case ProtocolHelper::PartPayload: { bool isExternal = false; QByteArray fileKey = fetchResponse.value( i + 1 ); if (fileKey == "[FILE]") { isExternal = true; i++; kDebug() << "Payload is external: " << isExternal << " filename: " << fetchResponse.value( i + 1 ); } ItemSerializer::deserialize( item, plainKey, fetchResponse.value( i + 1 ), version, isExternal ); break; } case ProtocolHelper::PartAttribute: { Attribute* attr = AttributeFactory::createAttribute( plainKey ); Q_ASSERT( attr ); if ( fetchResponse.value( i + 1 ) == "[FILE]" ) { ++i; QFile f( QString::fromUtf8( fetchResponse.value( i + 1 ) ) ); if ( f.open( QFile::ReadOnly ) ) attr->deserialize( f.readAll() ); else { kWarning() << "Failed to open attribute file: " << fetchResponse.value( i + 1 ); delete attr; } } else { attr->deserialize( fetchResponse.value( i + 1 ) ); } item.addAttribute( attr ); break; } case ProtocolHelper::PartGlobal: default: kWarning() << "Unknown item part type:" << key; } } } item.d_ptr->resetChangeLog(); d->mItems.append( item ); d->mPendingItems.append( item ); if ( !d->mEmitTimer->isActive() ) d->mEmitTimer->start(); return; } } kDebug() << "Unhandled response: " << tag << data; } Item::List ItemFetchJob::items() const { Q_D( const ItemFetchJob ); return d->mItems; } void ItemFetchJob::setFetchScope( ItemFetchScope &fetchScope ) { Q_D( ItemFetchJob ); d->mFetchScope = fetchScope; } void ItemFetchJob::setFetchScope( const ItemFetchScope &fetchScope ) { Q_D( ItemFetchJob ); d->mFetchScope = fetchScope; } ItemFetchScope &ItemFetchJob::fetchScope() { Q_D( ItemFetchJob ); return d->mFetchScope; } Item ItemFetchJob::item() const { Q_D( const ItemFetchJob ); return d->mItem; } Collection ItemFetchJob::collection() const { Q_D( const ItemFetchJob ); return d->mCollection; } void ItemFetchJob::setCollection(const Akonadi::Collection& collection) { Q_D( ItemFetchJob ); d->mCollection = collection; } #include "itemfetchjob.moc" diff --git a/akonadi/itemfetchscope.cpp b/akonadi/itemfetchscope.cpp index 087b48e03..cdf3117fc 100644 --- a/akonadi/itemfetchscope.cpp +++ b/akonadi/itemfetchscope.cpp @@ -1,109 +1,119 @@ /* Copyright (c) 2008 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemfetchscope.h" #include "itemfetchscope_p.h" #include using namespace Akonadi; ItemFetchScope::ItemFetchScope() { d = new ItemFetchScopePrivate(); } ItemFetchScope::ItemFetchScope( const ItemFetchScope &other ) : d( other.d ) { } ItemFetchScope::~ItemFetchScope() { } ItemFetchScope &ItemFetchScope::operator=( const ItemFetchScope &other ) { if ( &other != this ) d = other.d; return *this; } QSet< QByteArray > ItemFetchScope::payloadParts() const { return d->mPayloadParts; } void ItemFetchScope::fetchPayloadPart(const QByteArray & part, bool fetch) { if ( fetch ) d->mPayloadParts.insert( part ); else d->mPayloadParts.remove( part ); } bool ItemFetchScope::fullPayload() const { return d->mFullPayload; } void ItemFetchScope::fetchFullPayload(bool fetch) { d->mFullPayload = fetch; } QSet< QByteArray > ItemFetchScope::attributes() const { return d->mAttributes; } void ItemFetchScope::fetchAttribute(const QByteArray & type, bool fetch) { if ( fetch ) d->mAttributes.insert( type ); else d->mAttributes.remove( type ); } bool ItemFetchScope::allAttributes() const { return d->mAllAttributes; } void ItemFetchScope::fetchAllAttributes(bool fetch) { d->mAllAttributes = fetch; } bool ItemFetchScope::isEmpty() const { return d->mPayloadParts.isEmpty() && d->mAttributes.isEmpty() && !d->mFullPayload && !d->mAllAttributes; } bool ItemFetchScope::cacheOnly() const { return d->mCacheOnly; } void ItemFetchScope::setCacheOnly(bool cacheOnly) { d->mCacheOnly = cacheOnly; } + +ItemFetchScope::AncestorRetrieval ItemFetchScope::ancestorRetrieval() const +{ + return d->mAncestorDepth; +} + +void ItemFetchScope::setAncestorRetrieval( AncestorRetrieval depth ) +{ + d->mAncestorDepth = depth; +} diff --git a/akonadi/itemfetchscope.h b/akonadi/itemfetchscope.h index 4c2f6f836..fecc165ba 100644 --- a/akonadi/itemfetchscope.h +++ b/akonadi/itemfetchscope.h @@ -1,195 +1,221 @@ /* Copyright (c) 2008 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMFETCHSCOPE_H #define ITEMFETCHSCOPE_H #include "akonadi_export.h" #include class QStringList; template class QSet; namespace Akonadi { class ItemFetchScopePrivate; /** * @short Specifies which parts of an item should be fetched from the Akonadi storage. * * When items are fetched from server either by using ItemFetchJob explicitly or * when it is being used internally by other classes, e.g. ItemModel, the scope * of the fetch operation can be tailored to the application's current needs. * * There are two supported ways of changing the currently active ItemFetchScope * of classes: * - in-place: modify the ItemFetchScope object the other class holds as a member * - replace: replace the other class' member with a new scope object * * Example: modifying an ItemFetchJob's scope @c in-place * @code * Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( collection ); * job->fetchScope().fetchFullPayload(); * job->fetchScope().fetchAttribute(); * @endcode * * Example: @c replacing an ItemFetchJob's scope * @code * Akonadi::ItemFetchScope scope; * scope.fetchFullPayload(); * scope.fetchAttribute(); * * Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( collection ); * job->setFetchScope( scope ); * @endcode * * This class is implicitly shared. * * @author Kevin Krammer */ class AKONADI_EXPORT ItemFetchScope { public: + /** + * Describes the ancestor retrieval depth. + * @since 4.4 + */ + enum AncestorRetrieval { + None, ///< No ancestor retrieval at all (the default) + Parent, ///< Only retrieve the immediate parent collection + All ///< Retrieve all ancestors, up to Collection::root() + }; + /** * Creates an empty item fetch scope. * * Using an empty scope will only fetch the very basic meta data of items, * e.g. local id, remote id and mime type */ ItemFetchScope(); /** * Creates a new item fetch scope from an @p other. */ ItemFetchScope( const ItemFetchScope &other ); /** * Destroys the item fetch scope. */ ~ItemFetchScope(); /** * Assigns the @p other to this scope and returns a reference to this scope. */ ItemFetchScope &operator=( const ItemFetchScope &other ); /** * Returns the payload parts that should be fetched. * * @see fetchPayloadPart() */ QSet payloadParts() const; /** * Sets which payload parts shall be fetched. * * @param part The payload part identifier. * Valid values depend on the item type. * @param fetch @c true to fetch this part, @c false otherwise. */ void fetchPayloadPart( const QByteArray &part, bool fetch = true ); /** * Returns whether the full payload should be fetched. * * @see fetchFullPayload() */ bool fullPayload() const; /** * Sets whether the full payload shall be fetched. * * @param fetch @c true if the full payload should be fetched, @c false otherwise. */ void fetchFullPayload( bool fetch = true ); /** * Returns all explicitly fetched attributes. * * Undefined if fetchAllAttributes() returns true. * * @see fetchAttribute() */ QSet attributes() const; /** * Sets whether the attribute of the given @p type should be fetched. * * @param type The attribute type to fetch. * @param fetch @c true if the attribute should be fetched, @c false otherwise. */ void fetchAttribute( const QByteArray &type, bool fetch = true ); /** * Sets whether the attribute of the requested type should be fetched. * * @param fetch @c true if the attribute should be fetched, @c false otherwise. */ template inline void fetchAttribute( bool fetch = true ) { T dummy; fetchAttribute( dummy.type(), fetch ); } /** * Returns whether all available attributes should be fetched. * * @see fetchAllAttributes() */ bool allAttributes() const; /** * Sets whether all available attributes should be fetched. * * @param fetch @c true if all available attributes should be fetched, @c false otherwise. */ void fetchAllAttributes( bool fetch = true ); /** * Returns whether payload data should be requested from remote sources or just * from the local cache. * * @see setCacheOnly() */ bool cacheOnly() const; /** * Sets whether payload data should be requested from remote sources or just * from the local cache. * * @param cacheOnly @c true if no remote data should be requested, * @c false otherwise (the default). */ void setCacheOnly( bool cacheOnly ); + /** + * Sets how many levels of ancestor collections should be included in the retrieval. + * + * @param ancestorDepth The desired ancestor retrieval depth. + * @since 4.4 + */ + void setAncestorRetrieval( AncestorRetrieval ancestorDepth ); + + /** + * Returns the ancestor retrieval depth. + * + * @see setAncestorRetrieval() + * @since 4.4 + */ + AncestorRetrieval ancestorRetrieval() const; + /** * Returns @c true if there is nothing to fetch. */ bool isEmpty() const; private: //@cond PRIVATE QSharedDataPointer d; //@endcond }; } #endif diff --git a/akonadi/itemfetchscope_p.h b/akonadi/itemfetchscope_p.h index 32188762c..6848a57d9 100644 --- a/akonadi/itemfetchscope_p.h +++ b/akonadi/itemfetchscope_p.h @@ -1,61 +1,65 @@ /* Copyright (c) 2008 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMFETCHSCOPE_P_H #define ITEMFETCHSCOPE_P_H #include #include +#include "itemfetchscope.h" namespace Akonadi { /** * @internal */ class ItemFetchScopePrivate : public QSharedData { public: - ItemFetchScopePrivate() - : mFullPayload( false ), + ItemFetchScopePrivate() : + mAncestorDepth( ItemFetchScope::None ), + mFullPayload( false ), mAllAttributes( false ), mCacheOnly( false ) { } ItemFetchScopePrivate( const ItemFetchScopePrivate &other ) : QSharedData( other ) { mPayloadParts = other.mPayloadParts; mAttributes = other.mAttributes; + mAncestorDepth = other.mAncestorDepth; mFullPayload = other.mFullPayload; mAllAttributes = other.mAllAttributes; mCacheOnly = other.mCacheOnly; } public: QSet mPayloadParts; QSet mAttributes; + ItemFetchScope::AncestorRetrieval mAncestorDepth; bool mFullPayload; bool mAllAttributes; bool mCacheOnly; }; } #endif diff --git a/akonadi/itemsearchjob.h b/akonadi/itemsearchjob.h index aaba51f20..845ee1301 100644 --- a/akonadi/itemsearchjob.h +++ b/akonadi/itemsearchjob.h @@ -1,93 +1,93 @@ /* Copyright (c) 2009 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_ITEMSEARCHJOB_H #define AKONADI_ITEMSEARCHJOB_H #include #include namespace Akonadi { class ItemSearchJobPrivate; /** * @short Job that searches for items in the Akonadi storage. * * This job searches for items that match a given search query and returns * the list of item ids. * * @code * * const QString query = "..."; // some sparql query * * Akonadi::ItemSearchJob *job = new Akonadi::ItemSearchJob( query ); * connect( job, SIGNAL( result( KJob* ) ), this, SLOT( searchResult( KJob* ) ) ); * * ... * * MyClass::searchResult( KJob *job ) * { * Akonadi::ItemSearchJob *searchJob = qobject_cast( job ); * const Akonadi::Item::List items = searchJob->items(); * foreach ( const Akonadi::Item &item, items ) { * // fetch the full payload of 'item' * } * } * * @endcode * * @author Tobias Koenig */ class AKONADI_EXPORT ItemSearchJob : public Job { Q_OBJECT public: /** * Creates an item search job. * * @param query The search query in SPARQL format. * @param parent The parent object. */ - ItemSearchJob( const QString &query, QObject *parent = 0 ); + explicit ItemSearchJob( const QString &query, QObject *parent = 0 ); /** * Destroys the item search job. */ ~ItemSearchJob(); /** * Returns the items that matched the search query. * * @note The items only contain the uid but no payload. */ Item::List items() const; protected: void doStart(); virtual void doHandleResponse( const QByteArray &tag, const QByteArray &data ); private: Q_DECLARE_PRIVATE( ItemSearchJob ) }; } #endif diff --git a/akonadi/protocolhelper.cpp b/akonadi/protocolhelper.cpp index 29ab112a0..6a40163f3 100644 --- a/akonadi/protocolhelper.cpp +++ b/akonadi/protocolhelper.cpp @@ -1,248 +1,253 @@ /* Copyright (c) 2008 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 "protocolhelper_p.h" #include "attributefactory.h" #include "collectionstatistics.h" #include "exception.h" #include #include #include #include #include #include #include using namespace Akonadi; int ProtocolHelper::parseCachePolicy(const QByteArray & data, CachePolicy & policy, int start) { QVarLengthArray params; int end = Akonadi::ImapParser::parseParenthesizedList( data, params, start ); for ( int i = 0; i < params.count() - 1; i += 2 ) { const QByteArray key = params[i]; const QByteArray value = params[i + 1]; if ( key == "INHERIT" ) policy.setInheritFromParent( value == "true" ); else if ( key == "INTERVAL" ) policy.setIntervalCheckTime( value.toInt() ); else if ( key == "CACHETIMEOUT" ) policy.setCacheTimeout( value.toInt() ); else if ( key == "SYNCONDEMAND" ) policy.setSyncOnDemand( value == "true" ); else if ( key == "LOCALPARTS" ) { QVarLengthArray tmp; QStringList parts; Akonadi::ImapParser::parseParenthesizedList( value, tmp ); for ( int j=0; j ancestors; + ImapParser::parseParenthesizedList( data, ancestors ); + Entity* current = entity; + foreach ( const QByteArray &uidRidPair, ancestors ) { + QList parentIds; + ImapParser::parseParenthesizedList( uidRidPair, parentIds ); + if ( parentIds.size() != 2 ) + break; + const Collection::Id uid = parentIds.at( 0 ).toLongLong(); + const QString rid = QString::fromUtf8( parentIds.at( 1 ) ); + if ( uid == Collection::root().id() ) { + current->setParentCollection( Collection::root() ); + break; + } + current->parentCollection().setId( uid ); + current->parentCollection().setRemoteId( rid ); + current = ¤t->parentCollection(); + } +} + int ProtocolHelper::parseCollection(const QByteArray & data, Collection & collection, int start) { int pos = start; // collection and parent id Collection::Id colId = -1; bool ok = false; pos = ImapParser::parseNumber( data, colId, &ok, pos ); if ( !ok || colId <= 0 ) { kDebug() << "Could not parse collection id from response:" << data; return start; } Collection::Id parentId = -1; pos = ImapParser::parseNumber( data, parentId, &ok, pos ); if ( !ok || parentId < 0 ) { kDebug() << "Could not parse parent id from response:" << data; return start; } collection = Collection( colId ); collection.setParentCollection( Collection( parentId ) ); // attributes QVarLengthArray attributes; pos = ImapParser::parseParenthesizedList( data, attributes, pos ); for ( int i = 0; i < attributes.count() - 1; i += 2 ) { const QByteArray key = attributes[i]; const QByteArray value = attributes[i + 1]; if ( key == "NAME" ) { collection.setName( QString::fromUtf8( value ) ); } else if ( key == "REMOTEID" ) { collection.setRemoteId( QString::fromUtf8( value ) ); } else if ( key == "RESOURCE" ) { collection.setResource( QString::fromUtf8( value ) ); } else if ( key == "MIMETYPE" ) { QVarLengthArray ct; ImapParser::parseParenthesizedList( value, ct ); QStringList ct2; for ( int j = 0; j < ct.size(); j++ ) ct2 << QString::fromLatin1( ct[j] ); collection.setContentMimeTypes( ct2 ); } else if ( key == "MESSAGES" ) { CollectionStatistics s = collection.statistics(); s.setCount( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "UNSEEN" ) { CollectionStatistics s = collection.statistics(); s.setUnreadCount( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "SIZE" ) { CollectionStatistics s = collection.statistics(); s.setSize( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "CACHEPOLICY" ) { CachePolicy policy; ProtocolHelper::parseCachePolicy( value, policy ); collection.setCachePolicy( policy ); } else if ( key == "ANCESTORS" ) { - QList ancestors; - ImapParser::parseParenthesizedList( value, ancestors ); - Collection* currentCol = &collection; - foreach ( const QByteArray &uidRidPair, ancestors ) { - QList parentIds; - ImapParser::parseParenthesizedList( uidRidPair, parentIds ); - if ( parentIds.size() != 2 ) - break; - const Collection::Id uid = parentIds.at( 0 ).toLongLong(); - const QString rid = QString::fromUtf8( parentIds.at( 1 ) ); - if ( uid == Collection::root().id() ) { - currentCol->setParentCollection( Collection::root() ); - break; - } - currentCol->parentCollection().setId( uid ); - currentCol->parentCollection().setRemoteId( rid ); - currentCol = ¤tCol->parentCollection(); - } + parseAncestors( value, &collection ); } else { Attribute* attr = AttributeFactory::createAttribute( key ); Q_ASSERT( attr ); attr->deserialize( value ); collection.addAttribute( attr ); } } return pos; } QByteArray ProtocolHelper::attributesToByteArray(const Entity & entity, bool ns ) { QList l; foreach ( const Attribute *attr, entity.attributes() ) { l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() ); l << ImapParser::quote( attr->serialized() ); } return ImapParser::join( l, " " ); } QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray & label, int version ) { const QByteArray versionString( version != 0 ? '[' + QByteArray::number( version ) + ']' : "" ); switch ( ns ) { case PartGlobal: return label + versionString; case PartPayload: return "PLD:" + label + versionString; case PartAttribute: return "ATR:" + label + versionString; default: Q_ASSERT( false ); } return QByteArray(); } QByteArray ProtocolHelper::decodePartIdentifier( const QByteArray &data, PartNamespace & ns ) { if ( data.startsWith( "PLD:" ) ) { //krazy:exclude=strings ns = PartPayload; return data.mid( 4 ); } else if ( data.startsWith( "ATR:" ) ) { //krazy:exclude=strings ns = PartAttribute; return data.mid( 4 ); } else { ns = PartGlobal; return data; } } QByteArray ProtocolHelper::itemSetToByteArray( const Item::List &_items, const QByteArray &command ) { if ( _items.isEmpty() ) throw Exception( "No items specified" ); Item::List items( _items ); QByteArray rv; std::sort( items.begin(), items.end(), boost::bind( &Item::id, _1 ) < boost::bind( &Item::id, _2 ) ); if ( items.first().isValid() ) { // all items have a uid set rv += " " AKONADI_CMD_UID " "; rv += command; rv += ' '; QList uids; foreach ( const Item &item, items ) uids << item.id(); ImapSet set; set.add( uids ); rv += set.toImapSequenceSet(); } else { // check if all items have a remote id QList rids; foreach ( const Item &item, items ) { if ( item.remoteId().isEmpty() ) throw Exception( i18n( "No remote identifier specified" ) ); rids << ImapParser::quote( item.remoteId().toUtf8() ); } rv += " " AKONADI_CMD_RID " "; rv += command; rv += " ("; rv += ImapParser::join( rids, " " ); rv += ')'; } return rv; } diff --git a/akonadi/protocolhelper_p.h b/akonadi/protocolhelper_p.h index d32f76137..c64767234 100644 --- a/akonadi/protocolhelper_p.h +++ b/akonadi/protocolhelper_p.h @@ -1,94 +1,99 @@ /* Copyright (c) 2008 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_PROTOCOLHELPER_P_H #define AKONADI_PROTOCOLHELPER_P_H #include #include #include namespace Akonadi { /** @internal Helper methods for converting between libakonadi objects and their protocol representation. @todo Add unit tests for this. @todo Use exceptions for a useful error handling */ class ProtocolHelper { public: /** Part namespaces. */ enum PartNamespace { PartGlobal, PartPayload, PartAttribute }; /** Parse a cache policy definition. @param data The input data. @param policy The parsed cache policy. @param start Start of the data, ie. postion after the label. @returns Position in data after the cache policy description. */ static int parseCachePolicy( const QByteArray &data, CachePolicy &policy, int start = 0 ); /** Convert a cache policy object into its protocol representation. */ static QByteArray cachePolicyToByteArray( const CachePolicy &policy ); + /** + Convert a ancestor chain from its protocol representation into an Entity object. + */ + static void parseAncestors( const QByteArray &data, Entity *entity, int start = 0 ); + /** Parse a collection description. @param data The input data. @param collection The parsed collection. @param start Start of the data. @returns Position in data after the collection description. */ static int parseCollection( const QByteArray &data, Collection &collection, int start = 0 ); /** Convert attributes to their protocol representation. */ static QByteArray attributesToByteArray( const Entity &entity, bool ns = false ); /** Encodes part label and namespace. */ static QByteArray encodePartIdentifier( PartNamespace ns, const QByteArray &label, int version = 0 ); /** Decode part label and namespace. */ static QByteArray decodePartIdentifier( const QByteArray &data, PartNamespace &ns ); /** Converts the given set of items into a protocol representation. @throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers. */ static QByteArray itemSetToByteArray( const Item::List &items, const QByteArray &command ); }; } #endif diff --git a/akonadi/session_p.h b/akonadi/session_p.h index 0c7653196..7c889327d 100644 --- a/akonadi/session_p.h +++ b/akonadi/session_p.h @@ -1,115 +1,115 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SESSION_P_H #define AKONADI_SESSION_P_H #include "session.h" #include "imapparser_p.h" #include #include #include #include class QLocalSocket; namespace Akonadi { /** * @internal */ class SessionPrivate { public: SessionPrivate( Session *parent ) : mParent( parent ), mConnectionSettings( 0 ), protocolVersion( 0 ) { parser = new ImapParser(); } ~SessionPrivate() { delete parser; delete mConnectionSettings; } void startNext(); void reconnect(); void socketDisconnected(); void socketError( QLocalSocket::LocalSocketError error ); void dataReceived(); void doStartNext(); void startJob( Job* job ); void jobDone( KJob* job ); void jobWriteFinished( Akonadi::Job* job ); void jobDestroyed( QObject *job ); bool canPipelineNext(); /** * Creates a new default session for this thread with * the given @p sessionId. The session can be accessed * later by defaultSession(). * * You only need to call this method if you want that the * default session has a special custom id, otherwise a random unique * id is used automatically. */ static void createDefaultSession( const QByteArray &sessionId ); /** Associates the given Job object with this session. */ void addJob( Job* job ); /** Returns the next IMAP tag. */ int nextTag(); /** Sends the given raw data. */ void writeData( const QByteArray &data ); - static int minimumProtocolVersion() { return 18; } + static int minimumProtocolVersion() { return 19; } Session *mParent; QByteArray sessionId; QSettings *mConnectionSettings; QLocalSocket* socket; bool connected; int theNextTag; int protocolVersion; // job management QQueue queue; QQueue pipeline; Job* currentJob; bool jobRunning; // parser stuff ImapParser *parser; }; } #endif diff --git a/akonadi/tests/itemfetchtest.cpp b/akonadi/tests/itemfetchtest.cpp index 592bade2b..ee798a0c0 100644 --- a/akonadi/tests/itemfetchtest.cpp +++ b/akonadi/tests/itemfetchtest.cpp @@ -1,238 +1,257 @@ /* 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 "itemfetchtest.h" #include "itemfetchtest.moc" #include "collectionpathresolver_p.h" #include "testattribute.h" #include #include #include #include #include #include using namespace Akonadi; #include QTEST_AKONADIMAIN( ItemFetchTest, NoGUI ) void ItemFetchTest::initTestCase() { qRegisterMetaType(); AttributeFactory::registerAttribute(); } void ItemFetchTest::testFetch() { CollectionPathResolver *resolver = new CollectionPathResolver( "res1", this ); QVERIFY( resolver->exec() ); int colId = resolver->collection(); // listing of an empty folder ItemFetchJob *job = new ItemFetchJob( Collection( colId ), this ); QVERIFY( job->exec() ); QVERIFY( job->items().isEmpty() ); resolver = new CollectionPathResolver( "res1/foo", this ); QVERIFY( resolver->exec() ); int colId2 = resolver->collection(); // listing of a non-empty folder job = new ItemFetchJob( Collection( colId2 ), this ); QSignalSpy spy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); QVERIFY( spy.isValid() ); QVERIFY( job->exec() ); Item::List items = job->items(); QCOMPARE( items.count(), 15 ); int count = 0; for ( int i = 0; i < spy.count(); ++i ) { Item::List l = spy[i][0].value(); for ( int j = 0; j < l.count(); ++j ) { QVERIFY( items.count() > count + j ); QCOMPARE( items[count + j], l[j] ); } count += l.count(); } QCOMPARE( count, items.count() ); // check if the fetch response is parsed correctly Item item = items[0]; QCOMPARE( item.remoteId(), QString( "A" ) ); QCOMPARE( item.flags().count(), 3 ); QVERIFY( item.hasFlag( "\\Seen" ) ); QVERIFY( item.hasFlag( "\\Flagged" ) ); QVERIFY( item.hasFlag( "\\Draft" ) ); item = items[1]; QCOMPARE( item.flags().count(), 1 ); QVERIFY( item.hasFlag( "\\Flagged" ) ); item = items[2]; QVERIFY( item.flags().isEmpty() ); } void ItemFetchTest::testResourceRetrieval() { Item item( 1 ); ItemFetchJob *job = new ItemFetchJob( item, this ); job->fetchScope().fetchFullPayload( true ); job->fetchScope().fetchAllAttributes( true ); job->fetchScope().setCacheOnly( true ); QVERIFY( job->exec() ); QCOMPARE( job->items().count(), 1 ); item = job->items().first(); QCOMPARE( item.id(), 1ll ); QVERIFY( !item.remoteId().isEmpty() ); QVERIFY( !item.hasPayload() ); // not yet in cache QCOMPARE( item.attributes().count(), 1 ); job = new ItemFetchJob( item, this ); job->fetchScope().fetchFullPayload( true ); job->fetchScope().fetchAllAttributes( true ); job->fetchScope().setCacheOnly( false ); QVERIFY( job->exec() ); QCOMPARE( job->items().count(), 1 ); item = job->items().first(); QCOMPARE( item.id(), 1ll ); QVERIFY( !item.remoteId().isEmpty() ); QVERIFY( item.hasPayload() ); QCOMPARE( item.attributes().count(), 1 ); } void ItemFetchTest::testIllegalFetch() { // fetch non-existing folder ItemFetchJob *job = new ItemFetchJob( Collection( INT_MAX ), this ); QVERIFY( !job->exec() ); // listing of root job = new ItemFetchJob( Collection::root(), this ); QVERIFY( !job->exec() ); // fetch a non-existing message job = new ItemFetchJob( Item( INT_MAX ), this ); QVERIFY( job->exec() ); QVERIFY( job->items().isEmpty() ); // fetch message with empty reference job = new ItemFetchJob( Item(), this ); QVERIFY( !job->exec() ); } void ItemFetchTest::testMultipartFetch_data() { QTest::addColumn( "fetchFullPayload" ); QTest::addColumn( "fetchAllAttrs" ); QTest::addColumn( "fetchSinglePayload" ); QTest::addColumn( "fetchSingleAttr" ); QTest::newRow( "empty" ) << false << false << false << false; QTest::newRow( "full" ) << true << true << false << false; QTest::newRow( "full payload" ) << true << false << false << false; QTest::newRow( "single payload" ) << false << false << true << false; QTest::newRow( "single" ) << false << false << true << true; QTest::newRow( "attr full" ) << false << true << false << false; QTest::newRow( "attr single" ) << false << false << false << true; QTest::newRow( "mixed cross 1" ) << true << false << false << true; QTest::newRow( "mixed cross 2" ) << false << true << true << false; QTest::newRow( "all" ) << true << true << true << true; QTest::newRow( "all payload" ) << true << false << true << false; QTest::newRow( "all attr" ) << false << true << true << false; } void ItemFetchTest::testMultipartFetch() { QFETCH( bool, fetchFullPayload ); QFETCH( bool, fetchAllAttrs ); QFETCH( bool, fetchSinglePayload ); QFETCH( bool, fetchSingleAttr ); CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this ); QVERIFY( resolver->exec() ); int colId = resolver->collection(); Item item; item.setMimeType( "application/octet-stream" ); item.setPayload( "body data" ); item.attribute( Item::AddIfMissing )->data = "extra data"; ItemCreateJob *job = new ItemCreateJob( item, Collection( colId ), this ); QVERIFY( job->exec() ); Item ref = job->item(); ItemFetchJob *fjob = new ItemFetchJob( ref, this ); if ( fetchFullPayload ) fjob->fetchScope().fetchFullPayload(); if ( fetchAllAttrs ) fjob->fetchScope().fetchAttribute(); if ( fetchSinglePayload ) fjob->fetchScope().fetchPayloadPart( Item::FullPayload ); if ( fetchSingleAttr ) fjob->fetchScope().fetchAttribute(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items().first(); if ( fetchFullPayload || fetchSinglePayload ) { QCOMPARE( item.loadedPayloadParts().count(), 1 ); QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray( "body data" ) ); } else { QCOMPARE( item.loadedPayloadParts().count(), 0 ); QVERIFY( !item.hasPayload() ); } if ( fetchAllAttrs || fetchSingleAttr ) { QCOMPARE( item.attributes().count(), 1 ); QVERIFY( item.hasAttribute() ); QCOMPARE( item.attribute()->data, QByteArray( "extra data" ) ); } else { QCOMPARE( item.attributes().count(), 0 ); } // cleanup ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); QVERIFY( djob->exec() ); } void ItemFetchTest::testRidFetch() { Item item; item.setRemoteId( "A" ); Collection col; col.setRemoteId( "10" ); ResourceSelectJob *select = new ResourceSelectJob( "akonadi_knut_resource_0", this ); QVERIFY( select->exec() ); ItemFetchJob *job = new ItemFetchJob( item, this ); job->setCollection( col ); QVERIFY( job->exec() ); QCOMPARE( job->items().count(), 1 ); item = job->items().first(); QVERIFY( item.isValid() ); QCOMPARE( item.remoteId(), QString::fromLatin1( "A" ) ); QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); } + +void ItemFetchTest::testAncestorRetrieval() +{ + ItemFetchJob *job = new ItemFetchJob( Item( 1 ), this ); + job->fetchScope().setAncestorRetrieval( ItemFetchScope::All ); + AKVERIFYEXEC( job ); + QCOMPARE( job->items().count(), 1 ); + const Item item = job->items().first(); + QVERIFY( item.isValid() ); + QCOMPARE( item.remoteId(), QString::fromLatin1( "A" ) ); + QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); + const Collection c = item.parentCollection(); + QCOMPARE( c.remoteId(), QString( "10" ) ); + const Collection c2 = c.parentCollection(); + QCOMPARE( c2.remoteId(), QString( "6" ) ); + const Collection c3 = c2.parentCollection(); + QCOMPARE( c3, Collection::root() ); + +} \ No newline at end of file diff --git a/akonadi/tests/itemfetchtest.h b/akonadi/tests/itemfetchtest.h index a28bc3dfd..167fef248 100644 --- a/akonadi/tests/itemfetchtest.h +++ b/akonadi/tests/itemfetchtest.h @@ -1,39 +1,40 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMFETCHTEST_H #define ITEMFETCHTEST_H #include class ItemFetchTest : public QObject { Q_OBJECT private slots: void initTestCase(); void testFetch(); void testResourceRetrieval(); void testIllegalFetch(); void testMultipartFetch_data(); void testMultipartFetch(); void testRidFetch(); + void testAncestorRetrieval(); }; #endif diff --git a/includes/KCal/HtmlExportSettings b/includes/KCal/HtmlExportSettings new file mode 100644 index 000000000..e999c3a5e --- /dev/null +++ b/includes/KCal/HtmlExportSettings @@ -0,0 +1,4 @@ +#ifdef __GNUC__ +#warning HtmlExportSettings is deprecated. Use HTMLExportSettings instead. +#endif +#include "../../kcal/htmlexportsettings.h" diff --git a/kcal/person.cpp b/kcal/person.cpp index 6d55f5ad4..f05975c9f 100644 --- a/kcal/person.cpp +++ b/kcal/person.cpp @@ -1,162 +1,162 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer 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. */ /** @file This file is part of the API for handling calendar data and defines the Person class. @brief Represents a person, by name and email address. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "person.h" #include "kpimutils/email.h" #include #include #include using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::Person::Private { public: QString mName; // person name QString mEmail; // person email address }; //@endcond Person::Person() : d( new KCal::Person::Private ) { } Person::Person( const QString &fullName ) : d( new Private ) { KPIMUtils::extractEmailAddressAndName( fullName, d->mEmail, d->mName ); } Person Person::fromFullName( const QString &fullName ) { QString email, name; KPIMUtils::extractEmailAddressAndName( fullName, email, name ); return Person( name, email ); } Person::Person( const QString &name, const QString &email ) : d( new KCal::Person::Private ) { d->mName = name; d->mEmail = email; } Person::Person( const Person &person ) : d( new KCal::Person::Private( *person.d ) ) { } Person::~Person() { delete d; } -bool KCal::Person::operator==( const Person &person ) +bool KCal::Person::operator==( const Person &person ) const { return d->mName == person.d->mName && d->mEmail == person.d->mEmail; } Person &KCal::Person::operator=( const Person &person ) { // check for self assignment if ( &person == this ) { return *this; } *d = *person.d; return *this; } QString Person::fullName() const { if ( d->mName.isEmpty() ) { return d->mEmail; } else { if ( d->mEmail.isEmpty() ) { return d->mName; } else { // Taken from KABC::Addressee::fullEmail QString name = d->mName; QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); bool weNeedToQuote = name.indexOf( needQuotes ) != -1; if ( weNeedToQuote ) { if ( name[0] != '"' ) { name.prepend( '"' ); } if ( name[ name.length()-1 ] != '"' ) { name.append( '"' ); } } return name + " <" + d->mEmail + '>'; } } } QString Person::name() const { return d->mName; } QString Person::email() const { return d->mEmail; } bool Person::isEmpty() const { return d->mEmail.isEmpty() && d->mName.isEmpty(); } void Person::setName( const QString &name ) { d->mName = name; } void Person::setEmail( const QString &email ) { if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { d->mEmail = email.mid( 7 ); } else { d->mEmail = email; } } diff --git a/kcal/person.h b/kcal/person.h index d5c64c28f..5b69f988b 100644 --- a/kcal/person.h +++ b/kcal/person.h @@ -1,154 +1,160 @@ /* This file is part of the kcal library. Copyright (c) 2001-2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer 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. */ /** @file This file is part of the API for handling calendar data and defines the Person class. @brief Represents a person, by name ane email address. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #ifndef KCAL_PERSON_H #define KCAL_PERSON_H #include +#include #include "kcal_export.h" namespace KCal { /** This class represents a person, with a name and an email address. It supports the "FirstName LastName " format. */ class KCAL_EXPORT Person { public: /** Constructs a blank person. */ Person(); /** Constructs a person with name and email address taken from @p fullName. @param fullName is the name and email of the person in the form FirstName LastName \. */ static Person fromFullName( const QString &fullName ); /** \deprecated Use fromFullName() instead. */ KDE_CONSTRUCTOR_DEPRECATED explicit Person( const QString &fullName ); /** Constructs a person with the name @p name and email address @p email. @param name is the name of this person. @param email is the email address of this person. */ Person( const QString &name, const QString &email ); /** Constructs a person as a copy of another person object. @param person is the person to copy. */ Person( const Person &person ); /** Destroys a person. */ ~Person(); /** Returns true if the person name and email address are empty. */ bool isEmpty() const; /** Returns the full name of this person. */ QString fullName( ) const; /** Sets the name of the person to @p name. @param name is the name of this person. @see name() */ void setName( const QString &name ); /** Returns the person name string. @see setName() */ QString name() const; /** Sets the email address for this person to @p email. @param email is the email address for this person. @see email() */ void setEmail( const QString &email ); /** Returns the email address for this person. @see setEmail() */ QString email() const; /** Compares this with @p person for equality. @param person is the person to compare. */ - bool operator==( const Person &person ); + bool operator==( const Person &person ) const; /** Sets this person equal to @p person. @param person is the person to copy. */ Person &operator=( const Person &person ); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } +inline uint qHash(const KCal::Person &key) +{ + return qHash(key.fullName()); +} + #endif diff --git a/kimap/acljobbase.cpp b/kimap/acljobbase.cpp index edb83add7..d2aec8827 100644 --- a/kimap/acljobbase.cpp +++ b/kimap/acljobbase.cpp @@ -1,100 +1,99 @@ /* Copyright (c) 2009 Andras Mantia 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 "acljobbase.h" - -#include -#include - #include "acljobbase_p.h" #include "message_p.h" #include "session_p.h" +#include +#include + using namespace KIMAP; void AclJobBasePrivate::setIdentifier( const QByteArray &identifier ) { id = identifier; } QByteArray AclJobBasePrivate::identifier() const { return id; } bool AclJobBasePrivate::hasRightEnabled(Acl::Right right) { return rightList & right; } void AclJobBasePrivate::setRights(const QByteArray& rights) { switch ( rights[0] ) { case '+': modifier = AclJobBase::Add; break; case '-': modifier = AclJobBase::Remove; break; default: modifier = AclJobBase::Change; break; } rightList = Acl::rightsFromString(rights); } void AclJobBasePrivate::setRights(AclJobBase::AclModifier _modifier, Acl::Rights rights) { modifier = _modifier; rightList|= rights; } AclJobBase::AclJobBase( Session *session ) : Job( *new AclJobBasePrivate(session, i18n("AclJobBase")) ) { } AclJobBase::AclJobBase( JobPrivate &dd ) : Job(dd) { } AclJobBase::~AclJobBase() { } void AclJobBase::setMailBox( const QString &mailBox ) { Q_D(AclJobBase); d->mailBox = mailBox; } QString AclJobBase::mailBox() const { Q_D(const AclJobBase); return d->mailBox; } #include "acljobbase.moc" diff --git a/kimap/fetchjob.cpp b/kimap/fetchjob.cpp index 968f206d9..349f23239 100644 --- a/kimap/fetchjob.cpp +++ b/kimap/fetchjob.cpp @@ -1,515 +1,515 @@ /* Copyright (c) 2009 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fetchjob.h" #include #include #include "job_p.h" #include "message_p.h" #include "session_p.h" namespace KIMAP { class FetchJobPrivate : public JobPrivate { public: FetchJobPrivate( FetchJob *job, Session *session, const QString& name ) : JobPrivate( session, name ), q(job), uidBased(false) { } ~FetchJobPrivate() { } void parseBodyStructure( const QByteArray &structure, int &pos, KMime::Content *content ); void parsePart( const QByteArray &structure, int &pos, KMime::Content *content ); QByteArray parseString( const QByteArray &structure, int &pos ); QByteArray parseSentence( const QByteArray &structure, int &pos ); void skipLeadingSpaces( const QByteArray &structure, int &pos ); MessagePtr message(int id) { if ( !messages.contains(id) ) { messages[id] = MessagePtr(new KMime::Message); } return messages[id]; } ContentPtr part(int id, QByteArray partName) { if ( !parts[id].contains(partName) ) { parts[id][partName] = ContentPtr(new KMime::Content); } return parts[id][partName]; } void emitPendings() { if ( pendingUids.isEmpty() ) { return; } if ( !pendingParts.isEmpty() ) { emit q->partsReceived( selectedMailBox, pendingUids, pendingParts ); } else if ( !pendingSizes.isEmpty() || !pendingFlags.isEmpty() ) { emit q->headersReceived( selectedMailBox, pendingUids, pendingSizes, pendingFlags, pendingMessages ); } else { emit q->messagesReceived( selectedMailBox, pendingUids, pendingMessages ); } pendingUids.clear(); pendingMessages.clear(); pendingParts.clear(); pendingSizes.clear(); pendingFlags.clear(); } FetchJob * const q; ImapSet set; bool uidBased; FetchJob::FetchScope scope; QString selectedMailBox; QMap messages; QMap parts; QMap flags; QMap sizes; QMap uids; QTimer emitPendingsTimer; QMap pendingMessages; QMap pendingParts; QMap pendingFlags; QMap pendingSizes; QMap pendingUids; }; } using namespace KIMAP; FetchJob::FetchJob( Session *session ) : Job( *new FetchJobPrivate(this, session, i18n("Fetch")) ) { Q_D(FetchJob); d->scope.mode = FetchScope::Content; connect( &d->emitPendingsTimer, SIGNAL( timeout() ), this, SLOT( emitPendings() ) ); } FetchJob::~FetchJob() { } void FetchJob::setSequenceSet( const ImapSet &set ) { Q_D(FetchJob); d->set = set; } ImapSet FetchJob::sequenceSet() const { Q_D(const FetchJob); return d->set; } void FetchJob::setUidBased(bool uidBased) { Q_D(FetchJob); d->uidBased = uidBased; } bool FetchJob::isUidBased() const { Q_D(const FetchJob); return d->uidBased; } void FetchJob::setScope( const FetchScope &scope ) { Q_D(FetchJob); d->scope = scope; } FetchJob::FetchScope FetchJob::scope() const { Q_D(const FetchJob); return d->scope; } QMap FetchJob::messages() const { Q_D(const FetchJob); return d->messages; } QMap FetchJob::parts() const { Q_D(const FetchJob); return d->parts; } QMap FetchJob::flags() const { Q_D(const FetchJob); return d->flags; } QMap FetchJob::sizes() const { Q_D(const FetchJob); return d->sizes; } QMap FetchJob::uids() const { Q_D(const FetchJob); return d->uids; } void FetchJob::doStart() { Q_D(FetchJob); QByteArray parameters = d->set.toImapSequenceSet()+' '; switch ( d->scope.mode ) { case FetchScope::Headers: if ( d->scope.parts.isEmpty() ) { parameters+="(RFC822.SIZE INTERNALDATE BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT)] FLAGS UID)"; } else { parameters+='('; foreach ( const QByteArray &part, d->scope.parts ) { parameters+="BODY["+part+".MIME] "; } parameters+="UID)"; } break; case FetchScope::Flags: parameters+="(FLAGS UID)"; break; case FetchScope::Structure: parameters+="(BODYSTRUCTURE UID)"; break; case FetchScope::Content: if ( d->scope.parts.isEmpty() ) { parameters+="(BODY[] UID)"; } else { parameters+='('; foreach ( const QByteArray &part, d->scope.parts ) { parameters+="BODY["+part+"] "; } parameters+="UID)"; } break; } QByteArray command = "FETCH"; if ( d->uidBased ) { command = "UID "+command; } d->emitPendingsTimer.start( 100 ); d->selectedMailBox = d->sessionInternal()->selectedMailBox(); d->tag = d->sessionInternal()->sendCommand( command, parameters ); } void FetchJob::handleResponse( const Message &response ) { Q_D(FetchJob); // We can predict it'll be handled by handleErrorReplies() so stop // the timer now so that result() will really be the last emitted signal. if ( !response.content.isEmpty() && response.content.first().toString() == d->tag ) { d->emitPendingsTimer.stop(); d->emitPendings(); } if (handleErrorReplies(response) == NotHandled ) { if ( response.content.size() == 4 && response.content[2].toString()=="FETCH" && response.content[3].type()==Message::Part::List ) { qint64 id = response.content[1].toString().toLongLong(); QList content = response.content[3].toList(); for ( QList::ConstIterator it = content.constBegin(); it!=content.constEnd(); ++it ) { QByteArray str = *it; ++it; if ( str=="UID" ) { d->uids[id] = it->toLongLong(); } else if ( str=="RFC822.SIZE" ) { d->sizes[id] = it->toLongLong(); } else if ( str=="INTERNALDATE" ) { d->message(id)->date()->setDateTime( KDateTime::fromString( *it, KDateTime::RFCDate ) ); } else if ( str=="FLAGS" ) { if ( (*it).startsWith('(') && (*it).endsWith(')') ) { QByteArray str = *it; str.chop(1); str.remove(0, 1); d->flags[id] = str.split(' '); } else { d->flags[id] << *it; } } else if ( str=="BODYSTRUCTURE" ) { int pos = 0; d->parseBodyStructure(*it, pos, d->message(id).get()); d->message(id)->assemble(); - } else if ( str.startsWith("BODY[") ) { + } else if ( str.startsWith( "BODY[") ) { //krazy:exclude=strings if ( !str.endsWith(']') ) { // BODY[ ... ] might have been split, skip until we find the ] while ( !(*it).endsWith(']') ) ++it; ++it; } int index; if ( (index=str.indexOf("HEADER"))>0 || (index=str.indexOf("MIME"))>0 ) { // headers if ( str[index-1]=='.' ) { QByteArray partId = str.mid( 5, index-6 ); d->part( id, partId )->setHead(*it); d->part( id, partId )->parse(); } else { d->message(id)->setHead(*it); d->message(id)->parse(); } } else { // full payload if ( str=="BODY[]" ) { d->message(id)->setContent( KMime::CRLFtoLF(*it) ); d->message(id)->parse(); d->pendingUids[id] = d->uids[id]; d->pendingMessages[id] = d->message(id); } else { QByteArray partId = str.mid( 5, str.size()-6 ); d->part( id, partId )->setBody(*it); d->part( id, partId )->parse(); d->pendingUids[id] = d->uids[id]; d->pendingParts[id] = d->parts[id]; } } } } if ( d->scope.mode == FetchScope::Headers ) { d->pendingUids[id] = d->uids[id]; d->pendingSizes[id] = d->sizes[id]; d->pendingFlags[id] = d->flags[id]; d->pendingMessages[id] = d->message(id); } } } } void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content) { skipLeadingSpaces(structure, pos); if ( structure[pos]!='(' ) { return; } pos++; if ( structure[pos]!='(' ) { // simple part pos--; parsePart( structure, pos, content ); } else { // multi part content->contentType()->setMimeType("MULTIPART/MIXED"); while ( posaddContent( child ); parseBodyStructure( structure, pos, child ); child->assemble(); } QByteArray subType = parseString( structure, pos ); content->contentType()->setMimeType( "MULTIPART/"+subType ); parseSentence( structure, pos ); // Ditch the parameters... FIXME: Read it to get charset and name QByteArray disposition = parseSentence( structure, pos ); if ( disposition.contains("INLINE") ) { content->contentDisposition()->setDisposition( KMime::Headers::CDinline ); } else if ( disposition.contains("ATTACHMENT") ) { content->contentDisposition()->setDisposition( KMime::Headers::CDattachment ); } parseSentence( structure, pos ); // Ditch the body language } // Consume what's left while ( poscontentType()->setMimeType( mainType+'/'+subType ); parseSentence( structure, pos ); // Ditch the parameters... FIXME: Read it to get charset and name parseString( structure, pos ); // ... and the id content->contentDescription()->from7BitString( parseString( structure, pos ) ); parseString( structure, pos ); // Ditch the encoding too parseString( structure, pos ); // ... and the size if ( mainType=="TEXT" ) { parseString( structure, pos ); // ... and the line count } QByteArray disposition = parseSentence( structure, pos ); if ( disposition.contains("INLINE") ) { content->contentDisposition()->setDisposition( KMime::Headers::CDinline ); } else if ( disposition.contains("ATTACHMENT") ) { content->contentDisposition()->setDisposition( KMime::Headers::CDattachment ); } // Consume what's left while ( pos 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 "getmetadatajob.h" #include #include #include "metadatajobbase_p.h" #include "message_p.h" #include "session_p.h" #include "rfccodecs.h" namespace KIMAP { class GetMetaDataJobPrivate : public MetaDataJobBasePrivate { public: GetMetaDataJobPrivate( Session *session, const QString& name ) : MetaDataJobBasePrivate(session, name), maxSize(-1), depth("0") { } ~GetMetaDataJobPrivate() { } qint64 maxSize; QByteArray depth; QList entries; QList attributes; QMap > > metadata; // ^ mailbox ^ entry ^attribute ^ value }; } using namespace KIMAP; GetMetaDataJob::GetMetaDataJob( Session *session ) : MetaDataJobBase( *new GetMetaDataJobPrivate(session, i18n("GetMetaData")) ) { } GetMetaDataJob::~GetMetaDataJob() { } void GetMetaDataJob::doStart() { Q_D(GetMetaDataJob); QByteArray parameters; parameters = '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" "; QByteArray command = "GETMETADATA"; if (d->serverCapability == Annotatemore) { d->m_name = i18n("GetAnnotation"); command = "GETANNOTATION"; if (d->entries.size() > 1) - parameters += "("; - Q_FOREACH(QByteArray entry, d->entries) { + parameters += '('; + Q_FOREACH(const QByteArray &entry, d->entries) { parameters += '\"' + entry + "\" "; } if (d->entries.size() > 1) parameters[parameters.length() -1 ] = ')'; else parameters.truncate(parameters.length() -1); parameters += ' '; if (d->attributes.size() > 1) - parameters += "("; - Q_FOREACH(QByteArray attribute, d->attributes) { + parameters += '('; + Q_FOREACH(const QByteArray &attribute, d->attributes) { parameters += '\"' + attribute + "\" "; } if (d->attributes.size() > 1) parameters[parameters.length() -1 ] = ')'; else parameters.truncate(parameters.length() -1); } else { if (d->depth != "0") { parameters += "(DEPTH " + d->depth; } if (d->maxSize != -1) { parameters += "(MAXSIZE " + QByteArray::number(d->maxSize) + ')'; } if (d->depth != "0") { parameters += " )"; } if (d->entries.size() > 1) - parameters += "("; - Q_FOREACH(QByteArray entry, d->entries) { + parameters += '('; + Q_FOREACH(const QByteArray &entry, d->entries) { parameters += '\"' + entry + "\" "; } if (d->entries.size() > 1) parameters[parameters.length() -1 ] = ')'; } if (d->entries.isEmpty()) { parameters += ')'; } d->tag = d->sessionInternal()->sendCommand( command, parameters ); kDebug() << "SENT: " << command << " " << parameters; } void GetMetaDataJob::handleResponse( const Message &response ) { Q_D(GetMetaDataJob); kDebug() << "GOT: " << response.toString(); //TODO: handle NO error messages having [METADATA MAXSIZE NNN], [METADATA TOOMANY], [METADATA NOPRIVATE] (see rfc5464) // or [ANNOTATEMORE TOOBIG], [ANNOTATEMORE TOOMANY] respectively if (handleErrorReplies(response) == NotHandled ) { if ( response.content.size() >= 4 ) { if (d->serverCapability == Annotatemore && response.content[1].toString() == "ANNOTATION" ) { QString mailBox = QString::fromUtf8( KIMAP::decodeImapFolderName( response.content[2].toString() ) ); int i = 3; while (i < response.content.size() - 1) { QByteArray entry = response.content[i].toString(); QList attributes = response.content[i + 1].toList(); int j = 0; while ( j < attributes.size() - 1) { d->metadata[mailBox][entry][attributes[j]] = attributes[j + 1]; j += 2; } i += 2; } } else if (d->serverCapability == Metadata && response.content[1].toString() == "METADATA" ) { QString mailBox = QString::fromUtf8( KIMAP::decodeImapFolderName( response.content[2].toString() ) ); QList entries = response.content[3].toList(); int i = 0; while ( i < entries.size() - 1) { d->metadata[mailBox][entries[i]][""] = entries[i + 1]; i += 2; } } } } } void GetMetaDataJob::addEntry(const QByteArray &entry, const QByteArray &attribute) { Q_D(GetMetaDataJob); if (d->serverCapability == Annotatemore && attribute.isNull()) qWarning() << "In ANNOTATEMORE mode and attribute must be specified with addEnty!"; d->entries.append(entry); d->attributes.append(attribute); } void GetMetaDataJob::setMaximumSize(qint64 size) { Q_D(GetMetaDataJob); d->maxSize = size; } void GetMetaDataJob::setDepth(Depth depth) { Q_D(GetMetaDataJob); switch (depth) { case OneLevel: - d->depth = "1"; + d->depth = "1"; //krazy:exclude=doublequote_chars break; case AllLevels: d->depth = "infinity"; break; default: - d->depth = "0"; + d->depth = "0"; //krazy:exclude=doublequote_chars } } QByteArray GetMetaDataJob::metaData(const QString &mailBox, const QByteArray &entry, const QByteArray &attribute) const { Q_D(const GetMetaDataJob); QByteArray attr = attribute; if (d->serverCapability == Metadata) attr = ""; QByteArray result; if (d->metadata.contains(mailBox)) { if (d->metadata[mailBox].contains(entry)) { result = d->metadata[mailBox][entry].value(attr); } } return result; } QMap > GetMetaDataJob::allMetaData(const QString &mailBox) const { Q_D(const GetMetaDataJob); return d->metadata[mailBox]; } #include "getmetadatajob.moc" diff --git a/kimap/idlejob.cpp b/kimap/idlejob.cpp index 7f9b6745b..d4003bab4 100644 --- a/kimap/idlejob.cpp +++ b/kimap/idlejob.cpp @@ -1,119 +1,119 @@ /* Copyright (c) 2009 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "idlejob.h" #include #include #include "job_p.h" #include "message_p.h" #include "session_p.h" namespace KIMAP { class IdleJobPrivate : public JobPrivate { public: IdleJobPrivate( IdleJob *job, Session *session, const QString& name ) : JobPrivate( session, name ), q(job), messageCount( -1 ), recentCount( -1 ), lastMessageCount( -1 ), lastRecentCount( -1 ) { } ~IdleJobPrivate() { } IdleJob * const q; int messageCount; int recentCount; int lastMessageCount; int lastRecentCount; }; } using namespace KIMAP; IdleJob::IdleJob( Session *session ) - : Job( *new IdleJobPrivate(this, session, i18n("Idle")) ) + : Job( *new IdleJobPrivate(this, session, i18nc("name of the idle job", "Idle")) ) { } IdleJob::~IdleJob() { } void KIMAP::IdleJob::stop() { Q_D(IdleJob); d->sessionInternal()->sendData( "DONE" ); } void IdleJob::doStart() { Q_D(IdleJob); d->tag = d->sessionInternal()->sendCommand( "IDLE" ); } void IdleJob::handleResponse( const Message &response ) { Q_D(IdleJob); if (handleErrorReplies(response) == NotHandled ) { if ( response.content.size() > 0 && response.content[0].toString()=="+" ) { // Got the continuation all is fine return; } else if ( response.content[2].toString()=="EXISTS" ) { d->messageCount = response.content[1].toString().toInt(); } else if ( response.content[2].toString()=="RECENT" ) { d->recentCount = response.content[1].toString().toInt(); } if ( d->messageCount>=0 && d->recentCount>=0 ) { emit mailBoxStats(this, d->sessionInternal()->selectedMailBox(), d->messageCount, d->recentCount); d->lastMessageCount = d->messageCount; d->lastRecentCount = d->recentCount; d->messageCount = -1; d->recentCount = -1; } } } QString KIMAP::IdleJob::lastMailBox() const { Q_D(const IdleJob); return d->sessionInternal()->selectedMailBox(); } int KIMAP::IdleJob::lastMessageCount() const { Q_D(const IdleJob); return d->lastMessageCount; } int KIMAP::IdleJob::lastRecentCount() const { Q_D(const IdleJob); return d->lastRecentCount; } #include "idlejob.moc" diff --git a/kimap/imapset.cpp b/kimap/imapset.cpp index 1399d3cab..502c94fbc 100644 --- a/kimap/imapset.cpp +++ b/kimap/imapset.cpp @@ -1,244 +1,244 @@ /* 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 "imapset.h" #include using namespace KIMAP; class ImapInterval::Private : public QSharedData { public: Private() : QSharedData(), begin( 0 ), end( 0 ) {} Private( const Private &other ) : QSharedData( other ) { begin = other.begin; end = other.end; } Id begin; Id end; }; class ImapSet::Private : public QSharedData { public: Private() : QSharedData() {} Private( const Private &other ) : QSharedData( other ) { } ImapInterval::List intervals; }; ImapInterval::ImapInterval() : d( new Private ) { } ImapInterval::ImapInterval(const ImapInterval & other) : d( other.d ) { } ImapInterval::ImapInterval(Id begin, Id end) : d( new Private ) { d->begin = begin; d->end = end; } ImapInterval::~ ImapInterval() { } ImapInterval& ImapInterval::operator =(const ImapInterval & other) { if ( this != & other ) d = other.d; return *this; } bool ImapInterval::operator ==(const ImapInterval & other) const { return ( d->begin == other.d->begin && d->end == other.d->end ); } ImapInterval::Id ImapInterval::size() const { if ( !d->begin && !d->end ) return 0; return d->end - d->begin + 1; } bool ImapInterval::hasDefinedBegin() const { return d->begin != 0; } ImapInterval::Id ImapInterval::begin() const { return d->begin; } bool ImapInterval::hasDefinedEnd() const { return d->end != 0; } ImapInterval::Id ImapInterval::end() const { if ( hasDefinedEnd() ) return d->end; return 0xFFFFFFFF; // should be INT_MAX, but where is that defined again? } void ImapInterval::setBegin(Id value) { Q_ASSERT( value >= 0 ); Q_ASSERT( value <= d->end || !hasDefinedEnd() ); d->begin = value; } void ImapInterval::setEnd(Id value) { Q_ASSERT( value >= 0 ); Q_ASSERT( value >= d->begin || !hasDefinedBegin() ); d->end = value; } QByteArray ImapInterval::toImapSequence() const { if ( size() == 0 ) return QByteArray(); if ( size() == 1 ) return QByteArray::number( d->begin ); QByteArray rv; rv += QByteArray::number( d->begin ) + ':'; if ( hasDefinedEnd() ) rv += QByteArray::number( d->end ); else rv += '*'; return rv; } ImapSet::ImapSet() : d( new Private ) { } ImapSet::ImapSet( Id begin, Id end ) : d( new Private ) { add( ImapInterval( begin, end ) ); } ImapSet::ImapSet( Id value ) : d( new Private ) { add( QList() << value ); } ImapSet::ImapSet(const ImapSet & other) : d( other.d ) { } ImapSet::~ImapSet() { } ImapSet & ImapSet::operator =(const ImapSet & other) { if ( this != &other ) d = other.d; return *this; } void ImapSet::add(const QList & values) { QList vals = values; qSort( vals ); for( int i = 0; i < vals.count(); ++i ) { const int begin = vals[i]; Q_ASSERT( begin >= 0 ); if ( i == vals.count() - 1 ) { d->intervals << ImapInterval( begin, begin ); break; } do { ++i; Q_ASSERT( vals[i] >= 0 ); if ( vals[i] != (vals[i - 1] + 1) ) { --i; break; } } while ( i < vals.count() - 1 ); d->intervals << ImapInterval( begin, vals[i] ); } } void ImapSet::add(const ImapInterval & interval) { d->intervals << interval; } QByteArray ImapSet::toImapSequenceSet() const { QList rv; - foreach ( const ImapInterval interval, d->intervals ) { + foreach ( const ImapInterval &interval, d->intervals ) { rv << interval.toImapSequence(); } QByteArray result = rv.first(); QList::ConstIterator it = rv.constBegin(); ++it; for ( ; it != rv.constEnd(); ++it ) { result += ',' + (*it); } return result; } ImapInterval::List ImapSet::intervals() const { return d->intervals; } bool ImapSet::isEmpty() const { return d->intervals.isEmpty(); } QDebug& operator<<( QDebug &d, const ImapInterval &interval ) { d << interval.toImapSequence(); return d; } diff --git a/kimap/loginjob.cpp b/kimap/loginjob.cpp index 95922e964..8772bac8f 100644 --- a/kimap/loginjob.cpp +++ b/kimap/loginjob.cpp @@ -1,437 +1,437 @@ /* Copyright (c) 2009 Kevin Ottens Copyright (c) 2009 Andras Mantia 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 "loginjob.h" #include #include #include #include "job_p.h" #include "message_p.h" #include "session_p.h" #include "rfccodecs.h" #include "common.h" extern "C" { #include } 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 } }; namespace KIMAP { class LoginJobPrivate : public JobPrivate { public: enum AuthState { StartTls = 0, Capability, Login, Authenticate }; LoginJobPrivate( LoginJob *job, Session *session, const QString& name ) : JobPrivate(session, name), q(job), encryptionMode(LoginJob::Unencrypted), authState(Login), plainLoginDisabled(false) { conn = 0; client_interact = 0; } ~LoginJobPrivate() { } bool sasl_interact(); bool startAuthentication(); bool answerChallenge(const QByteArray &data); void sslResponse(bool response); LoginJob *q; QString userName; QString password; LoginJob::EncryptionMode encryptionMode; QString authMode; AuthState authState; QStringList capabilities; bool plainLoginDisabled; sasl_conn_t *conn; sasl_interact_t *client_interact; }; } using namespace KIMAP; bool LoginJobPrivate::sasl_interact() { kDebug() <<"sasl_interact"; sasl_interact_t *interact = client_interact; //some mechanisms do not require username && pass, so it doesn'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 ) { //TODO: dialog for use name?? break; } } interact = client_interact; while( interact->id != SASL_CB_LIST_END ) { kDebug() <<"SASL_INTERACT id:" << interact->id; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kDebug() <<"SASL_CB_[USER|AUTHNAME]: '" << userName <<"'"; interact->result = strdup( userName.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kDebug() <<"SASL_CB_PASS: [hidden]"; interact->result = strdup( password.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = 0; interact->len = 0; break; } interact++; } return true; } LoginJob::LoginJob( Session *session ) : Job( *new LoginJobPrivate(this, session, i18n("Login")) ) { Q_D(LoginJob); connect(d->sessionInternal(), SIGNAL(encryptionNegotiationResult(bool)), this, SLOT(sslResponse(bool))); } LoginJob::~LoginJob() { } QString LoginJob::userName() const { Q_D(const LoginJob); return d->userName; } void LoginJob::setUserName( const QString &userName ) { Q_D(LoginJob); d->userName = userName; } QString LoginJob::password() const { Q_D(const LoginJob); return d->password; } void LoginJob::setPassword( const QString &password ) { Q_D(LoginJob); d->password = password; } void LoginJob::doStart() { Q_D(LoginJob); if (d->encryptionMode == SslV2 || d->encryptionMode == SslV3 || d->encryptionMode == SslV3_1 || d->encryptionMode == AnySslVersion) { KTcpSocket::SslVersion version = KTcpSocket::SslV2; if (d->encryptionMode == SslV3) version = KTcpSocket::SslV3; if (d->encryptionMode == SslV3_1) version = KTcpSocket::SslV3_1; if (d->encryptionMode == AnySslVersion) version = KTcpSocket::AnySslVersion; d->sessionInternal()->startSsl(version); } else if (d->encryptionMode == Unencrypted ) { if (d->authMode.isEmpty()) { d->tag = d->sessionInternal()->sendCommand( "LOGIN", quoteIMAP( d->userName ).toUtf8() +' ' +quoteIMAP(d->password ).toUtf8() ); } else { if (!d->startAuthentication()) { emitResult(); } } } else if (d->encryptionMode == TlsV1) { d->authState = LoginJobPrivate::StartTls; d->tag = d->sessionInternal()->sendCommand( "STARTTLS" ); } } void LoginJob::handleResponse( const Message &response ) { Q_D(LoginJob); //set the actual command name for standard responses QString commandName = i18n("Login"); if (d->authState == LoginJobPrivate::Capability) { commandName = i18n("Capability"); } else if (d->authState == LoginJobPrivate::StartTls) { commandName = i18n("StartTls"); } if ( !response.content.isEmpty() && response.content.first().toString() == d->tag ) { if ( response.content.size() < 2 ) { setErrorText( i18n("%1 failed, malformed reply from the server.", commandName) ); emitResult(); } else if ( response.content[1].toString() != "OK" ) { //server replied with NO or BAD for SASL authentication if (d->authState == LoginJobPrivate::Authenticate) { sasl_dispose( &d->conn ); } setError( UserDefinedError ); setErrorText( i18n("%1 failed, server replied: %2", commandName, response.toString().constData()) ); emitResult(); } else if ( response.content[1].toString() == "OK") { if (d->authState == LoginJobPrivate::Authenticate) { sasl_dispose( &d->conn ); //SASL authentication done emitResult(); } else if (d->authState == LoginJobPrivate::Capability) { //cleartext login, if enabled if (d->authMode.isEmpty()) { if (d->plainLoginDisabled) { setError( UserDefinedError ); setErrorText( i18n("Login failed, plain login is disabled by the server.") ); emitResult(); } else { d->authState = LoginJobPrivate::Login; d->tag = d->sessionInternal()->sendCommand( "LOGIN", quoteIMAP( d->userName ).toUtf8() +' ' +quoteIMAP( d->password ).toUtf8() ); } } //find the selected SASL authentication method - Q_FOREACH(QString capability, d->capabilities) { - if (capability.startsWith("AUTH=")) { + Q_FOREACH(const QString &capability, d->capabilities) { + if (capability.startsWith(QLatin1String("AUTH="))) { QString authType = capability.mid(5); if (authType == d->authMode) { if (!d->startAuthentication()) { emitResult(); //problem, we're done } } } } } else if (d->authState == LoginJobPrivate::StartTls) { d->sessionInternal()->startSsl(KTcpSocket::TlsV1); } else { emitResult(); //got an OK, command done } } } else if ( response.content.size() >= 2 ) { if ( d->authState == LoginJobPrivate::Authenticate ) { if (!d->answerChallenge(response.content[1].toString())) { emitResult(); //error, we're done } } else if ( response.content[1].toString()=="CAPABILITY" ) { bool authModeSupported = d->authMode.isEmpty(); for (int i = 2; i < response.content.size(); ++i) { QString capability = response.content[i].toString(); d->capabilities << capability; if (capability == "LOGINDISABLED") { d->plainLoginDisabled = true; } QString authMode = capability.mid(5); if (authMode == d->authMode) { authModeSupported = true; } } kDebug() << "Capabilities after STARTTLS: " << d->capabilities; if (!authModeSupported) { setError( UserDefinedError ); setErrorText( i18n("Login failed, authentication mode %1 is not supported by the server.", d->authMode) ); d->authState = LoginJobPrivate::Login; //just to treat the upcoming OK correctly } } } } bool LoginJobPrivate::startAuthentication() { //SASL authentication if (!initSASL()) { q->setError( LoginJob::UserDefinedError ); q->setErrorText( i18n("Login failed, client cannot initialize the SASL library.") ); return false; } authState = LoginJobPrivate::Authenticate; const char *out = 0; uint outlen = 0; const char *mechusing = 0; int result = sasl_client_new( "imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { kDebug() <<"sasl_client_new failed with:" << result; q->setError( LoginJob::UserDefinedError ); q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) ); return false; } do { result = sasl_client_start(conn, authMode.toLatin1(), &client_interact, capabilities.contains("SASL-IR") ? &out : 0, &outlen, &mechusing); if ( result == SASL_INTERACT ) { if ( !sasl_interact() ) { sasl_dispose( &conn ); q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug() <<"sasl_client_start failed with:" << result; q->setError( LoginJob::UserDefinedError ); q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) ); sasl_dispose( &conn ); return false; } QByteArray tmp = QByteArray::fromRawData( out, outlen ); QByteArray challenge = tmp.toBase64(); tag = sessionInternal()->sendCommand( "AUTHENTICATE", authMode.toLatin1() + ' ' + challenge ); return true; } bool LoginJobPrivate::answerChallenge(const QByteArray &data) { QByteArray challenge = data; int result = -1; const char *out = 0; uint outlen = 0; do { result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), challenge.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) { if ( !sasl_interact() ) { q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug() <<"sasl_client_step failed with:" << result; q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) ); sasl_dispose( &conn ); return false; } QByteArray tmp = QByteArray::fromRawData( out, outlen ); challenge = tmp.toBase64(); sessionInternal()->sendData( challenge ); return true; } void LoginJobPrivate::sslResponse(bool response) { if (response) { authState = LoginJobPrivate::Capability; tag = sessionInternal()->sendCommand( "CAPABILITY" ); } else { q->setError( LoginJob::UserDefinedError ); q->setErrorText( i18n("Login failed, TLS negotiation failed." )); encryptionMode = LoginJob::Unencrypted; q->emitResult(); } } void LoginJob::setEncryptionMode(EncryptionMode mode) { Q_D(LoginJob); d->encryptionMode = mode; } LoginJob::EncryptionMode LoginJob::encryptionMode() { Q_D(LoginJob); return d->encryptionMode; } void LoginJob::setAuthenticationMode(AuthenticationMode mode) { Q_D(LoginJob); switch (mode) { case ClearText: d->authMode = ""; break; case Login: d->authMode = "LOGIN"; break; case Plain: d->authMode = "PLAIN"; break; case CramMD5: d->authMode = "CRAM-MD5"; break; case DigestMD5: d->authMode = "DIGEST-MD5"; break; case GSSAPI: d->authMode = "GSSAPI"; break; case Anonymous: d->authMode = "ANONYMOUS"; break; default: d->authMode = ""; } } void LoginJob::connectionLost() { Q_D(LoginJob); //don't emit the result if the connection was lost before getting the tls result, as it can mean //the TLS handshake failed and the socket was reconnected in normal mode if (d->authState != LoginJobPrivate::StartTls) { emitResult(); } } #include "loginjob.moc" diff --git a/kimap/metadatajobbase.cpp b/kimap/metadatajobbase.cpp index 0fa426cea..4eb900ffa 100644 --- a/kimap/metadatajobbase.cpp +++ b/kimap/metadatajobbase.cpp @@ -1,74 +1,72 @@ /* Copyright (c) 2009 Andras Mantia 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 "metadatajobbase.h" - -#include -#include - #include "metadatajobbase_p.h" #include "message_p.h" #include "session_p.h" -using namespace KIMAP; +#include +#include +using namespace KIMAP; MetaDataJobBase::MetaDataJobBase( Session *session ) : Job( *new MetaDataJobBasePrivate(session, i18n("MetaDataJobBase")) ) { } MetaDataJobBase::MetaDataJobBase( JobPrivate &dd ) : Job(dd) { } MetaDataJobBase::~MetaDataJobBase() { } void MetaDataJobBase::setMailBox( const QString &mailBox ) { Q_D(MetaDataJobBase); d->mailBox = mailBox; } QString MetaDataJobBase::mailBox() const { Q_D(const MetaDataJobBase); return d->mailBox; } void MetaDataJobBase::setServerCapability(const ServerCapability& capability) { Q_D(MetaDataJobBase); d->serverCapability = capability; } MetaDataJobBase::ServerCapability MetaDataJobBase::serverCapability() const { Q_D(const MetaDataJobBase); return d->serverCapability; } #include "metadatajobbase.moc" diff --git a/kimap/quotajobbase.cpp b/kimap/quotajobbase.cpp index c7d894101..d017266af 100644 --- a/kimap/quotajobbase.cpp +++ b/kimap/quotajobbase.cpp @@ -1,93 +1,92 @@ /* Copyright (c) 2009 Andras Mantia 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 "quotajobbase.h" - -#include -#include - #include "quotajobbase_p.h" #include "message_p.h" #include "session_p.h" +#include +#include + using namespace KIMAP; QMap > QuotaJobBasePrivate::readQuota( const Message::Part &content ) { QMap > quota; QList quotas = content.toList(); int i = 0; while ( i < quotas.size() - 2 ) { QByteArray resource = quotas[i].toUpper(); qint64 usage = quotas[i+1].toInt(); qint64 limit = quotas[i+2].toInt(); quota[resource] = qMakePair(usage, limit); i += 3; } return quota; } QuotaJobBase::QuotaJobBase( Session *session ) : Job( *new QuotaJobBasePrivate(session, i18n("QuotaJobBase")) ) { } QuotaJobBase::QuotaJobBase( JobPrivate &dd ) : Job(dd) { } QuotaJobBase::~QuotaJobBase() { } qint64 QuotaJobBase::usage(const QByteArray& resource) { Q_D(QuotaJobBase); QByteArray r = resource.toUpper(); if (d->quota.contains(r)) { return d->quota[r].first; } return -1; } qint64 QuotaJobBase::limit(const QByteArray& resource) { Q_D(QuotaJobBase); QByteArray r = resource.toUpper(); if (d->quota.contains(r)) { return d->quota[r].second; } return -1; } #include "quotajobbase.moc" diff --git a/kimap/searchjob.cpp b/kimap/searchjob.cpp index fa53c665f..29bf352ef 100644 --- a/kimap/searchjob.cpp +++ b/kimap/searchjob.cpp @@ -1,301 +1,301 @@ /* Copyright (c) 2009 Andras Mantia 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 "searchjob.h" #include #include #include #include "job_p.h" #include "message_p.h" #include "session_p.h" //TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response namespace KIMAP { class SearchJobPrivate : public JobPrivate { public: SearchJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name), logic(SearchJob::And) { criteriaMap[SearchJob::All] = "ALL"; criteriaMap[SearchJob::Answered] = "ANSWERED"; criteriaMap[SearchJob::BCC] = "BCC"; criteriaMap[SearchJob::Before] = "BEFORE"; criteriaMap[SearchJob::Body] = "BODY"; criteriaMap[SearchJob::CC] = "CC"; criteriaMap[SearchJob::Deleted] = "DELETED"; criteriaMap[SearchJob::Draft] = "DRAFT"; criteriaMap[SearchJob::Flagged] = "FLAGGED"; criteriaMap[SearchJob::From] = "FROM"; criteriaMap[SearchJob::Header] = "HEADER"; criteriaMap[SearchJob::Keyword] = "KEYWORD"; criteriaMap[SearchJob::Larger] = "LARGER"; criteriaMap[SearchJob::New] = "NEW"; criteriaMap[SearchJob::Old] = "OLD"; criteriaMap[SearchJob::On] = "ON"; criteriaMap[SearchJob::Recent] = "RECENT"; criteriaMap[SearchJob::Seen] = "SEEN"; criteriaMap[SearchJob::SentBefore] = "SENTBEFORE"; criteriaMap[SearchJob::SentOn] = "SENTON"; criteriaMap[SearchJob::SentSince] = "SENTSINCE"; criteriaMap[SearchJob::Since] = "SINCE"; criteriaMap[SearchJob::Smaller] = "SMALLER"; criteriaMap[SearchJob::Subject] = "SUBJECT"; criteriaMap[SearchJob::Text] = "TEXT"; criteriaMap[SearchJob::To] = "TO"; criteriaMap[SearchJob::Uid] = "UID"; criteriaMap[SearchJob::Unanswered] = "UNANSWERED"; criteriaMap[SearchJob::Undeleted] = "UNDELETED"; criteriaMap[SearchJob::Undraft] = "UNDRAFT"; criteriaMap[SearchJob::Unflagged] = "UNFLAGGED"; criteriaMap[SearchJob::Unkeyword] = "UNKEYWORD"; criteriaMap[SearchJob::Unseen] = "UNSEEN"; //don't use QDate::shortMonthName(), it returns a localized month name months[1] = "Jan"; months[2] = "Feb"; months[3] = "Mar"; months[4] = "Apr"; months[5] = "May"; months[6] = "Jun"; months[7] = "Jul"; months[8] = "Aug"; months[9] = "Sep"; months[10] = "Oct"; months[11] = "Nov"; months[12] = "Dec"; nextContent = 0; uidBased = false; } ~SearchJobPrivate() { } QByteArray charset; QList criterias; QMap criteriaMap; QMap months; SearchJob::SearchLogic logic; QList contents; QList results; uint nextContent; bool uidBased; }; } using namespace KIMAP; SearchJob::SearchJob( Session *session ) - : Job( *new SearchJobPrivate(session, i18n("Search")) ) + : Job( *new SearchJobPrivate(session, i18nc("Name of the search job", "Search")) ) { } SearchJob::~SearchJob() { } void SearchJob::doStart() { Q_D(SearchJob); QByteArray searchKey; if (!d->charset.isEmpty()) { searchKey = "[CHARSET] " + d->charset; } if (d->logic == SearchJob::Not) { searchKey += "NOT"; } else if (d->logic == SearchJob::Or) { searchKey += "OR"; } - Q_FOREACH(QByteArray key, d->criterias) { - searchKey += " (" + key + ")"; + Q_FOREACH(const QByteArray &key, d->criterias) { + searchKey += " (" + key + ')'; } QByteArray command = "SEARCH"; if ( d->uidBased ) { command = "UID "+ command; } d->tag = d->sessionInternal()->sendCommand( command, searchKey ); } void SearchJob::handleResponse( const Message &response ) { Q_D(SearchJob); if (handleErrorReplies(response) == NotHandled ) { if ( response.content[0].toString() == "+" ) { d->sessionInternal()->sendData( d->contents[d->nextContent] ); d->nextContent++; } else if ( response.content[1].toString() == "SEARCH" ) { for(int i = 2; i < response.content.size(); i++) { d->results.append(response.content[i].toString().toInt()); } } } } void SearchJob::setCharset( const QByteArray &charset ) { Q_D(SearchJob); d->charset = charset; } QByteArray SearchJob::charset() const { Q_D(const SearchJob); return d->charset; } void SearchJob::setSearchLogic( SearchLogic logic ) { Q_D(SearchJob); d->logic = logic; } void SearchJob::addSearchCriteria( SearchCriteria criteria ) { Q_D(SearchJob); switch (criteria) { case All: case Answered: case Deleted: case Draft: case Flagged: case New: case Old: case Recent: case Seen: case Unanswered: case Undeleted: case Undraft: case Unflagged: case Unseen: d->criterias.append(d->criteriaMap[criteria]); break; default: //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong kDebug() << "Criteria " << d->criteriaMap[criteria] << " needs an argument, but none was specified."; break; } } void SearchJob::addSearchCriteria( SearchCriteria criteria, int argument ) { Q_D(SearchJob); switch (criteria) { case Larger: case Smaller: - d->criterias.append(d->criteriaMap[criteria] + " " + QByteArray::number(argument)); + d->criterias.append(d->criteriaMap[criteria] + ' ' + QByteArray::number(argument)); break; default: //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong kDebug() << "Criteria " << d->criteriaMap[criteria] << " doesn't accept an integer as an argument."; break; } } void SearchJob::addSearchCriteria( SearchCriteria criteria, const QByteArray &argument ) { Q_D(SearchJob); switch (criteria) { case BCC: case Body: case CC: case From: case Subject: case Text: case To: d->contents.append(argument); d->criterias.append(d->criteriaMap[criteria] + " {" + QByteArray::number(argument.size()) + '}'); break; case Keyword: case Unkeyword: d->criterias.append(d->criteriaMap[criteria] + ' ' + argument); break; case Header: { int pos = argument.indexOf(' '); QByteArray fieldName = argument.left(pos); QByteArray content = argument.mid(pos + 1); d->contents.append(content); d->criterias.append(d->criteriaMap[criteria] + ' ' + fieldName + " {" + QByteArray::number(content.size()) + '}'); break; } default: //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong kDebug() << "Criteria " << d->criteriaMap[criteria] << " doesn't accept any argument."; break; } } void SearchJob::addSearchCriteria( SearchCriteria criteria, const QDate &argument ) { Q_D(SearchJob); switch (criteria) { case Before: case On: case SentBefore: case SentSince: case Since: { QByteArray date = QByteArray::number(argument.day()) + '-'; date += d->months[argument.month()] + '-'; date += QByteArray::number(argument.year()); d->criterias.append(d->criteriaMap[criteria] + " \"" + date + '\"'); break; } default: //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong kDebug() << "Criteria " << d->criteriaMap[criteria] << " doesn't accept a date as argument."; break; } } void SearchJob::addSearchCriteria( const QByteArray &searchCriteria ) { Q_D(SearchJob); d->criterias.append(searchCriteria); } void SearchJob::setUidBased(bool uidBased) { Q_D(SearchJob); d->uidBased = uidBased; } bool SearchJob::isUidBased() const { Q_D(const SearchJob); return d->uidBased; } QList SearchJob::foundItems() { Q_D(const SearchJob); return d->results; } #include "searchjob.moc" diff --git a/kimap/selectjob.cpp b/kimap/selectjob.cpp index 9f56d18f8..de0c039fc 100644 --- a/kimap/selectjob.cpp +++ b/kimap/selectjob.cpp @@ -1,195 +1,195 @@ /* Copyright (c) 2009 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "selectjob.h" #include #include "job_p.h" #include "message_p.h" #include "session_p.h" #include "rfccodecs.h" namespace KIMAP { class SelectJobPrivate : public JobPrivate { public: SelectJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name), readOnly(false), messageCount(-1), recentCount(-1), firstUnseenIndex(-1), uidValidity(-1), nextUid(-1) { } ~SelectJobPrivate() { } QString mailBox; bool readOnly; QList flags; QList permanentFlags; int messageCount; int recentCount; int firstUnseenIndex; qint64 uidValidity; qint64 nextUid; }; } using namespace KIMAP; SelectJob::SelectJob( Session *session ) - : Job( *new SelectJobPrivate(session, i18n("Select")) ) + : Job( *new SelectJobPrivate(session, i18nc("name of the select job", "Select")) ) { } SelectJob::~SelectJob() { } void SelectJob::setMailBox( const QString &mailBox ) { Q_D(SelectJob); d->mailBox = mailBox; } QString SelectJob::mailBox() const { Q_D(const SelectJob); return d->mailBox; } void SelectJob::setOpenReadOnly( bool readOnly ) { Q_D(SelectJob); d->readOnly = readOnly; } bool SelectJob::isOpenReadOnly() const { Q_D(const SelectJob); return d->readOnly; } QList SelectJob::flags() const { Q_D(const SelectJob); return d->flags; } QList SelectJob::permanentFlags() const { Q_D(const SelectJob); return d->permanentFlags; } int SelectJob::messageCount() const { Q_D(const SelectJob); return d->messageCount; } int SelectJob::recentCount() const { Q_D(const SelectJob); return d->recentCount; } int SelectJob::firstUnseenIndex() const { Q_D(const SelectJob); return d->firstUnseenIndex; } qint64 SelectJob::uidValidity() const { Q_D(const SelectJob); return d->uidValidity; } qint64 SelectJob::nextUid() const { Q_D(const SelectJob); return d->nextUid; } void SelectJob::doStart() { Q_D(SelectJob); QByteArray command = "SELECT"; if ( d->readOnly ) { command = "EXAMINE"; } d->tag = d->sessionInternal()->sendCommand( command, '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"' ); } void SelectJob::handleResponse( const Message &response ) { Q_D(SelectJob); if ( handleErrorReplies(response) == NotHandled) { if ( response.content.size() >= 2 ) { QByteArray code = response.content[1].toString(); if ( code=="OK" ) { if ( response.responseCode.size() < 2 ) return; code = response.responseCode[0].toString(); if ( code=="PERMANENTFLAGS" ) { d->permanentFlags = response.responseCode[1].toList(); } else { bool isInt; if ( code=="UIDVALIDITY" ) { qint64 value = response.responseCode[1].toString().toLongLong(&isInt); if ( !isInt ) return; d->uidValidity = value; } else { qint64 value = response.responseCode[1].toString().toLongLong(&isInt); if ( !isInt ) return; if ( code=="UNSEEN" ) { d->firstUnseenIndex = value; } else if ( code=="UIDNEXT" ) { d->nextUid = value; } } } } else if ( code=="FLAGS" ) { d->flags = response.content[2].toList(); } else { bool isInt; int value = response.content[1].toString().toInt(&isInt); if ( !isInt || response.content.size()<3 ) return; code = response.content[2].toString(); if ( code=="EXISTS" ) { d->messageCount = value; } else if ( code=="RECENT" ) { d->recentCount = value; } } } else { qDebug("%s", response.toString().constData()); } } else { Q_ASSERT( error() || d->sessionInternal()->selectedMailBox() == d->mailBox ); } } #include "selectjob.moc" diff --git a/kimap/session.cpp b/kimap/session.cpp index 6fcc81208..938c536d3 100644 --- a/kimap/session.cpp +++ b/kimap/session.cpp @@ -1,264 +1,264 @@ /* Copyright (c) 2009 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "session.h" #include "session_p.h" #include "sessionuiproxy.h" #include #include #include #include #include "job.h" #include "message_p.h" #include "sessionthread_p.h" #include "rfccodecs.h" Q_DECLARE_METATYPE(KTcpSocket::SslVersion) Q_DECLARE_METATYPE(QSslSocket::SslMode) static const int _kimap_sslVersionId = qRegisterMetaType(); using namespace KIMAP; Session::Session( const QString &hostName, quint16 port, QObject *parent) : QObject(parent), d(new SessionPrivate(this)) { d->state = Disconnected; d->jobRunning = false; d->thread = new SessionThread(hostName, port, this); connect(d->thread, SIGNAL(encryptionNegotiationResult(bool)), d, SIGNAL(encryptionNegotiationResult(bool))); connect(d->thread, SIGNAL(sslError(const KSslErrorUiData&)), this, SLOT(handleSslError(const KSslErrorUiData&))); d->thread->start(); } Session::~Session() { delete d->thread; } void Session::setUiProxy(SessionUiProxy *proxy) { d->uiProxy = proxy; } QString Session::hostName() const { return d->thread->hostName(); } quint16 Session::port() const { return d->thread->port(); } Session::State Session::state() const { return d->state; } void SessionPrivate::handleSslError(const KSslErrorUiData& errorData) { if (uiProxy && uiProxy->ignoreSslError(errorData)) { QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, true) ); } else { QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, false) ); } } SessionPrivate::SessionPrivate( Session *session ) : q(session), uiProxy(0), currentJob(0), tagCount(0) { } void SessionPrivate::addJob(Job *job) { queue.append(job); QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(jobDone(KJob*)) ); QObject::connect( job, SIGNAL(destroyed(QObject*)), q, SLOT(jobDestroyed(QObject*)) ); startNext(); } void SessionPrivate::startNext() { QTimer::singleShot( 0, q, SLOT(doStartNext()) ); } void SessionPrivate::doStartNext() { if ( queue.isEmpty() || jobRunning || state==Session::Disconnected ) { return; } jobRunning = true; currentJob = queue.dequeue(); currentJob->doStart(); } void SessionPrivate::jobDone( KJob *job ) { Q_ASSERT( job == currentJob ); jobRunning = false; currentJob = 0; startNext(); } void SessionPrivate::jobDestroyed( QObject *job ) { queue.removeAll( static_cast( job ) ); if ( currentJob == job ) currentJob = 0; } void SessionPrivate::responseReceived( const Message &response ) { QByteArray tag; QByteArray code; if ( response.content.size()>=1 ) { tag = response.content[0].toString(); } if ( response.content.size()>=2 ) { code = response.content[1].toString(); } switch ( state ) { case Session::Disconnected: if ( code=="OK" ) { state = Session::NotAuthenticated; startNext(); } else if ( code=="PREAUTH" ) { state = Session::Authenticated; startNext(); } else { thread->closeSocket(); QTimer::singleShot( 1000, thread, SLOT( reconnect() ) ); } return; case Session::NotAuthenticated: if ( code=="OK" && tag==authTag ) { state = Session::Authenticated; } break; case Session::Authenticated: if ( code=="OK" && tag==selectTag ) { state = Session::Selected; currentMailBox = upcomingMailBox; } break; case Session::Selected: if ( ( code=="OK" && tag==closeTag ) || ( code!="OK" && tag==selectTag) ) { state = Session::Authenticated; currentMailBox = QByteArray(); } else if ( code=="OK" && tag==selectTag ) { currentMailBox = upcomingMailBox; } break; } if (tag==authTag) authTag.clear(); if (tag==selectTag) selectTag.clear(); if (tag==closeTag) closeTag.clear(); // If a job is running forward it the response if ( currentJob!=0 ) { currentJob->handleResponse( response ); } else { qWarning() << "A message was received from the server with no job to handle it"; } } QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args ) { - QByteArray tag = "A" + QByteArray::number(++tagCount).rightJustified(6, '0'); + QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0'); QByteArray payload = tag+' '+command; if ( !args.isEmpty() ) { payload+= ' '+args; } payload+="\r\n"; thread->sendData(payload); if ( command=="LOGIN" || command=="AUTHENTICATE" ) { authTag = tag; } else if ( command=="SELECT" || command=="EXAMINE" ) { selectTag = tag; upcomingMailBox = args; upcomingMailBox.remove( 0, 1 ); upcomingMailBox.chop( 1 ); upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox ); } else if ( command=="CLOSE" ) { closeTag = tag; } return tag; } void SessionPrivate::sendData( const QByteArray &data ) { thread->sendData(data+"\r\n"); } void SessionPrivate::socketConnected() { state = Session::NotAuthenticated; startNext(); } void SessionPrivate::socketDisconnected() { state = Session::Disconnected; thread->closeSocket(); if ( currentJob ) { currentJob->connectionLost(); } } void SessionPrivate::socketError() { //qWarning() << "Socket error occurred:" << socket->errorString(); socketDisconnected(); } void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version) { QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version) ); } QString SessionPrivate::selectedMailBox() const { return QString::fromUtf8( currentMailBox ); } #include "session.moc" #include "session_p.moc" diff --git a/kimap/setmetadatajob.cpp b/kimap/setmetadatajob.cpp index 2496e6d6c..73902db98 100644 --- a/kimap/setmetadatajob.cpp +++ b/kimap/setmetadatajob.cpp @@ -1,154 +1,154 @@ /* Copyright (c) 2009 Andras Mantia 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 "setmetadatajob.h" #include #include #include "metadatajobbase_p.h" #include "message_p.h" #include "session_p.h" #include "rfccodecs.h" namespace KIMAP { class SetMetaDataJobPrivate : public MetaDataJobBasePrivate { public: SetMetaDataJobPrivate( Session *session, const QString& name ) : MetaDataJobBasePrivate(session, name), metaDataErrors(0), maxAcceptedSize(-1) { } ~SetMetaDataJobPrivate() { } QMap entries; QMap::ConstIterator entriesIt; QByteArray entryName; SetMetaDataJob::MetaDataErrors metaDataErrors; qint64 maxAcceptedSize; }; } using namespace KIMAP; SetMetaDataJob::SetMetaDataJob( Session *session ) : MetaDataJobBase( *new SetMetaDataJobPrivate(session, i18n("SetMetaData")) ) { } SetMetaDataJob::~SetMetaDataJob() { } void SetMetaDataJob::doStart() { Q_D(SetMetaDataJob); QByteArray parameters; parameters = '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" "; d->entriesIt = d->entries.constBegin(); QByteArray command = "SETMETADATA"; if (d->serverCapability == Annotatemore) { command = "SETANNOTATION"; parameters += '\"' + d->entryName + "\" ("; d->m_name = i18n("SetAnnotation"); if (!d->entries.isEmpty()) { for (; d->entriesIt != d->entries.constEnd(); ++d->entriesIt) { parameters += '\"' + d->entriesIt.key() + "\" \"" + d->entriesIt.value() + "\" "; } parameters[parameters.length() - 1] = ')'; } } else { - parameters += "("; + parameters += '('; if (!d->entries.isEmpty()) { parameters += '\"' + d->entriesIt.key() + '\"'; parameters += ' '; parameters +=" {" + QByteArray::number(d->entriesIt.value().size()) + '}'; } } if (d->entries.isEmpty()) { parameters += ')'; } d->tag = d->sessionInternal()->sendCommand( command, parameters ); // kDebug() << "SENT: " << command << " " << parameters; } void SetMetaDataJob::handleResponse( const Message &response ) { Q_D(SetMetaDataJob); //TODO: Test if a server can really return more then one untagged NO response. If not, no need to OR the error codes if ( !response.content.isEmpty() && response.content.first().toString() == d->tag ) { if ( response.content[1].toString() == "NO" ) { setError( UserDefinedError ); setErrorText( i18n("%1 failed, server replied: %2", d->m_name, response.toString().constData()) ); if (response.content[2].toString() == "[ANNOTATEMORE TOOMANY]" || response.content[2].toString() == "[METADATA TOOMANY]") { d->metaDataErrors |= TooMany; } else if (response.content[2].toString() == "[ANNOTATEMORE TOOBIG]" || response.content[2].toString().startsWith("[METADATA MAXSIZE")) { d->metaDataErrors |= TooBig; d->maxAcceptedSize = -1; - if (response.content[2].toString().startsWith("[METADATA MAXSIZE")) { + if (response.content[2].toString().startsWith("[METADATA MAXSIZE")) { //krazy:exclude=strings QByteArray max = response.content[2].toString(); - max.replace("[METADATA MAXSIZE", ""); - max.replace("]",""); + max.replace("[METADATA MAXSIZE",""); //krazy:exclude=doublequote_chars + max.replace("]", ""); //krazy:exclude=doublequote_chars d->maxAcceptedSize = max.toLongLong(); } } else if (response.content[2].toString() == "[METADATA NOPRIVATE]") { d->metaDataErrors |= NoPrivate; } } else if ( response.content.size() < 2 ) { setErrorText( i18n("%1 failed, malformed reply from the server.", d->m_name) ); } else if ( response.content[1].toString() != "OK" ) { setError( UserDefinedError ); setErrorText( i18n("%1 failed, server replied: %2", d->m_name, response.toString().constData()) ); } emitResult(); } else if ( d->serverCapability == Metadata && response.content[0].toString() == "+" ) { QByteArray content = d->entriesIt.value(); ++d->entriesIt; if (d->entriesIt == d->entries.constEnd()) { content += ')'; } else { content +=" {" + QByteArray::number(d->entriesIt.value().size()) + '}'; } // kDebug() << "SENT: " << content; d->sessionInternal()->sendData( content ); } } void SetMetaDataJob::addMetaData(const QByteArray &name, const QByteArray &value) { Q_D(SetMetaDataJob); d->entries[name] = value; } void SetMetaDataJob::setEntry(const QByteArray &entry) { Q_D(SetMetaDataJob); d->entryName = entry; } SetMetaDataJob::MetaDataErrors SetMetaDataJob::metaDataErrors() const { Q_D(const SetMetaDataJob); return d->metaDataErrors; } #include "setmetadatajob.moc" diff --git a/kimap/setmetadatajob.h b/kimap/setmetadatajob.h index 4302fcfb2..d6d64e4b8 100644 --- a/kimap/setmetadatajob.h +++ b/kimap/setmetadatajob.h @@ -1,90 +1,90 @@ /* Copyright (c) 2009 Andras Mantia 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 KIMAP_SETMETADATAJOB_H #define KIMAP_SETMETADATAJOB_H #include "kimap_export.h" #include "metadatajobbase.h" namespace KIMAP { class Session; struct Message; class SetMetaDataJobPrivate; class KIMAP_EXPORT SetMetaDataJob : public MetaDataJobBase { Q_OBJECT Q_DECLARE_PRIVATE(SetMetaDataJob) friend class SessionPrivate; public: explicit SetMetaDataJob( Session *session ); virtual ~SetMetaDataJob(); /** * Add a metadata to the mailbox. Depending on the supported standard by the server (setServerCapability), * the @param name can have a different meaning. * @param name the entry name if serverCapability() returns Metadata (RFC5464 mode), the attribute value name * if serverCapability() is Annotatemore (draft-daboo-imap-annotatemore-07 mode). * @param value the value of the entry/attribute */ void addMetaData(const QByteArray &name, const QByteArray &value); /** * Set the entry name for the metada, if the job is operating in Annotatemore mode. In Metadata mode, this setting is * ignored. * @param entry the metadata entry name */ void setEntry(const QByteArray &entry); enum MetaDataError { NoError = 0, TooMany = 1, TooBig = 2, NoPrivate = 4 }; // Q_DECLARE_WHATEVER_THAT_WAS missing Q_DECLARE_FLAGS(MetaDataErrors, MetaDataError) /** - * The metadata errors recived from the server. + * The metadata errors received from the server. * @return OR connected error codes, see MetaDataError */ MetaDataErrors metaDataErrors() const; /** * The maximum accepted metadata size. * @return the accepted metadata size, -1 means the limit is unknown. */ qint64 maxAcceptedSize(); protected: virtual void doStart(); virtual void handleResponse( const Message &response ); }; } Q_DECLARE_OPERATORS_FOR_FLAGS( KIMAP::SetMetaDataJob::MetaDataErrors ) #endif diff --git a/kimap/tests/testimapserver.cpp b/kimap/tests/testimapserver.cpp index ef74d7d2a..e1c06aa21 100644 --- a/kimap/tests/testimapserver.cpp +++ b/kimap/tests/testimapserver.cpp @@ -1,579 +1,582 @@ /** * This file is part of the KDE project * Copyright (C) 2009 Kevin Ottens * Copyright (C) 2009 Andras Mantia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kimap/acl.h" #include "kimap/session.h" #include "kimap/appendjob.h" #include "kimap/capabilitiesjob.h" #include "kimap/fetchjob.h" #include "kimap/listjob.h" #include "kimap/loginjob.h" #include "kimap/logoutjob.h" #include "kimap/selectjob.h" #include "kimap/closejob.h" #include "kimap/expungejob.h" #include "kimap/createjob.h" #include "kimap/deletejob.h" #include "kimap/subscribejob.h" #include "kimap/unsubscribejob.h" #include "kimap/renamejob.h" #include "kimap/storejob.h" #include "kimap/sessionuiproxy.h" #include "kimap/setacljob.h" #include "kimap/getacljob.h" #include "kimap/deleteacljob.h" #include "kimap/myrightsjob.h" #include "kimap/listrightsjob.h" #include "kimap/setmetadatajob.h" #include "kimap/getmetadatajob.h" using namespace KIMAP; class UiProxy: public SessionUiProxy { public: bool ignoreSslError(const KSslErrorUiData& errorData) { if (KIO::SslUi::askIgnoreSslErrors(errorData, KIO::SslUi::StoreRules)) { return true; } else { return false; } } }; void dumpContentHelper(KMime::Content *part, const QString &partId = QString()) { if (partId.isEmpty()) { kDebug() << "** Message root **"; } else { kDebug() << "** Part" << partId << "**"; } kDebug() << part->head(); KMime::Content::List children = part->contents(); for (int i=0; isetIncludeUnsubscribed(includeUnsubscribed); list->exec(); Q_ASSERT_X(list->error()==0, "ListJob", list->errorString().toLocal8Bit()); int count = list->mailBoxes().size(); for (int i=0; imailBoxes()[i]; - if (descriptor.name.endsWith(nameFilter)) + if (descriptor.name.endsWith(nameFilter)) { kDebug() << descriptor.separator << descriptor.name; + } } } void testMetaData(Session *session) { kDebug() << "TESTING: METADATA commands"; CreateJob *create = new CreateJob(session); create->setMailBox("INBOX/TestFolder"); create->exec(); SetMetaDataJob *setmetadata = new SetMetaDataJob(session); setmetadata->setMailBox("INBOX/TestFolder"); setmetadata->setServerCapability(SetMetaDataJob::Annotatemore); setmetadata->setEntry("/comment"); setmetadata->addMetaData("value.priv", "My new comment"); setmetadata->exec(); setmetadata = new SetMetaDataJob(session); setmetadata->setMailBox("INBOX/TestFolder"); setmetadata->setServerCapability(SetMetaDataJob::Annotatemore); setmetadata->setEntry("/check"); setmetadata->addMetaData("value.priv", "true"); setmetadata->exec(); GetMetaDataJob *getmetadata = new GetMetaDataJob(session); getmetadata->setMailBox("INBOX/TestFolder"); getmetadata->setServerCapability(SetMetaDataJob::Annotatemore); getmetadata->addEntry("/*","value.priv"); getmetadata->exec(); Q_ASSERT_X(getmetadata->metaData("INBOX/TestFolder", "/check", "value.priv") == "true", "", "/check metadata should be true"); Q_ASSERT_X(getmetadata->metaData("INBOX/TestFolder", "/comment", "value.priv") == "My new comment", "", "/check metadata should be My new comment"); //cleanup DeleteJob *deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/TestFolder"); deletejob->exec(); } void testAcl(Session *session, const QString &user) { kDebug() << "TESTING: ACL commands"; CreateJob *create = new CreateJob(session); create->setMailBox("INBOX/TestFolder"); create->exec(); ListRightsJob *listRights = new ListRightsJob(session); listRights->setMailBox("INBOX/TestFolder"); listRights->setIdentifier(user.toLatin1()); listRights->exec(); kDebug() << "Default rights on INBOX/TestFolder: " << Acl::rightsToString(listRights->defaultRights()); QList possible = listRights->possibleRights(); QStringList strList; Q_FOREACH(Acl::Rights r, possible) { strList << Acl::rightsToString(r); } kDebug() << "Possible rights on INBOX/TestFolder: " << strList; MyRightsJob *myRights = new MyRightsJob(session); myRights->setMailBox("INBOX/TestFolder"); myRights->exec(); Acl::Rights mine = myRights->rights(); kDebug() << "My rights on INBOX/TestFolder: " << Acl::rightsToString(mine); kDebug() << "Reading INBOX/TestFolder is possible: " << myRights->hasRightEnabled(Acl::Read); Q_ASSERT_X(myRights->hasRightEnabled(Acl::Read), "Reading INBOX is NOT possible", ""); GetAclJob *getAcl= new GetAclJob(session); getAcl->setMailBox("INBOX/TestFolder"); getAcl->exec(); kDebug() << "Anyone rights on INBOX/TestFolder: " << getAcl->rights("anyone"); Acl::Rights users = getAcl->rights(user.toLatin1()); kDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users); Q_ASSERT_X(mine == users, "GETACL returns different rights for the same user", ""); kDebug() << "Removing Delete right "; mine = Acl::Delete; SetAclJob *setAcl= new SetAclJob(session); setAcl->setMailBox("INBOX/TestFolder"); setAcl->setIdentifier(user.toLatin1()); setAcl->setRights(AclJobBase::Remove, mine); setAcl->exec(); getAcl= new GetAclJob(session); getAcl->setMailBox("INBOX/TestFolder"); getAcl->exec(); users = getAcl->rights(user.toLatin1()); kDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users); kDebug() << "Adding back Delete right "; mine = Acl::Delete; setAcl= new SetAclJob(session); setAcl->setMailBox("INBOX/TestFolder"); setAcl->setIdentifier(user.toLatin1()); setAcl->setRights(AclJobBase::Add, mine); setAcl->exec(); getAcl= new GetAclJob(session); getAcl->setMailBox("INBOX/TestFolder"); getAcl->exec(); users = getAcl->rights(user.toLatin1()); kDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users); //cleanup DeleteJob *deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/TestFolder"); deletejob->exec(); } void testAppendAndStore(Session *session) { kDebug() << "TESTING: APPEND and STORE"; //setup CreateJob *create = new CreateJob(session); create->setMailBox("INBOX/TestFolder"); create->exec(); QByteArray testMailContent = "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\r\n" "From: Fred Foobar \r\n" "Subject: afternoon meeting\r\n" "To: mooch@owatagu.siam.edu\r\n" "Message-Id: \r\n" "MIME-Version: 1.0\r\n" "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n" "\r\n" "Hello Joe, do you think we can meet at 3:30 tomorrow?\r\n"; kDebug() << "Append a message in INBOX/TestFolder..."; AppendJob *append = new AppendJob(session); append->setMailBox("INBOX/TestFolder"); append->setContent(testMailContent); append->exec(); Q_ASSERT_X(append->error()==0, "AppendJob", append->errorString().toLocal8Bit()); kDebug() << "Read the message back and compare..."; SelectJob *select = new SelectJob(session); select->setMailBox("INBOX/TestFolder"); select->exec(); FetchJob *fetch = new FetchJob(session); FetchJob::FetchScope scope; fetch->setSequenceSet(ImapSet(1)); scope.parts.clear(); scope.mode = FetchJob::FetchScope::Content; fetch->setScope(scope); fetch->exec(); MessagePtr message = fetch->messages()[1]; Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit()); testMailContent.replace( "\r\n", "\n" ); Q_ASSERT_X(testMailContent==message->head()+"\n"+message->body(), "Message differs from reference", message->head()+"\n"+message->body()); fetch = new FetchJob(session); fetch->setSequenceSet(ImapSet(1)); scope.parts.clear(); scope.mode = FetchJob::FetchScope::Flags; fetch->setScope(scope); fetch->exec(); MessageFlags expectedFlags = fetch->flags()[1]; kDebug() << "Read the message flags:" << expectedFlags; kDebug() << "Add the \\Deleted flag..."; expectedFlags << "\\Deleted"; qSort(expectedFlags); StoreJob *store = new StoreJob(session); store->setSequenceSet(ImapSet(1)); store->setMode(StoreJob::AppendFlags); store->setFlags(QList() << "\\Deleted"); store->exec(); Q_ASSERT_X(store->error()==0, "StoreJob", store->errorString().toLocal8Bit()); QList resultingFlags = store->resultingFlags()[1]; qSort(resultingFlags); - if (expectedFlags!=resultingFlags) kDebug() << resultingFlags; + if (expectedFlags!=resultingFlags) { + kDebug() << resultingFlags; + } Q_ASSERT(expectedFlags==resultingFlags); select = new SelectJob(session); select->setMailBox("INBOX"); select->exec(); //cleanup DeleteJob *deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/TestFolder"); deletejob->exec(); deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/RenamedTestFolder"); deletejob->exec(); } void testRename(Session *session) { kDebug() << "TESTING: RENAME"; //setup CreateJob *create = new CreateJob(session); create->setMailBox("INBOX/TestFolder"); create->exec(); kDebug() << "Listing mailboxes with name TestFolder:"; listFolders(session, true, "TestFolder"); //actual tests kDebug() << "Renaming to RenamedTestFolder"; RenameJob *rename = new RenameJob(session); rename->setSourceMailBox("INBOX/TestFolder"); rename->setDestinationMailBox("INBOX/RenamedTestFolder"); rename->exec(); kDebug() << "Listing mailboxes with name TestFolder:"; listFolders(session, true, "TestFolder"); kDebug() << "Listing mailboxes with name RenamedTestFolder:"; listFolders(session, true, "RenamedTestFolder"); //cleanup DeleteJob *deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/TestFolder"); deletejob->exec(); deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/RenamedTestFolder"); deletejob->exec(); } void testSubscribe(Session *session) { kDebug() << "TESTING: SUBSCRIBE/UNSUBSCRIBE"; //setup CreateJob *create = new CreateJob(session); create->setMailBox("INBOX/TestFolder"); create->exec(); kDebug() << "Listing subscribed mailboxes with name TestFolder:"; listFolders(session, false, "TestFolder"); //actual tests kDebug() << "Subscribing to INBOX/TestFolder"; SubscribeJob *subscribe = new SubscribeJob(session); subscribe->setMailBox("INBOX/TestFolder"); subscribe->exec(); kDebug() << "Listing subscribed mailboxes with name TestFolder:"; listFolders(session, false, "TestFolder"); kDebug() << "Unsubscribing from INBOX/TestFolder"; UnsubscribeJob *unsubscribe = new UnsubscribeJob(session); unsubscribe->setMailBox("INBOX/TestFolder"); unsubscribe->exec(); kDebug() << "Listing subscribed mailboxes with name TestFolder:"; listFolders(session, false, "TestFolder"); //cleanup DeleteJob *deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/TestFolder"); deletejob->exec(); } void testDelete(Session *session) { kDebug() << "TESTING: DELETE"; kDebug() << "Creating INBOX/TestFolder:"; CreateJob *create = new CreateJob(session); create->setMailBox("INBOX/TestFolder"); create->exec(); kDebug() << "Listing with name TestFolder before DELETE:"; listFolders(session, true, "TestFolder"); kDebug() << "Deleting INBOX/TestFolder"; DeleteJob *deletejob = new DeleteJob(session); deletejob->setMailBox("INBOX/TestFolder"); deletejob->exec(); kDebug() << "Listing with name TestFolder after DELETE:"; listFolders(session, true, "TestFolder"); } int main( int argc, char **argv ) { KAboutData about("TestImapServer", 0, ki18n("TestImapServer"), "version"); KComponentData cData(&about); if (argc < 4) { kError() << "Not enough parameters, expecting: "; } QString server = QString::fromLocal8Bit(argv[1]); int port = 143; if ( server.count( ':' ) == 1 ) { port = server.split( ':' ).last().toInt(); server = server.split( ':' ).first(); } QString user = QString::fromLocal8Bit(argv[2]); QString password = QString::fromLocal8Bit(argv[3]); kDebug() << "Querying:" << server << port << user << password; qDebug(); QApplication app(argc, argv); Session session(server, port); UiProxy *proxy = new UiProxy(); session.setUiProxy(proxy); kDebug() << "Logging in..."; LoginJob *login = new LoginJob(&session); login->setEncryptionMode(LoginJob::TlsV1); login->setAuthenticationMode(LoginJob::Plain); login->setUserName(user); login->setPassword(password); login->exec(); qDebug(); if (login->encryptionMode() == LoginJob::Unencrypted) { kDebug() << "Encrypted login not possible, try to log in without encryption"; login = new LoginJob(&session); login->setUserName(user); login->setPassword(password); login->exec(); Q_ASSERT_X(login->error()==0, "LoginJob", login->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Authenticated); qDebug(); } kDebug() << "Asking for capabilities:"; CapabilitiesJob *capabilities = new CapabilitiesJob(&session); capabilities->exec(); Q_ASSERT_X(capabilities->error()==0, "CapabilitiesJob", capabilities->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Authenticated); kDebug() << capabilities->capabilities(); qDebug(); kDebug() << "Listing mailboxes:"; listFolders(&session); Q_ASSERT(session.state()==Session::Authenticated); kDebug() << "Selecting INBOX:"; SelectJob *select = new SelectJob(&session); select->setMailBox("INBOX"); select->exec(); Q_ASSERT_X(select->error()==0, "SelectJob", select->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Selected); kDebug() << "Flags:" << select->flags(); kDebug() << "Permanent flags:" << select->permanentFlags(); kDebug() << "Total Number of Messages:" << select->messageCount(); kDebug() << "Number of recent Messages:" << select->recentCount(); kDebug() << "First Unseen Message Index:" << select->firstUnseenIndex(); kDebug() << "UID validity:" << select->uidValidity(); kDebug() << "Next UID:" << select->nextUid(); qDebug(); kDebug() << "Fetching first 3 messages headers:"; FetchJob *fetch = new FetchJob(&session); FetchJob::FetchScope scope; fetch->setSequenceSet(ImapSet(1, 3)); scope.parts.clear(); scope.mode = FetchJob::FetchScope::Headers; fetch->setScope(scope); fetch->exec(); Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Selected); QMap messages = fetch->messages(); foreach (qint64 id, messages.keys()) { kDebug() << "* Message" << id << "(" << fetch->sizes()[id] << "bytes )"; kDebug() << " From :" << messages[id]->from()->asUnicodeString(); kDebug() << " To :" << messages[id]->to()->asUnicodeString(); kDebug() << " Date :" << messages[id]->date()->asUnicodeString(); kDebug() << " Subject :" << messages[id]->subject()->asUnicodeString(); kDebug() << " Message-ID:" << messages[id]->messageID()->asUnicodeString(); } qDebug(); kDebug() << "Fetching first 3 messages flags:"; fetch = new FetchJob(&session); fetch->setSequenceSet(ImapSet(1, 3)); scope.parts.clear(); scope.mode = FetchJob::FetchScope::Flags; fetch->setScope(scope); fetch->exec(); Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Selected); QMap flags = fetch->flags(); foreach (qint64 id, flags.keys()) { kDebug() << "* Message" << id << "flags:" << flags[id]; } qDebug(); kDebug() << "Fetching first message structure:"; fetch = new FetchJob(&session); fetch->setSequenceSet(ImapSet(1)); scope.parts.clear(); scope.mode = FetchJob::FetchScope::Structure; fetch->setScope(scope); fetch->exec(); Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Selected); MessagePtr message = fetch->messages()[1]; dumpContentHelper(message.get()); qDebug(); kDebug() << "Fetching first message second part headers:"; fetch = new FetchJob(&session); fetch->setSequenceSet(ImapSet(1)); scope.parts.clear(); scope.parts << "2"; scope.mode = FetchJob::FetchScope::Headers; fetch->setScope(scope); fetch->exec(); Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Selected); QMap allParts = fetch->parts(); foreach (qint64 id, allParts.keys()) { kDebug() << "* Message" << id << "parts headers"; MessageParts parts = allParts[id]; foreach (const QByteArray &partId, parts.keys()) { kDebug() << " ** Part" << partId; kDebug() << " Name :" << parts[partId]->contentType()->name(); kDebug() << " Mimetype :" << parts[partId]->contentType()->mimeType(); kDebug() << " Description:" << parts[partId]->contentDescription()->asUnicodeString().simplified(); } } qDebug(); kDebug() << "Fetching first message second part content:"; fetch = new FetchJob(&session); fetch->setSequenceSet(ImapSet(1)); scope.parts.clear(); scope.parts << "2"; scope.mode = FetchJob::FetchScope::Content; fetch->setScope(scope); fetch->exec(); Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Selected); allParts = fetch->parts(); foreach (int id, allParts.keys()) { MessageParts parts = allParts[id]; foreach (const QByteArray &partId, parts.keys()) { kDebug() << "* Message" << id << "part" << partId << "content:"; kDebug() << parts[partId]->body(); } } qDebug(); testDelete(&session); testSubscribe(&session); testRename(&session); testAppendAndStore(&session); testAcl(&session, user); testMetaData(&session); kDebug() << "Expunge INBOX:"; ExpungeJob *expunge = new ExpungeJob(&session); expunge->exec(); kDebug() << "Closing INBOX:"; CloseJob *close = new CloseJob(&session); close->exec(); Q_ASSERT(session.state()==Session::Authenticated); qDebug(); kDebug() << "Logging out..."; LogoutJob *logout = new LogoutJob(&session); logout->exec(); Q_ASSERT_X(logout->error()==0, "LogoutJob", logout->errorString().toLocal8Bit()); Q_ASSERT(session.state()==Session::Disconnected); return 0; }