akonadi
itemmodifyjob.cpp
00001 /* 00002 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 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 // same as single item ctor 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 // nothing to do 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(); // hack to circumvent automatic response handling 00221 } 00222 00223 void ItemModifyJob::doHandleResponse(const QByteArray &_tag, const QByteArray & data) 00224 { 00225 Q_D( ItemModifyJob ); 00226 00227 if ( _tag == "+" ) { // ready for literal data 00228 d->writeData( d->mPendingData ); 00229 d->writeData( d->nextPartHeader() ); 00230 return; 00231 } 00232 00233 if ( _tag == d->mTag ) { 00234 if ( data.startsWith( "OK" ) ) { //krazy:exclude=strings 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"