diff --git a/akonadi/itemmodifyjob.cpp b/akonadi/itemmodifyjob.cpp index 156bd7a79..ac2a6915c 100644 --- a/akonadi/itemmodifyjob.cpp +++ b/akonadi/itemmodifyjob.cpp @@ -1,224 +1,229 @@ /* 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 "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "collection.h" #include "entity_p.h" #include "imapparser_p.h" #include "itemserializer.h" #include "job_p.h" #include "item_p.h" #include "protocolhelper.h" #include using namespace Akonadi; ItemModifyJobPrivate::ItemModifyJobPrivate( ItemModifyJob *parent, const Item &item ) : JobPrivate( parent ), mItem( item ), mRevCheck( true ), mIgnorePayload( false ) { mParts = mItem.loadedPayloadParts(); } void ItemModifyJobPrivate::setClean() { mOperations.insert( Dirty ); } QByteArray ItemModifyJobPrivate::nextPartHeader() { QByteArray command; if ( !mParts.isEmpty() ) { QSetIterator it( mParts ); const QByteArray label = it.next(); mParts.remove( label ); mPendingData.clear(); int version = 0; ItemSerializer::serialize( mItem, label, mPendingData, version ); command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, label, version ); - command += ".SILENT {" + QByteArray::number( mPendingData.size() ) + '}'; - if ( mPendingData.size() > 0 ) - command += '\n'; - else + command += ".SILENT"; + if ( mPendingData.size() > 0 ) { + command += " {" + QByteArray::number( mPendingData.size() ) + "}\n"; + } else { + if ( mPendingData.isNull() ) + command += " NIL"; + else + command += " \"\""; command += nextPartHeader(); + } } else { command += ")\n"; } return command; } ItemModifyJob::ItemModifyJob( const Item &item, QObject * parent ) : Job( new ItemModifyJobPrivate( this, item ), parent ) { Q_D( ItemModifyJob ); d->mOperations.insert( ItemModifyJobPrivate::RemoteId ); } ItemModifyJob::~ItemModifyJob() { } void ItemModifyJob::doStart() { Q_D( ItemModifyJob ); QList changes; foreach ( int op, d->mOperations ) { switch ( op ) { case ItemModifyJobPrivate::RemoteId: if ( !d->mItem.remoteId().isNull() ) { changes << "REMOTEID.SILENT"; changes << ImapParser::quote( d->mItem.remoteId().toLatin1() ); } break; case ItemModifyJobPrivate::Dirty: changes << "DIRTY.SILENT"; changes << "false"; break; } } if ( d->mItem.d_func()->mFlagsOverwritten ) { changes << "FLAGS.SILENT"; changes << '(' + ImapParser::join( d->mItem.flags(), " " ) + ')'; } else { if ( !d->mItem.d_func()->mAddedFlags.isEmpty() ) { changes << "+FLAGS.SILENT"; changes << '(' + ImapParser::join( d->mItem.d_func()->mAddedFlags, " " ) + ')'; } if ( !d->mItem.d_func()->mDeletedFlags.isEmpty() ) { changes << "-FLAGS.SILENT"; changes << '(' + ImapParser::join( d->mItem.d_func()->mDeletedFlags, " " ) + ')'; } } if ( !d->mItem.d_func()->mDeletedAttributes.isEmpty() ) { changes << "-PARTS.SILENT"; QList attrs; foreach ( const QByteArray &attr, d->mItem.d_func()->mDeletedAttributes ) attrs << ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, attr ); changes << '(' + ImapParser::join( attrs, " " ) + ')'; } // nothing to do if ( changes.isEmpty() && d->mParts.isEmpty() && d->mItem.attributes().isEmpty() ) { emitResult(); return; } d->mTag = d->newTag(); QByteArray command = d->mTag; command += " UID STORE " + QByteArray::number( d->mItem.id() ) + ' '; if ( !d->mRevCheck ) { command += "NOREV "; } else { command += "REV " + QByteArray::number( d->mItem.revision() ) + ' '; } command += "SIZE " + QByteArray::number( d->mItem.size() ); command += " (" + ImapParser::join( changes, " " ); const QByteArray attrs = ProtocolHelper::attributesToByteArray( d->mItem, true ); if ( !attrs.isEmpty() ) command += ' ' + attrs; command += d->nextPartHeader(); d->writeData( command ); d->newTag(); // hack to circumvent automatic response handling } void ItemModifyJob::doHandleResponse(const QByteArray &_tag, const QByteArray & data) { Q_D( ItemModifyJob ); if ( _tag == "+" ) { // ready for literal data d->writeData( d->mPendingData ); d->writeData( d->nextPartHeader() ); return; } if ( _tag == d->mTag ) { if ( data.startsWith( "OK" ) ) { QDateTime modificationDateTime; if ( int pos = data.indexOf( "DATETIME" ) ) { int resultPos = ImapParser::parseDateTime( data, modificationDateTime, pos + 8 ); if ( resultPos == (pos + 8) ) { kDebug( 5250 ) << "Invalid DATETIME response to STORE command: " << _tag << data; } } // increase item revision of own copy of item d->mItem.setRevision( d->mItem.revision() + 1 ); d->mItem.setModificationTime( modificationDateTime ); d->mItem.d_ptr->resetChangeLog(); } else { setError( Unknown ); setErrorText( QString::fromUtf8( data ) ); } emitResult(); return; } kDebug( 5250 ) << "Unhandled response: " << _tag << data; } void ItemModifyJob::setIgnorePayload( bool ignore ) { Q_D( ItemModifyJob ); if ( d->mIgnorePayload == ignore ) return; d->mIgnorePayload = ignore; if ( d->mIgnorePayload ) d->mParts = QSet(); else { Q_ASSERT( !d->mItem.mimeType().isEmpty() ); d->mParts = d->mItem.loadedPayloadParts(); } } bool ItemModifyJob::ignorePayload() const { Q_D( const ItemModifyJob ); return d->mIgnorePayload; } void ItemModifyJob::disableRevisionCheck() { Q_D( ItemModifyJob ); d->mRevCheck = false; } Item ItemModifyJob::item() const { Q_D( const ItemModifyJob ); return d->mItem; } #include "itemmodifyjob.moc" diff --git a/akonadi/tests/itemserializertest.cpp b/akonadi/tests/itemserializertest.cpp index 98b78327a..f75b6c7c3 100644 --- a/akonadi/tests/itemserializertest.cpp +++ b/akonadi/tests/itemserializertest.cpp @@ -1,57 +1,66 @@ /* 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 "itemserializertest.h" #include #include #include #include using namespace Akonadi; QTEST_KDEMAIN( ItemSerializerTest, NoGUI ) void ItemSerializerTest::testEmptyPayload() { // should not crash QByteArray data; Item item; ItemSerializer::deserialize( item, Item::FullPayload, data, 0 ); QVERIFY( data.isEmpty() ); } +void ItemSerializerTest::testDefaultSerializer_data() +{ + QTest::addColumn( "serialized" ); + + QTest::newRow( "empty" ) << QByteArray(); + QTest::newRow( "null" ) << QByteArray( "\0" ); + QTest::newRow( "mixed" ) << QByteArray( "\0\r\n\0bla" ); +} + void ItemSerializerTest::testDefaultSerializer() { - QByteArray serialized = "\0\r\n\0bla"; + QFETCH( QByteArray, serialized ); Item item; item.setMimeType( "application/octet-stream" ); ItemSerializer::deserialize( item, Item::FullPayload, serialized, 0 ); QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), serialized ); QByteArray data; int version = 0; ItemSerializer::serialize( item, Item::FullPayload, data, version ); QCOMPARE( serialized, data ); } #include "itemserializertest.moc" diff --git a/akonadi/tests/itemserializertest.h b/akonadi/tests/itemserializertest.h index ab5f9ad35..de545f978 100644 --- a/akonadi/tests/itemserializertest.h +++ b/akonadi/tests/itemserializertest.h @@ -1,34 +1,35 @@ /* 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 ITEMSERIALIZERTEST_H #define ITEMSERIALIZERTEST_H #include class ItemSerializerTest : public QObject { Q_OBJECT private slots: void testEmptyPayload(); + void testDefaultSerializer_data(); void testDefaultSerializer(); }; #endif diff --git a/akonadi/tests/itemstoretest.cpp b/akonadi/tests/itemstoretest.cpp index 2ce853a77..b5e079122 100644 --- a/akonadi/tests/itemstoretest.cpp +++ b/akonadi/tests/itemstoretest.cpp @@ -1,343 +1,347 @@ /* Copyright (c) 2006 Volker Krause Copyright (c) 2007 Robert Zwerus 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 "control.h" #include "itemstoretest.h" #include "testattribute.h" #include #include #include #include #include #include #include #include #include #include #include #include "test_utils.h" using namespace Akonadi; QTEST_AKONADIMAIN( ItemStoreTest, NoGUI ) static Collection res1_foo; static Collection res2; static Collection res3; void ItemStoreTest::initTestCase() { Control::start(); AttributeFactory::registerAttribute(); // get the collections we run the tests on res1_foo = Collection( collectionIdFromPath( "res1/foo" ) ); QVERIFY( res1_foo.isValid() ); res2 = Collection( collectionIdFromPath( "res2" ) ); QVERIFY( res2.isValid() ); res3 = Collection( collectionIdFromPath( "res3" ) ); QVERIFY( res3.isValid() ); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void ItemStoreTest::testFlagChange() { ItemFetchJob *fjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); Item item = fjob->items()[0]; // add a flag Item::Flags origFlags = item.flags(); Item::Flags expectedFlags = origFlags; expectedFlags.insert( "added_test_flag_1" ); item.setFlag( "added_test_flag_1" ); ItemModifyJob *sjob = new ItemModifyJob( item, this ); QVERIFY( sjob->exec() ); fjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.flags().count(), expectedFlags.count() ); Item::Flags diff = expectedFlags - item.flags(); QVERIFY( diff.isEmpty() ); // set flags expectedFlags.insert( "added_test_flag_2" ); item.setFlags( expectedFlags ); sjob = new ItemModifyJob( item, this ); QVERIFY( sjob->exec() ); fjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.flags().count(), expectedFlags.count() ); diff = expectedFlags - item.flags(); QVERIFY( diff.isEmpty() ); // remove a flag item.clearFlag( "added_test_flag_1" ); item.clearFlag( "added_test_flag_2" ); sjob = new ItemModifyJob( item, this ); QVERIFY( sjob->exec() ); fjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.flags().count(), origFlags.count() ); diff = origFlags - item.flags(); QVERIFY( diff.isEmpty() ); } void ItemStoreTest::testDataChange_data() { QTest::addColumn( "data" ); QTest::newRow( "empty" ) << QByteArray(); QTest::newRow( "nullbyte" ) << QByteArray("\0" ); QTest::newRow( "nullbyte2" ) << QByteArray( "\0X" ); QTest::newRow( "linebreaks" ) << QByteArray( "line1\nline2\n\rline3\rline4\r\n" ); QTest::newRow( "linebreaks2" ) << QByteArray( "line1\r\nline2\r\n\r\n" ); QTest::newRow( "linebreaks3" ) << QByteArray( "line1\nline2" ); QTest::newRow( "simple" ) << QByteArray( "testbody" ); + QByteArray b; + QTest::newRow( "big" ) << b.fill( 'a', 1 << 20 ); + QTest::newRow( "bignull" ) << b.fill( '\0', 1 << 20 ); + QTest::newRow( "bigcr" ) << b.fill( '\r', 1 << 20 ); + QTest::newRow( "biglf" ) << b.fill( '\n', 1 << 20 ); } void ItemStoreTest::testDataChange() { QFETCH( QByteArray, data ); Item item; ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); prefetchjob->exec(); item = prefetchjob->items()[0]; item.setMimeType( "application/octet-stream" ); item.setPayload( data ); QCOMPARE( item.payload(), data ); - // delete data + // modify data ItemModifyJob *sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); ItemFetchJob *fjob = new ItemFetchJob( Item( 1 ) ); fjob->fetchScope().fetchFullPayload(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QVERIFY( item.hasPayload() ); - QEXPECT_FAIL( "empty", "Item does not detect QByteArray() as loaded payload part", Continue ); QCOMPARE( item.payload(), data ); } void ItemStoreTest::testRemoteId_data() { QTest::addColumn( "rid" ); QTest::addColumn( "exprid" ); QTest::newRow( "set" ) << QString( "A" ) << QString( "A" ); QTest::newRow( "no-change" ) << QString() << QString( "A" ); QTest::newRow( "clear" ) << QString( "" ) << QString( "" ); QTest::newRow( "reset" ) << QString( "A" ) << QString( "A" ); } void ItemStoreTest::testRemoteId() { QFETCH( QString, rid ); QFETCH( QString, exprid ); ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); prefetchjob->exec(); Item item = prefetchjob->items()[0]; item.setId( 1 ); item.setRemoteId( rid ); ItemModifyJob *store = new ItemModifyJob( item, this ); QVERIFY( store->exec() ); ItemFetchJob *fetch = new ItemFetchJob( item, this ); QVERIFY( fetch->exec() ); QCOMPARE( fetch->items().count(), 1 ); item = fetch->items().at( 0 ); QCOMPARE( item.remoteId(), exprid ); } void ItemStoreTest::testMultiPart() { ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( prefetchjob->exec() ); QCOMPARE( prefetchjob->items().count(), 1 ); Item item = prefetchjob->items()[0]; item.setMimeType( "application/octet-stream" ); item.setPayload( "testmailbody" ); item.attribute( Item::AddIfMissing )->data = "extra"; // store item ItemModifyJob *sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); ItemFetchJob *fjob = new ItemFetchJob( Item( 1 ) ); fjob->fetchScope().fetchAttribute(); fjob->fetchScope().fetchFullPayload(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray("testmailbody") ); QVERIFY( item.hasAttribute() ); QCOMPARE( item.attribute()->data, QByteArray("extra") ); // clean up item.removeAttribute( "EXTRA" ); sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); } void ItemStoreTest::testPartRemove() { ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 2 ) ); prefetchjob->exec(); Item item = prefetchjob->items()[0]; item.setMimeType( "application/octet-stream" ); item.attribute( Item::AddIfMissing )->data = "extra"; // store item ItemModifyJob *sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); // fetch item and its parts (should be RFC822, HEAD and EXTRA) ItemFetchJob *fjob = new ItemFetchJob( Item( 2 ) ); fjob->fetchScope().fetchFullPayload(); fjob->fetchScope().fetchAllAttributes(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.attributes().count(), 2 ); QVERIFY( item.hasAttribute() ); // remove a part item.removeAttribute(); sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); // fetch item again (should only have RFC822 and HEAD left) ItemFetchJob *fjob2 = new ItemFetchJob( Item( 2 ) ); fjob2->fetchScope().fetchFullPayload(); fjob2->fetchScope().fetchAllAttributes(); QVERIFY( fjob2->exec() ); QCOMPARE( fjob2->items().count(), 1 ); item = fjob2->items()[0]; QCOMPARE( item.attributes().count(), 1 ); QVERIFY( !item.hasAttribute() ); } void ItemStoreTest::testRevisionCheck() { // make sure we don't have any other collection selected // otherwise EXPUNGE doesn't work and will be triggered by // the following tests and mess up the monitor testing CollectionSelectJob *sel = new CollectionSelectJob( Collection::root(), this ); QVERIFY( sel->exec() ); // fetch same item twice Item ref( 2 ); ItemFetchJob *prefetchjob = new ItemFetchJob( ref ); QVERIFY( prefetchjob->exec() ); QCOMPARE( prefetchjob->items().count(), 1 ); Item item1 = prefetchjob->items()[0]; Item item2 = prefetchjob->items()[0]; // store first item unmodified ItemModifyJob *sjob = new ItemModifyJob( item1 ); QVERIFY( sjob->exec() ); // try to store second item ItemModifyJob *sjob2 = new ItemModifyJob( item2 ); item2.attribute( Item::AddIfMissing )->data = "extra"; QVERIFY( !sjob2->exec() ); // fetch same again prefetchjob = new ItemFetchJob( ref ); prefetchjob->exec(); item1 = prefetchjob->items()[0]; // delete item ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); djob->exec(); // try to store it sjob = new ItemModifyJob( item1 ); QVERIFY( !sjob->exec() ); } void ItemStoreTest::testModificationTime() { Item item; item.setMimeType( "text/directory" ); QVERIFY( item.modificationTime().isNull() ); ItemCreateJob *job = new ItemCreateJob( item, res1_foo ); QVERIFY( job->exec() ); // The item should have a datetime set now. item = job->item(); QVERIFY( !item.modificationTime().isNull() ); QDateTime initialDateTime = item.modificationTime(); // Fetch the same item again. Item item2( item.id() ); ItemFetchJob *fjob = new ItemFetchJob( item2, this ); QVERIFY( fjob->exec() ); item2 = fjob->items().first(); QCOMPARE( initialDateTime, item2.modificationTime() ); // Lets wait 5 secs. QTest::qWait( 5000 ); // Modify the item item.attribute( Item::AddIfMissing )->data = "extra"; ItemModifyJob *mjob = new ItemModifyJob( item ); QVERIFY( mjob->exec() ); // The item should still have a datetime set and that date should be somewhere // after the initialDateTime. item = mjob->item(); QVERIFY( !item.modificationTime().isNull() ); QVERIFY( initialDateTime < item.modificationTime() ); // Fetch the item after modification. Item item3( item.id() ); ItemFetchJob *fjob2 = new ItemFetchJob( item3, this ); QVERIFY( fjob2->exec() ); // item3 should have the same modification time as item. item3 = fjob2->items().first(); QCOMPARE( item3.modificationTime(), item.modificationTime() ); // Clean up ItemDeleteJob *idjob = new ItemDeleteJob( item, this ); QVERIFY( idjob->exec() ); } #include "itemstoretest.moc"