diff --git a/autotests/server/handlertest.cpp b/autotests/server/handlertest.cpp index 9698dd6da..e88b9274f 100644 --- a/autotests/server/handlertest.cpp +++ b/autotests/server/handlertest.cpp @@ -1,181 +1,203 @@ /* Copyright (c) 2011 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 #include #include #include +#include + #include "handler.h" +#include "handler/create.h" +#include "handler/list.h" +#include "handler/searchpersistent.h" +#include "handler/search.h" +#include "handler/fetch.h" +#include "handler/store.h" +#include "handler/status.h" +#include "handler/delete.h" +#include "handler/modify.h" +#include "handler/transaction.h" +#include "handler/akappend.h" +#include "handler/copy.h" +#include "handler/colcopy.h" +#include "handler/link.h" +#include "handler/resourceselect.h" +#include "handler/remove.h" +#include "handler/move.h" +#include "handler/colmove.h" +#include "handler/login.h" +#include "handler/logout.h" using namespace Akonadi; using namespace Akonadi::Server; -#define MAKE_CMD_ROW( command, class ) QTest::newRow(#command) << command << "Akonadi::Server::" #class; +#define MAKE_CMD_ROW( command, class ) QTest::newRow(#command) << command << QByteArray(typeid(Akonadi::Server::class).name()); class HandlerTest : public QObject { Q_OBJECT private: void setupTestData() { QTest::addColumn("command"); - QTest::addColumn("className"); + QTest::addColumn("className"); } void addAuthCommands() { MAKE_CMD_ROW(Protocol::Command::CreateCollection, Create) MAKE_CMD_ROW(Protocol::Command::FetchCollections, List) MAKE_CMD_ROW(Protocol::Command::StoreSearch, SearchPersistent) MAKE_CMD_ROW(Protocol::Command::Search, Search) MAKE_CMD_ROW(Protocol::Command::FetchItems, Fetch) MAKE_CMD_ROW(Protocol::Command::ModifyItems, Store) MAKE_CMD_ROW(Protocol::Command::FetchCollectionStats, Status) MAKE_CMD_ROW(Protocol::Command::DeleteCollection, Delete) MAKE_CMD_ROW(Protocol::Command::ModifyCollection, Modify) MAKE_CMD_ROW(Protocol::Command::Transaction, TransactionHandler) MAKE_CMD_ROW(Protocol::Command::CreateItem, AkAppend) MAKE_CMD_ROW(Protocol::Command::CopyItems, Copy) MAKE_CMD_ROW(Protocol::Command::CopyCollection, ColCopy) MAKE_CMD_ROW(Protocol::Command::LinkItems, Link) MAKE_CMD_ROW(Protocol::Command::SelectResource, ResourceSelect) MAKE_CMD_ROW(Protocol::Command::DeleteItems, Remove) MAKE_CMD_ROW(Protocol::Command::MoveItems, Move) MAKE_CMD_ROW(Protocol::Command::MoveCollection, ColMove) } void addNonAuthCommands() { MAKE_CMD_ROW(Protocol::Command::Login, Login) } void addAlwaysCommands() { MAKE_CMD_ROW(Protocol::Command::Logout, Logout) } void addInvalidCommands() { //MAKE_CMD_ROW(Protocol::Command::Invalid, UnknownCommandHandler) } private Q_SLOTS: void testFindAuthenticatedCommand_data() { setupTestData(); addAuthCommands(); } void testFindAuthenticatedCommand() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAuthenticated(command)); QVERIFY(!handler.isNull()); - QCOMPARE(handler->metaObject()->className(), className.toLatin1().constData()); + QCOMPARE(QByteArray(typeid(*handler.get()).name()), className); } void testFindAuthenticatedCommandNegative_data() { setupTestData(); addNonAuthCommands(); addAlwaysCommands(); addInvalidCommands(); } void testFindAuthenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAuthenticated(command)); QVERIFY(handler.isNull()); } void testFindNonAutenticatedCommand_data() { setupTestData(); addNonAuthCommands(); } void testFindNonAutenticatedCommand() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandNonAuthenticated(command)); QVERIFY(!handler.isNull()); - QCOMPARE(handler->metaObject()->className(), className.toLatin1().constData()); + QCOMPARE(QByteArray(typeid(*handler.get()).name()), className); } void testFindNonAutenticatedCommandNegative_data() { setupTestData(); addAuthCommands(); addAlwaysCommands(); addInvalidCommands(); } void testFindNonAutenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandNonAuthenticated(command)); QVERIFY(handler.isNull()); } void testFindAlwaysCommand_data() { setupTestData(); addAlwaysCommands(); } void testFindAlwaysCommand() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAlwaysAllowed(command)); QVERIFY(!handler.isNull()); - QCOMPARE(handler->metaObject()->className(), className.toLatin1().constData()); + QCOMPARE(QByteArray(typeid(*handler.get()).name()), className); } void testFindAlwaysCommandNegative_data() { setupTestData(); addAuthCommands(); addNonAuthCommands(); addInvalidCommands(); } void testFindAlwaysCommandNegative() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAlwaysAllowed(command)); QVERIFY(handler.isNull()); } }; AKTEST_MAIN(HandlerTest) #include "handlertest.moc" diff --git a/src/server/connection.cpp b/src/server/connection.cpp index 95a4a600d..cdddd5147 100644 --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -1,528 +1,524 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * Copyright (C) 2013 by Volker Krause * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "connection.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include "storage/datastore.h" #include "handler.h" #include "notificationmanager.h" #include "tracer.h" #include "collectionreferencemanager.h" #include #ifndef Q_OS_WIN #include #endif #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; #define IDLE_TIMER_TIMEOUT 180000 // 3 min static QString connectionIdentifier(Connection *c) { QString id; id.sprintf("%p", static_cast(c)); return id; } namespace { Q_GLOBAL_STATIC(QThreadStorage>, sConnectionStore) } Connection::Connection(QObject *parent) : AkThread(connectionIdentifier(this), QThread::InheritPriority, parent) { } Connection::Connection(quintptr socketDescriptor, QObject *parent) : AkThread(connectionIdentifier(this), QThread::InheritPriority, parent) { m_socketDescriptor = socketDescriptor; m_identifier = connectionIdentifier(this); // same as objectName() const QSettings settings(Akonadi::StandardDirs::serverConfigFile(), QSettings::IniFormat); m_verifyCacheOnRetrieval = settings.value(QStringLiteral("Cache/VerifyOnRetrieval"), m_verifyCacheOnRetrieval).toBool(); } Connection *Connection::self() { Q_ASSERT(sConnectionStore->hasLocalData()); return sConnectionStore->localData(); } void Connection::init() { AkThread::init(); sConnectionStore->setLocalData(this); QLocalSocket *socket = new QLocalSocket(); if (!socket->setSocketDescriptor(m_socketDescriptor)) { qCWarning(AKONADISERVER_LOG) << "Connection(" << m_identifier << ")::run: failed to set socket descriptor: " << socket->error() << "(" << socket->errorString() << ")"; delete socket; return; } m_socket = socket; connect(socket, &QLocalSocket::disconnected, this, &Connection::slotSocketDisconnected); m_idleTimer = new QTimer(this); connect(m_idleTimer, &QTimer::timeout, this, &Connection::slotConnectionIdle); if (socket->state() == QLocalSocket::ConnectedState) { QTimer::singleShot(0, this, &Connection::handleIncomingData); } else { connect(socket, &QLocalSocket::connected, this, &Connection::handleIncomingData, Qt::QueuedConnection); } try { slotSendHello(); } catch (const ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "Protocol Exception sending \"hello\":" << e.what(); m_socket->disconnectFromServer(); } } void Connection::quit() { if (QThread::currentThread()->loopLevel() > 1) { m_connectionClosing = true; Q_EMIT connectionClosing(); return; } Tracer::self()->endConnection(m_identifier, QString()); collectionReferenceManager()->removeSession(m_sessionId); delete m_socket; m_socket = nullptr; if (m_idleTimer) { m_idleTimer->stop(); } delete m_idleTimer; AkThread::quit(); } void Connection::slotSendHello() { SchemaVersion version = SchemaVersion::retrieveAll().first(); auto hello = Protocol::HelloResponsePtr::create(); hello->setServerName(QStringLiteral("Akonadi")); hello->setMessage(QStringLiteral("Not Really IMAP server")); hello->setProtocolVersion(Protocol::version()); hello->setGeneration(version.generation()); sendResponse(0, hello); } DataStore *Connection::storageBackend() { if (!m_backend) { m_backend = DataStore::self(); } return m_backend; } CollectionReferenceManager *Connection::collectionReferenceManager() { return CollectionReferenceManager::instance(); } Connection::~Connection() { quitThread(); if (m_reportTime) { reportTime(); } } void Connection::slotConnectionIdle() { Q_ASSERT(m_currentHandler == nullptr); if (m_backend && m_backend->isOpened()) { if (m_backend->inTransaction()) { // This is a programming error, the timer should not have fired. // But it is safer to abort and leave the connection open, until // a later operation causes the idle timer to fire (than crash // the akonadi server). qCDebug(AKONADISERVER_LOG) << m_sessionId << "NOT Closing idle db connection; we are in transaction"; return; } m_backend->close(); } } void Connection::slotSocketDisconnected() { // If we have active handler, wait for it to finish, then we emit the signal // from slotNewDate() if (m_currentHandler) { return; } Q_EMIT disconnected(); } void Connection::handleIncomingData() { Q_FOREVER { if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) { break; } // Blocks with event loop until some data arrive, allows us to still use QTimers // and similar while waiting for some data to arrive if (m_socket->bytesAvailable() < int(sizeof(qint64))) { QEventLoop loop; connect(m_socket, &QLocalSocket::readyRead, &loop, &QEventLoop::quit); connect(m_socket, &QLocalSocket::stateChanged, &loop, &QEventLoop::quit); connect(this, &Connection::connectionClosing, &loop, &QEventLoop::quit); loop.exec(); } if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) { break; } m_idleTimer->stop(); // will only open() a previously idle backend. // Otherwise, a new backend could lazily be constructed by later calls. if (!storageBackend()->isOpened()) { m_backend->open(); } QString currentCommand; while (m_socket->bytesAvailable() >= int(sizeof(qint64))) { QDataStream stream(m_socket); qint64 tag = -1; stream >> tag; // TODO: Check tag is incremental sequence Protocol::CommandPtr cmd; try { cmd = Protocol::deserialize(m_socket); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); - slotConnectionStateChange(Server::LoggingOut); + setState(Server::LoggingOut); return; } catch (const std::exception &e) { qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); - slotConnectionStateChange(Server::LoggingOut); + setState(Server::LoggingOut); return; } if (cmd->type() == Protocol::Command::Invalid) { qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; - slotConnectionStateChange(Server::LoggingOut); + setState(Server::LoggingOut); return; } // Tag context and collection context is not persistent. context()->setTag(-1); context()->setCollection(Collection()); if (Tracer::self()->currentTracer() != QLatin1String("null")) { Tracer::self()->connectionInput(m_identifier, tag, cmd); } - m_currentHandler = findHandlerForCommand(cmd->type()); + m_currentHandler = std::unique_ptr(findHandlerForCommand(cmd->type())); if (!m_currentHandler) { qCWarning(AKONADISERVER_LOG) << "Invalid command: no such handler for" << cmd->type(); - slotConnectionStateChange(Server::LoggingOut); + setState(Server::LoggingOut); return; } if (m_reportTime) { startTime(); } - connect(m_currentHandler.data(), &Handler::connectionStateChange, - this, &Connection::slotConnectionStateChange, - Qt::DirectConnection); m_currentHandler->setConnection(this); m_currentHandler->setTag(tag); m_currentHandler->setCommand(cmd); try { if (!m_currentHandler->parseStream()) { try { m_currentHandler->failureResponse("Unknown error while handling a command"); } catch (...) { qCWarning(AKONADISERVER_LOG) << "Unknown error while handling a command"; m_connectionClosing = true; } } } catch (const Akonadi::Server::HandlerException &e) { if (m_currentHandler) { try { m_currentHandler->failureResponse(e.what()); } catch (...) { qCWarning(AKONADISERVER_LOG) << "Handler exception:" << e.what(); m_connectionClosing = true; } } } catch (const Akonadi::Server::Exception &e) { if (m_currentHandler) { try { m_currentHandler->failureResponse(QString::fromUtf8(e.type()) + QLatin1String(": ") + QString::fromUtf8(e.what())); } catch (...) { qCWarning(AKONADISERVER_LOG) << e.type() << "exception:" << e.what(); m_connectionClosing = true; } } } catch (const Akonadi::ProtocolException &e) { // No point trying to send anything back to client, the connection is // already messed up qCWarning(AKONADISERVER_LOG) << "Protocol exception:" << e.what(); m_connectionClosing = true; #if defined(Q_OS_LINUX) } catch (abi::__forced_unwind&) { // HACK: NPTL throws __forced_unwind during thread cancellation and // we *must* rethrow it otherwise the program aborts. Due to the issue // described in #376385 we might end up destroying (cancelling) the // thread from a nested loop executed inside parseStream() above, // so the exception raised in there gets caught by this try..catch // statement and it must be rethrown at all cost. Remove this hack // once the root problem is fixed. throw; #endif } catch (...) { qCCritical(AKONADISERVER_LOG) << "Unknown exception caught in Connection for session" << m_sessionId; if (m_currentHandler) { try { m_currentHandler->failureResponse("Unknown exception caught"); } catch (...) { qCWarning(AKONADISERVER_LOG) << "Unknown exception caught"; m_connectionClosing = true; } } } if (m_reportTime) { stopTime(currentCommand); } - delete m_currentHandler; - m_currentHandler = nullptr; + m_currentHandler.reset(); if (!m_socket || m_socket->state() != QLocalSocket::ConnectedState) { Q_EMIT disconnected(); return; } if (m_connectionClosing) { break; } } // reset, arm the timer m_idleTimer->start(IDLE_TIMER_TIMEOUT); if (m_connectionClosing) { break; } } if (m_connectionClosing) { m_socket->disconnect(this); m_socket->close(); QTimer::singleShot(0, this, &Connection::quit); } } CommandContext *Connection::context() const { return const_cast(&m_context); } Handler *Connection::findHandlerForCommand(Protocol::Command::Type command) { Handler *handler = Handler::findHandlerForCommandAlwaysAllowed(command); if (handler) { return handler; } switch (m_connectionState) { case NonAuthenticated: handler = Handler::findHandlerForCommandNonAuthenticated(command); break; case Authenticated: handler = Handler::findHandlerForCommandAuthenticated(command); break; case Selected: break; case LoggingOut: break; } return handler; } -void Connection::slotConnectionStateChange(ConnectionState state) +void Connection::setState(ConnectionState state) { if (state == m_connectionState) { return; } m_connectionState = state; switch (m_connectionState) { case NonAuthenticated: assert(0); // can't happen, it's only the initial state, we can't go back to it break; case Authenticated: break; case Selected: break; case LoggingOut: m_socket->disconnectFromServer(); break; } } void Connection::setSessionId(const QByteArray &id) { m_identifier.sprintf("%s (%p)", id.data(), static_cast(this)); Tracer::self()->beginConnection(m_identifier, QString()); //m_streamParser->setTracerIdentifier(m_identifier); m_sessionId = id; setObjectName(QString::fromLatin1(id)); // this races with the use of objectName() in QThreadPrivate::start //thread()->setObjectName(objectName() + QStringLiteral("-Thread")); storageBackend()->setSessionId(id); storageBackend()->notificationCollector()->setSessionId(id); } QByteArray Connection::sessionId() const { return m_sessionId; } bool Connection::isOwnerResource(const PimItem &item) const { if (context()->resource().isValid() && item.collection().resourceId() == context()->resource().id()) { return true; } // fallback for older resources if (sessionId() == item.collection().resource().name().toUtf8()) { return true; } return false; } bool Connection::isOwnerResource(const Collection &collection) const { if (context()->resource().isValid() && collection.resourceId() == context()->resource().id()) { return true; } if (sessionId() == collection.resource().name().toUtf8()) { return true; } return false; } bool Connection::verifyCacheOnRetrieval() const { return m_verifyCacheOnRetrieval; } void Connection::startTime() { m_time.start(); } void Connection::stopTime(const QString &identifier) { int elapsed = m_time.elapsed(); m_totalTime += elapsed; m_totalTimeByHandler[identifier] += elapsed; m_executionsByHandler[identifier]++; qCDebug(AKONADISERVER_LOG) << identifier << " time : " << elapsed << " total: " << m_totalTime; } void Connection::reportTime() const { qCDebug(AKONADISERVER_LOG) << "===== Time report for " << m_identifier << " ====="; qCDebug(AKONADISERVER_LOG) << " total: " << m_totalTime; for (auto it = m_totalTimeByHandler.cbegin(), end = m_totalTimeByHandler.cend(); it != end; ++it) { const QString &handler = it.key(); qCDebug(AKONADISERVER_LOG) << "handler : " << handler << " time: " << m_totalTimeByHandler.value(handler) << " executions " << m_executionsByHandler.value(handler) << " avg: " << m_totalTimeByHandler.value(handler) / m_executionsByHandler.value(handler); } } void Connection::sendResponse(qint64 tag, const Protocol::CommandPtr &response) { if (Tracer::self()->currentTracer() != QLatin1String("null")) { Tracer::self()->connectionOutput(m_identifier, tag, response); } QDataStream stream(m_socket); stream << tag; Protocol::serialize(m_socket, response); if (!m_socket->waitForBytesWritten()) { if (m_socket->state() == QLocalSocket::ConnectedState) { throw ProtocolException("Server write timeout"); } else { // The client has disconnected before we managed to send our response, // which is not an error } } } void Connection::sendResponse(const Protocol::CommandPtr &response) { Q_ASSERT(m_currentHandler); sendResponse(m_currentHandler->tag(), response); } Protocol::CommandPtr Connection::readCommand() { while (m_socket->bytesAvailable() < (int) sizeof(qint64)) { Protocol::DataStream::waitForData(m_socket, 10000); // 10 seconds, just in case client is busy } QDataStream stream(m_socket); qint64 tag; stream >> tag; // TODO: compare tag with m_currentHandler->tag() ? return Protocol::deserialize(m_socket); } diff --git a/src/server/connection.h b/src/server/connection.h index 5d58fe16f..9f982afe2 100644 --- a/src/server/connection.h +++ b/src/server/connection.h @@ -1,139 +1,140 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_CONNECTION_H #define AKONADI_CONNECTION_H #include #include #include #include #include #include "akthread.h" #include "entities.h" #include "global.h" #include "commandcontext.h" #include class QEventLoop; namespace Akonadi { namespace Server { class Handler; class Response; class DataStore; class Collection; class CollectionReferenceManager; /** An Connection represents one connection of a client to the server. */ class Connection : public AkThread { Q_OBJECT public: explicit Connection(quintptr socketDescriptor, QObject *parent = nullptr); ~Connection() override; static Connection *self(); virtual DataStore *storageBackend(); CollectionReferenceManager *collectionReferenceManager(); CommandContext *context() const; /** Returns @c true if this connection belongs to the owning resource of @p item. */ bool isOwnerResource(const PimItem &item) const; bool isOwnerResource(const Collection &collection) const; void setSessionId(const QByteArray &id); QByteArray sessionId() const; /** Returns @c true if permanent cache verification is enabled. */ bool verifyCacheOnRetrieval() const; Protocol::CommandPtr readCommand(); + void setState(ConnectionState state); + public Q_SLOTS: virtual void sendResponse(const Protocol::CommandPtr &response); Q_SIGNALS: void disconnected(); void connectionClosing(); protected Q_SLOTS: void handleIncomingData(); - void slotConnectionStateChange(ConnectionState state); void slotConnectionIdle(); void slotSocketDisconnected(); void slotSendHello(); protected: Connection(QObject *parent = nullptr); // used for testing void init() override; void quit() override; Handler *findHandlerForCommand(Protocol::Command::Type cmd); protected: quintptr m_socketDescriptor = {}; QLocalSocket *m_socket = nullptr; - QPointer m_currentHandler; + std::unique_ptr m_currentHandler; ConnectionState m_connectionState = NonAuthenticated; mutable DataStore *m_backend = nullptr; QList m_statusMessageQueue; QString m_identifier; QByteArray m_sessionId; bool m_verifyCacheOnRetrieval = false; CommandContext m_context; QTimer *m_idleTimer = nullptr; QEventLoop *m_waitLoop = nullptr; QTime m_time; qint64 m_totalTime = 0; QHash m_totalTimeByHandler; QHash m_executionsByHandler; bool m_connectionClosing = false; private: void sendResponse(qint64 tag, const Protocol::CommandPtr &response); /** For debugging */ void startTime(); void stopTime(const QString &identifier); void reportTime() const; bool m_reportTime = false; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler.cpp b/src/server/handler.cpp index 8022b23ff..a5a3c1686 100644 --- a/src/server/handler.cpp +++ b/src/server/handler.cpp @@ -1,280 +1,265 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "handler.h" #include #include #include "connection.h" #include "handler/akappend.h" #include "handler/copy.h" #include "handler/colcopy.h" #include "handler/colmove.h" #include "handler/create.h" #include "handler/delete.h" #include "handler/fetch.h" #include "handler/link.h" #include "handler/list.h" #include "handler/login.h" #include "handler/logout.h" #include "handler/modify.h" #include "handler/move.h" #include "handler/remove.h" #include "handler/resourceselect.h" #include "handler/search.h" #include "handler/searchpersistent.h" #include "handler/searchresult.h" #include "handler/status.h" #include "handler/store.h" #include "handler/transaction.h" #include "handler/tagappend.h" #include "handler/tagfetch.h" #include "handler/tagremove.h" #include "handler/tagstore.h" #include "handler/relationstore.h" #include "handler/relationremove.h" #include "handler/relationfetch.h" #include "storage/querybuilder.h" using namespace Akonadi; using namespace Akonadi::Server; -Handler::Handler() - : QObject() - , m_connection(nullptr) - , m_sentFailureResponse(false) -{ -} - -Handler::~Handler() -{ -} - Handler *Handler::findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd) { // allowed are LOGIN if (cmd == Protocol::Command::Login) { return new Login(); } return nullptr; } Handler *Handler::findHandlerForCommandAlwaysAllowed(Protocol::Command::Type cmd) { // allowed is LOGOUT if (cmd == Protocol::Command::Logout) { return new Logout(); } return nullptr; } -void Handler::setTag(quint64 tag) -{ - m_tag = tag; -} - -quint64 Handler::tag() const -{ - return m_tag; -} - -void Handler::setCommand(const Protocol::CommandPtr &cmd) -{ - m_command = cmd; -} - -Protocol::CommandPtr Handler::command() const -{ - return m_command; -} - Handler *Handler::findHandlerForCommandAuthenticated(Protocol::Command::Type cmd) { switch (cmd) { case Protocol::Command::Invalid: Q_ASSERT_X(cmd != Protocol::Command::Invalid, __FUNCTION__, "Invalid command is not allowed"); return nullptr; case Protocol::Command::Hello: Q_ASSERT_X(cmd != Protocol::Command::Hello, __FUNCTION__, "Hello command is not allowed in this context"); return nullptr; case Protocol::Command::Login: - Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, - "Login command is not allowed in this context"); return nullptr; case Protocol::Command::Logout: - Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, - "Logout command is not allowed in this context"); return nullptr; case Protocol::Command::_ResponseBit: Q_ASSERT_X(cmd != Protocol::Command::_ResponseBit, __FUNCTION__, "ResponseBit is not a valid command type"); return nullptr; case Protocol::Command::Transaction: return new TransactionHandler(); case Protocol::Command::CreateItem: return new AkAppend(); case Protocol::Command::CopyItems: return new Copy(); case Protocol::Command::DeleteItems: return new Remove(); case Protocol::Command::FetchItems: return new Fetch(); case Protocol::Command::LinkItems: return new Link(); case Protocol::Command::ModifyItems: return new Store(); case Protocol::Command::MoveItems: return new Move(); case Protocol::Command::CreateCollection: return new Create(); case Protocol::Command::CopyCollection: return new ColCopy(); case Protocol::Command::DeleteCollection: return new Delete(); case Protocol::Command::FetchCollections: return new List(); case Protocol::Command::FetchCollectionStats: return new Status(); case Protocol::Command::ModifyCollection: return new Modify(); case Protocol::Command::MoveCollection: return new ColMove(); case Protocol::Command::Search: return new Search(); case Protocol::Command::SearchResult: return new SearchResult(); case Protocol::Command::StoreSearch: return new SearchPersistent(); case Protocol::Command::CreateTag: return new TagAppend(); case Protocol::Command::DeleteTag: return new TagRemove(); case Protocol::Command::FetchTags: return new TagFetch(); case Protocol::Command::ModifyTag: return new TagStore(); case Protocol::Command::FetchRelations: return new RelationFetch(); case Protocol::Command::ModifyRelation: return new RelationStore(); case Protocol::Command::RemoveRelations: return new RelationRemove(); case Protocol::Command::SelectResource: return new ResourceSelect(); case Protocol::Command::StreamPayload: Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, "StreamPayload command is not allowed in this context"); return nullptr; case Protocol::Command::ItemChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::ItemChangeNotification, __FUNCTION__, "ItemChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::CollectionChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::CollectionChangeNotification, __FUNCTION__, "CollectionChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::TagChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::TagChangeNotification, __FUNCTION__, "TagChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::RelationChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::RelationChangeNotification, __FUNCTION__, "RelationChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::SubscriptionChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::SubscriptionChangeNotification, __FUNCTION__, "SubscriptionChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::DebugChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::DebugChangeNotification, __FUNCTION__, "DebugChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::ModifySubscription: Q_ASSERT_X(cmd != Protocol::Command::ModifySubscription, __FUNCTION__, "ModifySubscription command is not allowed on this connection"); return nullptr; case Protocol::Command::CreateSubscription: Q_ASSERT_X(cmd != Protocol::Command::CreateSubscription, __FUNCTION__, "CreateSubscription command is not allowed on this connection"); return nullptr; } return nullptr; } +void Handler::setTag(quint64 tag) +{ + m_tag = tag; +} + +quint64 Handler::tag() const +{ + return m_tag; +} + +void Handler::setCommand(const Protocol::CommandPtr &cmd) +{ + m_command = cmd; +} + +Protocol::CommandPtr Handler::command() const +{ + return m_command; +} + void Handler::setConnection(Connection *connection) { m_connection = connection; } Connection *Handler::connection() const { return m_connection; } bool Handler::failureResponse(const QByteArray &failureMessage) { return failureResponse(QString::fromUtf8(failureMessage)); } bool Handler::failureResponse(const char *failureMessage) { return failureResponse(QString::fromUtf8(failureMessage)); } bool Handler::failureResponse(const QString &failureMessage) { // Prevent sending multiple error responses from a single handler (or from // a handler and then from Connection, since clients only expect a single // error response if (!m_sentFailureResponse) { m_sentFailureResponse = true; Protocol::ResponsePtr r = Protocol::Factory::response(m_command->type()); // FIXME: Error enums? r->setError(1, failureMessage); sendResponse(r); } return false; } void Handler::sendResponse(const Protocol::CommandPtr &response) { m_connection->sendResponse(response); } bool Handler::checkScopeConstraints(const Akonadi::Scope &scope, int permittedScopes) { return scope.scope() & permittedScopes; } diff --git a/src/server/handler.h b/src/server/handler.h index bd5772fd2..285ff6db6 100644 --- a/src/server/handler.h +++ b/src/server/handler.h @@ -1,156 +1,144 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIHANDLER_H #define AKONADIHANDLER_H #include #include "global.h" #include "exception.h" #include "connection.h" #include namespace Akonadi { namespace Server { class Response; class Connection; AKONADI_EXCEPTION_MAKE_INSTANCE(HandlerException); /** \defgroup akonadi_server_handler Command handlers All commands supported by the Akonadi server are implemented as sub-classes of Akonadi::Handler. */ /** The handler interfaces describes an entity capable of handling an AkonadiIMAP command.*/ -class Handler : public QObject +class Handler { - Q_OBJECT public: - Handler(); - - ~Handler() override; + Handler() = default; + virtual ~Handler() = default; /** * Set the tag of the command to be processed, and thus of the response * generated by this handler. * @param tag The command tag, an alphanumerical string, normally. */ void setTag(quint64 tag); /** * The tag of the command associated with this handler. */ quint64 tag() const; void setCommand(const Protocol::CommandPtr &cmd); - Protocol::CommandPtr command() const; /** * Find a handler for a command that is always allowed, like LOGOUT. * @param cmd the command string * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception is thrown from handelLine. */ static Handler *findHandlerForCommandAlwaysAllowed(Protocol::Command::Type cmd); /** * Find a handler for a command that is allowed when the client is not yet authenticated, like LOGIN. * @param cmd the command string * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception is thrown from handelLine. */ static Handler *findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd); /** * Find a handler for a command that is allowed when the client is authenticated, like LIST, FETCH, etc. * @param cmd the command string * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception is thrown from handelLine. */ static Handler *findHandlerForCommandAuthenticated(Protocol::Command::Type cmd); void setConnection(Connection *connection); Connection *connection() const; bool failureResponse(const char *response); bool failureResponse(const QByteArray &response); bool failureResponse(const QString &response); template typename std::enable_if::value, bool>::type successResponse(const QSharedPointer &response = QSharedPointer()); template typename std::enable_if::value, void>::type sendResponse(const QSharedPointer &response = QSharedPointer()); /** * Parse and handle the IMAP message using the streaming parser. The implementation MUST leave the trailing newline character(s) in the stream! * @return true if parsed successfully, false in case of parse failure */ virtual bool parseStream() = 0; bool checkScopeConstraints(const Scope &scope, int permittedScopes); -public Q_SLOTS: +protected: void sendResponse(const Protocol::CommandPtr &response); -Q_SIGNALS: - /** - * Emitted whenever a handler wants the connection to change into a - * different state. The connection usually honors such requests, but - * the decision is up to it. - * @param state The new state the handler suggests to enter. - */ - void connectionStateChange(ConnectionState state); - private: - quint64 m_tag; + quint64 m_tag = 0; Connection *m_connection = nullptr; - bool m_sentFailureResponse; + bool m_sentFailureResponse = false; protected: Protocol::CommandPtr m_command; }; template typename std::enable_if::value, bool>::type Handler::successResponse(const QSharedPointer &response) { sendResponse(response ? response : QSharedPointer::create()); return true; } template typename std::enable_if::value, void>::type Handler::sendResponse(const QSharedPointer &response) { sendResponse(response.template staticCast()); } } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/akappend.h b/src/server/handler/akappend.h index 4a2b37c62..98c9bc206 100644 --- a/src/server/handler/akappend.h +++ b/src/server/handler/akappend.h @@ -1,70 +1,69 @@ /*************************************************************************** * Copyright (C) 2007 by Robert Zwerus * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_AKAPPEND_H #define AKONADI_AKAPPEND_H #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the X-AKAPPEND command. This command is used to append an item with multiple parts. */ class AkAppend : public Handler { - Q_OBJECT public: ~AkAppend() override = default; bool parseStream() override; private: bool buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, Collection &parentCollection); bool insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, const Collection &parentCollection); bool mergeItem(const Protocol::CreateItemCommand &cmd, PimItem &newItem, PimItem ¤tItem, const Collection &parentCollection); bool sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes); bool notify(const PimItem &item, bool seen, const Collection &collection); bool notify(const PimItem &item, const Collection &collection, const QSet &changedParts); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/colcopy.h b/src/server/handler/colcopy.h index 38ac352ea..115720d28 100644 --- a/src/server/handler/colcopy.h +++ b/src/server/handler/colcopy.h @@ -1,64 +1,63 @@ /* Copyright (c) 2008 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. */ #ifndef AKONADI_COLCOPY_H #define AKONADI_COLCOPY_H #include "handler/copy.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COLCOPY command. This command is used to copy a single collection into another collection, including all sub-collections and their content. The copied items differ in the following points from the originals: - new unique id - empty remote id - possible located in a different collection (and thus resource) The copied collections differ in the following points from the originals: - new unique id - empty remote id - owning resource is the same as the one of the target collection */ class ColCopy : public Copy { - Q_OBJECT public: ~ColCopy() override = default; bool parseStream() override; private: bool copyCollection(const Collection &source, const Collection &target); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/colmove.h b/src/server/handler/colmove.h index b27d35443..589129231 100644 --- a/src/server/handler/colmove.h +++ b/src/server/handler/colmove.h @@ -1,50 +1,49 @@ /* Copyright (c) 2009 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. */ #ifndef AKONADI_COLMOVE_H #define AKONADI_COLMOVE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COLMOVE command. This command is used to move a set of collections into another collection, including all sub-collections and their content. */ class ColMove : public Handler { - Q_OBJECT public: ~ColMove() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/copy.cpp b/src/server/handler/copy.cpp index 6bb750f0a..28984b538 100644 --- a/src/server/handler/copy.cpp +++ b/src/server/handler/copy.cpp @@ -1,124 +1,126 @@ /* Copyright (c) 2008 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 "copy.h" #include "connection.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "storage/datastore.h" #include "storage/itemqueryhelper.h" #include "storage/itemretriever.h" #include "storage/selectquerybuilder.h" #include "storage/transaction.h" #include "storage/parthelper.h" #include using namespace Akonadi; using namespace Akonadi::Server; bool Copy::copyItem(const PimItem &item, const Collection &target) { PimItem newItem = item; newItem.setId(-1); newItem.setRev(0); newItem.setDatetime(QDateTime::currentDateTimeUtc()); newItem.setAtime(QDateTime::currentDateTimeUtc()); newItem.setRemoteId(QString()); newItem.setRemoteRevision(QString()); newItem.setCollectionId(target.id()); Part::List parts; parts.reserve(item.parts().count()); Q_FOREACH (const Part &part, item.parts()) { Part newPart(part); newPart.setData(PartHelper::translateData(newPart.data(), part.storage())); newPart.setPimItemId(-1); newPart.setStorage(Part::Internal); parts << newPart; } DataStore *store = connection()->storageBackend(); if (!store->appendPimItem(parts, item.flags(), item.mimeType(), target, QDateTime::currentDateTimeUtc(), QString(), QString(), item.gid(), newItem)) { return false; } return true; } -void Copy::itemsRetrieved(const QList &ids) +void Copy::processItems(const QList &ids) { SelectQueryBuilder qb; ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb); if (!qb.exec()) { failureResponse(QStringLiteral("Unable to retrieve items")); return; } const PimItem::List items = qb.result(); qb.query().finish(); DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("COPY")); for (const PimItem &item : items) { if (!copyItem(item, mTargetCollection)) { failureResponse(QStringLiteral("Unable to copy item")); return; } } if (!transaction.commit()) { failureResponse(QStringLiteral("Cannot commit transaction.")); return; } } bool Copy::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.items(), Scope::Uid)) { return failureResponse(QStringLiteral("Only UID copy is allowed")); } if (cmd.items().isEmpty()) { return failureResponse(QStringLiteral("No items specified")); } mTargetCollection = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!mTargetCollection.isValid()) { return failureResponse(QStringLiteral("No valid target specified")); } if (mTargetCollection.isVirtual()) { return failureResponse(QStringLiteral("Copying items into virtual collections is not allowed")); } CacheCleanerInhibitor inhibitor; ItemRetriever retriever(connection()); retriever.setItemSet(cmd.items().uidSet()); retriever.setRetrieveFullPayload(true); - connect(&retriever, &ItemRetriever::itemsRetrieved, - this, &Copy::itemsRetrieved); + QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, + [this](const QList &ids) { + processItems(ids); + }); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } return successResponse(); } diff --git a/src/server/handler/copy.h b/src/server/handler/copy.h index b91ccda1f..08e2e230f 100644 --- a/src/server/handler/copy.h +++ b/src/server/handler/copy.h @@ -1,79 +1,69 @@ /* Copyright (c) 2008 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. */ #ifndef AKONADI_COPY_H #define AKONADI_COPY_H #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COPY command. This command is used to copy a set of items into the specific collection. It is syntactically identical to the IMAP COPY command. The copied items differ in the following points from the originals: - new unique id - empty remote id - possible located in a different collection (and thus resource) -

Syntax

- - Request: - @verbatim - request = tag " COPY " sequence-set " " collection-id - @endverbatim - There is only the usual status response indicating success or failure of the COPY command */ class Copy : public Handler { - Q_OBJECT public: ~Copy() override = default; bool parseStream() override; protected: /** Copy the given item and all its parts into the @p target. The changes mentioned above are applied. */ bool copyItem(const PimItem &item, const Collection &target); - -private Q_SLOTS: - void itemsRetrieved(const QList &ids); + void processItems(const QList &ids); private: Collection mTargetCollection; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/create.h b/src/server/handler/create.h index 52fc03cd9..fd70a122c 100644 --- a/src/server/handler/create.h +++ b/src/server/handler/create.h @@ -1,50 +1,43 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADICREATE_H #define AKONADICREATE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler - - Handler for the CREATE command. CREATE is backward-compatible with RFC 3051, - except recursive collection creation. - - Response: - A untagged response identical to AkList is sent for every created collection. */ class Create : public Handler { - Q_OBJECT public: ~Create() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/delete.h b/src/server/handler/delete.h index df43de00c..5464c6173 100644 --- a/src/server/handler/delete.h +++ b/src/server/handler/delete.h @@ -1,56 +1,55 @@ /* Copyright (c) 2006 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. */ #ifndef AKONADI_DELETE_H #define AKONADI_DELETE_H #include "handler.h" namespace Akonadi { namespace Server { class Collection; /** @ingroup akonadi_server_handler Handler for the collection deletion command. This commands deletes the selected collections including all their content and that of any child collection. */ class Delete : public Handler { - Q_OBJECT public: ~Delete() override = default; bool parseStream() override; private: bool deleteRecursive(Collection &col); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/fetch.h b/src/server/handler/fetch.h index eb94660b8..dd591e46b 100644 --- a/src/server/handler/fetch.h +++ b/src/server/handler/fetch.h @@ -1,47 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIFETCH_H #define AKONADIFETCH_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the fetch command. */ class Fetch : public Handler { - Q_OBJECT public: ~Fetch() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/link.h b/src/server/handler/link.h index d8ceb1559..6b031de1a 100644 --- a/src/server/handler/link.h +++ b/src/server/handler/link.h @@ -1,50 +1,49 @@ /* Copyright (c) 2008 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. */ #ifndef AKONADI_LINK_H #define AKONADI_LINK_H #include "handler.h" namespace Akonadi { namespace Server { /** * @ingroup akonadi_server_handler * * Handler for the LINK and UNLINK commands. * * These commands are used to add and remove references of a set of items to a * virtual collection. */ class Link : public Handler { - Q_OBJECT public: ~Link() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/list.cpp b/src/server/handler/list.cpp index eada0f7d4..68408a7f1 100644 --- a/src/server/handler/list.cpp +++ b/src/server/handler/list.cpp @@ -1,620 +1,609 @@ /* Copyright (c) 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 "list.h" #include "akonadiserver_debug.h" #include "connection.h" #include "handlerhelper.h" #include "collectionreferencemanager.h" #include "storage/datastore.h" #include "storage/selectquerybuilder.h" #include "storage/collectionqueryhelper.h" #include using namespace Akonadi; using namespace Akonadi::Server; template static bool intersect(const QVector &l1, const QVector &l2) { for (const T &e2 : l2) { if (l1.contains(e2.id())) { return true; } } return false; } -List::List() - : Handler() - , mAncestorDepth(0) - , mIncludeStatistics(false) - , mEnabledCollections(false) - , mCollectionsToDisplay(false) - , mCollectionsToSynchronize(false) - , mCollectionsToIndex(false) -{ -} - QStack List::ancestorsForCollection(const Collection &col) { if (mAncestorDepth <= 0) { return QStack(); } QStack ancestors; Collection parent = col; for (int i = 0; i < mAncestorDepth; ++i) { if (parent.parentId() == 0) { break; } if (mAncestors.contains(parent.parentId())) { parent = mAncestors.value(parent.parentId()); } else { parent = mCollections.value(parent.parentId()); } if (!parent.isValid()) { qCWarning(AKONADISERVER_LOG) << col.id(); throw HandlerException("Found invalid parent in ancestors"); } ancestors.prepend(parent); } return ancestors; } CollectionAttribute::List List::getAttributes(const Collection &col, const QSet &filter) { CollectionAttribute::List attributes; auto it = mCollectionAttributes.find(col.id()); while (it != mCollectionAttributes.end() && it.key() == col.id()) { if (filter.isEmpty() || filter.contains(it.value().type())) { attributes << it.value(); } ++it; } // We need the reference and enabled status, to not need to request the server mutliple times. // Mostly interessting for ancestors for i.e. updates provided by the monitor. const bool isReferenced = connection()->collectionReferenceManager()->isReferenced(col.id(), connection()->sessionId()); if (isReferenced) { CollectionAttribute attr; attr.setType(AKONADI_PARAM_REFERENCED); attr.setValue("TRUE"); attributes << attr; } { CollectionAttribute attr; attr.setType(AKONADI_PARAM_ENABLED); attr.setValue(col.enabled() ? "TRUE" : "FALSE"); attributes << attr; } return attributes; } void List::listCollection(const Collection &root, const QStack &ancestors, const QStringList &mimeTypes, const CollectionAttribute::List &attributes) { const bool isReferencedFromSession = connection()->collectionReferenceManager()->isReferenced(root.id(), connection()->sessionId()); //We always expose referenced collections to the resource as referenced (although it's a different session) //Otherwise syncing wouldn't work. const bool resourceIsSynchronizing = root.referenced() && mCollectionsToSynchronize && connection()->context()->resource().isValid(); QStack ancestorAttributes; //backwards compatibility, collectionToByteArray will automatically fall-back to id + remoteid if (!mAncestorAttributes.isEmpty()) { ancestorAttributes.reserve(ancestors.size()); for (const Collection &col : ancestors) { ancestorAttributes.push(getAttributes(col, mAncestorAttributes)); } } // write out collection details Collection dummy = root; DataStore *db = connection()->storageBackend(); db->activeCachePolicy(dummy); sendResponse(HandlerHelper::fetchCollectionsResponse(dummy, attributes, mIncludeStatistics, mAncestorDepth, ancestors, ancestorAttributes, isReferencedFromSession || resourceIsSynchronizing, mimeTypes)); } static Query::Condition filterCondition(const QString &column) { Query::Condition orCondition(Query::Or); orCondition.addValueCondition(column, Query::Equals, (int)Collection::True); Query::Condition andCondition(Query::And); andCondition.addValueCondition(column, Query::Equals, (int)Collection::Undefined); andCondition.addValueCondition(Collection::enabledFullColumnName(), Query::Equals, true); orCondition.addCondition(andCondition); orCondition.addValueCondition(Collection::referencedFullColumnName(), Query::Equals, true); return orCondition; } bool List::checkFilterCondition(const Collection &col) const { //Don't include the collection when only looking for enabled collections if (mEnabledCollections && !col.enabled()) { return false; } //Don't include the collection when only looking for collections to display/index/sync if (mCollectionsToDisplay && (((col.displayPref() == Collection::Undefined) && !col.enabled()) || (col.displayPref() == Collection::False))) { return false; } if (mCollectionsToIndex && (((col.indexPref() == Collection::Undefined) && !col.enabled()) || (col.indexPref() == Collection::False))) { return false; } //Single collection sync will still work since that is using a base fetch if (mCollectionsToSynchronize && (((col.syncPref() == Collection::Undefined) && !col.enabled()) || (col.syncPref() == Collection::False))) { return false; } return true; } static QSqlQuery getAttributeQuery(const QVariantList &ids, const QSet &requestedAttributes) { QueryBuilder qb(CollectionAttribute::tableName()); qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(), Query::In, ids); qb.addColumn(CollectionAttribute::collectionIdFullColumnName()); qb.addColumn(CollectionAttribute::typeFullColumnName()); qb.addColumn(CollectionAttribute::valueFullColumnName()); if (!requestedAttributes.isEmpty()) { QVariantList attributes; attributes.reserve(requestedAttributes.size()); for (const QByteArray &type : requestedAttributes) { attributes << type; } qb.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::In, attributes); } qb.addSortColumn(CollectionAttribute::collectionIdFullColumnName(), Query::Ascending); if (!qb.exec()) { throw HandlerException("Unable to retrieve attributes for listing"); } return qb.query(); } void List::retrieveAttributes(const QVariantList &collectionIds) { //We are querying for the attributes in batches because something can't handle WHERE IN queries with sets larger than 999 int start = 0; const int size = 999; while (start < collectionIds.size()) { const QVariantList ids = collectionIds.mid(start, size); QSqlQuery attributeQuery = getAttributeQuery(ids, mAncestorAttributes); while (attributeQuery.next()) { CollectionAttribute attr; attr.setType(attributeQuery.value(1).toByteArray()); attr.setValue(attributeQuery.value(2).toByteArray()); // qCDebug(AKONADISERVER_LOG) << "found attribute " << attr.type() << attr.value(); mCollectionAttributes.insert(attributeQuery.value(0).toLongLong(), attr); } start += size; } } static QSqlQuery getMimeTypeQuery(const QVariantList &ids) { QueryBuilder qb(CollectionMimeTypeRelation::tableName()); qb.addJoin(QueryBuilder::LeftJoin, MimeType::tableName(), MimeType::idFullColumnName(), CollectionMimeTypeRelation::rightFullColumnName()); qb.addValueCondition(CollectionMimeTypeRelation::leftFullColumnName(), Query::In, ids); qb.addColumn(CollectionMimeTypeRelation::leftFullColumnName()); qb.addColumn(CollectionMimeTypeRelation::rightFullColumnName()); qb.addColumn(MimeType::nameFullColumnName()); qb.addSortColumn(CollectionMimeTypeRelation::leftFullColumnName(), Query::Ascending); if (!qb.exec()) { throw HandlerException("Unable to retrieve mimetypes for listing"); } return qb.query(); } void List::retrieveCollections(const Collection &topParent, int depth) { /* * Retrieval of collections: * The aim is to reduce the amount of queries as much as possible, as this has the largest performance impact for large queries. * * First all collections that match the given criteria are queried * * We then filter the false positives: * ** all collections out that are not part of the tree we asked for are filtered * ** all collections that are referenced but not by this session or by the owning resource are filtered * * Finally we complete the tree by adding missing collections * * Mimetypes and attributes are also retrieved in single queries to avoid spawning two queries per collection (the N+1 problem). * Note that we're not querying attributes and mimetypes for the collections that are only included to complete the tree, * this results in no items being queried for those collections. */ const qint64 parentId = topParent.isValid() ? topParent.id() : 0; { SelectQueryBuilder qb; if (depth == 0) { qb.addValueCondition(Collection::idFullColumnName(), Query::Equals, parentId); } else if (depth == 1) { if (topParent.isValid()) { qb.addValueCondition(Collection::parentIdFullColumnName(), Query::Equals, parentId); } else { qb.addValueCondition(Collection::parentIdFullColumnName(), Query::Is, QVariant()); } } else { if (topParent.isValid()) { qb.addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, topParent.resourceId()); } else { // Gimme gimme gimme...everything! } } //Base listings should succeed always if (depth != 0) { if (mCollectionsToSynchronize) { qb.addCondition(filterCondition(Collection::syncPrefFullColumnName())); } else if (mCollectionsToDisplay) { qCDebug(AKONADISERVER_LOG) << "only display"; qb.addCondition(filterCondition(Collection::displayPrefFullColumnName())); } else if (mCollectionsToIndex) { qb.addCondition(filterCondition(Collection::indexPrefFullColumnName())); } else if (mEnabledCollections) { Query::Condition orCondition(Query::Or); orCondition.addValueCondition(Collection::enabledFullColumnName(), Query::Equals, true); orCondition.addValueCondition(Collection::referencedFullColumnName(), Query::Equals, true); qb.addCondition(orCondition); } if (mResource.isValid()) { qb.addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, mResource.id()); } if (!mMimeTypes.isEmpty()) { qb.addJoin(QueryBuilder::LeftJoin, CollectionMimeTypeRelation::tableName(), CollectionMimeTypeRelation::leftColumn(), Collection::idFullColumnName()); QVariantList mimeTypeFilter; mimeTypeFilter.reserve(mMimeTypes.size()); for (MimeType::Id mtId : qAsConst(mMimeTypes)) { mimeTypeFilter << mtId; } qb.addValueCondition(CollectionMimeTypeRelation::rightColumn(), Query::In, mimeTypeFilter); qb.addGroupColumn(Collection::idFullColumnName()); } } if (!qb.exec()) { throw HandlerException("Unable to retrieve collection for listing"); } Q_FOREACH (const Collection &col, qb.result()) { mCollections.insert(col.id(), col); } } //Post filtering that we couldn't do as part of the sql query if (depth > 0) { auto it = mCollections.begin(); while (it != mCollections.end()) { if (topParent.isValid()) { //Check that each collection is linked to the root collection bool foundParent = false; //We iterate over parents to link it to topParent if possible Collection::Id id = it->parentId(); while (id > 0) { if (id == parentId) { foundParent = true; break; } Collection col = mCollections.value(id); if (!col.isValid()) { col = Collection::retrieveById(id); } id = col.parentId(); } if (!foundParent) { it = mCollections.erase(it); continue; } } //If we matched referenced collections we need to ensure the collection was referenced from this session const bool isReferencedFromSession = connection()->collectionReferenceManager()->isReferenced(it->id(), connection()->sessionId()); //The collection is referenced, but not from this session. We need to reevaluate the filter condition if (it->referenced() && !isReferencedFromSession) { //Don't include the collection when only looking for enabled collections. //However, a referenced collection should be still synchronized by the resource, so we exclude this case. if (!checkFilterCondition(*it) && !(mCollectionsToSynchronize && connection()->context()->resource().isValid())) { it = mCollections.erase(it); continue; } } ++it; } } QVariantList mimeTypeIds; QVariantList attributeIds; QVariantList ancestorIds; const int collectionSize{mCollections.size()}; mimeTypeIds.reserve(collectionSize); attributeIds.reserve(collectionSize); //We'd only require the non-leaf collections, but we don't know which those are, so we take all. ancestorIds.reserve(collectionSize); for (auto it = mCollections.cbegin(), end = mCollections.cend(); it != end; ++it) { mimeTypeIds << it.key(); attributeIds << it.key(); ancestorIds << it.key(); } if (mAncestorDepth > 0 && topParent.isValid()) { //unless depth is 0 the base collection is not part of the listing mAncestors.insert(topParent.id(), topParent); ancestorIds << topParent.id(); //We need to retrieve additional ancestors to what we already have in the tree Collection parent = topParent; for (int i = 0; i < mAncestorDepth; ++i) { if (parent.parentId() == 0) { break; } parent = parent.parent(); mAncestors.insert(parent.id(), parent); //We also require the attributes ancestorIds << parent.id(); } } QSet missingCollections; if (depth > 0) { for (const Collection &col : qAsConst(mCollections)) { if (col.parentId() != parentId && !mCollections.contains(col.parentId())) { missingCollections.insert(col.parentId()); } } } /* QSet knownIds; for (const Collection &col : mCollections) { knownIds.insert(col.id()); } qCDebug(AKONADISERVER_LOG) << "HAS:" << knownIds; qCDebug(AKONADISERVER_LOG) << "MISSING:" << missingCollections; */ //Fetch missing collections that are part of the tree while (!missingCollections.isEmpty()) { SelectQueryBuilder qb; QVariantList ids; ids.reserve(missingCollections.size()); for (qint64 id : qAsConst(missingCollections)) { ids << id; } qb.addValueCondition(Collection::idFullColumnName(), Query::In, ids); if (!qb.exec()) { throw HandlerException("Unable to retrieve collections for listing"); } missingCollections.clear(); Q_FOREACH (const Collection &missingCol, qb.result()) { mCollections.insert(missingCol.id(), missingCol); ancestorIds << missingCol.id(); attributeIds << missingCol.id(); mimeTypeIds << missingCol.id(); //We have to do another round if the parents parent is missing if (missingCol.parentId() != parentId && !mCollections.contains(missingCol.parentId())) { missingCollections.insert(missingCol.parentId()); } } } //Since we don't know when we'll need the ancestor attributes, we have to fetch them all together. //The alternative would be to query for each collection which would reintroduce the N+1 query performance problem. if (!mAncestorAttributes.isEmpty()) { retrieveAttributes(ancestorIds); } //We are querying in batches because something can't handle WHERE IN queries with sets larger than 999 const int querySizeLimit = 999; int mimetypeQueryStart = 0; int attributeQueryStart = 0; QSqlQuery mimeTypeQuery(DataStore::self()->database()); QSqlQuery attributeQuery(DataStore::self()->database()); auto it = mCollections.begin(); while (it != mCollections.end()) { const Collection col = it.value(); // qCDebug(AKONADISERVER_LOG) << "col " << col.id(); QStringList mimeTypes; { //Get new query if necessary if (!mimeTypeQuery.isValid() && mimetypeQueryStart < mimeTypeIds.size()) { const QVariantList ids = mimeTypeIds.mid(mimetypeQueryStart, querySizeLimit); mimetypeQueryStart += querySizeLimit; mimeTypeQuery = getMimeTypeQuery(ids); mimeTypeQuery.next(); //place at first record } // qCDebug(AKONADISERVER_LOG) << mimeTypeQuery.isValid() << mimeTypeQuery.value(0).toLongLong(); while (mimeTypeQuery.isValid() && mimeTypeQuery.value(0).toLongLong() < col.id()) { qCDebug(AKONADISERVER_LOG) << "skipped: " << mimeTypeQuery.value(0).toLongLong() << mimeTypeQuery.value(2).toString(); if (!mimeTypeQuery.next()) { break; } } //Advance query while a mimetype for this collection is returned while (mimeTypeQuery.isValid() && mimeTypeQuery.value(0).toLongLong() == col.id()) { mimeTypes << mimeTypeQuery.value(2).toString(); if (!mimeTypeQuery.next()) { break; } } } CollectionAttribute::List attributes; { //Get new query if necessary if (!attributeQuery.isValid() && attributeQueryStart < attributeIds.size()) { const QVariantList ids = attributeIds.mid(attributeQueryStart, querySizeLimit); attributeQueryStart += querySizeLimit; attributeQuery = getAttributeQuery(ids, QSet()); attributeQuery.next(); //place at first record } // qCDebug(AKONADISERVER_LOG) << attributeQuery.isValid() << attributeQuery.value(0).toLongLong(); while (attributeQuery.isValid() && attributeQuery.value(0).toLongLong() < col.id()) { qCDebug(AKONADISERVER_LOG) << "skipped: " << attributeQuery.value(0).toLongLong() << attributeQuery.value(1).toByteArray(); if (!attributeQuery.next()) { break; } } //Advance query while a mimetype for this collection is returned while (attributeQuery.isValid() && attributeQuery.value(0).toLongLong() == col.id()) { CollectionAttribute attr; attr.setType(attributeQuery.value(1).toByteArray()); attr.setValue(attributeQuery.value(2).toByteArray()); attributes << attr; if (!attributeQuery.next()) { break; } } } listCollection(col, ancestorsForCollection(col), mimeTypes, attributes); it++; } } bool List::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!cmd.resource().isEmpty()) { mResource = Resource::retrieveByName(cmd.resource()); if (!mResource.isValid()) { return failureResponse("Unknown resource"); } } const QStringList lstMimeTypes = cmd.mimeTypes(); for (const QString &mtName : lstMimeTypes) { const MimeType mt = MimeType::retrieveByNameOrCreate(mtName); if (!mt.isValid()) { return failureResponse("Failed to create mimetype record"); } mMimeTypes.append(mt.id()); } mEnabledCollections = cmd.enabled(); mCollectionsToSynchronize = cmd.syncPref(); mCollectionsToDisplay = cmd.displayPref(); mCollectionsToIndex = cmd.indexPref(); mIncludeStatistics = cmd.fetchStats(); int depth = 0; switch (cmd.depth()) { case Protocol::FetchCollectionsCommand::BaseCollection: depth = 0; break; case Protocol::FetchCollectionsCommand::ParentCollection: depth = 1; break; case Protocol::FetchCollectionsCommand::AllCollections: depth = INT_MAX; break; } switch (cmd.ancestorsDepth()) { case Protocol::Ancestor::NoAncestor: mAncestorDepth = 0; break; case Protocol::Ancestor::ParentAncestor: mAncestorDepth = 1; break; case Protocol::Ancestor::AllAncestors: mAncestorDepth = INT_MAX; break; } mAncestorAttributes = cmd.ancestorsAttributes(); Scope scope = cmd.collections(); if (!scope.isEmpty()) { // not root Collection col; if (scope.scope() == Scope::Uid) { col = Collection::retrieveById(scope.uid()); } else if (scope.scope() == Scope::Rid) { SelectQueryBuilder qb; qb.addValueCondition(Collection::remoteIdFullColumnName(), Query::Equals, scope.rid()); qb.addJoin(QueryBuilder::InnerJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); if (mCollectionsToSynchronize) { qb.addCondition(filterCondition(Collection::syncPrefFullColumnName())); } else if (mCollectionsToDisplay) { qb.addCondition(filterCondition(Collection::displayPrefFullColumnName())); } else if (mCollectionsToIndex) { qb.addCondition(filterCondition(Collection::indexPrefFullColumnName())); } if (mResource.isValid()) { qb.addValueCondition(Resource::idFullColumnName(), Query::Equals, mResource.id()); } else if (connection()->context()->resource().isValid()) { qb.addValueCondition(Resource::idFullColumnName(), Query::Equals, connection()->context()->resource().id()); } else { return failureResponse("Cannot retrieve collection based on remote identifier without a resource context"); } if (!qb.exec()) { return failureResponse("Unable to retrieve collection for listing"); } Collection::List results = qb.result(); if (results.count() != 1) { return failureResponse(QString::number(results.count()) + QStringLiteral(" collections found")); } col = results.first(); } else if (scope.scope() == Scope::HierarchicalRid) { if (!connection()->context()->resource().isValid()) { return failureResponse("Cannot retrieve collection based on hierarchical remote identifier without a resource context"); } col = CollectionQueryHelper::resolveHierarchicalRID(scope.hridChain(), connection()->context()->resource().id()); } else { return failureResponse("Unexpected error"); } if (!col.isValid()) { return failureResponse("Collection does not exist"); } retrieveCollections(col, depth); } else { //Root folder listing if (depth != 0) { retrieveCollections(Collection(), depth); } } return successResponse(); } diff --git a/src/server/handler/list.h b/src/server/handler/list.h index 03885adad..1e9b1177c 100644 --- a/src/server/handler/list.h +++ b/src/server/handler/list.h @@ -1,128 +1,103 @@ /* Copyright (c) 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. */ #ifndef AKONADI_LIST_H #define AKONADI_LIST_H #include "entities.h" #include "handler.h" template class QStack; namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the LIST command. This command is used to get a (limited) listing of the available collections. It is different from the LIST command and is more similar to FETCH. -

Syntax

- - Request: - @verbatim - request = tag " " command " " collection-id " " depth " (" filter-list ")" " (" option-list ")" - command = "LIST" | "LSUB" | "RID LIST" | "RID LSUB" - depth = number | "INF" - filter-list = *(filter-key " " filter-value) - filter-key = "RESOURCE" | "MIMETYPE" | "ENABLED" | "SYNC" | "DISPLAY" | "INDEX" - option-list = *(option-key " " option-value) - option-key = "STATISTICS" - @endverbatim - - @c LIST will include all known collections, @c LSUB only those that are - subscribed or contains subscribed collections (cf. RFC 3501, LIST vs. LSUB). - The @c RID command prefix indicates that @c collection-id is a remote identifier instead of a unique identifier. In this case a resource context has to be specified previously using the @c RESSELECT command. @c depths chooses between recursive (@c INF), flat (1) and local (0, ie. just the base collection) listing, 0 indicates the root collection. The @c filter-list is used to restrict the listing to collection of a specific resource or content type. The @c option-list allows to specify the response content to some extend: - @c STATISTICS (boolean) allows to include the collection statistics (see Status) - @c ANCESTORDEPTH (numeric) allows you to specify the number of ancestor nodes that should be included additionally to the @c parent-id included anyway. Possible values are @c 0 (the default), @c 1 for the direct parent node and @c INF for all, terminating with the root collection. - Response: - @verbatim - response = "*" collection-id " " parent-id " ("attribute-list")" - attribute-list = *(attribute-identifier " " attribute-value) - attribute-identifier = "NAME" | "MIMETYPE" | "REMOTEID" | "REMOTEREVISION" | "RESOURCE" | "VIRTUAL" | "MESSAGES" | "UNSEEN" | "SIZE" | "ANCESTORS" | "custom-attr-identifier - @endverbatim - The name is encoded as an quoted UTF-8 string. There is no order defined for the single responses. The ancestors property is encoded as a list of UID/RID pairs. */ class List : public Handler { - Q_OBJECT - public: - List(); + List() = default; ~List() override = default; bool parseStream() override; private: void listCollection(const Collection &root, const QStack &ancestors, const QStringList &mimeTypes, const CollectionAttribute::List &attributes); QStack ancestorsForCollection(const Collection &col); void retrieveCollections(const Collection &topParent, int depth); bool checkFilterCondition(const Collection &col) const; bool checkChildrenForMimeTypes(const QHash &collectionsMap, const QHash &parentMap, const Collection &col); CollectionAttribute::List getAttributes(const Collection &colId, const QSet &filter = QSet()); void retrieveAttributes(const QVariantList &collectionIds); Resource mResource; QVector mMimeTypes; - int mAncestorDepth; - bool mIncludeStatistics; - bool mEnabledCollections; - bool mCollectionsToDisplay; - bool mCollectionsToSynchronize; - bool mCollectionsToIndex; + int mAncestorDepth = 0; + bool mIncludeStatistics = false; + bool mEnabledCollections = false; + bool mCollectionsToDisplay = false; + bool mCollectionsToSynchronize = false; + bool mCollectionsToIndex = false; QSet mAncestorAttributes; QMap mCollections; QHash mAncestors; QMultiHash mCollectionAttributes; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/login.cpp b/src/server/handler/login.cpp index 01d702885..ad4e7f279 100644 --- a/src/server/handler/login.cpp +++ b/src/server/handler/login.cpp @@ -1,39 +1,39 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "login.h" #include "connection.h" using namespace Akonadi; using namespace Akonadi::Server; bool Login::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.sessionId().isEmpty()) { return failureResponse(QStringLiteral("Missing session identifier")); } connection()->setSessionId(cmd.sessionId()); - Q_EMIT connectionStateChange(Server::Authenticated); + connection()->setState(Server::Authenticated); return successResponse(); } diff --git a/src/server/handler/login.h b/src/server/handler/login.h index 12f2a0814..9d7e8be32 100644 --- a/src/server/handler/login.h +++ b/src/server/handler/login.h @@ -1,46 +1,45 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADILOGIN_H #define AKONADILOGIN_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the login command. */ class Login : public Handler { - Q_OBJECT public: ~Login() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/logout.cpp b/src/server/handler/logout.cpp index e66ad2b57..07a065ab8 100644 --- a/src/server/handler/logout.cpp +++ b/src/server/handler/logout.cpp @@ -1,31 +1,31 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "logout.h" using namespace Akonadi; using namespace Akonadi::Server; bool Logout::parseStream() { sendResponse(); - Q_EMIT connectionStateChange(LoggingOut); + connection()->setState(LoggingOut); return true; } diff --git a/src/server/handler/logout.h b/src/server/handler/logout.h index 6c0558233..02fe630ac 100644 --- a/src/server/handler/logout.h +++ b/src/server/handler/logout.h @@ -1,47 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADILOGOUT_H #define AKONADILOGOUT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the logout command. */ class Logout : public Handler { - Q_OBJECT public: ~Logout() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/modify.h b/src/server/handler/modify.h index b23a16355..3f00c4e13 100644 --- a/src/server/handler/modify.h +++ b/src/server/handler/modify.h @@ -1,50 +1,49 @@ /* Copyright (c) 2006 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. */ #ifndef AKONADI_MODIFY_H #define AKONADI_MODIFY_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the MODIFY command (not in RFC 3501). This command is used to modify collections. Its syntax is similar to the STORE command. */ class Modify : public Handler { - Q_OBJECT public: ~Modify() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/move.cpp b/src/server/handler/move.cpp index 11ce77a4c..5053935cb 100644 --- a/src/server/handler/move.cpp +++ b/src/server/handler/move.cpp @@ -1,166 +1,168 @@ /* Copyright (c) 2009 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 "move.h" #include "connection.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include "storage/itemqueryhelper.h" #include "storage/selectquerybuilder.h" #include "storage/transaction.h" #include "storage/collectionqueryhelper.h" #include "akonadiserver_debug.h" using namespace Akonadi; using namespace Akonadi::Server; void Move::itemsRetrieved(const QList &ids) { DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("MOVE")); SelectQueryBuilder qb; ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb); qb.addValueCondition(PimItem::collectionIdFullColumnName(), Query::NotEquals, mDestination.id()); if (!qb.exec()) { failureResponse("Unable to execute query"); return; } const QVector items = qb.result(); if (items.isEmpty()) { return; } const QDateTime mtime = QDateTime::currentDateTimeUtc(); // Split the list by source collection QMap toMove; QMap sources; ImapSet toMoveIds; Q_FOREACH (/*sic!*/ PimItem item, items) { //krazy:exclude=foreach if (!item.isValid()) { failureResponse("Invalid item in result set!?"); return; } const Collection source = item.collection(); if (!source.isValid()) { failureResponse("Item without collection found!?"); return; } if (!sources.contains(source.id())) { sources.insert(source.id(), source); } Q_ASSERT(item.collectionId() != mDestination.id()); item.setCollectionId(mDestination.id()); item.setAtime(mtime); item.setDatetime(mtime); // if the resource moved itself, we assume it did so because the change happend in the backend if (connection()->context()->resource().id() != mDestination.resourceId()) { item.setDirty(true); } if (!item.update()) { failureResponse("Unable to update item"); return; } toMove.insertMulti(source.id(), item); toMoveIds.add(QVector{ item.id() }); } if (!transaction.commit()) { failureResponse("Unable to commit transaction."); return; } // Emit notification for each source collection separately Collection source; PimItem::List itemsToMove; for (auto it = toMove.cbegin(), end = toMove.cend(); it != end; ++it) { if (source.id() != it.key()) { if (!itemsToMove.isEmpty()) { store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination); } source = sources.value(it.key()); itemsToMove.clear(); } itemsToMove.push_back(*it); } if (!itemsToMove.isEmpty()) { store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination); } // Batch-reset RID // The item should have an empty RID in the destination collection to avoid // RID conflicts with existing items (see T3904 in Phab). // We do it after emitting notification so that the FetchHelper can still // retrieve the RID QueryBuilder qb2(PimItem::tableName(), QueryBuilder::Update); qb2.setColumnValue(PimItem::remoteIdColumn(), QString()); ItemQueryHelper::itemSetToQuery(toMoveIds, connection()->context(), qb2); if (!qb2.exec()) { failureResponse("Unable to update RID"); return; } } bool Move::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); mDestination = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (mDestination.isVirtual()) { return failureResponse("Moving items into virtual collection is not allowed"); } if (!mDestination.isValid()) { return failureResponse("Invalid destination collection"); } connection()->context()->setScopeContext(cmd.itemsContext()); if (cmd.items().scope() == Scope::Rid) { if (!connection()->context()->collection().isValid()) { return failureResponse("RID move requires valid source collection"); } } CacheCleanerInhibitor inhibitor; // make sure all the items we want to move are in the cache ItemRetriever retriever(connection()); retriever.setScope(cmd.items()); retriever.setRetrieveFullPayload(true); - connect(&retriever, &ItemRetriever::itemsRetrieved, - this, &Move::itemsRetrieved); + QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, + [this](const QList &ids) { + itemsRetrieved(ids); + }); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } return successResponse(); } diff --git a/src/server/handler/move.h b/src/server/handler/move.h index 97bad3558..4c52989b5 100644 --- a/src/server/handler/move.h +++ b/src/server/handler/move.h @@ -1,62 +1,59 @@ /* Copyright (c) 2009 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. */ #ifndef AKONADI_MOVE_H #define AKONADI_MOVE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item move command.

Semantics

Moves the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a global uid set (UID) - based on a list of remote identifiers within the currently selected collection (RID) Destination is a collection id. */ class Move : public Handler { - Q_OBJECT - public: ~Move() override = default; bool parseStream() override; -private Q_SLOTS: +private: void itemsRetrieved(const QList &ids); -private: Collection mDestination; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/relationfetch.h b/src/server/handler/relationfetch.h index 94d8fd7d2..7936b0c7c 100644 --- a/src/server/handler/relationfetch.h +++ b/src/server/handler/relationfetch.h @@ -1,47 +1,46 @@ /*************************************************************************** * Copyright (C) 2014 by Christian Mollekopf * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIFETCHRELATION_H #define AKONADIFETCHRELATION_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the RELATIONFETCH command. */ class RelationFetch : public Handler { - Q_OBJECT public: ~RelationFetch() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/relationremove.h b/src/server/handler/relationremove.h index 5d898f09a..7baaa08c4 100644 --- a/src/server/handler/relationremove.h +++ b/src/server/handler/relationremove.h @@ -1,42 +1,41 @@ /* Copyright (c) 2014 Christian Mollekopf 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. */ #ifndef AKONADI_RELATIONREMOVE_H #define AKONADI_RELATIONREMOVE_H #include "handler.h" namespace Akonadi { namespace Server { class RelationRemove : public Handler { - Q_OBJECT public: ~RelationRemove() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_RELATIONREMOVE_H diff --git a/src/server/handler/relationstore.h b/src/server/handler/relationstore.h index e453a3395..630012d24 100644 --- a/src/server/handler/relationstore.h +++ b/src/server/handler/relationstore.h @@ -1,48 +1,46 @@ /* Copyright (c) 2014 Christian Mollekop 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. */ #ifndef AKONADI_RELATIONSTORE_H #define AKONADI_RELATIONSTORE_H #include "handler.h" namespace Akonadi { namespace Server { class Relation; class RelationStore : public Handler { - Q_OBJECT - public: ~RelationStore() override = default; bool parseStream() override; private: Relation fetchRelation(qint64 leftId, qint64 rightId, qint64 typeId); }; } // namespace Server } // namespace Akonadi #endif // AKONADI_RELATIONSTORE_H diff --git a/src/server/handler/remove.h b/src/server/handler/remove.h index 92b25df79..1c6c46016 100644 --- a/src/server/handler/remove.h +++ b/src/server/handler/remove.h @@ -1,61 +1,52 @@ /* Copyright (c) 2009 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. */ #ifndef AKONADI_REMOVE_H #define AKONADI_REMOVE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item deletion command. -

Syntax

- One of the following three: - @verbatim - REMOVE - UID REMOVE - RID REMOVE - @endverbatim -

Semantics

Removes the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a global uid set (UID) - based on a remote identifier within the currently selected collection (RID) */ class Remove : public Handler { - Q_OBJECT public: ~Remove() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/resourceselect.h b/src/server/handler/resourceselect.h index 006349f6f..6c75de881 100644 --- a/src/server/handler/resourceselect.h +++ b/src/server/handler/resourceselect.h @@ -1,52 +1,51 @@ /* Copyright (c) 2009 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. */ #ifndef AKONADI_RESOURCESELECT_H #define AKONADI_RESOURCESELECT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the resource selection command.

Semantics

Limits the scope of remote id based operations. Remote ids of collections are only guaranteed to be unique per resource, so this command should be issued before running any RID based collection commands. */ class ResourceSelect : public Handler { - Q_OBJECT public: ~ResourceSelect() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/search.cpp b/src/server/handler/search.cpp index e4c0f1993..0a2681e49 100644 --- a/src/server/handler/search.cpp +++ b/src/server/handler/search.cpp @@ -1,99 +1,101 @@ /*************************************************************************** * Copyright (C) 2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "search.h" #include "connection.h" #include "fetchhelper.h" #include "handlerhelper.h" #include "searchhelper.h" #include "search/searchrequest.h" #include "search/searchmanager.h" using namespace Akonadi; using namespace Akonadi::Server; bool Search::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.query().isEmpty()) { return failureResponse("No query specified"); } QVector collectionIds; bool recursive = cmd.recursive(); if (cmd.collections().isEmpty() || cmd.collections() == QVector { 0ll }) { collectionIds << 0; recursive = true; } QVector collections = collectionIds; if (recursive) { collections += SearchHelper::matchSubcollectionsByMimeType(collectionIds, cmd.mimeTypes()); } qCDebug(AKONADISERVER_LOG) << "SEARCH:"; qCDebug(AKONADISERVER_LOG) << "\tQuery:" << cmd.query(); qCDebug(AKONADISERVER_LOG) << "\tMimeTypes:" << cmd.mimeTypes(); qCDebug(AKONADISERVER_LOG) << "\tCollections:" << collections; qCDebug(AKONADISERVER_LOG) << "\tRemote:" << cmd.remote(); qCDebug(AKONADISERVER_LOG) << "\tRecursive" << recursive; if (collections.isEmpty()) { return successResponse(); } mFetchScope = cmd.fetchScope(); SearchRequest request(connection()->sessionId()); request.setCollections(collections); request.setMimeTypes(cmd.mimeTypes()); request.setQuery(cmd.query()); request.setRemoteSearch(cmd.remote()); - connect(&request, &SearchRequest::resultsAvailable, - this, &Search::slotResultsAvailable); + QObject::connect(&request, &SearchRequest::resultsAvailable, + [this](const QSet &results) { + processResults(results); + }); request.exec(); //qCDebug(AKONADISERVER_LOG) << "\tResult:" << uids; qCDebug(AKONADISERVER_LOG) << "\tResult:" << mAllResults.count() << "matches"; return successResponse(); } -void Search::slotResultsAvailable(const QSet &results) +void Search::processResults(const QSet &results) { QSet newResults = results; newResults.subtract(mAllResults); mAllResults.unite(newResults); if (newResults.isEmpty()) { return; } ImapSet imapSet; imapSet.add(newResults); Scope scope; scope.setUidSet(imapSet); FetchHelper fetchHelper(connection(), scope, mFetchScope); fetchHelper.fetchItems(); } diff --git a/src/server/handler/search.h b/src/server/handler/search.h index 8cab46bdc..4bf8de2dd 100644 --- a/src/server/handler/search.h +++ b/src/server/handler/search.h @@ -1,57 +1,54 @@ /*************************************************************************** * Copyright (C) 2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISEARCH_H #define AKONADISEARCH_H #include "handler.h" #include namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search commands. */ class Search : public Handler { - Q_OBJECT - public: ~Search() override = default; bool parseStream() override; -private Q_SLOTS: - void slotResultsAvailable(const QSet &results); - private: + void processResults(const QSet &results); + Protocol::ItemFetchScope mFetchScope; QSet mAllResults; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/searchpersistent.h b/src/server/handler/searchpersistent.h index abe1ae26b..104d34a34 100644 --- a/src/server/handler/searchpersistent.h +++ b/src/server/handler/searchpersistent.h @@ -1,47 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISEARCHPERSISTENT_H #define AKONADISEARCHPERSISTENT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search_store search_delete commands. */ class SearchPersistent : public Handler { - Q_OBJECT public: ~SearchPersistent() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/searchresult.h b/src/server/handler/searchresult.h index 5129c5874..9a3023037 100644 --- a/src/server/handler/searchresult.h +++ b/src/server/handler/searchresult.h @@ -1,50 +1,49 @@ /* * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef AKONADI_SEARCHRESULT_H #define AKONADI_SEARCHRESULT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search_result command */ class SearchResult : public Handler { - Q_OBJECT public: ~SearchResult() override = default; bool parseStream() override; private: bool fail(const QByteArray &searchId, const QString &error); }; } // namespace Server } // namespace Akonadi #endif // AKONADI_SEARCHRESULT_H diff --git a/src/server/handler/status.h b/src/server/handler/status.h index 9c6e8a6d0..cb2d60694 100644 --- a/src/server/handler/status.h +++ b/src/server/handler/status.h @@ -1,46 +1,45 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISTATUS_H #define AKONADISTATUS_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the STATUS command. */ class Status : public Handler { - Q_OBJECT public: ~Status() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/store.h b/src/server/handler/store.h index 483ccae6f..6829f2845 100644 --- a/src/server/handler/store.h +++ b/src/server/handler/store.h @@ -1,117 +1,88 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * Copyright (C) 2009 by Volker Krause * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISTORE_H #define AKONADISTORE_H #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item modification command. -

Syntax

- One of the following three: - @verbatim - STORE - UID MOVE [] - RID MOVE [] - @endverbatim - - @c revision-check is one of the following and allowed iff one item was selected for modification: - @verbatim - NOREV - REV - @endverbatim - - @c modifcations is a parenthesized list containing any of the following: - @verbatim - SIZE - [+-]FLAGS - REMOTEID - REMOTEREVISION - GID - DIRTY false - INVALIDATECACHE - - - @endverbatim -

Semantics

Modifies the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a uid set (UID) - based on a list of remote identifiers within the currently selected collection (RID) The following item properties can be modified: - the remote identifier (@c REMOTEID) - the remote revision (@c REMOTEREVISION) - the global identifier (@c GID) - resetting the dirty flag indication local changes not yet replicated to the backend (@c DIRTY) - adding/deleting/setting item flags (@c FLAGS) - setting the item size hint (@c SIZE) - changing item attributes - changing item payload parts If multiple items are selected only the following operations are valid: - adding flags - removing flags - settings flags The following operations are only allowed by resources: - resetting the dirty flag - invalidating the cache - modifying the remote identifier - modifying the remote revision Conflict detection: - only available when modifying a single item - requires the previous item revision to be provided (@c REV) */ class Store : public Handler { - Q_OBJECT - public: ~Store() override = default; bool parseStream() override; private: bool replaceFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool addFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool deleteFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool replaceTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); bool addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); bool deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/tagappend.h b/src/server/handler/tagappend.h index d95c29987..e91454b0f 100644 --- a/src/server/handler/tagappend.h +++ b/src/server/handler/tagappend.h @@ -1,43 +1,41 @@ /* Copyright (c) 2014 Daniel Vrátil 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. */ #ifndef AKONADI_TAGAPPEND_H #define AKONADI_TAGAPPEND_H #include "handler.h" namespace Akonadi { namespace Server { class TagAppend : public Handler { - Q_OBJECT - public: ~TagAppend() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGAPPEND_H diff --git a/src/server/handler/tagfetch.h b/src/server/handler/tagfetch.h index 984a1abec..8c367f82f 100644 --- a/src/server/handler/tagfetch.h +++ b/src/server/handler/tagfetch.h @@ -1,47 +1,46 @@ /*************************************************************************** * Copyright (C) 2014 by Daniel Vrátil * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIFETCHTAG_H #define AKONADIFETCHTAG_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the FETCHTAG command. */ class TagFetch : public Handler { - Q_OBJECT public: ~TagFetch() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/tagfetchhelper.cpp b/src/server/handler/tagfetchhelper.cpp index b1b03ed8a..a50798dad 100644 --- a/src/server/handler/tagfetchhelper.cpp +++ b/src/server/handler/tagfetchhelper.cpp @@ -1,159 +1,158 @@ /* Copyright (c) 2014 Daniel Vrátil 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 "tagfetchhelper.h" #include "handler.h" #include "connection.h" #include "utils.h" #include "storage/querybuilder.h" #include "storage/tagqueryhelper.h" using namespace Akonadi; using namespace Akonadi::Server; TagFetchHelper::TagFetchHelper(Connection *connection, const Scope &scope) - : QObject() - , mConnection(connection) + : mConnection(connection) , mScope(scope) { } QSqlQuery TagFetchHelper::buildAttributeQuery() const { QueryBuilder qb(TagAttribute::tableName()); qb.addColumn(TagAttribute::tagIdFullColumnName()); qb.addColumn(TagAttribute::typeFullColumnName()); qb.addColumn(TagAttribute::valueFullColumnName()); qb.addSortColumn(TagAttribute::tagIdFullColumnName(), Query::Descending); qb.addJoin(QueryBuilder::InnerJoin, Tag::tableName(), TagAttribute::tagIdFullColumnName(), Tag::idFullColumnName()); TagQueryHelper::scopeToQuery(mScope, mConnection->context(), qb); if (!qb.exec()) { throw HandlerException("Unable to list tag attributes"); } qb.query().next(); return qb.query(); } QSqlQuery TagFetchHelper::buildAttributeQuery(qint64 id) { QueryBuilder qb(TagAttribute::tableName()); qb.addColumn(TagAttribute::tagIdColumn()); qb.addColumn(TagAttribute::typeColumn()); qb.addColumn(TagAttribute::valueColumn()); qb.addSortColumn(TagAttribute::tagIdColumn(), Query::Descending); qb.addValueCondition(TagAttribute::tagIdColumn(), Query::Equals, id); if (!qb.exec()) { throw HandlerException("Unable to list tag attributes"); } qb.query().next(); return qb.query(); } QSqlQuery TagFetchHelper::buildTagQuery() { QueryBuilder qb(Tag::tableName()); qb.addColumn(Tag::idFullColumnName()); qb.addColumn(Tag::gidFullColumnName()); qb.addColumn(Tag::parentIdFullColumnName()); qb.addJoin(QueryBuilder::InnerJoin, TagType::tableName(), Tag::typeIdFullColumnName(), TagType::idFullColumnName()); qb.addColumn(TagType::nameFullColumnName()); // Expose tag's remote ID only to resources if (mConnection->context()->resource().isValid()) { qb.addColumn(TagRemoteIdResourceRelation::remoteIdFullColumnName()); Query::Condition joinCondition; joinCondition.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, mConnection->context()->resource().id()); joinCondition.addColumnCondition(TagRemoteIdResourceRelation::tagIdFullColumnName(), Query::Equals, Tag::idFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), joinCondition); } qb.addSortColumn(Tag::idFullColumnName(), Query::Descending); TagQueryHelper::scopeToQuery(mScope, mConnection->context(), qb); if (!qb.exec()) { throw HandlerException("Unable to list tags"); } qb.query().next(); return qb.query(); } QMap TagFetchHelper::fetchTagAttributes(qint64 tagId) { QMap attributes; QSqlQuery attributeQuery = buildAttributeQuery(tagId); while (attributeQuery.isValid()) { attributes.insert(Utils::variantToByteArray(attributeQuery.value(1)), Utils::variantToByteArray(attributeQuery.value(2))); attributeQuery.next(); } return attributes; } bool TagFetchHelper::fetchTags() { QSqlQuery tagQuery = buildTagQuery(); QSqlQuery attributeQuery = buildAttributeQuery(); while (tagQuery.isValid()) { const qint64 tagId = tagQuery.value(0).toLongLong(); auto response = Protocol::FetchTagsResponsePtr::create(); response->setId(tagId); response->setGid(Utils::variantToByteArray(tagQuery.value(1))); response->setParentId(tagQuery.value(2).toLongLong()); response->setType(Utils::variantToByteArray(tagQuery.value(3))); if (mConnection->context()->resource().isValid()) { response->setRemoteId(Utils::variantToByteArray(tagQuery.value(4))); } QMap tagAttributes; while (attributeQuery.isValid()) { const qint64 id = attributeQuery.value(0).toLongLong(); if (id > tagId) { attributeQuery.next(); continue; } else if (id < tagId) { break; } tagAttributes.insert(Utils::variantToByteArray(attributeQuery.value(1)), Utils::variantToByteArray(attributeQuery.value(2))); attributeQuery.next(); } response->setAttributes(tagAttributes); mConnection->sendResponse(response); tagQuery.next(); } return true; } diff --git a/src/server/handler/tagfetchhelper.h b/src/server/handler/tagfetchhelper.h index 16dc617b3..bd144b38a 100644 --- a/src/server/handler/tagfetchhelper.h +++ b/src/server/handler/tagfetchhelper.h @@ -1,61 +1,59 @@ /* Copyright (c) 2014 Daniel Vrátil 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. */ #ifndef AKONADI_TAGFETCHHELPER_H #define AKONADI_TAGFETCHHELPER_H #include #include #include namespace Akonadi { namespace Server { class Connection; -class TagFetchHelper : public QObject +class TagFetchHelper { - Q_OBJECT - public: TagFetchHelper(Connection *connection, const Scope &scope); - ~TagFetchHelper() override = default; + ~TagFetchHelper() = default; bool fetchTags(); static QMap fetchTagAttributes(qint64 tagId); private: QSqlQuery buildTagQuery(); QSqlQuery buildAttributeQuery() const; static QSqlQuery buildAttributeQuery(qint64 id); private: Connection *mConnection = nullptr; Scope mScope; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGFETCHHELPER_H diff --git a/src/server/handler/tagremove.h b/src/server/handler/tagremove.h index 434210cae..1a498be4b 100644 --- a/src/server/handler/tagremove.h +++ b/src/server/handler/tagremove.h @@ -1,42 +1,41 @@ /* Copyright (c) 2014 Daniel Vrátil 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. */ #ifndef AKONADI_TAGREMOVE_H #define AKONADI_TAGREMOVE_H #include "handler.h" namespace Akonadi { namespace Server { class TagRemove : public Handler { - Q_OBJECT public: ~TagRemove() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGREMOVE_H diff --git a/src/server/handler/tagstore.h b/src/server/handler/tagstore.h index 6734982df..9c590f185 100644 --- a/src/server/handler/tagstore.h +++ b/src/server/handler/tagstore.h @@ -1,43 +1,41 @@ /* Copyright (c) 2014 Daniel Vrátil 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. */ #ifndef AKONADI_TAGSTORE_H #define AKONADI_TAGSTORE_H #include "handler.h" namespace Akonadi { namespace Server { class TagStore : public Handler { - Q_OBJECT - public: ~TagStore() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGSTORE_H diff --git a/src/server/handler/transaction.h b/src/server/handler/transaction.h index 67ad70b67..c4a5a487d 100644 --- a/src/server/handler/transaction.h +++ b/src/server/handler/transaction.h @@ -1,48 +1,46 @@ /* Copyright (c) 2006 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. */ #ifndef AKONADI_TRANSACTION_HANDLER_H #define AKONADI_TRANSACTION_HANDLER_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for transaction commands (BEGIN, COMMIT, ROLLBACK). */ class TransactionHandler : public Handler { - Q_OBJECT - public: ~TransactionHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif