mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
478 lines
14 KiB
C++
478 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"
|