/* 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 "changemediator_p.h" #include "collection.h" #include "conflicthandling/conflicthandler_p.h" #include "entity_p.h" #include "imapparser_p.h" #include "item_p.h" #include "itemserializer_p.h" #include "job_p.h" #include "protocolhelper_p.h" #include "gid/gidextractor_p.h" #include using namespace Akonadi; ItemModifyJobPrivate::ItemModifyJobPrivate(ItemModifyJob *parent) : JobPrivate(parent) , mRevCheck(true) , mIgnorePayload(false) , mAutomaticConflictHandlingEnabled(true) , mSilent(false) { } 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(mItems.first(), label, mPendingData, version); command += ' ' + ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, label, version); 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; } void ItemModifyJobPrivate::conflictResolved() { Q_Q(ItemModifyJob); q->setError(KJob::NoError); q->setErrorText(QString()); q->emitResult(); } void ItemModifyJobPrivate::conflictResolveError(const QString &message) { Q_Q(ItemModifyJob); q->setErrorText(q->errorText() + message); q->emitResult(); } void ItemModifyJobPrivate::doUpdateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision) { Item::List::iterator it = std::find_if(mItems.begin(), mItems.end(), boost::bind(&Item::id, _1) == itemId); if (it != mItems.end() && (*it).revision() == oldRevision) { (*it).setRevision(newRevision); } } QString ItemModifyJobPrivate::jobDebuggingString() const { try { return QString::fromUtf8(fullCommand()); } catch (const Exception &e) { return QString::fromUtf8(e.what()); } } void ItemModifyJobPrivate::setSilent( bool silent ) { mSilent = silent; } QByteArray ItemModifyJobPrivate::tagsToCommandParameter(const Tag::List &tags) const { QByteArray c; if (tags.first().id() >= 0) { c += "TAGS"; c += ' ' + ProtocolHelper::tagSetToImapSequenceSet(tags); } else if (std::find_if(tags.constBegin(), tags.constEnd(), boost::bind(&QByteArray::isEmpty, boost::bind(&Tag::remoteId, _1))) == tags.constEnd()) { //All items have a remoteId c += "RTAGS"; QList rids; Q_FOREACH (const Tag &object, tags) { rids << ImapParser::quote(object.remoteId()); } c += '('; c += ImapParser::join(rids, " "); c += ')'; } else if (std::find_if(tags.constBegin(), tags.constEnd(), boost::bind(&QByteArray::isEmpty, boost::bind(&Tag::gid, _1))) == tags.constEnd()) { //All items have a gid c += "GTAGS"; QList gids; Q_FOREACH (const Tag &object, tags) { gids << ImapParser::quote(object.gid()); } c += '('; c += ImapParser::join(gids, " "); c += ')'; } else { throw Exception("Cannot identify all tags"); } return c; } ItemModifyJob::ItemModifyJob(const Item &item, QObject *parent) : Job(new ItemModifyJobPrivate(this), parent) { Q_D(ItemModifyJob); d->mItems.append(item); d->mParts = item.loadedPayloadParts(); d->mOperations.insert(ItemModifyJobPrivate::RemoteId); d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision); } ItemModifyJob::ItemModifyJob(const Akonadi::Item::List &items, QObject *parent) : Job(new ItemModifyJobPrivate(this), parent) { Q_ASSERT(!items.isEmpty()); Q_D(ItemModifyJob); d->mItems = items; // same as single item ctor if (d->mItems.size() == 1) { d->mParts = items.first().loadedPayloadParts(); d->mOperations.insert(ItemModifyJobPrivate::RemoteId); d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision); } else { d->mIgnorePayload = true; d->mRevCheck = false; } } ItemModifyJob::~ItemModifyJob() { } QByteArray ItemModifyJobPrivate::fullCommand() const { const Akonadi::Item item = mItems.first(); QList changes; foreach (int op, mOperations) { switch (op) { case ItemModifyJobPrivate::RemoteId: if (!item.remoteId().isNull()) { changes << "REMOTEID"; changes << ImapParser::quote(item.remoteId().toUtf8()); } break; case ItemModifyJobPrivate::Gid: { const QString gid = GidExtractor::getGid(item); if (!gid.isNull()) { changes << "GID"; changes << ImapParser::quote(gid.toUtf8()); } break; } case ItemModifyJobPrivate::RemoteRevision: if (!item.remoteRevision().isNull()) { changes << "REMOTEREVISION"; changes << ImapParser::quote(item.remoteRevision().toUtf8()); } break; case ItemModifyJobPrivate::Dirty: changes << "DIRTY"; changes << "false"; break; } } if (item.d_func()->mClearPayload) { changes << "INVALIDATECACHE"; } if ( mSilent ) { changes << "SILENT"; } if (item.d_func()->mFlagsOverwritten) { changes << "FLAGS"; changes << '(' + ImapParser::join(item.flags(), " ") + ')'; } else { if (!item.d_func()->mAddedFlags.isEmpty()) { changes << "+FLAGS"; changes << '(' + ImapParser::join(item.d_func()->mAddedFlags, " ") + ')'; } if (!item.d_func()->mDeletedFlags.isEmpty()) { changes << "-FLAGS"; changes << '(' + ImapParser::join(item.d_func()->mDeletedFlags, " ") + ')'; } } if (item.d_func()->mTagsOverwritten) { changes << tagsToCommandParameter(item.tags()); } else { if (!item.d_func()->mAddedTags.isEmpty()) { changes << "+" + tagsToCommandParameter(item.d_func()->mAddedTags); } if (!item.d_func()->mDeletedTags.isEmpty()) { changes << "-" + tagsToCommandParameter(item.d_func()->mDeletedTags); } } if (!item.d_func()->mDeletedAttributes.isEmpty()) { changes << "-PARTS"; QList attrs; foreach (const QByteArray &attr, item.d_func()->mDeletedAttributes) { attrs << ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartAttribute, attr); } changes << '(' + ImapParser::join(attrs, " ") + ')'; } // nothing to do if (changes.isEmpty() && mParts.isEmpty() && item.attributes().isEmpty()) { return QByteArray(); } QByteArray command; command += ProtocolHelper::entitySetToByteArray(mItems, "STORE"); // can throw an exception command += ' '; if (!mRevCheck || item.revision() < 0) { command += "NOREV "; } else { command += "REV " + QByteArray::number(item.revision()) + ' '; } if (item.d_func()->mSizeChanged) { command += "SIZE " + QByteArray::number(item.size()); } command += " (" + ImapParser::join(changes, " "); const QByteArray attrs = ProtocolHelper::attributesToByteArray(item, true); if (!attrs.isEmpty()) { command += ' ' + attrs; } return command; } void ItemModifyJob::doStart() { Q_D(ItemModifyJob); QByteArray command; try { command = d->fullCommand(); } catch (const Exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); emitResult(); return; } if (command.isEmpty()) { emitResult(); return; } d->mTag = d->newTag(); command.prepend(d->mTag); 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 if (data.startsWith("STREAM")) { QByteArray error; if (!ProtocolHelper::streamPayloadToFile(data, d->mPendingData, error)) { d->writeData("* NO " + error); return; } } else { d->writeData(d->mPendingData); } d->writeData(d->nextPartHeader()); return; } if (_tag == d->mTag) { if (data.startsWith("OK")) { //krazy:exclude=strings QDateTime modificationDateTime; int dateTimePos = data.indexOf("DATETIME"); if (dateTimePos != -1) { int resultPos = ImapParser::parseDateTime(data, modificationDateTime, dateTimePos + 8); if (resultPos == (dateTimePos + 8)) { kDebug() << "Invalid DATETIME response to STORE command: " << _tag << data; } } Item &item = d->mItems.first(); item.setModificationTime(modificationDateTime); item.d_ptr->resetChangeLog(); } else { setError(Unknown); setErrorText(QString::fromUtf8(data)); if (data.contains("[LLCONFLICT]")) { if (d->mAutomaticConflictHandlingEnabled) { ConflictHandler *handler = new ConflictHandler(ConflictHandler::LocalLocalConflict, this); handler->setConflictingItems(d->mItems.first(), d->mItems.first()); connect(handler, SIGNAL(conflictResolved()), SLOT(conflictResolved())); connect(handler, SIGNAL(error(QString)), SLOT(conflictResolveError(QString))); QMetaObject::invokeMethod(handler, "start", Qt::QueuedConnection); return; } } } foreach (const Item &item, d->mItems) { ChangeMediator::invalidateItem(item); } emitResult(); return; } if (_tag == "*") { Akonadi::Item::Id id; ImapParser::parseNumber(data, id); int pos = data.indexOf('('); if (pos <= 0 || id <= 0) { kDebug() << "Ignoring strange response: " << _tag << data; return; } Item::List::iterator it = std::find_if(d->mItems.begin(), d->mItems.end(), boost::bind(&Item::id, _1) == id); if (it == d->mItems.end()) { kDebug() << "Received STORE response for an item we did not modify: " << _tag << data; return; } QList attrs; ImapParser::parseParenthesizedList(data, attrs, pos); for (int i = 0; i < attrs.size() - 1; i += 2) { const QByteArray key = attrs.at(i); if (key == "REV") { const int newRev = attrs.at(i + 1).toInt(); const int oldRev = (*it).revision(); if (newRev < oldRev || newRev < 0) { continue; } d->itemRevisionChanged((*it).id(), oldRev, newRev); (*it).setRevision(newRev); } } return; } kDebug() << "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->mItems.first().mimeType().isEmpty()); d->mParts = d->mItems.first().loadedPayloadParts(); } } bool ItemModifyJob::ignorePayload() const { Q_D(const ItemModifyJob); return d->mIgnorePayload; } void ItemModifyJob::setUpdateGid(bool update) { Q_D(ItemModifyJob); if (update && !updateGid()) { d->mOperations.insert(ItemModifyJobPrivate::Gid); } else { d->mOperations.remove(ItemModifyJobPrivate::Gid); } } bool ItemModifyJob::updateGid() const { Q_D(const ItemModifyJob); return d->mOperations.contains(ItemModifyJobPrivate::Gid); } void ItemModifyJob::disableRevisionCheck() { Q_D(ItemModifyJob); d->mRevCheck = false; } void ItemModifyJob::disableAutomaticConflictHandling() { Q_D(ItemModifyJob); d->mAutomaticConflictHandlingEnabled = false; } Item ItemModifyJob::item() const { Q_D(const ItemModifyJob); Q_ASSERT(d->mItems.size() == 1); return d->mItems.first(); } Item::List ItemModifyJob::items() const { Q_D(const ItemModifyJob); return d->mItems; } #include "moc_itemmodifyjob.cpp"