00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "itemmodifyjob.h"
00021 #include "itemmodifyjob_p.h"
00022
00023 #include "collection.h"
00024 #include "conflicthandling/conflicthandler_p.h"
00025 #include "entity_p.h"
00026 #include "imapparser_p.h"
00027 #include "item_p.h"
00028 #include "itemserializer_p.h"
00029 #include "job_p.h"
00030 #include "protocolhelper_p.h"
00031
00032 #include <kdebug.h>
00033
00034 using namespace Akonadi;
00035
00036 ItemModifyJobPrivate::ItemModifyJobPrivate( ItemModifyJob *parent )
00037 : JobPrivate( parent ),
00038 mRevCheck( true ),
00039 mIgnorePayload( false ),
00040 mAutomaticConflictHandlingEnabled( true )
00041 {
00042 }
00043
00044 void ItemModifyJobPrivate::setClean()
00045 {
00046 mOperations.insert( Dirty );
00047 }
00048
00049 QByteArray ItemModifyJobPrivate::nextPartHeader()
00050 {
00051 QByteArray command;
00052 if ( !mParts.isEmpty() ) {
00053 QSetIterator<QByteArray> it( mParts );
00054 const QByteArray label = it.next();
00055 mParts.remove( label );
00056
00057 mPendingData.clear();
00058 int version = 0;
00059 ItemSerializer::serialize( mItems.first(), label, mPendingData, version );
00060 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, label, version );
00061 if ( mPendingData.size() > 0 ) {
00062 command += " {" + QByteArray::number( mPendingData.size() ) + "}\n";
00063 } else {
00064 if ( mPendingData.isNull() )
00065 command += " NIL";
00066 else
00067 command += " \"\"";
00068 command += nextPartHeader();
00069 }
00070 } else {
00071 command += ")\n";
00072 }
00073 return command;
00074 }
00075
00076 void ItemModifyJobPrivate::conflictResolved()
00077 {
00078 Q_Q( ItemModifyJob );
00079
00080 q->setError( KJob::NoError );
00081 q->setErrorText( QString() );
00082 q->emitResult();
00083 }
00084
00085 void ItemModifyJobPrivate::conflictResolveError( const QString &message )
00086 {
00087 Q_Q( ItemModifyJob );
00088
00089 q->setErrorText( q->errorText() + message );
00090 q->emitResult();
00091 }
00092
00093 void ItemModifyJobPrivate::doUpdateItemRevision( Akonadi::Item::Id itemId, int oldRevision, int newRevision )
00094 {
00095 Item::List::iterator it = std::find_if( mItems.begin(), mItems.end(), boost::bind( &Item::id, _1 ) == itemId );
00096 if ( it != mItems.end() && (*it).revision() == oldRevision )
00097 (*it).setRevision( newRevision );
00098 }
00099
00100
00101 ItemModifyJob::ItemModifyJob( const Item &item, QObject * parent )
00102 : Job( new ItemModifyJobPrivate( this ), parent )
00103 {
00104 Q_D( ItemModifyJob );
00105
00106 d->mItems.append( item );
00107 d->mParts = item.loadedPayloadParts();
00108
00109 d->mOperations.insert( ItemModifyJobPrivate::RemoteId );
00110 d->mOperations.insert( ItemModifyJobPrivate::RemoteRevision );
00111 }
00112
00113 ItemModifyJob::ItemModifyJob( const Akonadi::Item::List &items, QObject *parent)
00114 : Job( new ItemModifyJobPrivate( this ), parent )
00115 {
00116 Q_ASSERT( !items.isEmpty() );
00117 Q_D( ItemModifyJob );
00118 d->mItems = items;
00119
00120
00121 if ( d->mItems.size() == 1 ) {
00122 d->mParts = items.first().loadedPayloadParts();
00123 d->mOperations.insert( ItemModifyJobPrivate::RemoteId );
00124 d->mOperations.insert( ItemModifyJobPrivate::RemoteRevision );
00125 } else {
00126 d->mIgnorePayload = true;
00127 d->mRevCheck = false;
00128 }
00129 }
00130
00131
00132 ItemModifyJob::~ItemModifyJob()
00133 {
00134 }
00135
00136 void ItemModifyJob::doStart()
00137 {
00138 Q_D( ItemModifyJob );
00139
00140 const Akonadi::Item item = d->mItems.first();
00141 QList<QByteArray> changes;
00142 foreach ( int op, d->mOperations ) {
00143 switch ( op ) {
00144 case ItemModifyJobPrivate::RemoteId:
00145 if ( !item.remoteId().isNull() ) {
00146 changes << "REMOTEID";
00147 changes << ImapParser::quote( item.remoteId().toUtf8() );
00148 }
00149 break;
00150 case ItemModifyJobPrivate::RemoteRevision:
00151 if ( !item.remoteRevision().isNull() ) {
00152 changes << "REMOTEREVISION";
00153 changes << ImapParser::quote( item.remoteRevision().toUtf8() );
00154 }
00155 break;
00156 case ItemModifyJobPrivate::Dirty:
00157 changes << "DIRTY";
00158 changes << "false";
00159 break;
00160 }
00161 }
00162
00163 if ( item.d_func()->mClearPayload )
00164 changes << "INVALIDATECACHE";
00165
00166 if ( item.d_func()->mFlagsOverwritten ) {
00167 changes << "FLAGS";
00168 changes << '(' + ImapParser::join( item.flags(), " " ) + ')';
00169 } else {
00170 if ( !item.d_func()->mAddedFlags.isEmpty() ) {
00171 changes << "+FLAGS";
00172 changes << '(' + ImapParser::join( item.d_func()->mAddedFlags, " " ) + ')';
00173 }
00174 if ( !item.d_func()->mDeletedFlags.isEmpty() ) {
00175 changes << "-FLAGS";
00176 changes << '(' + ImapParser::join( item.d_func()->mDeletedFlags, " " ) + ')';
00177 }
00178 }
00179
00180 if ( !item.d_func()->mDeletedAttributes.isEmpty() ) {
00181 changes << "-PARTS";
00182 QList<QByteArray> attrs;
00183 foreach ( const QByteArray &attr, item.d_func()->mDeletedAttributes )
00184 attrs << ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, attr );
00185 changes << '(' + ImapParser::join( attrs, " " ) + ')';
00186 }
00187
00188
00189 if ( changes.isEmpty() && d->mParts.isEmpty() && item.attributes().isEmpty() ) {
00190 emitResult();
00191 return;
00192 }
00193
00194 d->mTag = d->newTag();
00195 QByteArray command = d->mTag;
00196 try {
00197 command += ProtocolHelper::entitySetToByteArray( d->mItems, "STORE" );
00198 } catch ( const Exception &e ) {
00199 setError( Job::Unknown );
00200 setErrorText( QString::fromUtf8( e.what() ) );
00201 emitResult();
00202 return;
00203 }
00204 command += ' ';
00205 if ( !d->mRevCheck || item.revision() < 0 ) {
00206 command += "NOREV ";
00207 } else {
00208 command += "REV " + QByteArray::number( item.revision() ) + ' ';
00209 }
00210
00211 if ( item.d_func()->mSizeChanged )
00212 command += "SIZE " + QByteArray::number( item.size() );
00213
00214 command += " (" + ImapParser::join( changes, " " );
00215 const QByteArray attrs = ProtocolHelper::attributesToByteArray( item, true );
00216 if ( !attrs.isEmpty() )
00217 command += ' ' + attrs;
00218 command += d->nextPartHeader();
00219 d->writeData( command );
00220 d->newTag();
00221 }
00222
00223 void ItemModifyJob::doHandleResponse(const QByteArray &_tag, const QByteArray & data)
00224 {
00225 Q_D( ItemModifyJob );
00226
00227 if ( _tag == "+" ) {
00228 d->writeData( d->mPendingData );
00229 d->writeData( d->nextPartHeader() );
00230 return;
00231 }
00232
00233 if ( _tag == d->mTag ) {
00234 if ( data.startsWith( "OK" ) ) {
00235 QDateTime modificationDateTime;
00236 int dateTimePos = data.indexOf( "DATETIME" );
00237 if ( dateTimePos != -1 ) {
00238 int resultPos = ImapParser::parseDateTime( data, modificationDateTime, dateTimePos + 8 );
00239 if ( resultPos == (dateTimePos + 8) ) {
00240 kDebug() << "Invalid DATETIME response to STORE command: " << _tag << data;
00241 }
00242 }
00243
00244 Item &item = d->mItems.first();
00245 item.setModificationTime( modificationDateTime );
00246 item.d_ptr->resetChangeLog();
00247 } else {
00248 setError( Unknown );
00249 setErrorText( QString::fromUtf8( data ) );
00250
00251 if ( data.contains( "[LLCONFLICT]" ) ) {
00252 if ( d->mAutomaticConflictHandlingEnabled ) {
00253 ConflictHandler *handler = new ConflictHandler( ConflictHandler::LocalLocalConflict, this );
00254 handler->setConflictingItems( d->mItems.first(), d->mItems.first() );
00255 connect( handler, SIGNAL( conflictResolved() ), SLOT( conflictResolved() ) );
00256 connect( handler, SIGNAL( error( const QString& ) ), SLOT( conflictResolveError( const QString& ) ) );
00257
00258 QMetaObject::invokeMethod( handler, "start", Qt::QueuedConnection );
00259 return;
00260 }
00261 }
00262 }
00263 emitResult();
00264 return;
00265 }
00266
00267 if ( _tag == "*" ) {
00268 Akonadi::Item::Id id;
00269 ImapParser::parseNumber( data, id );
00270 int pos = data.indexOf( '(' );
00271 if ( pos <= 0 || id <= 0 ) {
00272 kDebug() << "Ignoring strange response: " << _tag << data;
00273 return;
00274 }
00275 Item::List::iterator it = std::find_if( d->mItems.begin(), d->mItems.end(), boost::bind( &Item::id, _1 ) == id );
00276 if ( it == d->mItems.end() ) {
00277 kDebug() << "Received STORE response for an item we did not modify: " << _tag << data;
00278 return;
00279 }
00280 QList<QByteArray> attrs;
00281 ImapParser::parseParenthesizedList( data, attrs, pos );
00282 for ( int i = 0; i < attrs.size() - 1; i += 2 ) {
00283 const QByteArray key = attrs.at( i );
00284 if ( key == "REV" ) {
00285 const int newRev = attrs.at( i + 1 ).toInt();
00286 const int oldRev = (*it).revision();
00287 if ( newRev < oldRev || newRev < 0 )
00288 continue;
00289 d->itemRevisionChanged( (*it).id(), oldRev, newRev );
00290 (*it).setRevision( newRev );
00291 }
00292 }
00293 return;
00294 }
00295
00296 kDebug() << "Unhandled response: " << _tag << data;
00297 }
00298
00299 void ItemModifyJob::setIgnorePayload( bool ignore )
00300 {
00301 Q_D( ItemModifyJob );
00302
00303 if ( d->mIgnorePayload == ignore )
00304 return;
00305
00306 d->mIgnorePayload = ignore;
00307 if ( d->mIgnorePayload )
00308 d->mParts = QSet<QByteArray>();
00309 else {
00310 Q_ASSERT( !d->mItems.first().mimeType().isEmpty() );
00311 d->mParts = d->mItems.first().loadedPayloadParts();
00312 }
00313 }
00314
00315 bool ItemModifyJob::ignorePayload() const
00316 {
00317 Q_D( const ItemModifyJob );
00318
00319 return d->mIgnorePayload;
00320 }
00321
00322 void ItemModifyJob::disableRevisionCheck()
00323 {
00324 Q_D( ItemModifyJob );
00325
00326 d->mRevCheck = false;
00327 }
00328
00329 void ItemModifyJob::disableAutomaticConflictHandling()
00330 {
00331 Q_D( ItemModifyJob );
00332
00333 d->mAutomaticConflictHandlingEnabled = false;
00334 }
00335
00336 Item ItemModifyJob::item() const
00337 {
00338 Q_D( const ItemModifyJob );
00339 Q_ASSERT( d->mItems.size() == 1 );
00340
00341 return d->mItems.first();
00342 }
00343
00344 Item::List ItemModifyJob::items() const
00345 {
00346 Q_D( const ItemModifyJob );
00347 return d->mItems;
00348 }
00349
00350 #include "itemmodifyjob.moc"