kde-playground/kdepimlibs/akonadi/itemmodifyjob.cpp
2015-04-14 21:49:29 +00:00

477 lines
14 KiB
C++

/*
Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
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 <kdebug.h>
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<QByteArray> 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<QByteArray> 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<QByteArray> 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<QByteArray> 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<QByteArray> 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<QByteArray> 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<QByteArray>();
} 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"