diff --git a/autotests/libs/entitydisplayattributetest.cpp b/autotests/libs/entitydisplayattributetest.cpp index f839d238d..b77bbd4ee 100644 --- a/autotests/libs/entitydisplayattributetest.cpp +++ b/autotests/libs/entitydisplayattributetest.cpp @@ -1,72 +1,72 @@ /* 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 "entitydisplayattribute.h" #include -#include +#include using namespace Akonadi; class EntityDisplayAttributeTest : public QObject { Q_OBJECT private Q_SLOTS: void testDeserialize_data() { QTest::addColumn("input"); QTest::addColumn("name"); QTest::addColumn("icon"); QTest::addColumn("activeIcon"); QTest::addColumn("output"); QTest::newRow("empty") << QByteArray("(\"\" \"\")") << QString() << QString() << QString() << QByteArray("(\"\" \"\" \"\" ())"); QTest::newRow("name+icon") << QByteArray("(\"name\" \"icon\")") << QStringLiteral("name") << QStringLiteral("icon") << QString() << QByteArray("(\"name\" \"icon\" \"\" ())"); QTest::newRow("name+icon+activeIcon") << QByteArray("(\"name\" \"icon\" \"activeIcon\")") << QStringLiteral("name") << QStringLiteral("icon") << QStringLiteral("activeIcon") << QByteArray("(\"name\" \"icon\" \"activeIcon\" ())"); } void testDeserialize() { QFETCH(QByteArray, input); QFETCH(QString, name); QFETCH(QString, icon); QFETCH(QString, activeIcon); QFETCH(QByteArray, output); EntityDisplayAttribute *attr = new EntityDisplayAttribute(); attr->deserialize(input); QCOMPARE(attr->displayName(), name); QCOMPARE(attr->iconName(), icon); QCOMPARE(attr->activeIconName(), activeIcon); QCOMPARE(attr->serialized(), output); EntityDisplayAttribute *copy = attr->clone(); QCOMPARE(copy->serialized(), output); delete attr; delete copy; } }; QTEST_MAIN(EntityDisplayAttributeTest) #include "entitydisplayattributetest.moc" diff --git a/autotests/libs/imapparsertest.cpp b/autotests/libs/imapparsertest.cpp index e6fb5414c..44ce0ee25 100644 --- a/autotests/libs/imapparsertest.cpp +++ b/autotests/libs/imapparsertest.cpp @@ -1,610 +1,610 @@ /* 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. */ #include "imapparsertest.h" #include "private/imapparser_p.h" -#include +#include #include Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) using namespace Akonadi; QTEST_MAIN(ImapParserTest) void ImapParserTest::testStripLeadingSpaces() { QByteArray input = " a "; int result; // simple leading spaces at the beginning result = ImapParser::stripLeadingSpaces(input, 0); QCOMPARE(result, 2); // simple leading spaces in the middle result = ImapParser::stripLeadingSpaces(input, 1); QCOMPARE(result, 2); // no leading spaces result = ImapParser::stripLeadingSpaces(input, 2); QCOMPARE(result, 2); // trailing spaces result = ImapParser::stripLeadingSpaces(input, 3); QCOMPARE(result, 5); // out of bounds access result = ImapParser::stripLeadingSpaces(input, input.length()); QCOMPARE(result, input.length()); } void ImapParserTest::testParseQuotedString() { QByteArray input = "\"quoted \\\"NIL\\\" string inside\""; QByteArray result; int consumed; // the whole thing consumed = ImapParser::parseQuotedString(input, result, 0); QCOMPARE(result, QByteArray("quoted \"NIL\" string inside")); QCOMPARE(consumed, 32); // unquoted string consumed = ImapParser::parseQuotedString(input, result, 1); QCOMPARE(result, QByteArray("quoted")); QCOMPARE(consumed, 7); // whitespaces in quoted string consumed = ImapParser::parseQuotedString(input, result, 14); QCOMPARE(result, QByteArray(" string inside")); QCOMPARE(consumed, 32); // whitespaces before unquoted string consumed = ImapParser::parseQuotedString(input, result, 15); QCOMPARE(result, QByteArray("string")); QCOMPARE(consumed, 24); // NIL and emptiness tests input = "NIL \"NIL\" \"\""; // unquoted NIL consumed = ImapParser::parseQuotedString(input, result, 0); QVERIFY(result.isNull()); QCOMPARE(consumed, 3); // quoted NIL consumed = ImapParser::parseQuotedString(input, result, 3); QCOMPARE(result, QByteArray("NIL")); QCOMPARE(consumed, 9); // quoted empty string consumed = ImapParser::parseQuotedString(input, result, 9); QCOMPARE(result, QByteArray("")); QCOMPARE(consumed, 12); // unquoted string at input end input = "some string"; consumed = ImapParser::parseQuotedString(input, result, 4); QCOMPARE(result, QByteArray("string")); QCOMPARE(consumed, 11); // out of bounds access consumed = ImapParser::parseQuotedString(input, result, input.length()); QVERIFY(result.isEmpty()); QCOMPARE(consumed, input.length()); // de-quoting input = "\"\\\"some \\\\ quoted stuff\\\"\""; consumed = ImapParser::parseQuotedString(input, result, 0); QCOMPARE(result, QByteArray("\"some \\ quoted stuff\"")); QCOMPARE(consumed, input.length()); // linebreak as separator input = "LOGOUT\nFOO"; consumed = ImapParser::parseQuotedString(input, result, 0); QCOMPARE(result, QByteArray("LOGOUT")); QCOMPARE(consumed, 6); } void ImapParserTest::testParseString() { QByteArray input = "\"quoted\" unquoted {7}\nliteral {0}\n empty literal"; QByteArray result; int consumed; // quoted strings consumed = ImapParser::parseString(input, result, 0); QCOMPARE(result, QByteArray("quoted")); QCOMPARE(consumed, 8); // unquoted string consumed = ImapParser::parseString(input, result, 8); QCOMPARE(result, QByteArray("unquoted")); QCOMPARE(consumed, 17); // literal string consumed = ImapParser::parseString(input, result, 17); QCOMPARE(result, QByteArray("literal")); QCOMPARE(consumed, 29); // empty literal string consumed = ImapParser::parseString(input, result, 29); QCOMPARE(result, QByteArray("")); QCOMPARE(consumed, 34); // out of bounds access consumed = ImapParser::parseString(input, result, input.length()); QCOMPARE(result, QByteArray()); QCOMPARE(consumed, input.length()); } void ImapParserTest::testParseParenthesizedList_data() { QTest::addColumn("input"); QTest::addColumn >("result"); QTest::addColumn("consumed"); QList reference; QTest::newRow("null") << QByteArray() << reference << 0; QTest::newRow("empty") << QByteArray("()") << reference << 2; QTest::newRow("empty with space") << QByteArray(" ( )") << reference << 4; QTest::newRow("no list") << QByteArray("some list-less text") << reference << 0; QTest::newRow("\n") << QByteArray() << reference << 0; reference << "entry1"; reference << "entry2()"; reference << "(sub list)"; reference << ")))"; reference << "entry3"; QTest::newRow("complex") << QByteArray("(entry1 \"entry2()\" (sub list) \")))\" {6}\nentry3) end") << reference << 47; reference.clear(); reference << "foo"; reference << "\n\nbar\n"; reference << "bla"; QTest::newRow("newline literal") << QByteArray("(foo {6}\n\n\nbar\n bla)") << reference << 20; reference.clear(); reference << "partid"; reference << "body"; QTest::newRow("CRLF literal separator") << QByteArray("(partid {4}\r\nbody)") << reference << 18; reference.clear(); reference << "partid"; reference << "\n\rbody\n\r"; QTest::newRow("CRLF literal separator 2") << QByteArray("(partid {8}\r\n\n\rbody\n\r)") << reference << 22; reference.clear(); reference << "NAME"; reference << "net)"; QTest::newRow("spurious newline") << QByteArray("(NAME \"net)\"\n)") << reference << 14; reference.clear(); reference << "(42 \"net)\")"; reference << "(0 \"\")"; QTest::newRow("list of lists") << QByteArray("((42 \"net)\") (0 \"\"))") << reference << 20; } void ImapParserTest::testParseParenthesizedList() { QFETCH(QByteArray, input); QFETCH(QList, result); QFETCH(int, consumed); QList realResult; int reallyConsumed = ImapParser::parseParenthesizedList(input, realResult, 0); QCOMPARE(realResult, result); QCOMPARE(reallyConsumed, consumed); // briefly also test the other overload QVarLengthArray realVLAResult; reallyConsumed = ImapParser::parseParenthesizedList(input, realVLAResult, 0); QCOMPARE(reallyConsumed, consumed); // newline literal (based on itemappendtest bug) input = "(foo {6}\n\n\nbar\n bla)"; consumed = ImapParser::parseParenthesizedList(input, result); } void ImapParserTest::testParseNumber() { QByteArray input = " 1a23.4"; qint64 result; int pos; bool ok; // empty string pos = ImapParser::parseNumber(QByteArray(), result, &ok); QCOMPARE(ok, false); QCOMPARE(pos, 0); // leading spaces at the beginning pos = ImapParser::parseNumber(input, result, &ok, 0); QCOMPARE(ok, true); QCOMPARE(pos, 2); QCOMPARE(result, 1ll); // multiple digits pos = ImapParser::parseNumber(input, result, &ok, 3); QCOMPARE(ok, true); QCOMPARE(pos, 5); QCOMPARE(result, 23ll); // number at input end pos = ImapParser::parseNumber(input, result, &ok, 6); QCOMPARE(ok, true); QCOMPARE(pos, 7); QCOMPARE(result, 4ll); // out of bounds access pos = ImapParser::parseNumber(input, result, &ok, input.length()); QCOMPARE(ok, false); QCOMPARE(pos, input.length()); } void ImapParserTest::testQuote_data() { QTest::addColumn("unquoted"); QTest::addColumn("quoted"); QTest::newRow("empty") << QByteArray("") << QByteArray("\"\""); QTest::newRow("simple") << QByteArray("bla") << QByteArray("\"bla\""); QTest::newRow("double-quotes") << QByteArray("\"test\"test\"") << QByteArray("\"\\\"test\\\"test\\\"\""); QTest::newRow("backslash") << QByteArray("\\") << QByteArray("\"\\\\\""); QByteArray binaryNonEncoded; binaryNonEncoded += '\000'; QByteArray binaryEncoded("\""); binaryEncoded += '\000'; binaryEncoded += '"'; QTest::newRow("binary") << binaryNonEncoded << binaryEncoded; QTest::newRow("LF") << QByteArray("\n") << QByteArray("\"\\n\""); QTest::newRow("CR") << QByteArray("\r") << QByteArray("\"\\r\""); QTest::newRow("double quote") << QByteArray("\"") << QByteArray("\"\\\"\""); QTest::newRow("mixed 1") << QByteArray("a\nb\\c") << QByteArray("\"a\\nb\\\\c\""); QTest::newRow("mixed 2") << QByteArray("\"a\rb\"") << QByteArray("\"\\\"a\\rb\\\"\""); } void ImapParserTest::testQuote() { QFETCH(QByteArray, unquoted); QFETCH(QByteArray, quoted); QCOMPARE(ImapParser::quote(unquoted), quoted); } void ImapParserTest::testMessageParser_data() { QTest::addColumn >("input"); QTest::addColumn("tag"); QTest::addColumn("data"); QTest::addColumn("complete"); QTest::addColumn >("continuations"); QList input; QList continuations; QTest::newRow("empty") << input << QByteArray() << QByteArray() << false << continuations; input << "*"; QTest::newRow("tag-only") << input << QByteArray("*") << QByteArray() << true << continuations; input.clear(); input << "20 UID FETCH (foo)"; QTest::newRow("simple") << input << QByteArray("20") << QByteArray("UID FETCH (foo)") << true << continuations; input.clear(); input << "1 (bla (" << ") blub)"; QTest::newRow("parenthesis") << input << QByteArray("1") << QByteArray("(bla () blub)") << true << continuations; input.clear(); input << "1 {3}" << "bla"; continuations << 0; QTest::newRow("literal") << input << QByteArray("1") << QByteArray("{3}bla") << true << continuations; input.clear(); input << "1 FETCH (UID 5 DATA {3}" << "bla" << " RID 5)"; QTest::newRow("parenthesisEnclosedLiteral") << input << QByteArray("1") << QByteArray("FETCH (UID 5 DATA {3}bla RID 5)") << true << continuations; input.clear(); input << "1 {3}" << "bla {4}" << "blub"; continuations.clear(); continuations << 0 << 1; QTest::newRow("2literal") << input << QByteArray("1") << QByteArray("{3}bla {4}blub") << true << continuations; input.clear(); input << "1 {4}" << "A{9}"; continuations.clear(); continuations << 0; QTest::newRow("literal in literal") << input << QByteArray("1") << QByteArray("{4}A{9}") << true << continuations; input.clear(); input << "* FETCH (UID 1 DATA {3}" << "bla" << " ENVELOPE {4}" << "blub" << " RID 5)"; continuations.clear(); continuations << 0 << 2; QTest::newRow("enclosed2literal") << input << QByteArray("*") << QByteArray("FETCH (UID 1 DATA {3}bla ENVELOPE {4}blub RID 5)") << true << continuations; input.clear(); input << "1 DATA {0}"; continuations.clear(); QTest::newRow("empty literal") << input << QByteArray("1") << QByteArray("DATA {0}") << true << continuations; } void ImapParserTest::testMessageParser() { QFETCH(QList, input); QFETCH(QByteArray, tag); QFETCH(QByteArray, data); QFETCH(bool, complete); QFETCH(QList, continuations); QList cont = continuations; ImapParser *parser = new ImapParser(); QVERIFY(parser->tag().isEmpty()); QVERIFY(parser->data().isEmpty()); for (int i = 0; i < input.count(); ++i) { bool res = parser->parseNextLine(input.at(i)); if (i != input.count() - 1) { QVERIFY(!res); } else { QCOMPARE(res, complete); } if (parser->continuationStarted()) { QVERIFY(cont.contains(i)); cont.removeAll(i); } } QCOMPARE(parser->tag(), tag); QCOMPARE(parser->data(), data); QVERIFY(cont.isEmpty()); // try again, this time with a not fresh parser parser->reset(); QVERIFY(parser->tag().isEmpty()); QVERIFY(parser->data().isEmpty()); cont = continuations; for (int i = 0; i < input.count(); ++i) { bool res = parser->parseNextLine(input.at(i)); if (i != input.count() - 1) { QVERIFY(!res); } else { QCOMPARE(res, complete); } if (parser->continuationStarted()) { QVERIFY(cont.contains(i)); cont.removeAll(i); } } QCOMPARE(parser->tag(), tag); QCOMPARE(parser->data(), data); QVERIFY(cont.isEmpty()); delete parser; } void ImapParserTest::testParseSequenceSet_data() { QTest::addColumn("data"); QTest::addColumn("begin"); QTest::addColumn("result"); QTest::addColumn("end"); QByteArray data(" 1 0:* 3:4,8:* *:5,1"); QTest::newRow("empty") << QByteArray() << 0 << ImapInterval::List() << 0; QTest::newRow("input end") << data << 20 << ImapInterval::List() << 20; ImapInterval::List result; result << ImapInterval(1, 1); QTest::newRow("single value 1") << data << 0 << result << 2; QTest::newRow("single value 2") << data << 1 << result << 2; QTest::newRow("single value 3") << data << 19 << result << 20; result.clear(); result << ImapInterval(); QTest::newRow("full interval") << data << 2 << result << 6; result.clear(); result << ImapInterval(3, 4) << ImapInterval(8); QTest::newRow("complex 1") << data << 7 << result << 14; result.clear(); result << ImapInterval(0, 5) << ImapInterval(1, 1); QTest::newRow("complex 2") << data << 14 << result << 20; } void ImapParserTest::testParseSequenceSet() { QFETCH(QByteArray, data); QFETCH(int, begin); QFETCH(ImapInterval::List, result); QFETCH(int, end); ImapSet res; int pos = ImapParser::parseSequenceSet(data, res, begin); QCOMPARE(res.intervals(), result); QCOMPARE(pos, end); } void ImapParserTest::testParseDateTime_data() { QTest::addColumn("data"); QTest::addColumn("begin"); QTest::addColumn("result"); QTest::addColumn("end"); QTest::newRow("emtpy") << QByteArray() << 0 << QDateTime() << 0; QByteArray data(" \"28-May-2006 01:03:35 +0200\""); QByteArray data2("22-Jul-2008 16:31:48 +0000"); QDateTime dt(QDate(2006, 5, 27), QTime(23, 3, 35), Qt::UTC); QDateTime dt2(QDate(2008, 7, 22), QTime(16, 31, 48), Qt::UTC); QTest::newRow("quoted 1") << data << 0 << dt << 29; QTest::newRow("quoted 2") << data << 1 << dt << 29; QTest::newRow("unquoted") << data << 2 << dt << 28; QTest::newRow("unquoted2") << data2 << 0 << dt2 << 26; QTest::newRow("invalid") << data << 4 << QDateTime() << 4; } void ImapParserTest::testParseDateTime() { QFETCH(QByteArray, data); QFETCH(int, begin); QFETCH(QDateTime, result); QFETCH(int, end); QDateTime actualResult; int actualEnd = ImapParser::parseDateTime(data, actualResult, begin); QCOMPARE(actualResult, result); QCOMPARE(actualEnd, end); } void ImapParserTest::testBulkParser_data() { QTest::addColumn("input"); QTest::addColumn("data"); QTest::newRow("empty") << QByteArray("* PRE {0} POST\n") << QByteArray("PRE {0} POST\n"); QTest::newRow("small block") << QByteArray("* PRE {2}\nXX POST\n") << QByteArray("PRE {2}\nXX POST\n"); QTest::newRow("small block 2") << QByteArray("* (PRE {2}\nXX\n POST)\n") << QByteArray("(PRE {2}\nXX\n POST)\n"); QTest::newRow("large block") << QByteArray("* PRE {10}\n0123456789\n") << QByteArray("PRE {10}\n0123456789\n"); QTest::newRow("store failure") << QByteArray("3 UID STORE (FOO bar ENV {3}\n(a) HEAD {3}\na\n\n BODY {3}\nabc)\n") << QByteArray("UID STORE (FOO bar ENV {3}\n(a) HEAD {3}\na\n\n BODY {3}\nabc)\n"); } void ImapParserTest::testBulkParser() { QFETCH(QByteArray, input); QFETCH(QByteArray, data); ImapParser *parser = new ImapParser(); QBuffer buffer; buffer.setData(input); QVERIFY(buffer.open(QBuffer::ReadOnly)); // reading continuation as a single block forever { if (buffer.atEnd()) { break; } if (parser->continuationSize() > 0) { parser->parseBlock(buffer.read(parser->continuationSize())); } else if (buffer.canReadLine()) { const QByteArray line = buffer.readLine(); bool res = parser->parseNextLine(line); QCOMPARE(res, buffer.atEnd()); } } QCOMPARE(parser->data(), data); // reading continuations as smaller blocks buffer.reset(); parser->reset(); forever { if (buffer.atEnd()) { break; } if (parser->continuationSize() > 4) { parser->parseBlock(buffer.read(4)); } else if (parser->continuationSize() > 0) { parser->parseBlock(buffer.read(parser->continuationSize())); } else if (buffer.canReadLine()) { bool res = parser->parseNextLine(buffer.readLine()); QCOMPARE(res, buffer.atEnd()); } } delete parser; } void ImapParserTest::testJoin_data() { QTest::addColumn >("list"); QTest::addColumn("joined"); QTest::newRow("empty") << QList() << QByteArray(); QTest::newRow("one") << (QList() << "abab") << QByteArray("abab"); QTest::newRow("two") << (QList() << "abab" << "cdcd") << QByteArray("abab cdcd"); QTest::newRow("three") << (QList() << "abab" << "cdcd" << "efef") << QByteArray("abab cdcd efef"); } void ImapParserTest::testJoin() { QFETCH(QList, list); QFETCH(QByteArray, joined); QCOMPARE(ImapParser::join(list, " "), joined); } void ImapParserTest::benchParseQuotedString_data() { QTest::addColumn("data"); QTest::addColumn("expectedResult"); QTest::addColumn("expectedConsumed"); QTest::newRow("quoted") << QByteArray("\"foo bar asdf\"") << QByteArray("foo bar asdf") << 14; QTest::newRow("unquoted") << QByteArray("foo bar asdf") << QByteArray("foo") << 3; } void ImapParserTest::benchParseQuotedString() { QFETCH(QByteArray, data); QFETCH(QByteArray, expectedResult); QFETCH(int, expectedConsumed); QByteArray result; QBENCHMARK { int consumed = ImapParser::parseQuotedString(data, result, 0); // use data, to prevent it from getting optimized away if (consumed != expectedConsumed || result != expectedResult) { // NOTE: don't use QCOMPARE in the outer hot loop, it's quite slow // just do it when something fails QCOMPARE(result, expectedResult); QCOMPARE(consumed, expectedConsumed); } } } diff --git a/autotests/libs/imapsettest.cpp b/autotests/libs/imapsettest.cpp index 85f06cdc4..28f12fd9c 100644 --- a/autotests/libs/imapsettest.cpp +++ b/autotests/libs/imapsettest.cpp @@ -1,76 +1,76 @@ /* 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 "imapsettest.h" #include "private/imapset_p.h" -#include +#include QTEST_MAIN(ImapSetTest) Q_DECLARE_METATYPE(QList) using namespace Akonadi; void ImapSetTest::testAddList_data() { QTest::addColumn >("source"); QTest::addColumn("intervals"); QTest::addColumn("seqset"); // empty set QList source; ImapInterval::List intervals; QTest::newRow("empty") << source << intervals << QByteArray(); // single value source << 4; intervals << ImapInterval(4, 4); QTest::newRow("single value") << source << intervals << QByteArray("4"); // single 2-value interval source << 5; intervals.clear(); intervals << ImapInterval(4, 5); QTest::newRow("single 2 interval") << source << intervals << QByteArray("4:5"); // single large interval source << 6 << 3 << 7 << 2 << 8; intervals.clear(); intervals << ImapInterval(2, 8); QTest::newRow("single 7 interval") << source << intervals << QByteArray("2:8"); // double interval source << 12; intervals << ImapInterval(12, 12); QTest::newRow("double interval") << source << intervals << QByteArray("2:8,12"); } void ImapSetTest::testAddList() { QFETCH(QList, source); QFETCH(ImapInterval::List, intervals); QFETCH(QByteArray, seqset); ImapSet set; set.add(source); ImapInterval::List result = set.intervals(); QCOMPARE(result, intervals); QCOMPARE(set.toImapSequenceSet(), seqset); } diff --git a/autotests/libs/itemhydratest.cpp b/autotests/libs/itemhydratest.cpp index 939e58da5..eff9e503d 100644 --- a/autotests/libs/itemhydratest.cpp +++ b/autotests/libs/itemhydratest.cpp @@ -1,362 +1,362 @@ /* Copyright (c) 2006 Till Adam 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 "itemhydratest.h" #include #include "item.h" -#include +#include #include #include #include using namespace Akonadi; struct Volker { bool operator==(const Volker &f) const { return f.who == who; } virtual ~Volker() { } virtual Volker *clone() const = 0; QString who; }; typedef std::shared_ptr VolkerPtr; typedef QSharedPointer VolkerQPtr; struct Rudi: public Volker { Rudi() { who = QStringLiteral("Rudi"); } virtual ~Rudi() { } Rudi *clone() const override { return new Rudi(*this); } }; typedef std::shared_ptr RudiPtr; typedef QSharedPointer RudiQPtr; struct Gerd: public Volker { Gerd() { who = QStringLiteral("Gerd"); } Gerd *clone() const override { return new Gerd(*this); } }; typedef std::shared_ptr GerdPtr; typedef QSharedPointer GerdQPtr; Q_DECLARE_METATYPE(Volker *) Q_DECLARE_METATYPE(Rudi *) Q_DECLARE_METATYPE(Gerd *) Q_DECLARE_METATYPE(Rudi) Q_DECLARE_METATYPE(Gerd) namespace Akonadi { template <> struct SuperClass : public SuperClassTrait {}; template <> struct SuperClass : public SuperClassTrait {}; } QTEST_MAIN(ItemHydra) ItemHydra::ItemHydra() { } void ItemHydra::initTestCase() { } void ItemHydra::testItemValuePayload() { Item f; Rudi rudi; f.setPayload(rudi); QVERIFY(f.hasPayload()); Item b; Gerd gerd; b.setPayload(gerd); QVERIFY(b.hasPayload()); QCOMPARE(f.payload(), rudi); QVERIFY(!(f.payload() == gerd)); QCOMPARE(b.payload(), gerd); QVERIFY(!(b.payload() == rudi)); } void ItemHydra::testItemPointerPayload() { Item f; Rudi *rudi = new Rudi; // the below should not compile //f.setPayload( rudi ); // std::auto_ptr is not copyconstructable and assignable, therefore this will fail as well //f.setPayload( std::auto_ptr( rudi ) ); //QVERIFY( f.hasPayload() ); //QCOMPARE( f.payload< std::auto_ptr >()->who, rudi->who ); // below doesn't compile, hopefully //QCOMPARE( f.payload< Rudi* >()->who, rudi->who ); delete rudi; } void ItemHydra::testItemCopy() { Item f; Rudi rudi; f.setPayload(rudi); Item r = f; QCOMPARE(r.payload(), rudi); Item s; s = f; QVERIFY(s.hasPayload()); QCOMPARE(s.payload(), rudi); } void ItemHydra::testEmptyPayload() { Item i1; Item i2; i1 = i2; // should not crash QVERIFY(!i1.hasPayload()); QVERIFY(!i2.hasPayload()); QVERIFY(!i1.hasPayload()); QVERIFY(!i1.hasPayload()); bool caughtException = false; bool caughtRightException = true; try { Rudi r = i1.payload(); } catch (const Akonadi::PayloadException &e) { qDebug() << e.what(); caughtException = true; caughtRightException = true; } catch (const Akonadi::Exception &e) { qDebug() << "Caught Akonadi exception of type " << typeid(e).name() << ": " << e.what() << ", expected type" << typeid(Akonadi::PayloadException).name(); caughtException = true; caughtRightException = false; } catch (const std::exception &e) { qDebug() << "Caught exception of type " << typeid(e).name() << ": " << e.what() << ", expected type" << typeid(Akonadi::PayloadException).name(); caughtException = true; caughtRightException = false; } catch (...) { qDebug() << "Caught unknown exception"; caughtException = true; caughtRightException = false; } QVERIFY(caughtException); QVERIFY(caughtRightException); } void ItemHydra::testPointerPayload() { Rudi *r = new Rudi; RudiPtr p(r); std::weak_ptr w(p); QCOMPARE(p.use_count(), (long)1); { Item i1; i1.setPayload(p); QVERIFY(i1.hasPayload()); QCOMPARE(p.use_count(), (long)2); { QVERIFY(i1.hasPayload< RudiPtr >()); RudiPtr p2 = i1.payload< RudiPtr >(); QCOMPARE(p.use_count(), (long)3); } { QVERIFY(i1.hasPayload< VolkerPtr >()); VolkerPtr p2 = i1.payload< VolkerPtr >(); QCOMPARE(p.use_count(), (long)3); } QCOMPARE(p.use_count(), (long)2); } QCOMPARE(p.use_count(), (long)1); QCOMPARE(w.use_count(), (long)1); p.reset(); QCOMPARE(w.use_count(), (long)0); } void ItemHydra::testPolymorphicPayload() { VolkerPtr p(new Rudi); { Item i1; i1.setPayload(p); QVERIFY(i1.hasPayload()); QVERIFY(i1.hasPayload()); QVERIFY(i1.hasPayload()); QVERIFY(!i1.hasPayload()); QCOMPARE(p.use_count(), (long)2); { RudiPtr p2 = std::dynamic_pointer_cast(i1.payload< VolkerPtr >()); QCOMPARE(p.use_count(), (long)3); QCOMPARE(p2->who, QStringLiteral("Rudi")); } { RudiPtr p2 = i1.payload< RudiPtr >(); QCOMPARE(p.use_count(), (long)3); QCOMPARE(p2->who, QStringLiteral("Rudi")); } bool caughtException = false; try { GerdPtr p3 = i1.payload(); } catch (const Akonadi::PayloadException &e) { qDebug() << e.what(); caughtException = true; } QVERIFY(caughtException); QCOMPARE(p.use_count(), (long)2); } } void ItemHydra::testNullPointerPayload() { RudiPtr p((Rudi *)nullptr); Item i; i.setPayload(p); QVERIFY(i.hasPayload()); QVERIFY(i.hasPayload()); QVERIFY(i.hasPayload()); // Fails, because GerdQPtr is QSharedPointer, while RudiPtr is std::shared_ptr // and we cannot do sharedptr casting for null pointers QVERIFY(!i.hasPayload()); QCOMPARE(i.payload().get(), (Rudi *)nullptr); QCOMPARE(i.payload().get(), (Volker *)nullptr); } void ItemHydra::testQSharedPointerPayload() { RudiQPtr p(new Rudi); Item i; i.setPayload(p); QVERIFY(i.hasPayload()); QVERIFY(i.hasPayload()); QVERIFY(i.hasPayload()); QVERIFY(!i.hasPayload()); { VolkerQPtr p2 = i.payload< VolkerQPtr >(); QCOMPARE(p2->who, QStringLiteral("Rudi")); } { RudiQPtr p2 = i.payload< RudiQPtr >(); QCOMPARE(p2->who, QStringLiteral("Rudi")); } bool caughtException = false; try { GerdQPtr p3 = i.payload(); } catch (const Akonadi::PayloadException &e) { qDebug() << e.what(); caughtException = true; } QVERIFY(caughtException); } void ItemHydra::testHasPayload() { Item i1; QVERIFY(!i1.hasPayload()); QVERIFY(!i1.hasPayload()); Rudi r; i1.setPayload(r); QVERIFY(i1.hasPayload()); QVERIFY(!i1.hasPayload()); } void ItemHydra::testSharedPointerConversions() { Item a; RudiQPtr rudi(new Rudi); a.setPayload(rudi); // only the root base classes should show up with their metatype ids: QVERIFY(a.availablePayloadMetaTypeIds().contains(qMetaTypeId())); QVERIFY(a.hasPayload()); QVERIFY(a.hasPayload()); QVERIFY(a.hasPayload()); QVERIFY(!a.hasPayload()); QVERIFY(a.payload().get()); QVERIFY(a.payload().get()); bool thrown = false, thrownCorrectly = true; try { QVERIFY(!a.payload()); } catch (const Akonadi::PayloadException &e) { thrown = thrownCorrectly = true; } catch (...) { thrown = true; thrownCorrectly = false; } QVERIFY(thrown); QVERIFY(thrownCorrectly); } void ItemHydra::testForeignPayload() { QTemporaryFile file; QVERIFY(file.open()); file.write("123456789"); file.close(); Item a(QStringLiteral("application/octet-stream")); a.setPayloadPath(file.fileName()); QVERIFY(a.hasPayload()); QCOMPARE(a.payload(), QByteArray("123456789")); Item b(QStringLiteral("application/octet-stream")); b.apply(a); QVERIFY(b.hasPayload()); QCOMPARE(b.payload(), QByteArray("123456789")); QCOMPARE(b.payloadPath(), file.fileName()); Item c = b; QVERIFY(c.hasPayload()); QCOMPARE(c.payload(), QByteArray("123456789")); QCOMPARE(c.payloadPath(), file.fileName()); } diff --git a/autotests/libs/itemserializertest.cpp b/autotests/libs/itemserializertest.cpp index 0b659f6ad..2a7a3121f 100644 --- a/autotests/libs/itemserializertest.cpp +++ b/autotests/libs/itemserializertest.cpp @@ -1,68 +1,68 @@ /* 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 "itemserializertest.h" #include "attributefactory.h" #include "item.h" #include "itemserializer_p.h" -#include +#include using namespace Akonadi; QTEST_MAIN(ItemSerializerTest) void ItemSerializerTest::testEmptyPayload() { // should not crash QByteArray data; Item item; ItemSerializer::deserialize(item, Item::FullPayload, data, 0, ItemSerializer::Internal); QVERIFY(data.isEmpty()); } void ItemSerializerTest::testDefaultSerializer_data() { QTest::addColumn("serialized"); QTest::newRow("null") << QByteArray(); QTest::newRow("empty") << QByteArray(""); QTest::newRow("nullbytei") << QByteArray("\0", 1); QTest::newRow("mixed") << QByteArray("\0\r\n\0bla", 7); } void ItemSerializerTest::testDefaultSerializer() { QFETCH(QByteArray, serialized); Item item; item.setMimeType(QStringLiteral("application/octet-stream")); ItemSerializer::deserialize(item, Item::FullPayload, serialized, 0, ItemSerializer::Internal); QVERIFY(item.hasPayload()); QCOMPARE(item.payload(), serialized); QByteArray data; int version = 0; ItemSerializer::serialize(item, Item::FullPayload, data, version); QCOMPARE(data, serialized); QEXPECT_FAIL("null", "Serializer cannot distinguish null vs. empty", Continue); QCOMPARE(data.isNull(), serialized.isNull()); } diff --git a/autotests/libs/itemtest.cpp b/autotests/libs/itemtest.cpp index c833d27b4..28a1c781a 100644 --- a/autotests/libs/itemtest.cpp +++ b/autotests/libs/itemtest.cpp @@ -1,125 +1,125 @@ /* 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 "itemtest.h" #include "testattribute.h" #include "item.h" -#include +#include #include #include QTEST_MAIN(ItemTest) using namespace Akonadi; void ItemTest::testMultipart() { Item item; item.setMimeType(QStringLiteral("application/octet-stream")); QSet parts; QCOMPARE(item.loadedPayloadParts(), parts); QByteArray bodyData = "bodydata"; item.setPayload(bodyData); parts << Item::FullPayload; QCOMPARE(item.loadedPayloadParts(), parts); QCOMPARE(item.payload(), bodyData); QByteArray myData = "mypartdata"; item.attribute(Item::AddIfMissing)->data = myData; QCOMPARE(item.loadedPayloadParts(), parts); QCOMPARE(item.attributes().count(), 1); QVERIFY(item.hasAttribute()); QCOMPARE(item.attribute()->data, myData); } void ItemTest::testInheritance() { Item a; a.setRemoteId(QStringLiteral("Hello World")); a.setSize(10); Item b(a); b.setFlag("\\send"); QCOMPARE(b.remoteId(), QStringLiteral("Hello World")); QCOMPARE(b.size(), (qint64)10); } void ItemTest::testParentCollection() { Item a; QVERIFY(!a.parentCollection().isValid()); a.setParentCollection(Collection::root()); QCOMPARE(a.parentCollection(), Collection::root()); Item b = a; QCOMPARE(b.parentCollection(), Collection::root()); Item c; c.parentCollection().setRemoteId(QStringLiteral("foo")); QCOMPARE(c.parentCollection().remoteId(), QStringLiteral("foo")); const Item d = c; QCOMPARE(d.parentCollection().remoteId(), QStringLiteral("foo")); const Item e; QVERIFY(!e.parentCollection().isValid()); Collection col(5); Item f; f.setParentCollection(col); QCOMPARE(f.parentCollection(), col); Item g = f; QCOMPARE(g.parentCollection(), col); b = g; QCOMPARE(b.parentCollection(), col); } void ItemTest::testComparison_data() { QTest::addColumn("itemA"); QTest::addColumn("itemB"); QTest::addColumn("match"); QTest::newRow("both invalid, same invalid IDs") << Item(-10) << Item(-10) << true; QTest::newRow("both invalid, different invalid IDs") << Item(-11) << Item(-12) << true; QTest::newRow("one valid") << Item(1) << Item() << false; QTest::newRow("both valid, same IDs") << Item(2) << Item(2) << true; QTest::newRow("both valid, different IDs") << Item(3) << Item(4) << false; } void ItemTest::testComparison() { QFETCH(Akonadi::Item, itemA); QFETCH(Akonadi::Item, itemB); QFETCH(bool, match); if (match) { QVERIFY(itemA == itemB); QVERIFY(!(itemA != itemB)); } else { QVERIFY(itemA != itemB); QVERIFY(!(itemA == itemB)); } } diff --git a/autotests/libs/mimetypecheckertest.cpp b/autotests/libs/mimetypecheckertest.cpp index 8f626c9db..2cc3f934e 100644 --- a/autotests/libs/mimetypecheckertest.cpp +++ b/autotests/libs/mimetypecheckertest.cpp @@ -1,307 +1,307 @@ /* Copyright (c) 2009 Kevin Krammer 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 "mimetypecheckertest.h" #include "testattribute.h" #include "collection.h" #include "item.h" #include "krandom.h" #include -#include +#include QTEST_MAIN(MimeTypeCheckerTest) using namespace Akonadi; MimeTypeCheckerTest::MimeTypeCheckerTest(QObject *parent) : QObject(parent) { mCalendarSubTypes << QStringLiteral("application/x-vnd.akonadi.calendar.event") << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); } void MimeTypeCheckerTest::initTestCase() { QVERIFY(QMimeDatabase().mimeTypeForName(QLatin1String("application/x-vnd.akonadi.calendar.event")).isValid()); MimeTypeChecker emptyChecker; MimeTypeChecker calendarChecker; MimeTypeChecker subTypeChecker; MimeTypeChecker aliasChecker; // for testing reset through assignments const QLatin1String textPlain = QLatin1String("text/plain"); mEmptyChecker.addWantedMimeType(textPlain); QVERIFY(!mEmptyChecker.wantedMimeTypes().isEmpty()); QVERIFY(mEmptyChecker.hasWantedMimeTypes()); const QLatin1String textCalendar = QLatin1String("text/calendar"); calendarChecker.addWantedMimeType(textCalendar); QCOMPARE(calendarChecker.wantedMimeTypes().count(), 1); subTypeChecker.setWantedMimeTypes(mCalendarSubTypes); QCOMPARE(subTypeChecker.wantedMimeTypes().count(), 2); const QLatin1String textVCard = QLatin1String("text/directory"); aliasChecker.addWantedMimeType(textVCard); QCOMPARE(aliasChecker.wantedMimeTypes().count(), 1); // test assignment works correctly mEmptyChecker = emptyChecker; mCalendarChecker = calendarChecker; mSubTypeChecker = subTypeChecker; mAliasChecker = aliasChecker; QVERIFY(mEmptyChecker.wantedMimeTypes().isEmpty()); QVERIFY(!mEmptyChecker.hasWantedMimeTypes()); QCOMPARE(mCalendarChecker.wantedMimeTypes().count(), 1); QCOMPARE(mCalendarChecker.wantedMimeTypes(), QStringList() << textCalendar); QCOMPARE(mSubTypeChecker.wantedMimeTypes().count(), 2); const QSet calendarSubTypes = QSet::fromList(mCalendarSubTypes); const QSet wantedSubTypes = QSet::fromList(mSubTypeChecker.wantedMimeTypes()); QCOMPARE(wantedSubTypes, calendarSubTypes); QCOMPARE(mAliasChecker.wantedMimeTypes().count(), 1); QCOMPARE(mAliasChecker.wantedMimeTypes(), QStringList() << textVCard); } void MimeTypeCheckerTest::testCollectionCheck() { Collection invalidCollection; Collection emptyCollection(1); Collection calendarCollection(2); Collection eventCollection(3); Collection journalCollection(4); Collection vcardCollection(5); Collection aliasCollection(6); const QLatin1String textCalendar = QLatin1String("text/calendar"); calendarCollection.setContentMimeTypes(QStringList() << textCalendar); const QLatin1String akonadiEvent = QLatin1String("application/x-vnd.akonadi.calendar.event"); eventCollection.setContentMimeTypes(QStringList() << akonadiEvent); journalCollection.setContentMimeTypes(QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.journal")); const QLatin1String textDirectory = QLatin1String("text/directory"); vcardCollection.setContentMimeTypes(QStringList() << textDirectory); aliasCollection.setContentMimeTypes(QStringList() << QStringLiteral("text/x-vcard")); Collection::List voidCollections; voidCollections << invalidCollection << emptyCollection; Collection::List subTypeCollections; subTypeCollections << eventCollection << journalCollection; Collection::List calendarCollections = subTypeCollections; calendarCollections << calendarCollection; Collection::List contactCollections; contactCollections << vcardCollection << aliasCollection; //// empty checker fails for all Collection::List collections = voidCollections + calendarCollections + contactCollections; foreach (const Collection &collection, collections) { QVERIFY(!mEmptyChecker.isWantedCollection(collection)); QVERIFY(!MimeTypeChecker::isWantedCollection(collection, QString())); } //// calendar checker fails for void and contact collections collections = voidCollections + contactCollections; foreach (const Collection &collection, collections) { QVERIFY(!mCalendarChecker.isWantedCollection(collection)); QVERIFY(!MimeTypeChecker::isWantedCollection(collection, textCalendar)); } // but accepts all calendar collections collections = calendarCollections; foreach (const Collection &collection, collections) { QVERIFY(mCalendarChecker.isWantedCollection(collection)); QVERIFY(MimeTypeChecker::isWantedCollection(collection, textCalendar)); } //// sub type checker fails for all but the event collection collections = voidCollections + calendarCollections + contactCollections; collections.removeAll(eventCollection); foreach (const Collection &collection, collections) { QVERIFY(!mSubTypeChecker.isWantedCollection(collection)); QVERIFY(!MimeTypeChecker::isWantedCollection(collection, akonadiEvent)); } // but accepts the event collection collections = Collection::List() << eventCollection; foreach (const Collection &collection, collections) { QVERIFY(mSubTypeChecker.isWantedCollection(collection)); QVERIFY(MimeTypeChecker::isWantedCollection(collection, akonadiEvent)); } //// alias checker fails for void and calendar collections collections = voidCollections + calendarCollections; foreach (const Collection &collection, collections) { QVERIFY(!mAliasChecker.isWantedCollection(collection)); QVERIFY(!MimeTypeChecker::isWantedCollection(collection, textDirectory)); } // but accepts all contact collections collections = contactCollections; foreach (const Collection &collection, collections) { QVERIFY(mAliasChecker.isWantedCollection(collection)); QVERIFY(MimeTypeChecker::isWantedCollection(collection, textDirectory)); } } void MimeTypeCheckerTest::testItemCheck() { Item invalidItem; Item emptyItem(1); Item calendarItem(2); Item eventItem(3); Item journalItem(4); Item vcardItem(5); Item aliasItem(6); const QLatin1String textCalendar = QLatin1String("text/calendar"); calendarItem.setMimeType(textCalendar); const QLatin1String akonadiEvent = QLatin1String("application/x-vnd.akonadi.calendar.event"); eventItem.setMimeType(akonadiEvent); journalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.journal")); const QLatin1String textDirectory = QLatin1String("text/directory"); vcardItem.setMimeType(textDirectory); aliasItem.setMimeType(QStringLiteral("text/x-vcard")); Item::List voidItems; voidItems << invalidItem << emptyItem; Item::List subTypeItems; subTypeItems << eventItem << journalItem; Item::List calendarItems = subTypeItems; calendarItems << calendarItem; Item::List contactItems; contactItems << vcardItem << aliasItem; //// empty checker fails for all Item::List items = voidItems + calendarItems + contactItems; foreach (const Item &item, items) { QVERIFY(!mEmptyChecker.isWantedItem(item)); QVERIFY(!MimeTypeChecker::isWantedItem(item, QString())); } //// calendar checker fails for void and contact items items = voidItems + contactItems; foreach (const Item &item, items) { QVERIFY(!mCalendarChecker.isWantedItem(item)); QVERIFY(!MimeTypeChecker::isWantedItem(item, textCalendar)); } // but accepts all calendar items items = calendarItems; foreach (const Item &item, items) { QVERIFY(mCalendarChecker.isWantedItem(item)); QVERIFY(MimeTypeChecker::isWantedItem(item, textCalendar)); } //// sub type checker fails for all but the event item items = voidItems + calendarItems + contactItems; items.removeAll(eventItem); foreach (const Item &item, items) { QVERIFY(!mSubTypeChecker.isWantedItem(item)); QVERIFY(!MimeTypeChecker::isWantedItem(item, akonadiEvent)); } // but accepts the event item items = Item::List() << eventItem; foreach (const Item &item, items) { QVERIFY(mSubTypeChecker.isWantedItem(item)); QVERIFY(MimeTypeChecker::isWantedItem(item, akonadiEvent)); } //// alias checker fails for void and calendar items items = voidItems + calendarItems; foreach (const Item &item, items) { QVERIFY(!mAliasChecker.isWantedItem(item)); QVERIFY(!MimeTypeChecker::isWantedItem(item, textDirectory)); } // but accepts all contact items items = contactItems; foreach (const Item &item, items) { QVERIFY(mAliasChecker.isWantedItem(item)); QVERIFY(MimeTypeChecker::isWantedItem(item, textDirectory)); } } void MimeTypeCheckerTest::testStringMatchEquivalent() { // check that a random and thus not installed MIME type // can still be checked just like with direct string comparison const QLatin1String installedMimeType("text/plain"); const QString randomMimeType = QLatin1String("application/x-vnd.test.") + KRandom::randomString(10); MimeTypeChecker installedTypeChecker; installedTypeChecker.addWantedMimeType(installedMimeType); MimeTypeChecker randomTypeChecker; randomTypeChecker.addWantedMimeType(randomMimeType); Item item1(1); item1.setMimeType(installedMimeType); Item item2(2); item2.setMimeType(randomMimeType); Collection collection1(1); collection1.setContentMimeTypes(QStringList() << installedMimeType); Collection collection2(2); collection2.setContentMimeTypes(QStringList() << randomMimeType); Collection collection3(3); collection3.setContentMimeTypes(QStringList() << installedMimeType << randomMimeType); QVERIFY(installedTypeChecker.isWantedItem(item1)); QVERIFY(!randomTypeChecker.isWantedItem(item1)); QVERIFY(MimeTypeChecker::isWantedItem(item1, installedMimeType)); QVERIFY(!MimeTypeChecker::isWantedItem(item1, randomMimeType)); QVERIFY(!installedTypeChecker.isWantedItem(item2)); QVERIFY(randomTypeChecker.isWantedItem(item2)); QVERIFY(!MimeTypeChecker::isWantedItem(item2, installedMimeType)); QVERIFY(MimeTypeChecker::isWantedItem(item2, randomMimeType)); QVERIFY(installedTypeChecker.isWantedCollection(collection1)); QVERIFY(!randomTypeChecker.isWantedCollection(collection1)); QVERIFY(MimeTypeChecker::isWantedCollection(collection1, installedMimeType)); QVERIFY(!MimeTypeChecker::isWantedCollection(collection1, randomMimeType)); QVERIFY(!installedTypeChecker.isWantedCollection(collection2)); QVERIFY(randomTypeChecker.isWantedCollection(collection2)); QVERIFY(!MimeTypeChecker::isWantedCollection(collection2, installedMimeType)); QVERIFY(MimeTypeChecker::isWantedCollection(collection2, randomMimeType)); QVERIFY(installedTypeChecker.isWantedCollection(collection3)); QVERIFY(randomTypeChecker.isWantedCollection(collection3)); QVERIFY(MimeTypeChecker::isWantedCollection(collection3, installedMimeType)); QVERIFY(MimeTypeChecker::isWantedCollection(collection3, randomMimeType)); } diff --git a/autotests/libs/modelspy.cpp b/autotests/libs/modelspy.cpp index a4fc031a6..f0ae0cee8 100644 --- a/autotests/libs/modelspy.cpp +++ b/autotests/libs/modelspy.cpp @@ -1,225 +1,225 @@ /* Copyright (c) 2009 Stephen Kelly 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 "modelspy.h" -#include +#include #include QVariantList extractModelColumn(const QAbstractItemModel &model, const QModelIndex &parent, const int firstRow, const int lastRow) { QVariantList result; for (auto row = firstRow; row <= lastRow; ++row) { result.append(model.index(row, 0, parent).data()); } return result; } ModelSpy::ModelSpy(QAbstractItemModel *model, QObject *parent) : QObject{parent}, m_model{model}, m_isSpying{false} { qRegisterMetaType("QModelIndex"); } bool ModelSpy::isEmpty() const { return QList::isEmpty() && m_expectedSignals.isEmpty(); } void ModelSpy::setExpectedSignals(const QList< ExpectedSignal > &expectedSignals) { m_expectedSignals = expectedSignals; } QList ModelSpy::expectedSignals() const { return m_expectedSignals; } void ModelSpy::verifySignal(SignalType type, const QModelIndex &parent, int start, int end) { const auto expectedSignal = m_expectedSignals.takeFirst(); QCOMPARE(int(type), int(expectedSignal.signalType)); QCOMPARE(parent.data(), expectedSignal.parentData); QCOMPARE(start, expectedSignal.startRow); QCOMPARE(end, expectedSignal.endRow); if (!expectedSignal.newData.isEmpty()) { // TODO } } void ModelSpy::verifySignal(SignalType type, const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destStart) { const auto expectedSignal = m_expectedSignals.takeFirst(); QCOMPARE(int(type), int(expectedSignal.signalType)); QCOMPARE(start, expectedSignal.startRow); QCOMPARE(end, expectedSignal.endRow); QCOMPARE(parent.data(), expectedSignal.sourceParentData); QCOMPARE(destParent.data(), expectedSignal.parentData); QCOMPARE(destStart, expectedSignal.destRow); if (type == RowsAboutToBeMoved) { QCOMPARE(extractModelColumn(*m_model, parent, start, end), expectedSignal.newData); } else { const auto destEnd = destStart + (end - start); QCOMPARE(extractModelColumn(*m_model, destParent, destStart, destEnd), expectedSignal.newData); } } void ModelSpy::verifySignal(SignalType type, const QModelIndex &topLeft, const QModelIndex &bottomRight) { QCOMPARE(type, DataChanged); const auto expectedSignal = m_expectedSignals.takeFirst(); QCOMPARE(int(type), int(expectedSignal.signalType)); QModelIndex parent = topLeft.parent(); //This check won't work for toplevel indexes if (parent.isValid()) { if (expectedSignal.parentData.isValid()) { QCOMPARE(parent.data(), expectedSignal.parentData); } } QCOMPARE(topLeft.row(), expectedSignal.startRow); QCOMPARE(bottomRight.row(), expectedSignal.endRow); QCOMPARE(extractModelColumn(*m_model, parent, topLeft.row(), bottomRight.row()), expectedSignal.newData); } void ModelSpy::startSpying() { m_isSpying = true; // If a signal is connected to a slot multiple times, the slot gets called multiple times. // As we're doing start and stop spying all the time, we disconnect here first to make sure. disconnect(m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); connect(m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), SLOT(rowsAboutToBeInserted(QModelIndex,int,int))); connect(m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); connect(m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); connect(m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(rowsRemoved(QModelIndex,int,int))); connect(m_model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); connect(m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int))); connect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(dataChanged(QModelIndex,QModelIndex))); } void ModelSpy::stopSpying() { m_isSpying = false; disconnect(m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int))); disconnect(m_model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } void ModelSpy::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { if (!m_expectedSignals.isEmpty()) { verifySignal(RowsAboutToBeInserted, parent, start, end); } else { append(QVariantList{RowsAboutToBeInserted, QVariant::fromValue(parent), start, end}); } } void ModelSpy::rowsInserted(const QModelIndex &parent, int start, int end) { if (!m_expectedSignals.isEmpty()) { verifySignal(RowsInserted, parent, start, end); } else { append(QVariantList{RowsInserted, QVariant::fromValue(parent), start, end}); } } void ModelSpy::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { if (!m_expectedSignals.isEmpty()) { verifySignal(RowsAboutToBeRemoved, parent, start, end); } else { append(QVariantList{RowsAboutToBeRemoved, QVariant::fromValue(parent), start, end}); } } void ModelSpy::rowsRemoved(const QModelIndex &parent, int start, int end) { if (!m_expectedSignals.isEmpty()) { verifySignal(RowsRemoved, parent, start, end); } else { append(QVariantList{RowsRemoved, QVariant::fromValue(parent), start, end}); } } void ModelSpy::rowsAboutToBeMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destStart) { if (!m_expectedSignals.isEmpty()) { verifySignal(RowsAboutToBeMoved, srcParent, start, end, destParent, destStart); } else { append(QVariantList{RowsAboutToBeMoved, QVariant::fromValue(srcParent), start, end, QVariant::fromValue(destParent), destStart}); } } void ModelSpy::rowsMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destStart) { if (!m_expectedSignals.isEmpty()) { verifySignal(RowsMoved, srcParent, start, end, destParent, destStart); } else { append(QVariantList{RowsMoved, QVariant::fromValue(srcParent), start, end, QVariant::fromValue(destParent), destStart}); } } void ModelSpy::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (!m_expectedSignals.isEmpty()) { verifySignal(DataChanged, topLeft, bottomRight); } else { append(QVariantList{DataChanged, QVariant::fromValue(topLeft), QVariant::fromValue(bottomRight)}); } } diff --git a/autotests/libs/monitorfiltertest.cpp b/autotests/libs/monitorfiltertest.cpp index f505b5350..6e24738d4 100644 --- a/autotests/libs/monitorfiltertest.cpp +++ b/autotests/libs/monitorfiltertest.cpp @@ -1,340 +1,340 @@ /* Copyright (c) 2010 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 "monitor_p.h" -#include +#include #include using namespace Akonadi; Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotification::Operation) Q_DECLARE_METATYPE(QSet) class MonitorFilterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { AkonadiTest::checkTestIsIsolated(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType(); } void filterConnected_data() { QTest::addColumn("op"); QTest::addColumn("signalName"); QTest::newRow("itemAdded") << Protocol::ChangeNotification::Add << QByteArray(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemChanged") << Protocol::ChangeNotification::Modify << QByteArray(SIGNAL(itemChanged(Akonadi::Item,QSet))); QTest::newRow("itemsFlagsChanged") << Protocol::ChangeNotification::ModifyFlags << QByteArray(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))); QTest::newRow("itemRemoved") << Protocol::ChangeNotification::Remove << QByteArray(SIGNAL(itemRemoved(Akonadi::Item))); QTest::newRow("itemMoved") << Protocol::ChangeNotification::Move << QByteArray(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("itemLinked") << Protocol::ChangeNotification::Link << QByteArray(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemUnlinked") << Protocol::ChangeNotification::Unlink << QByteArray(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))); } void filterConnected() { QFETCH(Protocol::ChangeNotification::Operation, op); QFETCH(QByteArray, signalName); Monitor dummyMonitor; MonitorPrivate m(0, &dummyMonitor); Protocol::ChangeNotification msg; msg.addEntity(1); msg.setOperation(op); msg.setType(Akonadi::Protocol::ChangeNotification::Items); QVERIFY(!m.acceptNotification(msg)); m.monitorAll = true; QVERIFY(m.acceptNotification(msg)); QSignalSpy spy(&dummyMonitor, signalName.constData()); QVERIFY(spy.isValid()); QVERIFY(m.acceptNotification(msg)); m.monitorAll = false; QVERIFY(!m.acceptNotification(msg)); } void filterSession() { Monitor dummyMonitor; MonitorPrivate m(0, &dummyMonitor); m.monitorAll = true; QSignalSpy spy(&dummyMonitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))); QVERIFY(spy.isValid()); Protocol::ChangeNotification msg; msg.addEntity(1); msg.setOperation(Protocol::ChangeNotification::Add); msg.setType(Akonadi::Protocol::ChangeNotification::Items); msg.setSessionId("foo"); QVERIFY(m.acceptNotification(msg)); m.sessions.append("bar"); QVERIFY(m.acceptNotification(msg)); m.sessions.append("foo"); QVERIFY(!m.acceptNotification(msg)); } void filterResource_data() { QTest::addColumn("op"); QTest::addColumn("type"); QTest::addColumn("signalName"); QTest::newRow("itemAdded") << Protocol::ChangeNotification::Add << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemChanged") << Protocol::ChangeNotification::Modify << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemChanged(Akonadi::Item,QSet))); QTest::newRow("itemsFlagsChanged") << Protocol::ChangeNotification::ModifyFlags << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))); QTest::newRow("itemRemoved") << Protocol::ChangeNotification::Remove << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemRemoved(Akonadi::Item))); QTest::newRow("itemMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("itemLinked") << Protocol::ChangeNotification::Link << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemUnlinked") << Protocol::ChangeNotification::Unlink << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("colAdded") << Protocol::ChangeNotification::Add << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colChanged") << Protocol::ChangeNotification::Modify << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionChanged(Akonadi::Collection,QSet))); QTest::newRow("colRemoved") << Protocol::ChangeNotification::Remove << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionRemoved(Akonadi::Collection))); QTest::newRow("colMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colSubscribed") << Protocol::ChangeNotification::Subscribe << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionSubscribed(Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colSubscribed") << Protocol::ChangeNotification::Unsubscribe << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionUnsubscribed(Akonadi::Collection))); } void filterResource() { QFETCH(Protocol::ChangeNotification::Operation, op); QFETCH(Protocol::ChangeNotification::Type, type); QFETCH(QByteArray, signalName); Monitor dummyMonitor; MonitorPrivate m(0, &dummyMonitor); QSignalSpy spy(&dummyMonitor, signalName.constData()); QVERIFY(spy.isValid()); Protocol::ChangeNotification msg; msg.addEntity(1); msg.setOperation(op); msg.setParentCollection(2); msg.setType(type); msg.setResource("foo"); msg.setSessionId("mysession"); // using the right resource makes it pass QVERIFY(!m.acceptNotification(msg)); m.resources.insert("bar"); QVERIFY(!m.acceptNotification(msg)); m.resources.insert("foo"); QVERIFY(m.acceptNotification(msg)); // filtering out the session overwrites the resource m.sessions.append("mysession"); QVERIFY(!m.acceptNotification(msg)); } void filterDestinationResource_data() { QTest::addColumn("op"); QTest::addColumn("type"); QTest::addColumn("signalName"); QTest::newRow("itemMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); } void filterDestinationResource() { QFETCH(Protocol::ChangeNotification::Operation, op); QFETCH(Protocol::ChangeNotification::Type, type); QFETCH(QByteArray, signalName); Monitor dummyMonitor; MonitorPrivate m(0, &dummyMonitor); QSignalSpy spy(&dummyMonitor, signalName.constData()); QVERIFY(spy.isValid()); Protocol::ChangeNotification msg; msg.setOperation(op); msg.setType(type); msg.setResource("foo"); msg.setDestinationResource("bar"); msg.setSessionId("mysession"); msg.addEntity(1); // using the right resource makes it pass QVERIFY(!m.acceptNotification(msg)); m.resources.insert("bla"); QVERIFY(!m.acceptNotification(msg)); m.resources.insert("bar"); QVERIFY(m.acceptNotification(msg)); // filtering out the mimetype does not overwrite resources msg.addEntity(1, QString(), QString(), QStringLiteral("my/type")); QVERIFY(m.acceptNotification(msg)); // filtering out the session overwrites the resource m.sessions.append("mysession"); QVERIFY(!m.acceptNotification(msg)); } void filterMimeType_data() { QTest::addColumn("op"); QTest::addColumn("type"); QTest::addColumn("signalName"); QTest::newRow("itemAdded") << Protocol::ChangeNotification::Add << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemChanged") << Protocol::ChangeNotification::Modify << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemChanged(Akonadi::Item,QSet))); QTest::newRow("itemsFlagsChanged") << Protocol::ChangeNotification::ModifyFlags << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))); QTest::newRow("itemRemoved") << Protocol::ChangeNotification::Remove << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemRemoved(Akonadi::Item))); QTest::newRow("itemMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("itemLinked") << Protocol::ChangeNotification::Link << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemUnlinked") << Protocol::ChangeNotification::Unlink << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("colAdded") << Protocol::ChangeNotification::Add << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colChanged") << Protocol::ChangeNotification::Modify << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionChanged(Akonadi::Collection,QSet))); QTest::newRow("colRemoved") << Protocol::ChangeNotification::Remove << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionRemoved(Akonadi::Collection))); QTest::newRow("colMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colSubscribed") << Protocol::ChangeNotification::Subscribe << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionSubscribed(Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colSubscribed") << Protocol::ChangeNotification::Unsubscribe << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionUnsubscribed(Akonadi::Collection))); } void filterMimeType() { QFETCH(Protocol::ChangeNotification::Operation, op); QFETCH(Protocol::ChangeNotification::Type, type); QFETCH(QByteArray, signalName); Monitor dummyMonitor; MonitorPrivate m(0, &dummyMonitor); QSignalSpy spy(&dummyMonitor, signalName.constData()); QVERIFY(spy.isValid()); Protocol::ChangeNotification msg; msg.addEntity(1, QString(), QString(), QStringLiteral("my/type")); msg.setOperation(op); msg.setParentCollection(2); msg.setType(type); msg.setResource("foo"); msg.setSessionId("mysession"); // using the right resource makes it pass QVERIFY(!m.acceptNotification(msg)); m.mimetypes.insert(QStringLiteral("your/type")); QVERIFY(!m.acceptNotification(msg)); m.mimetypes.insert(QStringLiteral("my/type")); QCOMPARE(m.acceptNotification(msg), type == Protocol::ChangeNotification::Items); // filter out the resource does not overwrite mimetype m.resources.insert("bar"); QCOMPARE(m.acceptNotification(msg), type == Protocol::ChangeNotification::Items); // filtering out the session overwrites the mimetype m.sessions.append("mysession"); QVERIFY(!m.acceptNotification(msg)); } void filterCollection_data() { QTest::addColumn("op"); QTest::addColumn("type"); QTest::addColumn("signalName"); QTest::newRow("itemAdded") << Protocol::ChangeNotification::Add << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemChanged") << Protocol::ChangeNotification::Modify << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemChanged(Akonadi::Item,QSet))); QTest::newRow("itemsFlagsChanged") << Protocol::ChangeNotification::ModifyFlags << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))); QTest::newRow("itemRemoved") << Protocol::ChangeNotification::Remove << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemRemoved(Akonadi::Item))); QTest::newRow("itemMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("itemLinked") << Protocol::ChangeNotification::Link << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("itemUnlinked") << Protocol::ChangeNotification::Unlink << Protocol::ChangeNotification::Items << QByteArray(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))); QTest::newRow("colAdded") << Protocol::ChangeNotification::Add << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colChanged") << Protocol::ChangeNotification::Modify << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionChanged(Akonadi::Collection,QSet))); QTest::newRow("colRemoved") << Protocol::ChangeNotification::Remove << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionRemoved(Akonadi::Collection))); QTest::newRow("colMoved") << Protocol::ChangeNotification::Move << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colSubscribed") << Protocol::ChangeNotification::Subscribe << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionSubscribed(Akonadi::Collection,Akonadi::Collection))); QTest::newRow("colSubscribed") << Protocol::ChangeNotification::Unsubscribe << Protocol::ChangeNotification::Collections << QByteArray(SIGNAL(collectionUnsubscribed(Akonadi::Collection))); } void filterCollection() { QFETCH(Protocol::ChangeNotification::Operation, op); QFETCH(Protocol::ChangeNotification::Type, type); QFETCH(QByteArray, signalName); Monitor dummyMonitor; MonitorPrivate m(0, &dummyMonitor); QSignalSpy spy(&dummyMonitor, signalName.constData()); QVERIFY(spy.isValid()); Protocol::ChangeNotification msg; msg.addEntity(1, QString(), QString(), QStringLiteral("my/type")); msg.setOperation(op); msg.setParentCollection(2); msg.setType(type); msg.setResource("foo"); msg.setSessionId("mysession"); // using the right resource makes it pass QVERIFY(!m.acceptNotification(msg)); m.collections.append(Collection(3)); QVERIFY(!m.acceptNotification(msg)); for (int colId = 0; colId < 3; ++colId) { // 0 == root, 1 == this, 2 == parent if (colId == 1 && type == Protocol::ChangeNotification::Items) { continue; } m.collections.clear(); m.collections.append(Collection(colId)); QVERIFY(m.acceptNotification(msg)); // filter out the resource does overwrite collection m.resources.insert("bar"); QVERIFY(!m.acceptNotification(msg)); m.resources.clear(); // filter out the mimetype does overwrite collection, for item operations (mimetype filter has no effect on collections) m.mimetypes.insert(QStringLiteral("your/type")); QCOMPARE(!m.acceptNotification(msg), type == Protocol::ChangeNotification::Items); m.mimetypes.clear(); // filtering out the session overwrites the mimetype m.sessions.append("mysession"); QVERIFY(!m.acceptNotification(msg)); m.sessions.clear(); // filter non-matching resource and matching mimetype make it pass m.resources.insert("bar"); m.mimetypes.insert(QStringLiteral("my/type")); QVERIFY(m.acceptNotification(msg)); m.resources.clear(); m.mimetypes.clear(); } } }; QTEST_MAIN(MonitorFilterTest) #include "monitorfiltertest.moc" diff --git a/autotests/libs/newmailnotifierattributetest.cpp b/autotests/libs/newmailnotifierattributetest.cpp index b0c702316..8ce96dac7 100644 --- a/autotests/libs/newmailnotifierattributetest.cpp +++ b/autotests/libs/newmailnotifierattributetest.cpp @@ -1,75 +1,75 @@ /* Copyright (c) 2014-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 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 "newmailnotifierattributetest.h" #include "newmailnotifierattribute.h" -#include +#include using namespace Akonadi; NewMailNotifierAttributeTest::NewMailNotifierAttributeTest(QObject *parent) : QObject(parent) { } NewMailNotifierAttributeTest::~NewMailNotifierAttributeTest() { } void NewMailNotifierAttributeTest::shouldHaveDefaultValue() { NewMailNotifierAttribute attr; QVERIFY(!attr.ignoreNewMail()); } void NewMailNotifierAttributeTest::shouldSetIgnoreNotification() { NewMailNotifierAttribute attr; bool ignore = false; attr.setIgnoreNewMail(ignore); QCOMPARE(attr.ignoreNewMail(), ignore); ignore = true; attr.setIgnoreNewMail(ignore); QCOMPARE(attr.ignoreNewMail(), ignore); } void NewMailNotifierAttributeTest::shouldSerializedData() { NewMailNotifierAttribute attr; attr.setIgnoreNewMail(true); QByteArray ba = attr.serialized(); NewMailNotifierAttribute result; result.deserialize(ba); QVERIFY(attr == result); } void NewMailNotifierAttributeTest::shouldCloneAttribute() { NewMailNotifierAttribute attr; attr.setIgnoreNewMail(true); NewMailNotifierAttribute *result = attr.clone(); QVERIFY(attr == *result); delete result; } void NewMailNotifierAttributeTest::shouldHaveType() { NewMailNotifierAttribute attr; QCOMPARE(attr.type(), QByteArray("newmailnotifierattribute")); } QTEST_MAIN(NewMailNotifierAttributeTest) diff --git a/autotests/libs/pop3resourceattributetest.cpp b/autotests/libs/pop3resourceattributetest.cpp index d92ccbda8..16d53277c 100644 --- a/autotests/libs/pop3resourceattributetest.cpp +++ b/autotests/libs/pop3resourceattributetest.cpp @@ -1,73 +1,73 @@ /* Copyright (c) 2014-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 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 "pop3resourceattributetest.h" #include "pop3resourceattribute.h" -#include +#include Pop3ResourceAttributeTest::Pop3ResourceAttributeTest(QObject *parent) : QObject(parent) { } Pop3ResourceAttributeTest::~Pop3ResourceAttributeTest() { } void Pop3ResourceAttributeTest::shouldHaveDefaultValue() { Akonadi::Pop3ResourceAttribute attr; QVERIFY(attr.pop3AccountName().isEmpty()); } void Pop3ResourceAttributeTest::shouldAssignValue() { Akonadi::Pop3ResourceAttribute attr; QString accountName; attr.setPop3AccountName(accountName); QCOMPARE(attr.pop3AccountName(), accountName); accountName = QStringLiteral("foo"); attr.setPop3AccountName(accountName); QCOMPARE(attr.pop3AccountName(), accountName); accountName.clear(); attr.setPop3AccountName(accountName); QCOMPARE(attr.pop3AccountName(), accountName); } void Pop3ResourceAttributeTest::shouldDeserializeValue() { Akonadi::Pop3ResourceAttribute attr; QString accountName = QStringLiteral("foo"); attr.setPop3AccountName(accountName); const QByteArray ba = attr.serialized(); Akonadi::Pop3ResourceAttribute result; result.deserialize(ba); QVERIFY(attr == result); } void Pop3ResourceAttributeTest::shouldCloneAttribute() { Akonadi::Pop3ResourceAttribute attr; QString accountName = QStringLiteral("foo"); attr.setPop3AccountName(accountName); Akonadi::Pop3ResourceAttribute *result = attr.clone(); QVERIFY(attr == *result); delete result; } QTEST_MAIN(Pop3ResourceAttributeTest) diff --git a/autotests/libs/proxymodelstest.cpp b/autotests/libs/proxymodelstest.cpp index 0d8a74ada..4804316e2 100644 --- a/autotests/libs/proxymodelstest.cpp +++ b/autotests/libs/proxymodelstest.cpp @@ -1,126 +1,126 @@ /* Copyright (c) 2010 Till Adam 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 class KRFPTestModel : public KRecursiveFilterProxyModel { public: KRFPTestModel(QObject *parent) : KRecursiveFilterProxyModel(parent) { } bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const override { const QModelIndex modelIndex = sourceModel()->index(sourceRow, 0, sourceParent); return !modelIndex.data().toString().contains(QStringLiteral("three")); } }; class ProxyModelsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testMatch(); private: QStandardItemModel m_model; KRecursiveFilterProxyModel *m_krfp = nullptr; KRFPTestModel *m_krfptest = nullptr; }; void ProxyModelsTest::initTestCase() { } void ProxyModelsTest::init() { m_model.setRowCount(5); m_model.setColumnCount(1); m_model.setData(m_model.index(0, 0, QModelIndex()), QStringLiteral("one")); QModelIndex idx = m_model.index(1, 0, QModelIndex()); m_model.setData(idx, QStringLiteral("two")); m_model.insertRows(0, 1, idx); m_model.insertColumns(0, 1, idx); m_model.setData(m_model.index(0, 0, idx), QStringLiteral("three")); m_model.setData(m_model.index(2, 0, QModelIndex()), QStringLiteral("three")); m_model.setData(m_model.index(3, 0, QModelIndex()), QStringLiteral("four")); m_model.setData(m_model.index(4, 0, QModelIndex()), QStringLiteral("five")); m_model.setData(m_model.index(4, 0, QModelIndex()), QStringLiteral("mystuff"), Qt::UserRole + 42); m_krfp = new KRecursiveFilterProxyModel(this); m_krfp->setSourceModel(&m_model); m_krfptest = new KRFPTestModel(this); m_krfptest->setSourceModel(m_krfp); // some sanity checks that setup worked QCOMPARE(m_model.rowCount(QModelIndex()), 5); QCOMPARE(m_model.data(m_model.index(0, 0)).toString(), QStringLiteral("one")); QCOMPARE(m_krfp->rowCount(QModelIndex()), 5); QCOMPARE(m_krfp->data(m_krfp->index(0, 0)).toString(), QStringLiteral("one")); QCOMPARE(m_krfptest->rowCount(QModelIndex()), 4); QCOMPARE(m_krfptest->data(m_krfptest->index(0, 0)).toString(), QStringLiteral("one")); QCOMPARE(m_krfp->rowCount(m_krfp->index(1, 0)), 1); QCOMPARE(m_krfptest->rowCount(m_krfptest->index(1, 0)), 0); } void ProxyModelsTest::testMatch() { QModelIndexList results = m_model.match(m_model.index(0, 0), Qt::DisplayRole, QStringLiteral("three")); QCOMPARE(results.size(), 1); results = m_model.match(m_model.index(0, 0), Qt::DisplayRole, QStringLiteral("fourtytwo")); QCOMPARE(results.size(), 0); results = m_model.match(m_model.index(0, 0), Qt::UserRole + 42, QStringLiteral("mystuff")); QCOMPARE(results.size(), 1); results = m_krfp->match(m_krfp->index(0, 0), Qt::DisplayRole, QStringLiteral("three")); QCOMPARE(results.size(), 1); results = m_krfp->match(m_krfp->index(0, 0), Qt::UserRole + 42, QStringLiteral("mystuff")); QCOMPARE(results.size(), 1); results = m_krfptest->match(m_krfptest->index(0, 0), Qt::DisplayRole, QStringLiteral("three")); QCOMPARE(results.size(), 0); results = m_krfptest->match(m_krfptest->index(0, 0), Qt::UserRole + 42, QStringLiteral("mystuff")); QCOMPARE(results.size(), 1); results = m_model.match(QModelIndex(), Qt::DisplayRole, QStringLiteral("three")); QCOMPARE(results.size(), 0); results = m_krfp->match(QModelIndex(), Qt::DisplayRole, QStringLiteral("three")); QCOMPARE(results.size(), 0); results = m_krfptest->match(QModelIndex(), Qt::DisplayRole, QStringLiteral("three")); QCOMPARE(results.size(), 0); const QModelIndex index = m_model.index(0, 0, QModelIndex()); results = m_model.match(index, Qt::DisplayRole, QStringLiteral("three"), -1, Qt::MatchRecursive | Qt::MatchStartsWith | Qt::MatchWrap); QCOMPARE(results.size(), 2); } #include "proxymodelstest.moc" QTEST_MAIN(ProxyModelsTest) diff --git a/autotests/libs/resourceschedulertest.cpp b/autotests/libs/resourceschedulertest.cpp index 9c62d296a..2cf4a41e6 100644 --- a/autotests/libs/resourceschedulertest.cpp +++ b/autotests/libs/resourceschedulertest.cpp @@ -1,373 +1,373 @@ /* Copyright (c) 2009 Thomas McGuire 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 "resourceschedulertest.h" #include "../src/agentbase/resourcescheduler_p.h" -#include +#include #include using namespace Akonadi; QTEST_MAIN(ResourceSchedulerTest) Q_DECLARE_METATYPE(QSet) ResourceSchedulerTest::ResourceSchedulerTest(QObject *parent): QObject(parent) { qRegisterMetaType >(); } void ResourceSchedulerTest::testTaskComparison() { ResourceScheduler::Task t1; t1.type = ResourceScheduler::ChangeReplay; ResourceScheduler::Task t2; t2.type = ResourceScheduler::ChangeReplay; QCOMPARE(t1, t2); QList taskList; taskList.append(t1); QVERIFY(taskList.contains(t2)); ResourceScheduler::Task t3; t3.type = ResourceScheduler::DeleteResourceCollection; QVERIFY(!(t2 == t3)); QVERIFY(!taskList.contains(t3)); ResourceScheduler::Task t4; t4.type = ResourceScheduler::Custom; t4.receiver = this; t4.methodName = "customTask"; t4.argument = QStringLiteral("call1"); ResourceScheduler::Task t5(t4); QVERIFY(t4 == t5); t5.argument = QStringLiteral("call2"); QVERIFY(!(t4 == t5)); } void ResourceSchedulerTest::testChangeReplaySchedule() { ResourceScheduler scheduler; scheduler.setOnline(true); qRegisterMetaType("Akonadi::Collection"); QSignalSpy changeReplaySpy(&scheduler, SIGNAL(executeChangeReplay())); QSignalSpy collectionTreeSyncSpy(&scheduler, SIGNAL(executeCollectionTreeSync())); QSignalSpy syncSpy(&scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection))); QVERIFY(changeReplaySpy.isValid()); QVERIFY(collectionTreeSyncSpy.isValid()); QVERIFY(syncSpy.isValid()); // Schedule a change replay, it should be executed first thing when we enter the // event loop, but not before QVERIFY(scheduler.isEmpty()); scheduler.scheduleChangeReplay(); QVERIFY(!scheduler.isEmpty()); QVERIFY(changeReplaySpy.isEmpty()); QTest::qWait(1); QCOMPARE(changeReplaySpy.count(), 1); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(changeReplaySpy.count(), 1); // Schedule two change replays. The duplicate one should not be executed. changeReplaySpy.clear(); scheduler.scheduleChangeReplay(); scheduler.scheduleChangeReplay(); QVERIFY(changeReplaySpy.isEmpty()); QTest::qWait(1); QCOMPARE(changeReplaySpy.count(), 1); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(changeReplaySpy.count(), 1); // Schedule a second change replay while one is in progress, should give as two signal emissions changeReplaySpy.clear(); scheduler.scheduleChangeReplay(); QVERIFY(changeReplaySpy.isEmpty()); QTest::qWait(1); QCOMPARE(changeReplaySpy.count(), 1); scheduler.scheduleChangeReplay(); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(changeReplaySpy.count(), 2); scheduler.taskDone(); // // Schedule various stuff. // Collection collection(42); changeReplaySpy.clear(); scheduler.scheduleCollectionTreeSync(); scheduler.scheduleChangeReplay(); scheduler.scheduleSync(collection); scheduler.scheduleChangeReplay(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 0); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); // Omit a taskDone() here, there shouldn't be a new signal QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 1); // At this point, we're done, check that nothing else is emitted scheduler.taskDone(); QVERIFY(scheduler.isEmpty()); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 1); } void ResourceSchedulerTest::customTaskNoArg() { ++mCustomCallCount; } void ResourceSchedulerTest::customTask(const QVariant &argument) { ++mCustomCallCount; mLastArgument = argument; } void ResourceSchedulerTest::testCustomTask() { ResourceScheduler scheduler; scheduler.setOnline(true); mCustomCallCount = 0; scheduler.scheduleCustomTask(this, "customTask", QStringLiteral("call1")); scheduler.scheduleCustomTask(this, "customTask", QStringLiteral("call1")); scheduler.scheduleCustomTask(this, "customTask", QStringLiteral("call2")); scheduler.scheduleCustomTask(this, "customTaskNoArg", QVariant()); QCOMPARE(mCustomCallCount, 0); QTest::qWait(1); QCOMPARE(mCustomCallCount, 1); QCOMPARE(mLastArgument.toString(), QStringLiteral("call1")); scheduler.taskDone(); QVERIFY(!scheduler.isEmpty()); QTest::qWait(1); QCOMPARE(mCustomCallCount, 2); QCOMPARE(mLastArgument.toString(), QStringLiteral("call2")); scheduler.taskDone(); QVERIFY(!scheduler.isEmpty()); QTest::qWait(1); QCOMPARE(mCustomCallCount, 3); scheduler.taskDone(); QVERIFY(scheduler.isEmpty()); } void ResourceSchedulerTest::testCompression() { ResourceScheduler scheduler; scheduler.setOnline(true); qRegisterMetaType("Akonadi::Collection"); qRegisterMetaType("Akonadi::Item"); QSignalSpy fullSyncSpy(&scheduler, SIGNAL(executeFullSync())); QSignalSpy collectionTreeSyncSpy(&scheduler, SIGNAL(executeCollectionTreeSync())); QSignalSpy syncSpy(&scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection))); QSignalSpy fetchSpy(&scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet))); QVERIFY(fullSyncSpy.isValid()); QVERIFY(collectionTreeSyncSpy.isValid()); QVERIFY(syncSpy.isValid()); QVERIFY(fetchSpy.isValid()); // full sync QVERIFY(scheduler.isEmpty()); scheduler.scheduleFullSync(); scheduler.scheduleFullSync(); QTest::qWait(1); // start execution QCOMPARE(fullSyncSpy.count(), 1); scheduler.scheduleCollectionTreeSync(); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(fullSyncSpy.count(), 1); QVERIFY(scheduler.isEmpty()); // collection tree sync QVERIFY(scheduler.isEmpty()); scheduler.scheduleCollectionTreeSync(); scheduler.scheduleCollectionTreeSync(); QTest::qWait(1); // start execution QCOMPARE(collectionTreeSyncSpy.count(), 1); scheduler.scheduleCollectionTreeSync(); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QVERIFY(scheduler.isEmpty()); // sync collection scheduler.scheduleSync(Akonadi::Collection(42)); scheduler.scheduleSync(Akonadi::Collection(42)); QTest::qWait(1); // start execution QCOMPARE(syncSpy.count(), 1); scheduler.scheduleSync(Akonadi::Collection(43)); scheduler.scheduleSync(Akonadi::Collection(42)); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(syncSpy.count(), 2); scheduler.taskDone(); QTest::qWait(2); QVERIFY(scheduler.isEmpty()); // sync collection scheduler.scheduleItemFetch(Akonadi::Item(42), QSet(), QDBusMessage()); scheduler.scheduleItemFetch(Akonadi::Item(42), QSet(), QDBusMessage()); QTest::qWait(1); // start execution QCOMPARE(fetchSpy.count(), 1); scheduler.scheduleItemFetch(Akonadi::Item(43), QSet(), QDBusMessage()); scheduler.scheduleItemFetch(Akonadi::Item(42), QSet(), QDBusMessage()); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(fetchSpy.count(), 2); scheduler.taskDone(); QTest::qWait(2); QVERIFY(scheduler.isEmpty()); } void ResourceSchedulerTest::testSyncCompletion() { ResourceScheduler scheduler; scheduler.setOnline(true); QSignalSpy completionSpy(&scheduler, SIGNAL(fullSyncComplete())); QVERIFY(completionSpy.isValid()); // sync completion does not do compression QVERIFY(scheduler.isEmpty()); scheduler.scheduleFullSyncCompletion(); scheduler.scheduleFullSyncCompletion(); QTest::qWait(1); // start execution QCOMPARE(completionSpy.count(), 1); scheduler.scheduleFullSyncCompletion(); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(completionSpy.count(), 2); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(completionSpy.count(), 3); scheduler.taskDone(); QVERIFY(scheduler.isEmpty()); } void ResourceSchedulerTest::testPriorities() { ResourceScheduler scheduler; scheduler.setOnline(true); qRegisterMetaType("Akonadi::Collection"); qRegisterMetaType("Akonadi::Item"); QSignalSpy changeReplaySpy(&scheduler, SIGNAL(executeChangeReplay())); QSignalSpy fullSyncSpy(&scheduler, SIGNAL(executeFullSync())); QSignalSpy collectionTreeSyncSpy(&scheduler, SIGNAL(executeCollectionTreeSync())); QSignalSpy syncSpy(&scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection))); QSignalSpy fetchSpy(&scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet))); QSignalSpy attributesSyncSpy(&scheduler, SIGNAL(executeCollectionAttributesSync(Akonadi::Collection))); QVERIFY(changeReplaySpy.isValid()); QVERIFY(fullSyncSpy.isValid()); QVERIFY(collectionTreeSyncSpy.isValid()); QVERIFY(syncSpy.isValid()); QVERIFY(fetchSpy.isValid()); QVERIFY(attributesSyncSpy.isValid()); scheduler.scheduleCollectionTreeSync(); scheduler.scheduleChangeReplay(); scheduler.scheduleSync(Akonadi::Collection(42)); scheduler.scheduleItemFetch(Akonadi::Item(42), QSet(), QDBusMessage()); scheduler.scheduleAttributesSync(Akonadi::Collection(42)); scheduler.scheduleFullSync(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 0); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); QCOMPARE(fullSyncSpy.count(), 0); QCOMPARE(fetchSpy.count(), 0); QCOMPARE(attributesSyncSpy.count(), 0); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 0); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); QCOMPARE(fullSyncSpy.count(), 0); QCOMPARE(fetchSpy.count(), 1); QCOMPARE(attributesSyncSpy.count(), 0); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 0); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); QCOMPARE(fullSyncSpy.count(), 0); QCOMPARE(fetchSpy.count(), 1); QCOMPARE(attributesSyncSpy.count(), 1); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 0); QCOMPARE(fullSyncSpy.count(), 0); QCOMPARE(fetchSpy.count(), 1); QCOMPARE(attributesSyncSpy.count(), 1); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 1); QCOMPARE(fullSyncSpy.count(), 0); QCOMPARE(fetchSpy.count(), 1); QCOMPARE(attributesSyncSpy.count(), 1); scheduler.taskDone(); QTest::qWait(1); QCOMPARE(collectionTreeSyncSpy.count(), 1); QCOMPARE(changeReplaySpy.count(), 1); QCOMPARE(syncSpy.count(), 1); QCOMPARE(fullSyncSpy.count(), 1); QCOMPARE(fetchSpy.count(), 1); QCOMPARE(attributesSyncSpy.count(), 1); scheduler.taskDone(); QVERIFY(scheduler.isEmpty()); } diff --git a/autotests/libs/sharedvaluepooltest.cpp b/autotests/libs/sharedvaluepooltest.cpp index 93d5f84ad..7dffd21b8 100644 --- a/autotests/libs/sharedvaluepooltest.cpp +++ b/autotests/libs/sharedvaluepooltest.cpp @@ -1,91 +1,91 @@ /* Copyright (c) 2010 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 "../sharedvaluepool_p.h" -#include +#include #include #include #include #include using namespace Akonadi; class SharedValuePoolTest : public QObject { Q_OBJECT private Q_SLOTS: void testQVector_data() { QTest::addColumn("size"); QTest::newRow("10") << 10; QTest::newRow("100") << 100; } void testQVector() { QFETCH(int, size); QVector data; Internal::SharedValuePool pool; for (int i = 0; i < size; ++i) { QByteArray b(10, (char)i); data.push_back(b); QCOMPARE(pool.sharedValue(b), b); QCOMPARE(pool.sharedValue(b), b); } QBENCHMARK { foreach (const QByteArray &b, data) { pool.sharedValue(b); } } } /*void testQSet_data() { QTest::addColumn( "size" ); QTest::newRow( "10" ) << 10; QTest::newRow( "100" ) << 100; } void testQSet() { QFETCH( int, size ); QVector data; Internal::SharedValuePool pool; for ( int i = 0; i < size; ++i ) { QByteArray b( 10, (char)i ); data.push_back( b ); QCOMPARE( pool.sharedValue( b ), b ); QCOMPARE( pool.sharedValue( b ), b ); } QBENCHMARK { foreach ( const QByteArray &b, data ) pool.sharedValue( b ); } }*/ }; QTEST_MAIN(SharedValuePoolTest) #include "sharedvaluepooltest.moc" diff --git a/autotests/libs/statisticsproxymodeltest.cpp b/autotests/libs/statisticsproxymodeltest.cpp index 873a5bf6e..cc64dc39d 100644 --- a/autotests/libs/statisticsproxymodeltest.cpp +++ b/autotests/libs/statisticsproxymodeltest.cpp @@ -1,250 +1,250 @@ /* Copyright (c) 2016 David Faure 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 #include #include "test_model_helpers.h" using namespace TestModelHelpers; using Akonadi::StatisticsProxyModel; #ifndef Q_OS_WIN void initLocale() { setenv("LC_ALL", "en_US.utf-8", 1); } Q_CONSTRUCTOR_FUNCTION(initLocale) #endif class StatisticsProxyModelTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void shouldDoNothingIfNoExtraColumns(); void shouldShowExtraColumns(); void shouldShowToolTip(); void shouldHandleDataChanged(); void shouldHandleDataChangedInExtraColumn(); private: QStandardItemModel m_model; }; // Helper functions static QString indexToText(const QModelIndex &index) { if (!index.isValid()) { return QStringLiteral("invalid"); } return QString::number(index.row()) + QLatin1Char(',') + QString::number(index.column()) + QLatin1Char(',') + QString::number(reinterpret_cast(index.internalPointer()), 16) + QLatin1String(" in ") + QString::number(reinterpret_cast(index.model()), 16); } static QString indexRowCol(const QModelIndex &index) { if (!index.isValid()) { return QStringLiteral("invalid"); } return QString::number(index.row()) + QLatin1Char(',') + QString::number(index.column()); } static Akonadi::CollectionStatistics makeStats(qint64 unread, qint64 count, qint64 size) { Akonadi::CollectionStatistics stats; stats.setUnreadCount(unread); stats.setCount(count); stats.setSize(size); return stats; } void StatisticsProxyModelTest::initTestCase() { } void StatisticsProxyModelTest::init() { // Prepare the source model to use later on m_model.clear(); m_model.appendRow(makeStandardItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("D"))); m_model.item(0, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("m") << QStringLiteral("n") << QStringLiteral("o") << QStringLiteral("p"))); m_model.item(0, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("q") << QStringLiteral("r") << QStringLiteral("s") << QStringLiteral("t"))); m_model.appendRow(makeStandardItems(QStringList() << QStringLiteral("E") << QStringLiteral("F") << QStringLiteral("G") << QStringLiteral("H"))); m_model.item(1, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("x") << QStringLiteral("y") << QStringLiteral("z") << QStringLiteral("."))); m_model.setHorizontalHeaderLabels(QStringList() << QStringLiteral("H1") << QStringLiteral("H2") << QStringLiteral("H3") << QStringLiteral("H4")); // Set Collection c1 for row A Akonadi::Collection c1(1); c1.setName(QStringLiteral("c1")); c1.setStatistics(makeStats(2, 6, 9)); // unread, count, size in bytes m_model.item(0, 0)->setData(QVariant::fromValue(c1), Akonadi::EntityTreeModel::CollectionRole); // Set Collection c2 for first child (row m) Akonadi::Collection c2(2); c2.setName(QStringLiteral("c2")); c2.setStatistics(makeStats(1, 3, 4)); // unread, count, size in bytes m_model.item(0, 0)->child(0)->setData(QVariant::fromValue(c2), Akonadi::EntityTreeModel::CollectionRole); // Set Collection c2 for first child (row m) Akonadi::Collection c3(3); c3.setName(QStringLiteral("c3")); c3.setStatistics(makeStats(0, 1, 1)); // unread, count, size in bytes m_model.item(0, 0)->child(1)->setData(QVariant::fromValue(c3), Akonadi::EntityTreeModel::CollectionRole); QCOMPARE(extractRowTexts(&m_model, 0), QStringLiteral("ABCD")); QCOMPARE(extractRowTexts(&m_model, 0, m_model.index(0, 0)), QStringLiteral("mnop")); QCOMPARE(extractRowTexts(&m_model, 1, m_model.index(0, 0)), QStringLiteral("qrst")); QCOMPARE(extractRowTexts(&m_model, 1), QStringLiteral("EFGH")); QCOMPARE(extractRowTexts(&m_model, 0, m_model.index(1, 0)), QStringLiteral("xyz.")); QCOMPARE(extractHorizontalHeaderTexts(&m_model), QStringLiteral("H1H2H3H4")); } void StatisticsProxyModelTest::shouldDoNothingIfNoExtraColumns() { // Given a statistics proxy with no extra columns StatisticsProxyModel pm; pm.setExtraColumnsEnabled(false); // When setting it to a source model pm.setSourceModel(&m_model); // Then the proxy should show the same as the model QCOMPARE(pm.rowCount(), m_model.rowCount()); QCOMPARE(pm.columnCount(), m_model.columnCount()); QCOMPARE(pm.rowCount(pm.index(0, 0)), 2); QCOMPARE(pm.index(0, 0).parent(), QModelIndex()); // (verify that the mapFromSource(mapToSource(x)) == x roundtrip works) for (int row = 0; row < pm.rowCount(); ++row) { for (int col = 0; col < pm.columnCount(); ++col) { QCOMPARE(pm.mapFromSource(pm.mapToSource(pm.index(row, col))), pm.index(row, col)); } } QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD")); QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QStringLiteral("mnop")); QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QStringLiteral("qrst")); QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("EFGH")); QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QStringLiteral("xyz.")); QCOMPARE(extractHorizontalHeaderTexts(&pm), QStringLiteral("H1H2H3H4")); } void StatisticsProxyModelTest::shouldShowExtraColumns() { // Given a extra-columns proxy with three extra columns StatisticsProxyModel pm; // When setting it to a source model pm.setSourceModel(&m_model); // Then the proxy should show the extra column QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD269 B")); QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QStringLiteral("mnop134 B")); QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QStringLiteral("qrst 11 B")); QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("EFGH ")); QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QStringLiteral("xyz. ")); QCOMPARE(extractHorizontalHeaderTexts(&pm), QStringLiteral("H1H2H3H4UnreadTotalSize")); // Verify tree structure of proxy const QModelIndex secondParent = pm.index(1, 0); QVERIFY(!secondParent.parent().isValid()); QCOMPARE(indexToText(pm.index(0, 0, secondParent).parent()), indexToText(secondParent)); QCOMPARE(indexToText(pm.index(0, 3, secondParent).parent()), indexToText(secondParent)); QVERIFY(indexToText(pm.index(0, 4)).startsWith(QStringLiteral("0,4,"))); QCOMPARE(indexToText(pm.index(0, 4, secondParent).parent()), indexToText(secondParent)); QVERIFY(indexToText(pm.index(0, 5)).startsWith(QStringLiteral("0,5,"))); QCOMPARE(indexToText(pm.index(0, 5, secondParent).parent()), indexToText(secondParent)); QCOMPARE(pm.index(0, 0).sibling(0, 4).column(), 4); QCOMPARE(pm.index(0, 4).sibling(0, 1).column(), 1); QVERIFY(!pm.canFetchMore(QModelIndex())); } void StatisticsProxyModelTest::shouldShowToolTip() { // Given a extra-columns proxy with three extra columns StatisticsProxyModel pm; pm.setSourceModel(&m_model); // When enabling tooltips and getting the tooltip for the first folder pm.setToolTipEnabled(true); QString toolTip = pm.index(0, 0).data(Qt::ToolTipRole).toString(); // Then the tooltip should contain the expected information toolTip.remove(QStringLiteral("")); toolTip.remove(QStringLiteral("")); QVERIFY2(toolTip.contains(QLatin1String("Total Messages: 6")), qPrintable(toolTip)); QVERIFY2(toolTip.contains(QLatin1String("Unread Messages: 2")), qPrintable(toolTip)); QVERIFY2(toolTip.contains(QLatin1String("Storage Size: 9 B")), qPrintable(toolTip)); QVERIFY2(toolTip.contains(QLatin1String("Subfolder Storage Size: 5 B")), qPrintable(toolTip)); } void StatisticsProxyModelTest::shouldHandleDataChanged() { // Given a extra-columns proxy with three extra columns StatisticsProxyModel pm; pm.setSourceModel(&m_model); QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex))); // When ETM says the collection changed m_model.item(0, 0)->setData(QStringLiteral("a"), Qt::EditRole); // Then the change should be notified to the proxy -- including the extra columns QCOMPARE(dataChangedSpy.count(), 1); QCOMPARE(indexRowCol(dataChangedSpy.at(0).at(0).toModelIndex()), QStringLiteral("0,0")); QCOMPARE(indexRowCol(dataChangedSpy.at(0).at(1).toModelIndex()), QStringLiteral("0,6")); QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("aBCD269 B")); } void StatisticsProxyModelTest::shouldHandleDataChangedInExtraColumn() { // Given a extra-columns proxy with three extra columns StatisticsProxyModel pm; pm.setSourceModel(&m_model); QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex))); // When the proxy wants to signal a change in an extra column Akonadi::Collection c1(1); c1.setName(QStringLiteral("c1")); c1.setStatistics(makeStats(3, 5, 8)); // changed: unread, count, size in bytes m_model.item(0, 0)->setData(QVariant::fromValue(c1), Akonadi::EntityTreeModel::CollectionRole); // Then the change should be available and notified QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD358 B")); QCOMPARE(dataChangedSpy.count(), 1); QCOMPARE(indexRowCol(dataChangedSpy.at(0).at(0).toModelIndex()), QStringLiteral("0,0")); QCOMPARE(indexRowCol(dataChangedSpy.at(0).at(1).toModelIndex()), QStringLiteral("0,6")); } #include "statisticsproxymodeltest.moc" QTEST_MAIN(StatisticsProxyModelTest) diff --git a/autotests/libs/tagselectwidgettest.cpp b/autotests/libs/tagselectwidgettest.cpp index fa3e60d4b..02184ffac 100644 --- a/autotests/libs/tagselectwidgettest.cpp +++ b/autotests/libs/tagselectwidgettest.cpp @@ -1,40 +1,40 @@ /* Copyright (c) 2015-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 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 "tagselectwidgettest.h" #include "../src/widgets/tagselectwidget.h" #include "../src/widgets/tageditwidget.h" -#include +#include TagSelectWidgetTest::TagSelectWidgetTest(QObject *parent) : QObject(parent) { } TagSelectWidgetTest::~TagSelectWidgetTest() { } void TagSelectWidgetTest::shouldHaveDefaultValue() { Akonadi::TagSelectWidget widget; Akonadi::TagEditWidget *edit = widget.findChild(QStringLiteral("tageditwidget")); QVERIFY(edit); } QTEST_MAIN(TagSelectWidgetTest) diff --git a/autotests/libs/testrunner/setup.cpp b/autotests/libs/testrunner/setup.cpp index a71ca575b..30cc03c4f 100644 --- a/autotests/libs/testrunner/setup.cpp +++ b/autotests/libs/testrunner/setup.cpp @@ -1,449 +1,449 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * Copyright (c) 2013 Volker Krause * * 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, see . */ #include "setup.h" #include "config.h" //krazy:exclude=includes #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include bool SetupTest::startAkonadiDaemon() { Q_ASSERT(Akonadi::ServerManager::hasInstanceIdentifier()); if (!mAkonadiDaemonProcess) { mAkonadiDaemonProcess = new KProcess(this); connect(mAkonadiDaemonProcess, QOverload::of(&KProcess::finished), this, &SetupTest::slotAkonadiDaemonProcessFinished); } mAkonadiDaemonProcess->setProgram(Akonadi::StandardDirs::findExecutable(QStringLiteral("akonadi_control")), { QStringLiteral("--instance"), instanceId() }); mAkonadiDaemonProcess->start(); const bool started = mAkonadiDaemonProcess->waitForStarted(5000); qDebug() << "Started akonadi daemon with pid:" << mAkonadiDaemonProcess->pid(); return started; } void SetupTest::stopAkonadiDaemon() { if (!mAkonadiDaemonProcess) { return; } disconnect(mAkonadiDaemonProcess, SIGNAL(finished(int)), this, nullptr); mAkonadiDaemonProcess->terminate(); const bool finished = mAkonadiDaemonProcess->waitForFinished(5000); if (!finished) { qDebug() << "Problem finishing process."; } mAkonadiDaemonProcess->close(); mAkonadiDaemonProcess->deleteLater(); mAkonadiDaemonProcess = nullptr; } void SetupTest::setupAgents() { if (mAgentsCreated) { return; } mAgentsCreated = true; Config *config = Config::instance(); const auto agents = config->agents(); for (const auto agent : agents) { qDebug() << "Creating agent" << agent.first << "..."; ++mSetupJobCount; Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob(agent.first, this); job->setProperty("sync", agent.second); connect(job, &Akonadi::AgentInstanceCreateJob::result, this, &SetupTest::agentCreationResult); job->start(); } if (isSetupDone()) { Q_EMIT setupDone(); } } void SetupTest::agentCreationResult(KJob *job) { qDebug() << "Agent created"; --mSetupJobCount; if (job->error()) { qCritical() << job->errorString(); setupFailed(); return; } const bool needsSync = job->property("sync").toBool(); if (needsSync) { ++mSetupJobCount; qDebug() << "Scheduling Agent sync"; Akonadi::ResourceSynchronizationJob *sync = new Akonadi::ResourceSynchronizationJob( qobject_cast(job)->instance(), this); connect(sync, &Akonadi::ResourceSynchronizationJob::result, this, &SetupTest::synchronizationResult); sync->start(); } if (isSetupDone()) { Q_EMIT setupDone(); } } void SetupTest::synchronizationResult(KJob *job) { qDebug() << "Sync done"; --mSetupJobCount; if (job->error()) { qCritical() << job->errorString(); setupFailed(); } if (isSetupDone()) { Q_EMIT setupDone(); } } void SetupTest::serverStateChanged(Akonadi::ServerManager::State state) { if (state == Akonadi::ServerManager::Running) { setupAgents(); } else if (mShuttingDown && state == Akonadi::ServerManager::NotRunning) { shutdownHarder(); } } void SetupTest::copyXdgDirectory(const QString &src, const QString &dst) { qDebug() << "Copying" << src << "to" << dst; const QDir srcDir(src); const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); for (const auto &fi : entries) { if (fi.isDir()) { if (fi.fileName() == QStringLiteral("akonadi")) { // namespace according to instance identifier #ifdef Q_OS_WIN const bool isXdgConfig = src.contains(QLatin1String("/xdgconfig/")); copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/") + (isXdgConfig ? QStringLiteral("config/") : QStringLiteral("data/")) + QStringLiteral("instance/") + instanceId()); #else copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/instance/") + instanceId()); #endif } else { copyDirectory(fi.absoluteFilePath(), dst + QLatin1Char('/') + fi.fileName()); } } else { if (fi.fileName().startsWith(QStringLiteral("akonadi_")) && fi.fileName().endsWith(QStringLiteral("rc"))) { // namespace according to instance identifier const QString baseName = fi.fileName().left(fi.fileName().size() - 2); const QString dstPath = dst + QLatin1Char('/') + Akonadi::ServerManager::addNamespace(baseName) + QStringLiteral("rc"); if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; } } else { const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; } } } } } void SetupTest::copyDirectory(const QString &src, const QString &dst) { const QDir srcDir(src); QDir::root().mkpath(dst); const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); for (const auto &fi : entries) { const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); if (fi.isDir()) { copyDirectory(fi.absoluteFilePath(), dstPath); } else { if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; } } } } void SetupTest::createTempEnvironment() { qDebug() << "Creating test environment in" << basePath(); const Config *config = Config::instance(); #ifdef Q_OS_WIN // Always copy the generic xdgconfig dir copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath()); if (!config->xdgConfigHome().isEmpty()) { copyXdgDirectory(config->xdgConfigHome(), basePath()); } copyXdgDirectory(config->xdgDataHome(), basePath()); setEnvironmentVariable("XDG_DATA_HOME", basePath()); setEnvironmentVariable("XDG_CONFIG_HOME", basePath()); writeAkonadiserverrc(basePath() + QStringLiteral("/akonadi/config/instance/%1/akonadiserverrc").arg(instanceId())); #else const QDir tmpDir(basePath()); const QString testRunnerDataDir = QStringLiteral("data"); const QString testRunnerConfigDir = QStringLiteral("config"); const QString testRunnerTmpDir = QStringLiteral("tmp"); tmpDir.mkpath(testRunnerConfigDir); tmpDir.mkpath(testRunnerDataDir); tmpDir.mkpath(testRunnerTmpDir); // Always copy the generic xdgconfig dir copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath() + testRunnerConfigDir); if (!config->xdgConfigHome().isEmpty()) { copyXdgDirectory(config->xdgConfigHome(), basePath() + testRunnerConfigDir); } copyXdgDirectory(config->xdgDataHome(), basePath() + testRunnerDataDir); setEnvironmentVariable("XDG_DATA_HOME", basePath() + testRunnerDataDir); setEnvironmentVariable("XDG_CONFIG_HOME", basePath() + testRunnerConfigDir); setEnvironmentVariable("TMPDIR", basePath() + testRunnerTmpDir); writeAkonadiserverrc(basePath() + testRunnerConfigDir + QStringLiteral("/akonadi/instance/%1/akonadiserverrc").arg(instanceId())); #endif QString backend; if (Config::instance()->dbBackend() == QLatin1String("pgsql")) { backend = QStringLiteral("postgresql"); } else { backend = Config::instance()->dbBackend(); } setEnvironmentVariable("TESTRUNNER_DB_ENVIRONMENT", backend); } void SetupTest::writeAkonadiserverrc(const QString &path) { QString backend; if (Config::instance()->dbBackend() == QLatin1String("sqlite")) { backend = QStringLiteral("QSQLITE3"); } else if (Config::instance()->dbBackend() == QLatin1String("mysql")) { backend = QStringLiteral("QMYSQL"); } else if (Config::instance()->dbBackend() == QLatin1String("pgsql")) { backend = QStringLiteral("QPSQL"); } else { qCritical("Invalid backend name %s", qPrintable(backend)); return; } QSettings settings(path, QSettings::IniFormat); settings.beginGroup(QStringLiteral("General")); settings.setValue(QStringLiteral("Driver"), backend); settings.endGroup(); settings.beginGroup(QStringLiteral("Search")); settings.setValue(QStringLiteral("Manager"), QStringLiteral("Dummy")); settings.endGroup(); settings.beginGroup(QStringLiteral("Debug")); settings.setValue(QStringLiteral("Tracer"), QStringLiteral("null")); settings.endGroup(); qDebug() << "Written akonadiserverrc to" << settings.fileName(); } void SetupTest::cleanTempEnvironment() { #ifdef Q_OS_WIN QDir(basePath() + QStringLiteral("akonadi/config/instance/") + instanceId()).removeRecursively(); QDir(basePath() + QStringLiteral("akonadi/data/instance/") + instanceId()).removeRecursively(); #else QDir(basePath()).removeRecursively(); #endif } SetupTest::SetupTest() : mAkonadiDaemonProcess(nullptr) , mShuttingDown(false) , mAgentsCreated(false) , mTrackAkonadiProcess(true) , mSetupJobCount(0) , mExitCode(0) { setupInstanceId(); cleanTempEnvironment(); createTempEnvironment(); // switch off agent auto-starting by default, can be re-enabled if really needed inside the config.xml setEnvironmentVariable("AKONADI_DISABLE_AGENT_AUTOSTART", QStringLiteral("true")); setEnvironmentVariable("AKONADI_TESTRUNNER_PID", QString::number(QCoreApplication::instance()->applicationPid())); // enable all debugging, so we get some useful information when test fails setEnvironmentVariable("QT_LOGGING_RULES", QStringLiteral("* = true\n" "qt.* = false\n" "kf5.coreaddons.desktopparser.debug = false")); QHashIterator iter(Config::instance()->envVars()); while (iter.hasNext()) { iter.next(); qDebug() << "Setting environment variable" << iter.key() << "=" << iter.value(); setEnvironmentVariable(iter.key().toLocal8Bit(), iter.value()); } // No kres-migrator please KConfig migratorConfig(basePath() + QStringLiteral("config/kres-migratorrc")); KConfigGroup migrationCfg(&migratorConfig, "Migration"); migrationCfg.writeEntry("Enabled", false); connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &SetupTest::serverStateChanged); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Akonadi.Testrunner-") + QString::number(QCoreApplication::instance()->applicationPid())); QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), this, QDBusConnection::ExportScriptableSlots); } SetupTest::~SetupTest() { cleanTempEnvironment(); } void SetupTest::shutdown() { if (mShuttingDown) { return; } mShuttingDown = true; switch (Akonadi::ServerManager::self()->state()) { case Akonadi::ServerManager::Running: case Akonadi::ServerManager::Starting: case Akonadi::ServerManager::Upgrading: qDebug() << "Shutting down Akonadi control..."; Akonadi::ServerManager::self()->stop(); // safety timeout QTimer::singleShot(30 * 1000, this, &SetupTest::shutdownHarder); break; case Akonadi::ServerManager::NotRunning: case Akonadi::ServerManager::Broken: shutdownHarder(); Q_FALLTHROUGH(); case Akonadi::ServerManager::Stopping: // safety timeout QTimer::singleShot(30 * 1000, this, &SetupTest::shutdownHarder); break; } } void SetupTest::shutdownHarder() { qDebug(); mShuttingDown = false; stopAkonadiDaemon(); QCoreApplication::instance()->exit(mExitCode); } void SetupTest::restartAkonadiServer() { qDebug(); disconnect(mAkonadiDaemonProcess, SIGNAL(finished(int)), this, nullptr); Akonadi::ServerManager::self()->stop(); const bool shutdownResult = mAkonadiDaemonProcess->waitForFinished(); if (!shutdownResult) { qWarning() << "Akonadi control did not shut down in time, killing it."; mAkonadiDaemonProcess->kill(); } // we don't use Control::start() since we want to be able to kill // it forcefully, if necessary, and know the pid startAkonadiDaemon(); // from here on, the server exiting is an error again connect(mAkonadiDaemonProcess, SIGNAL(finished(int)), this, SLOT(slotAkonadiDaemonProcessFinished(int))); } QString SetupTest::basePath() const { #ifdef Q_OS_WIN // On Windows we are forced to share the same data directory as production instances // because there's no way to override QStandardPaths like we can on Unix. // This means that on Windows we rely on Instances providing us the necessary isolation return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); #else QString sysTempDirPath = QDir::tempPath(); #ifdef Q_OS_UNIX // QDir::tempPath() makes sure to use the fully sym-link exploded // absolute path to the temp dir. That is nice, but on OSX it makes // that path really long. MySQL chokes on this, for it's socket path, // so work around that sysTempDirPath = QStringLiteral("/tmp"); #endif const QDir sysTempDir(sysTempDirPath); const QString tempDir = QStringLiteral("/akonadi_testrunner-%1/") .arg(QCoreApplication::instance()->applicationPid()); if (!sysTempDir.exists(tempDir)) { sysTempDir.mkdir(tempDir); } return sysTempDirPath + tempDir; #endif } void SetupTest::slotAkonadiDaemonProcessFinished(int exitCode) { if (mTrackAkonadiProcess || exitCode != EXIT_SUCCESS) { qWarning() << "Akonadi server process was terminated externally!"; Q_EMIT serverExited(exitCode); } mAkonadiDaemonProcess = nullptr; } void SetupTest::trackAkonadiProcess(bool track) { mTrackAkonadiProcess = track; } QString SetupTest::instanceId() const { return QStringLiteral("testrunner-") + QString::number(QCoreApplication::instance()->applicationPid()); } void SetupTest::setupInstanceId() { setEnvironmentVariable("AKONADI_INSTANCE", instanceId()); } bool SetupTest::isSetupDone() const { qDebug() << "isSetupDone:" << mSetupJobCount << mExitCode; return mSetupJobCount == 0 && mExitCode == 0; } void SetupTest::setupFailed() { mExitCode = 1; shutdown(); } void SetupTest::setEnvironmentVariable(const QByteArray &name, const QString &value) { mEnvVars.push_back(qMakePair(name, value.toLocal8Bit())); qputenv(name.constData(), value.toLatin1()); } QVector< SetupTest::EnvVar > SetupTest::environmentVariables() const { return mEnvVars; } diff --git a/autotests/libs/virtualresource.cpp b/autotests/libs/virtualresource.cpp index b126bfdc3..aaf44390b 100644 --- a/autotests/libs/virtualresource.cpp +++ b/autotests/libs/virtualresource.cpp @@ -1,111 +1,111 @@ /* 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. */ #include "virtualresource.h" #include #include #include #include #include #include -#include +#include #include #include #define EXEC(job) \ do { \ if (!job->exec()) { \ kFatal() << "Job failed: " << job->errorString(); \ } \ } while ( 0 ) using namespace Akonadi; VirtualResource::VirtualResource(const QString &name, QObject *parent) : QObject(parent), mResourceName(name) { // QDBusInterface *interface = new QDBusInterface(ServerManager::serviceName(ServerManager::Control), // QString::fromLatin1("/"), // QString::fromLatin1("org.freedesktop.Akonadi.AgentManager"), // DBusConnectionPool::threadConnection(), this); // if (interface->isValid()) { // const QDBusMessage reply = interface->call(QString::fromUtf8("createAgentInstance"), name, QStringList()); // if (reply.type() == QDBusMessage::ErrorMessage) { // // This means that the resource doesn't provide a synchronizeCollectionAttributes method, so we just finish the job // return; // } // } else { // Q_ASSERT(false); // } // mSession = new Akonadi::Session(name.toLatin1(), this); // Since this is in the same process as the test, all jobs in the test get executed in the resource session by default SessionPrivate::createDefaultSession(name.toLatin1()); mSession = Session::defaultSession(); ResourceSelectJob *select = new ResourceSelectJob(name, mSession); EXEC(select); } VirtualResource::~VirtualResource() { if (mRootCollection.isValid()) { CollectionDeleteJob *d = new CollectionDeleteJob(mRootCollection, mSession); EXEC(d); } } Akonadi::Collection VirtualResource::createCollection(const Akonadi::Collection &collection) { // kDebug() << collection.name() << collection.parentCollection().remoteId(); // kDebug() << "contentMimeTypes: " << collection.contentMimeTypes(); Q_ASSERT(!collection.name().isEmpty()); Collection col = collection; if (!col.parentCollection().isValid()) { col.setParentCollection(mRootCollection); } CollectionCreateJob *create = new CollectionCreateJob(col, mSession); EXEC(create); return create->collection(); } Akonadi::Collection VirtualResource::createRootCollection(const Akonadi::Collection &collection) { kDebug() << collection.name(); mRootCollection = createCollection(collection); return mRootCollection; } Akonadi::Item VirtualResource::createItem(const Akonadi::Item &item, const Collection &parent) { ItemCreateJob *create = new ItemCreateJob(item, parent, mSession); EXEC(create); return create->item(); } void VirtualResource::reset() { Q_ASSERT(mRootCollection.isValid()); Akonadi::Collection col = mRootCollection; CollectionDeleteJob *d = new CollectionDeleteJob(mRootCollection, mSession); EXEC(d); col.setId(-1); createRootCollection(col); } diff --git a/src/agentserver/agentthread.cpp b/src/agentserver/agentthread.cpp index b71cb24e1..5e63772a6 100644 --- a/src/agentserver/agentthread.cpp +++ b/src/agentserver/agentthread.cpp @@ -1,62 +1,62 @@ /* Copyright (c) 2010 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 "agentthread.h" #include "akonadiagentserver_debug.h" #include // Needed for WId #include -#include +#include using namespace Akonadi; AgentThread::AgentThread(const QString &identifier, QObject *factory, QObject *parent) : QThread(parent) , m_identifier(identifier) , m_factory(factory) , m_instance(nullptr) { } void AgentThread::run() { const bool invokeSucceeded = QMetaObject::invokeMethod(m_factory, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(QObject*, m_instance), Q_ARG(QString, m_identifier)); if (invokeSucceeded) { qCDebug(AKONADIAGENTSERVER_LOG) << Q_FUNC_INFO << "agent instance created: " << m_instance; } else { qCDebug(AKONADIAGENTSERVER_LOG) << Q_FUNC_INFO << "agent instance creation failed"; } exec(); delete m_instance; m_instance = nullptr; } void AgentThread::configure(qlonglong windowId) { QMetaObject::invokeMethod(m_instance, "configure", Qt::DirectConnection, Q_ARG(WId, (WId)windowId)); } diff --git a/src/core/changerecorder.cpp b/src/core/changerecorder.cpp index 39ed047e1..50cabbe98 100644 --- a/src/core/changerecorder.cpp +++ b/src/core/changerecorder.cpp @@ -1,128 +1,128 @@ /* 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 "changerecorder.h" #include "changerecorder_p.h" -#include +#include #include using namespace Akonadi; ChangeRecorder::ChangeRecorder(QObject *parent) : Monitor(new ChangeRecorderPrivate(nullptr, this), parent) { } ChangeRecorder::ChangeRecorder(ChangeRecorderPrivate *privateclass, QObject *parent) : Monitor(privateclass, parent) { } ChangeRecorder::~ChangeRecorder() { } void ChangeRecorder::setConfig(QSettings *settings) { Q_D(ChangeRecorder); if (settings) { d->settings = settings; Q_ASSERT(d->pendingNotifications.isEmpty()); d->loadNotifications(); } else if (d->settings) { if (d->enableChangeRecording) { d->saveNotifications(); } d->settings = settings; } } void ChangeRecorder::replayNext() { Q_D(ChangeRecorder); if (!d->enableChangeRecording) { return; } if (!d->pendingNotifications.isEmpty()) { const auto msg = d->pendingNotifications.head(); if (d->ensureDataAvailable(msg)) { d->emitNotification(msg); } else if (d->translateAndCompress(d->pipeline, msg)) { // The msg is now in both pipeline and pendingNotifications. // When data is available, MonitorPrivate::flushPipeline will emitNotification. // When changeProcessed is called, we'll finally remove it from pendingNotifications. } else { // In the case of a move where both source and destination are // ignored, we ignore the message and process the next one. d->dequeueNotification(); return replayNext(); } } else { // This is necessary when none of the notifications were accepted / processed // above, and so there is no one to call changeProcessed() and the ChangeReplay task // will be stuck forever in the ResourceScheduler. Q_EMIT nothingToReplay(); } } bool ChangeRecorder::isEmpty() const { Q_D(const ChangeRecorder); return d->pendingNotifications.isEmpty(); } void ChangeRecorder::changeProcessed() { Q_D(ChangeRecorder); if (!d->enableChangeRecording) { return; } // changerecordertest.cpp calls changeProcessed after receiving nothingToReplay, // so test for emptiness. Not sure real code does this though. // Q_ASSERT( !d->pendingNotifications.isEmpty() ) if (!d->pendingNotifications.isEmpty()) { d->dequeueNotification(); } } void ChangeRecorder::setChangeRecordingEnabled(bool enable) { Q_D(ChangeRecorder); if (d->enableChangeRecording == enable) { return; } d->enableChangeRecording = enable; if (enable) { d->m_needFullSave = true; d->notificationsLoaded(); } else { d->dispatchNotifications(); } } QString Akonadi::ChangeRecorder::dumpNotificationListToString() const { Q_D(const ChangeRecorder); return d->dumpNotificationListToString(); } diff --git a/src/core/entitycache_p.h b/src/core/entitycache_p.h index 71c1cd12e..b9399434b 100644 --- a/src/core/entitycache_p.h +++ b/src/core/entitycache_p.h @@ -1,544 +1,544 @@ /* 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_ENTITYCACHE_P_H #define AKONADI_ENTITYCACHE_P_H #include "item.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "collection.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "tag.h" #include "tagfetchjob.h" #include "tagfetchscope.h" #include "session.h" #include "akonaditests_export.h" -#include +#include #include #include #include #include class KJob; Q_DECLARE_METATYPE(QList) namespace Akonadi { /** @internal QObject part of EntityCache. */ class AKONADI_TESTS_EXPORT EntityCacheBase : public QObject { Q_OBJECT public: explicit EntityCacheBase(Session *session, QObject *parent = nullptr); void setSession(Session *session); protected: Session *session = nullptr; Q_SIGNALS: void dataAvailable(); private Q_SLOTS: virtual void processResult(KJob *job) = 0; }; template struct EntityCacheNode { EntityCacheNode() : pending(false) , invalid(false) { } EntityCacheNode(typename T::Id id) : entity(T(id)) , pending(true) , invalid(false) { } T entity; bool pending; bool invalid; }; /** * @internal * A in-memory FIFO cache for a small amount of Item or Collection objects. */ template class EntityCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityCache(int maxCapacity, Session *session = nullptr, QObject *parent = nullptr) : EntityCacheBase(session, parent) , mCapacity(maxCapacity) { } ~EntityCache() override { qDeleteAll(mCache); } /** Object is available in the cache and can be retrieved. */ bool isCached(typename T::Id id) const { EntityCacheNode *node = cacheNodeForId(id); return node && !node->pending; } /** Object has been requested but is not yet loaded into the cache or is already available. */ bool isRequested(typename T::Id id) const { return cacheNodeForId(id); } /** Returns the cached object if available, an empty instance otherwise. */ virtual T retrieve(typename T::Id id) const { EntityCacheNode *node = cacheNodeForId(id); if (node && !node->pending && !node->invalid) { return node->entity; } return T(); } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate(typename T::Id id) { EntityCacheNode *node = cacheNodeForId(id); if (node) { node->invalid = true; } } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update(typename T::Id id, const FetchScope &scope) { EntityCacheNode *node = cacheNodeForId(id); if (node) { mCache.removeAll(node); if (node->pending) { request(id, scope); } delete node; } } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ virtual bool ensureCached(typename T::Id id, const FetchScope &scope) { EntityCacheNode *node = cacheNodeForId(id); if (!node) { request(id, scope); return false; } return !node->pending; } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ virtual void request(typename T::Id id, const FetchScope &scope) { Q_ASSERT(!isRequested(id)); shrinkCache(); EntityCacheNode *node = new EntityCacheNode(id); FetchJob *job = createFetchJob(id, scope); job->setProperty("EntityCacheNode", QVariant::fromValue(id)); connect(job, SIGNAL(result(KJob *)), SLOT(processResult(KJob *))); mCache.enqueue(node); } private: EntityCacheNode *cacheNodeForId(typename T::Id id) const { for (typename QQueue *>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it) { if ((*it)->entity.id() == id) { return *it; } } return nullptr; } void processResult(KJob *job) override { if (job->error()) { //This can happen if we have stale notifications for items that have already been removed } typename T::Id id = job->property("EntityCacheNode").template value(); EntityCacheNode *node = cacheNodeForId(id); if (!node) { return; // got replaced in the meantime } node->pending = false; extractResult(node, job); // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if (node->entity.id() != id) { // TODO: Recursion guard? If this is called with non-existing ids, the if will never be true! node->entity.setId(id); node->invalid = true; } Q_EMIT dataAvailable(); } void extractResult(EntityCacheNode *node, KJob *job) const; inline FetchJob *createFetchJob(typename T::Id id, const FetchScope &scope) { FetchJob *fetch = new FetchJob(T(id), session); fetch->setFetchScope(scope); return fetch; } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while (mCache.size() >= mCapacity && !mCache.first()->pending) { delete mCache.dequeue(); } } private: QQueue *> mCache; int mCapacity; }; template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { CollectionFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->collections().isEmpty()) { node->entity = Collection(); } else { node->entity = fetch->collections().at(0); } } template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->items().isEmpty()) { node->entity = Item(); } else { node->entity = fetch->items().at(0); } } template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { TagFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->tags().isEmpty()) { node->entity = Tag(); } else { node->entity = fetch->tags().at(0); } } template<> inline CollectionFetchJob *EntityCache::createFetchJob(Collection::Id id, const CollectionFetchScope &scope) { CollectionFetchJob *fetch = new CollectionFetchJob(Collection(id), CollectionFetchJob::Base, session); fetch->setFetchScope(scope); return fetch; } typedef EntityCache CollectionCache; typedef EntityCache ItemCache; typedef EntityCache TagCache; template struct EntityListCacheNode { EntityListCacheNode() : pending(false) , invalid(false) { } EntityListCacheNode(typename T::Id id) : entity(id) , pending(true) , invalid(false) { } T entity; bool pending; bool invalid; }; template class EntityListCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityListCache(int maxCapacity, Session *session = nullptr, QObject *parent = nullptr) : EntityCacheBase(session, parent) , mCapacity(maxCapacity) { } ~EntityListCache() override { qDeleteAll(mCache); } /** Returns the cached object if available, an empty instance otherwise. */ typename T::List retrieve(const QList &ids) const { typename T::List list; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node || node->pending || node->invalid) { return typename T::List(); } list << node->entity; } return list; } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ bool ensureCached(const QList &ids, const FetchScope &scope) { QList toRequest; bool result = true; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node) { toRequest << id; continue; } if (node->pending) { result = false; } } if (!toRequest.isEmpty()) { request(toRequest, scope, ids); return false; } return result; } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate(const QList &ids) { for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (node) { node->invalid = true; } } } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update(const QList &ids, const FetchScope &scope) { QList toRequest; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (node) { mCache.remove(id); if (node->pending) { toRequest << id; } delete node; } } if (!toRequest.isEmpty()) { request(toRequest, scope); } } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ void request(const QList &ids, const FetchScope &scope, const QList &preserveIds = QList()) { Q_ASSERT(isNotRequested(ids)); shrinkCache(preserveIds); for (typename T::Id id : ids) { EntityListCacheNode *node = new EntityListCacheNode(id); mCache.insert(id, node); } FetchJob *job = createFetchJob(ids, scope); job->setProperty("EntityListCacheIds", QVariant::fromValue>(ids)); connect(job, SIGNAL(result(KJob *)), SLOT(processResult(KJob *))); } bool isNotRequested(const QList &ids) const { for (typename T::Id id : ids) { if (mCache.contains(id)) { return false; } } return true; } /** Object is available in the cache and can be retrieved. */ bool isCached(const QList &ids) const { for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node || node->pending) { return false; } } return true; } private: /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache(const QList &preserveIds) { typename QHash *>::Iterator iter = mCache.begin(); while (iter != mCache.end() && mCache.size() >= mCapacity) { if (iter.value()->pending || preserveIds.contains(iter.key())) { ++iter; continue; } delete iter.value(); iter = mCache.erase(iter); } } inline FetchJob *createFetchJob(const QList &ids, const FetchScope &scope) { FetchJob *job = new FetchJob(ids, session); job->setFetchScope(scope); return job; } void processResult(KJob *job) override { if (job->error()) { qWarning() << job->errorString(); } const QList ids = job->property("EntityListCacheIds").value>(); typename T::List entities; extractResults(job, entities); for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node) { continue; // got replaced in the meantime } node->pending = false; T result; typename T::List::Iterator iter = entities.begin(); for (; iter != entities.end(); ++iter) { if ((*iter).id() == id) { result = *iter; entities.erase(iter); break; } } // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if (!result.isValid()) { node->entity = T(id); node->invalid = true; } else { node->entity = result; } } Q_EMIT dataAvailable(); } void extractResults(KJob *job, typename T::List &entities) const; private: QHash *> mCache; int mCapacity; }; template<> inline void EntityListCache::extractResults(KJob *job, Collection::List &collections) const { CollectionFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); collections = fetch->collections(); } template<> inline void EntityListCache::extractResults(KJob *job, Item::List &items) const { ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); items = fetch->items(); } template<> inline void EntityListCache::extractResults(KJob *job, Tag::List &tags) const { TagFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); tags = fetch->tags(); } template<> inline CollectionFetchJob *EntityListCache::createFetchJob(const QList &ids, const CollectionFetchScope &scope) { CollectionFetchJob *fetch = new CollectionFetchJob(ids, CollectionFetchJob::Base, session); fetch->setFetchScope(scope); return fetch; } typedef EntityListCache CollectionListCache; typedef EntityListCache ItemListCache; typedef EntityListCache TagListCache; } #endif diff --git a/src/core/jobs/agentinstancecreatejob.cpp b/src/core/jobs/agentinstancecreatejob.cpp index dcb3e15c2..dc545c2fa 100644 --- a/src/core/jobs/agentinstancecreatejob.cpp +++ b/src/core/jobs/agentinstancecreatejob.cpp @@ -1,222 +1,222 @@ /* 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 "agentinstancecreatejob.h" #include "agentinstance.h" #include "agentmanager.h" #include "agentmanager_p.h" #include "controlinterface.h" #include "KDBusConnectionPool" #include "kjobprivatebase_p.h" #include "servermanager.h" -#include +#include #include #include #ifdef Q_OS_UNIX #include #include #endif using namespace Akonadi; static const int safetyTimeout = 10000; // ms namespace Akonadi { /** * @internal */ class AgentInstanceCreateJobPrivate : public KJobPrivateBase { public: AgentInstanceCreateJobPrivate(AgentInstanceCreateJob *parent) : q(parent) , parentWidget(nullptr) , safetyTimer(new QTimer(parent)) , doConfig(false) , tooLate(false) { QObject::connect(AgentManager::self(), SIGNAL(instanceAdded(Akonadi::AgentInstance)), q, SLOT(agentInstanceAdded(Akonadi::AgentInstance))); QObject::connect(safetyTimer, &QTimer::timeout, q, [this]() {timeout(); }); } void agentInstanceAdded(const AgentInstance &instance) { if (agentInstance == instance && !tooLate) { safetyTimer->stop(); if (doConfig) { // return from dbus call first before doing the next one QTimer::singleShot(0, q, [this]() { doConfigure(); }); } else { q->emitResult(); } } } void doConfigure() { org::freedesktop::Akonadi::Agent::Control *agentControlIface = new org::freedesktop::Akonadi::Agent::Control(ServerManager::agentServiceName(ServerManager::Agent, agentInstance.identifier()), QStringLiteral("/"), KDBusConnectionPool::threadConnection(), q); if (!agentControlIface || !agentControlIface->isValid()) { delete agentControlIface; q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Unable to access D-Bus interface of created agent.")); q->emitResult(); return; } q->connect(agentControlIface, SIGNAL(configurationDialogAccepted()), q, SLOT(configurationDialogAccepted())); q->connect(agentControlIface, SIGNAL(configurationDialogRejected()), q, SLOT(configurationDialogRejected())); agentInstance.configure(parentWidget); } void configurationDialogAccepted() { // The user clicked 'Ok' in the initial configuration dialog, so we assume // he wants to keep the resource and the job is done. q->emitResult(); } void configurationDialogRejected() { // The user clicked 'Cancel' in the initial configuration dialog, so we assume // he wants to abort the 'create new resource' job and the new resource will be // removed again. AgentManager::self()->removeInstance(agentInstance); q->emitResult(); } void timeout() { tooLate = true; q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Agent instance creation timed out.")); q->emitResult(); } void emitResult() { q->emitResult(); } void doStart() override; AgentInstanceCreateJob *q; AgentType agentType; QString agentTypeId; AgentInstance agentInstance; QWidget *parentWidget = nullptr; QTimer *safetyTimer = nullptr; bool doConfig; bool tooLate; }; } AgentInstanceCreateJob::AgentInstanceCreateJob(const AgentType &agentType, QObject *parent) : KJob(parent) , d(new AgentInstanceCreateJobPrivate(this)) { d->agentType = agentType; } AgentInstanceCreateJob::AgentInstanceCreateJob(const QString &typeId, QObject *parent) : KJob(parent) , d(new AgentInstanceCreateJobPrivate(this)) { d->agentTypeId = typeId; } AgentInstanceCreateJob::~AgentInstanceCreateJob() { delete d; } void AgentInstanceCreateJob::configure(QWidget *parent) { d->parentWidget = parent; d->doConfig = true; } AgentInstance AgentInstanceCreateJob::instance() const { return d->agentInstance; } void AgentInstanceCreateJob::start() { d->start(); } void AgentInstanceCreateJobPrivate::doStart() { if (!agentType.isValid() && !agentTypeId.isEmpty()) { agentType = AgentManager::self()->type(agentTypeId); } if (!agentType.isValid()) { q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Unable to obtain agent type '%1'.", agentTypeId)); QTimer::singleShot(0, q, [this]() { emitResult(); }); return; } agentInstance = AgentManager::self()->d->createInstance(agentType); if (!agentInstance.isValid()) { q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Unable to create agent instance.")); QTimer::singleShot(0, q, [this]() { emitResult(); }); } else { int timeout = safetyTimeout; #ifdef Q_OS_UNIX // Increate the timeout when valgrinding the agent, because that slows down things a log. QString agentValgrind = QString::fromLocal8Bit(qgetenv("AKONADI_VALGRIND")); if (!agentValgrind.isEmpty() && agentType.identifier().contains(agentValgrind)) { timeout *= 15; } #endif // change the timeout when debugging the agent, because we need time to start the debugger const QString agentDebugging = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT")); if (!agentDebugging.isEmpty()) { // we are debugging const QString agentDebuggingTimeout = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_TIMEOUT")); if (agentDebuggingTimeout.isEmpty()) { // use default value of 150 seconds (the same as "valgrinding", this has to be checked) timeout = 15 * safetyTimeout; } else { // use own value timeout = agentDebuggingTimeout.toInt(); } } safetyTimer->start(timeout); } } #include "moc_agentinstancecreatejob.cpp" diff --git a/src/core/jobs/itemfetchjob.cpp b/src/core/jobs/itemfetchjob.cpp index ab4361c36..e2564b0d2 100644 --- a/src/core/jobs/itemfetchjob.cpp +++ b/src/core/jobs/itemfetchjob.cpp @@ -1,301 +1,301 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemfetchjob.h" #include "attributefactory.h" #include "collection.h" #include "itemfetchscope.h" #include "job_p.h" #include "protocolhelper_p.h" #include "session_p.h" #include "tagfetchscope.h" #include "private/protocol_p.h" -#include +#include #include using namespace Akonadi; class Akonadi::ItemFetchJobPrivate : public JobPrivate { public: ItemFetchJobPrivate(ItemFetchJob *parent) : JobPrivate(parent) { mCollection = Collection::root(); } ~ItemFetchJobPrivate() override { delete mValuePool; } void init() { Q_Q(ItemFetchJob); mEmitTimer = new QTimer(q); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q->connect(mEmitTimer, SIGNAL(timeout()), q, SLOT(timeout())); } void aboutToFinish() override { timeout(); } void timeout() { Q_Q(ItemFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingItems.isEmpty()) { if (!q->error()) { Q_EMIT q->itemsReceived(mPendingItems); } mPendingItems.clear(); } } QString jobDebuggingString() const override { if (mRequestedItems.isEmpty()) { QString str = QStringLiteral("All items from collection %1").arg(mCollection.id()); if (mFetchScope.fetchChangedSince().isValid()) { str += QStringLiteral(" changed since %1").arg(mFetchScope.fetchChangedSince().toString()); } return str; } else { try { QString itemStr = QStringLiteral("items id: "); bool firstItem = true; for (const Akonadi::Item &item : qAsConst(mRequestedItems)) { if (firstItem) { firstItem = false; } else { itemStr += QStringLiteral(", "); } itemStr += QString::number(item.id()); const Akonadi::Collection parentCollection = item.parentCollection(); if (parentCollection.isValid()) { itemStr += QStringLiteral(" from collection %1").arg(parentCollection.id()); } } return itemStr; //return QString(); //QString::fromLatin1(ProtocolHelper::entitySetToScope(mRequestedItems)); } catch (const Exception &e) { return QString::fromUtf8(e.what()); } } } Q_DECLARE_PUBLIC(ItemFetchJob) Collection mCollection; Tag mCurrentTag; Item::List mRequestedItems; Item::List mResultItems; ItemFetchScope mFetchScope; Item::List mPendingItems; // items pending for emitting itemsReceived() QTimer *mEmitTimer = nullptr; ProtocolHelperValuePool *mValuePool = nullptr; ItemFetchJob::DeliveryOptions mDeliveryOptions = ItemFetchJob::Default; int mCount = 0; }; ItemFetchJob::ItemFetchJob(const Collection &collection, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mCollection = collection; d->mValuePool = new ProtocolHelperValuePool; // only worth it for lots of results } ItemFetchJob::ItemFetchJob(const Item &item, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems.append(item); } ItemFetchJob::ItemFetchJob(const Item::List &items, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems = items; } ItemFetchJob::ItemFetchJob(const QList &items, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems.reserve(items.size()); for (auto id : items) { d->mRequestedItems.append(Item(id)); } } ItemFetchJob::ItemFetchJob(const QVector &items, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems.reserve(items.size()); for (auto id : items) { d->mRequestedItems.append(Item(id)); } } ItemFetchJob::ItemFetchJob(const Tag &tag, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mCurrentTag = tag; d->mValuePool = new ProtocolHelperValuePool; } ItemFetchJob::~ItemFetchJob() { } void ItemFetchJob::doStart() { Q_D(ItemFetchJob); try { d->sendCommand(Protocol::FetchItemsCommandPtr::create( d->mRequestedItems.isEmpty() ? Scope() : ProtocolHelper::entitySetToScope(d->mRequestedItems), ProtocolHelper::commandContextToProtocol(d->mCollection, d->mCurrentTag, d->mRequestedItems), ProtocolHelper::itemFetchScopeToProtocol(d->mFetchScope), ProtocolHelper::tagFetchScopeToProtocol(d->mFetchScope.tagFetchScope()))); } catch (const Akonadi::Exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); emitResult(); return; } } bool ItemFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(ItemFetchJob); if (!response->isResponse() || response->type() != Protocol::Command::FetchItems) { return Job::doHandleResponse(tag, response); } const auto resp = Protocol::cmdCast(response); // Invalid ID marks the last part of the response if (resp.id() < 0) { return true; } const Item item = ProtocolHelper::parseItemFetchResult(resp, nullptr, d->mValuePool); if (!item.isValid()) { return false; } d->mCount++; if (d->mDeliveryOptions & ItemGetter) { d->mResultItems.append(item); } if (d->mDeliveryOptions & EmitItemsInBatches) { d->mPendingItems.append(item); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } } else if (d->mDeliveryOptions & EmitItemsIndividually) { Q_EMIT itemsReceived(Item::List() << item); } return false; } Item::List ItemFetchJob::items() const { Q_D(const ItemFetchJob); return d->mResultItems; } void ItemFetchJob::clearItems() { Q_D(ItemFetchJob); d->mResultItems.clear(); } void ItemFetchJob::setFetchScope(const ItemFetchScope &fetchScope) { Q_D(ItemFetchJob); d->mFetchScope = fetchScope; } ItemFetchScope &ItemFetchJob::fetchScope() { Q_D(ItemFetchJob); return d->mFetchScope; } void ItemFetchJob::setCollection(const Akonadi::Collection &collection) { Q_D(ItemFetchJob); d->mCollection = collection; } void ItemFetchJob::setDeliveryOption(DeliveryOptions options) { Q_D(ItemFetchJob); d->mDeliveryOptions = options; } ItemFetchJob::DeliveryOptions ItemFetchJob::deliveryOptions() const { Q_D(const ItemFetchJob); return d->mDeliveryOptions; } int ItemFetchJob::count() const { Q_D(const ItemFetchJob); return d->mCount; } #include "moc_itemfetchjob.cpp" diff --git a/src/core/jobs/linkjobimpl_p.h b/src/core/jobs/linkjobimpl_p.h index 1d56613b6..31fca7df8 100644 --- a/src/core/jobs/linkjobimpl_p.h +++ b/src/core/jobs/linkjobimpl_p.h @@ -1,87 +1,87 @@ /* Copyright (c) 2008,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_LINKJOBIMPL_P_H #define AKONADI_LINKJOBIMPL_P_H #include "collection.h" #include "item.h" #include "job.h" #include "job_p.h" #include "protocolhelper_p.h" #include "private/protocol_p.h" -#include +#include #include namespace Akonadi { /** Shared implementation details between item and collection move jobs. */ template class LinkJobImpl : public JobPrivate { public: LinkJobImpl(Job *parent) : JobPrivate(parent) { } inline void sendCommand(Protocol::LinkItemsCommand::Action action) { LinkJob *q = static_cast(q_func()); // Job would be enough already, but then we don't have access to the non-public stuff... if (objectsToLink.isEmpty()) { q->emitResult(); return; } if (!destination.isValid() && destination.remoteId().isEmpty()) { q->setError(Job::Unknown); q->setErrorText(i18n("No valid destination specified")); q->emitResult(); return; } try { JobPrivate::sendCommand(Protocol::LinkItemsCommandPtr::create(action, ProtocolHelper::entitySetToScope(objectsToLink), ProtocolHelper::entityToScope(destination))); } catch (const std::exception &e) { q->setError(Job::Unknown); q->setErrorText(QString::fromUtf8(e.what())); q->emitResult(); return; } } inline bool handleResponse(qint64 tag, const Protocol::CommandPtr &response) { LinkJob *q = static_cast(q_func()); if (!response->isResponse() || response->type() != Protocol::Command::LinkItems) { return q->Job::doHandleResponse(tag, response); } return true; } Item::List objectsToLink; Collection destination; }; } #endif diff --git a/src/core/models/agentfilterproxymodel.cpp b/src/core/models/agentfilterproxymodel.cpp index 573cca21a..fbfbc0139 100644 --- a/src/core/models/agentfilterproxymodel.cpp +++ b/src/core/models/agentfilterproxymodel.cpp @@ -1,168 +1,168 @@ /* 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 "agentfilterproxymodel.h" #include "agenttypemodel.h" #include "agentinstancemodel.h" -#include +#include #include #include #include using namespace Akonadi; // ensure the role numbers are equivalent for both source models static_assert((int)AgentTypeModel::CapabilitiesRole == (int)AgentInstanceModel::CapabilitiesRole, "AgentTypeModel::CapabilitiesRole does not match AgentInstanceModel::CapabilitiesRole"); static_assert((int)AgentTypeModel::MimeTypesRole == (int)AgentInstanceModel::MimeTypesRole, "AgentTypeModel::MimeTypesRole does not match AgentInstanceModel::MimeTypesRole"); /** * @internal */ class Q_DECL_HIDDEN AgentFilterProxyModel::Private { public: QStringList mimeTypes; QStringList capabilities; QStringList excludeCapabilities; bool filterAcceptRegExp(const QModelIndex &index, const QRegExp &filterRegExpStr); }; AgentFilterProxyModel::AgentFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , d(new Private) { setDynamicSortFilter(true); } AgentFilterProxyModel::~AgentFilterProxyModel() { delete d; } void AgentFilterProxyModel::addMimeTypeFilter(const QString &mimeType) { d->mimeTypes << mimeType; invalidateFilter(); } void AgentFilterProxyModel::addCapabilityFilter(const QString &capability) { d->capabilities << capability; invalidateFilter(); } void AgentFilterProxyModel::excludeCapabilities(const QString &capability) { d->excludeCapabilities << capability; invalidateFilter(); } void AgentFilterProxyModel::clearFilters() { d->capabilities.clear(); d->mimeTypes.clear(); d->excludeCapabilities.clear(); invalidateFilter(); } bool AgentFilterProxyModel::Private::filterAcceptRegExp(const QModelIndex &index, const QRegExp &filterRegExpStr) { // First see if the name matches a set regexp filter. if (!filterRegExpStr.isEmpty()) { if (index.data(AgentTypeModel::IdentifierRole).toString().contains(filterRegExpStr)) { return true; } else if (index.data().toString().contains(filterRegExpStr)) { return true; } else { return false; } } return true; } bool AgentFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &) const { const QModelIndex index = sourceModel()->index(row, 0); if (!d->mimeTypes.isEmpty()) { QMimeDatabase mimeDb; bool found = false; const QStringList lst = index.data(AgentTypeModel::MimeTypesRole).toStringList(); for (const QString &mimeType : lst) { if (d->mimeTypes.contains(mimeType)) { found = true; } else { const QMimeType mt = mimeDb.mimeTypeForName(mimeType); if (mt.isValid()) { for (const QString &type : qAsConst(d->mimeTypes)) { if (mt.inherits(type)) { found = true; break; } } } } if (found) { break; } } if (!found) { return false; } } if (!d->capabilities.isEmpty()) { bool found = false; const QStringList lst = index.data(AgentTypeModel::CapabilitiesRole).toStringList(); for (const QString &capability : lst) { if (d->capabilities.contains(capability)) { found = true; break; } } if (!found) { return false; } if (found && !d->excludeCapabilities.isEmpty()) { const QStringList lst = index.data(AgentTypeModel::CapabilitiesRole).toStringList(); for (const QString &capability : lst) { if (d->excludeCapabilities.contains(capability)) { found = false; break; } } if (!found) { return false; } } } return d->filterAcceptRegExp(index, filterRegExp()); } diff --git a/src/core/models/collectionmodel.cpp b/src/core/models/collectionmodel.cpp index 105bf536c..91b1bca54 100644 --- a/src/core/models/collectionmodel.cpp +++ b/src/core/models/collectionmodel.cpp @@ -1,318 +1,318 @@ /* Copyright (c) 2006 - 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 "collectionmodel.h" #include "collectionmodel_p.h" #include "collectionutils.h" #include "collectionmodifyjob.h" #include "entitydisplayattribute.h" #include "monitor.h" #include "pastehelper_p.h" #include "session.h" -#include +#include #include #include using namespace Akonadi; CollectionModel::CollectionModel(QObject *parent) : QAbstractItemModel(parent) , d_ptr(new CollectionModelPrivate(this)) { Q_D(CollectionModel); d->init(); } //@cond PRIVATE CollectionModel::CollectionModel(CollectionModelPrivate *d, QObject *parent) : QAbstractItemModel(parent) , d_ptr(d) { d->init(); } //@endcond CollectionModel::~CollectionModel() { Q_D(CollectionModel); d->childCollections.clear(); d->collections.clear(); delete d->monitor; d->monitor = nullptr; delete d; } int CollectionModel::columnCount(const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) { return 0; } return 1; } QVariant CollectionModel::data(const QModelIndex &index, int role) const { Q_D(const CollectionModel); if (!index.isValid()) { return QVariant(); } const Collection col = d->collections.value(index.internalId()); if (!col.isValid()) { return QVariant(); } if (index.column() == 0 && (role == Qt::DisplayRole || role == Qt::EditRole)) { return col.displayName(); } switch (role) { case Qt::DecorationRole: if (index.column() == 0) { return d->iconForCollection(col); } break; case CollectionIdRole: return col.id(); case CollectionRole: return QVariant::fromValue(col); } return QVariant(); } QModelIndex CollectionModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const CollectionModel); if (column >= columnCount() || column < 0) { return QModelIndex(); } QVector list; if (!parent.isValid()) { list = d->childCollections.value(Collection::root().id()); } else { if (parent.column() > 0) { return QModelIndex(); } list = d->childCollections.value(parent.internalId()); } if (row < 0 || row >= list.size()) { return QModelIndex(); } if (!d->collections.contains(list.at(row))) { return QModelIndex(); } return createIndex(row, column, reinterpret_cast(d->collections.value(list.at(row)).id())); } QModelIndex CollectionModel::parent(const QModelIndex &index) const { Q_D(const CollectionModel); if (!index.isValid()) { return QModelIndex(); } const Collection col = d->collections.value(index.internalId()); if (!col.isValid()) { return QModelIndex(); } const Collection parentCol = d->collections.value(col.parentCollection().id()); if (!parentCol.isValid()) { return QModelIndex(); } const QVector list = d->childCollections.value(parentCol.parentCollection().id()); int parentRow = list.indexOf(parentCol.id()); if (parentRow < 0) { return QModelIndex(); } return createIndex(parentRow, 0, reinterpret_cast(parentCol.id())); } int CollectionModel::rowCount(const QModelIndex &parent) const { const Q_D(CollectionModel); QVector list; if (parent.isValid()) { list = d->childCollections.value(parent.internalId()); } else { list = d->childCollections.value(Collection::root().id()); } return list.size(); } QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, int role) const { const Q_D(CollectionModel); if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return d->headerContent; } return QAbstractItemModel::headerData(section, orientation, role); } bool CollectionModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { Q_D(CollectionModel); if (section == 0 && orientation == Qt::Horizontal && role == Qt::EditRole) { d->headerContent = value.toString(); return true; } return false; } bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_D(CollectionModel); if (index.column() == 0 && role == Qt::EditRole) { // rename collection Collection col = d->collections.value(index.internalId()); if (!col.isValid() || value.toString().isEmpty()) { return false; } col.setName(value.toString()); CollectionModifyJob *job = new CollectionModifyJob(col, d->session); connect(job, &CollectionModifyJob::result, this, [d](KJob* job) { d->editDone(job); }); return true; } return QAbstractItemModel::setData(index, value, role); } Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const { Q_D(const CollectionModel); // Pass modeltest. if (!index.isValid()) { return {}; } Qt::ItemFlags flags = QAbstractItemModel::flags(index); flags = flags | Qt::ItemIsDragEnabled; Collection col; if (index.isValid()) { col = d->collections.value(index.internalId()); Q_ASSERT(col.isValid()); } else { return flags | Qt::ItemIsDropEnabled; // HACK Workaround for a probable bug in Qt } if (col.isValid()) { if (col.rights() & (Collection::CanChangeCollection | Collection::CanCreateCollection | Collection::CanDeleteCollection | Collection::CanCreateItem)) { if (index.column() == 0) { flags = flags | Qt::ItemIsEditable; } flags = flags | Qt::ItemIsDropEnabled; } } return flags; } Qt::DropActions CollectionModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList CollectionModel::mimeTypes() const { return {QStringLiteral("text/uri-list")}; } QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QList urls; for (const QModelIndex &index : indexes) { if (index.column() != 0) { continue; } urls << Collection(index.internalId()).url(); } data->setUrls(urls); return data; } bool CollectionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_D(CollectionModel); if (!(action & supportedDropActions())) { return false; } // handle drops onto items as well as drops between items QModelIndex idx; if (row >= 0 && column >= 0) { idx = index(row, column, parent); } else { idx = parent; } if (!idx.isValid()) { return false; } const Collection parentCol = d->collections.value(idx.internalId()); if (!parentCol.isValid()) { return false; } KJob *job = PasteHelper::paste(data, parentCol, action != Qt::MoveAction); connect(job, SIGNAL(result(KJob*)), SLOT(dropResult(KJob*))); return true; } Collection CollectionModel::collectionForId(Collection::Id id) const { Q_D(const CollectionModel); return d->collections.value(id); } void CollectionModel::fetchCollectionStatistics(bool enable) { Q_D(CollectionModel); d->fetchStatistics = enable; d->monitor->fetchCollectionStatistics(enable); } void CollectionModel::includeUnsubscribed(bool include) { Q_D(CollectionModel); d->unsubscribed = include; } #include "moc_collectionmodel.cpp" diff --git a/src/core/models/entityrightsfiltermodel.cpp b/src/core/models/entityrightsfiltermodel.cpp index 3731ee561..de1c7a050 100644 --- a/src/core/models/entityrightsfiltermodel.cpp +++ b/src/core/models/entityrightsfiltermodel.cpp @@ -1,133 +1,133 @@ /* Copyright (c) 2007 Bruno Virlet Copyright (c) 2009 Stephen Kelly 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 "entityrightsfiltermodel.h" #include "entitytreemodel.h" -#include +#include using namespace Akonadi; namespace Akonadi { /** * @internal */ class EntityRightsFilterModelPrivate { public: EntityRightsFilterModelPrivate(EntityRightsFilterModel *parent) : q_ptr(parent) , mAccessRights(Collection::AllRights) { } bool rightsMatches(const QModelIndex &index) const { if (mAccessRights == Collection::AllRights || mAccessRights == Collection::ReadOnly) { return true; } const Collection collection = index.data(EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { return (mAccessRights & collection.rights()); } else { const Item item = index.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { const Collection collection = index.data(EntityTreeModel::ParentCollectionRole).value(); return (mAccessRights & collection.rights()); } else { return false; } } } Q_DECLARE_PUBLIC(EntityRightsFilterModel) EntityRightsFilterModel *q_ptr; Collection::Rights mAccessRights; }; } EntityRightsFilterModel::EntityRightsFilterModel(QObject *parent) : KRecursiveFilterProxyModel(parent) , d_ptr(new EntityRightsFilterModelPrivate(this)) { } EntityRightsFilterModel::~EntityRightsFilterModel() { delete d_ptr; } void EntityRightsFilterModel::setAccessRights(Collection::Rights rights) { Q_D(EntityRightsFilterModel); d->mAccessRights = rights; invalidateFilter(); } Collection::Rights EntityRightsFilterModel::accessRights() const { Q_D(const EntityRightsFilterModel); return d->mAccessRights; } bool EntityRightsFilterModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const { Q_D(const EntityRightsFilterModel); const QModelIndex modelIndex = sourceModel()->index(sourceRow, 0, sourceParent); return d->rightsMatches(modelIndex); } Qt::ItemFlags EntityRightsFilterModel::flags(const QModelIndex &index) const { Q_D(const EntityRightsFilterModel); if (d->rightsMatches(index)) { return KRecursiveFilterProxyModel::flags(index); } else { return KRecursiveFilterProxyModel::flags(index) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } } QModelIndexList EntityRightsFilterModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { if (role < Qt::UserRole) { return QSortFilterProxyModel::match(start, role, value, hits, flags); } QModelIndexList list; QModelIndex proxyIndex; foreach (const QModelIndex &idx, sourceModel()->match(mapToSource(start), role, value, hits, flags)) { proxyIndex = mapFromSource(idx); if (proxyIndex.isValid()) { list << proxyIndex; } } return list; } diff --git a/src/core/models/recursivecollectionfilterproxymodel.cpp b/src/core/models/recursivecollectionfilterproxymodel.cpp index 0273e0ad6..b8d564fc9 100644 --- a/src/core/models/recursivecollectionfilterproxymodel.cpp +++ b/src/core/models/recursivecollectionfilterproxymodel.cpp @@ -1,149 +1,149 @@ /* Copyright (c) 2009 Stephen Kelly Copyright (C) 2012-2019 Laurent Montel 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 "recursivecollectionfilterproxymodel.h" #include "entitytreemodel.h" #include "mimetypechecker.h" -#include +#include using namespace Akonadi; namespace Akonadi { class RecursiveCollectionFilterProxyModelPrivate { Q_DECLARE_PUBLIC(RecursiveCollectionFilterProxyModel) RecursiveCollectionFilterProxyModel *q_ptr; public: RecursiveCollectionFilterProxyModelPrivate(RecursiveCollectionFilterProxyModel *model) : q_ptr(model) { } QSet includedMimeTypes; Akonadi::MimeTypeChecker checker; QString pattern; bool checkOnlyChecked = false; }; } RecursiveCollectionFilterProxyModel::RecursiveCollectionFilterProxyModel(QObject *parent) : KRecursiveFilterProxyModel(parent) , d_ptr(new RecursiveCollectionFilterProxyModelPrivate(this)) { } RecursiveCollectionFilterProxyModel::~RecursiveCollectionFilterProxyModel() { delete d_ptr; } bool RecursiveCollectionFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const { Q_D(const RecursiveCollectionFilterProxyModel); const QModelIndex rowIndex = sourceModel()->index(sourceRow, 0, sourceParent); const Akonadi::Collection collection = rowIndex.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (!collection.isValid()) { return false; } const bool checked = (rowIndex.data(Qt::CheckStateRole).toInt() == Qt::Checked); const bool isCheckable = sourceModel()->flags(rowIndex) & Qt::ItemIsUserCheckable; if (isCheckable && (d->checkOnlyChecked && !checked)) { return false; } const bool collectionWanted = d->checker.isWantedCollection(collection); if (collectionWanted) { if (!d->pattern.isEmpty()) { const QString text = rowIndex.data(Qt::DisplayRole).toString(); return text.contains(d->pattern, Qt::CaseInsensitive); } } return collectionWanted; } void RecursiveCollectionFilterProxyModel::addContentMimeTypeInclusionFilter(const QString &mimeType) { Q_D(RecursiveCollectionFilterProxyModel); d->includedMimeTypes << mimeType; d->checker.setWantedMimeTypes(d->includedMimeTypes.toList()); invalidateFilter(); } void RecursiveCollectionFilterProxyModel::addContentMimeTypeInclusionFilters(const QStringList &mimeTypes) { Q_D(RecursiveCollectionFilterProxyModel); d->includedMimeTypes.unite(mimeTypes.toSet()); d->checker.setWantedMimeTypes(d->includedMimeTypes.toList()); invalidateFilter(); } void RecursiveCollectionFilterProxyModel::clearFilters() { Q_D(RecursiveCollectionFilterProxyModel); d->includedMimeTypes.clear(); d->checker.setWantedMimeTypes(QStringList()); invalidateFilter(); } void RecursiveCollectionFilterProxyModel::setContentMimeTypeInclusionFilters(const QStringList &mimeTypes) { Q_D(RecursiveCollectionFilterProxyModel); d->includedMimeTypes = mimeTypes.toSet(); d->checker.setWantedMimeTypes(d->includedMimeTypes.toList()); invalidateFilter(); } QStringList RecursiveCollectionFilterProxyModel::contentMimeTypeInclusionFilters() const { Q_D(const RecursiveCollectionFilterProxyModel); return d->includedMimeTypes.toList(); } int Akonadi::RecursiveCollectionFilterProxyModel::columnCount(const QModelIndex &index) const { // Optimization: we know that we're not changing the number of columns, so skip QSortFilterProxyModel return sourceModel()->columnCount(mapToSource(index)); } void Akonadi::RecursiveCollectionFilterProxyModel::setSearchPattern(const QString &pattern) { Q_D(RecursiveCollectionFilterProxyModel); if (d->pattern != pattern) { d->pattern = pattern; invalidate(); } } void Akonadi::RecursiveCollectionFilterProxyModel::setIncludeCheckedOnly(bool checked) { Q_D(RecursiveCollectionFilterProxyModel); if (d->checkOnlyChecked != checked) { d->checkOnlyChecked = checked; invalidate(); } } diff --git a/src/qsqlite/src/qsql_sqlite.cpp b/src/qsqlite/src/qsql_sqlite.cpp index 74e261519..d53524a14 100644 --- a/src/qsqlite/src/qsql_sqlite.cpp +++ b/src/qsqlite/src/qsql_sqlite.cpp @@ -1,875 +1,875 @@ /**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtSql module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsql_sqlite.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #if defined Q_OS_WIN # include #else # include #endif #include -#include +#include #include "sqlite_blocking.h" Q_DECLARE_OPAQUE_POINTER(sqlite3 *) Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt *) Q_DECLARE_METATYPE(sqlite3 *) Q_DECLARE_METATYPE(sqlite3_stmt *) QT_BEGIN_NAMESPACE static QString _q_escapeIdentifier(const QString &identifier) { QString res = identifier; if (!identifier.isEmpty() && identifier.at(0) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"'))) { res.replace(QLatin1Char('"'), QStringLiteral("\"\"")); res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); res.replace(QLatin1Char('.'), QStringLiteral("\".\"")); } return res; } static QVariant::Type qGetColumnType(const QString &tpName) { const QString typeName = tpName.toLower(); if (typeName == QLatin1String("integer") || typeName == QLatin1String("int")) { return QVariant::Int; } if (typeName == QLatin1String("double") || typeName == QLatin1String("float") || typeName == QLatin1String("real") || typeName.startsWith(QLatin1String("numeric"))) { return QVariant::Double; } if (typeName == QLatin1String("blob")) { return QVariant::ByteArray; } if (typeName == QLatin1String("boolean") || typeName == QLatin1String("bool")) { return QVariant::Bool; } return QVariant::String; } static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, int errorCode = -1) { return QSqlError(descr, QString(reinterpret_cast(sqlite3_errmsg16(access))), type, QString::number(errorCode)); } class QSQLiteResultPrivate; class QSQLiteResult : public QSqlCachedResult { friend class QSQLiteDriver; friend class QSQLiteResultPrivate; public: explicit QSQLiteResult(const QSQLiteDriver *db); ~QSQLiteResult(); QVariant handle() const override; protected: bool gotoNext(QSqlCachedResult::ValueCache &row, int idx) override; bool reset(const QString &query) override; bool prepare(const QString &query) override; bool exec() override; int size() override; int numRowsAffected() override; QVariant lastInsertId() const override; QSqlRecord record() const override; void detachFromResultSet() override; void virtual_hook(int id, void *data) override; private: Q_DECLARE_PRIVATE(QSQLiteResult) }; class QSQLiteDriverPrivate : public QSqlDriverPrivate { public: inline QSQLiteDriverPrivate() : access(nullptr) { dbmsType = QSqlDriver::SQLite; } sqlite3 *access; QList results; }; class QSQLiteResultPrivate : public QSqlCachedResultPrivate { public: QSQLiteResultPrivate(QSQLiteResult *res, const QSQLiteDriver *drv); void cleanup(); bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); // initializes the recordInfo and the cache void initColumns(bool emptyResultset); void finalize(); sqlite3 *access; sqlite3_stmt *stmt; bool skippedStatus; // the status of the fetchNext() that's skipped bool skipRow; // skip the next fetchNext()? QSqlRecord rInf; QVector firstRow; Q_DECLARE_PUBLIC(QSQLiteResult) }; QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *res, const QSQLiteDriver *drv) : QSqlCachedResultPrivate(res, drv) , access(nullptr) , stmt(nullptr) , skippedStatus(false) , skipRow(false) { } void QSQLiteResultPrivate::cleanup() { Q_Q(QSQLiteResult); finalize(); rInf.clear(); skippedStatus = false; skipRow = false; q->setAt(QSql::BeforeFirstRow); q->setActive(false); q->cleanup(); } void QSQLiteResultPrivate::finalize() { if (!stmt) { return; } sqlite3_finalize(stmt); stmt = nullptr; } void QSQLiteResultPrivate::initColumns(bool emptyResultset) { Q_Q(QSQLiteResult); int nCols = sqlite3_column_count(stmt); if (nCols <= 0) { return; } q->init(nCols); for (int i = 0; i < nCols; ++i) { QString colName = QString::fromUtf16( static_cast(sqlite3_column_name16(stmt, i)) ).remove(QLatin1Char('"')); // must use typeName for resolving the type to match QSqliteDriver::record QString typeName = QString::fromUtf16( static_cast(sqlite3_column_decltype16(stmt, i))); // sqlite3_column_type is documented to have undefined behavior if the result set is empty int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); QVariant::Type fieldType; if (typeName.isEmpty()) { fieldType = qGetColumnType(typeName); } else { // Get the proper type for the field based on stp value switch (stp) { case SQLITE_INTEGER: fieldType = QVariant::Int; break; case SQLITE_FLOAT: fieldType = QVariant::Double; break; case SQLITE_BLOB: fieldType = QVariant::ByteArray; break; case SQLITE_TEXT: fieldType = QVariant::String; break; case SQLITE_NULL: default: fieldType = QVariant::Invalid; break; } } QSqlField fld(colName, fieldType); fld.setSqlType(stp); rInf.append(fld); } } bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) { Q_Q(QSQLiteResult); int res; int i; if (skipRow) { // already fetched Q_ASSERT(!initialFetch); skipRow = false; for (int i = 0; i < firstRow.count(); i++) { values[i] = firstRow[i]; } return skippedStatus; } skipRow = initialFetch; if (initialFetch) { firstRow.clear(); firstRow.resize(sqlite3_column_count(stmt)); } if (!stmt) { q->setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); q->setAt(QSql::AfterLastRow); return false; } res = sqlite3_blocking_step(stmt); switch (res) { case SQLITE_ROW: // check to see if should fill out columns if (rInf.isEmpty()) // must be first call. { initColumns(false); } if (idx < 0 && !initialFetch) { return true; } for (i = 0; i < rInf.count(); ++i) { switch (sqlite3_column_type(stmt, i)) { case SQLITE_BLOB: values[i + idx] = QByteArray(static_cast( sqlite3_column_blob(stmt, i)), sqlite3_column_bytes(stmt, i)); break; case SQLITE_INTEGER: values[i + idx] = sqlite3_column_int64(stmt, i); break; case SQLITE_FLOAT: switch (q->numericalPrecisionPolicy()) { case QSql::LowPrecisionInt32: values[i + idx] = sqlite3_column_int(stmt, i); break; case QSql::LowPrecisionInt64: values[i + idx] = sqlite3_column_int64(stmt, i); break; case QSql::LowPrecisionDouble: case QSql::HighPrecision: default: values[i + idx] = sqlite3_column_double(stmt, i); break; }; break; case SQLITE_NULL: values[i + idx] = QVariant(QVariant::String); break; default: values[i + idx] = QString(reinterpret_cast( sqlite3_column_text16(stmt, i)), sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); break; } } return true; case SQLITE_DONE: if (rInf.isEmpty()) // must be first call. { initColumns(true); } q->setAt(QSql::AfterLastRow); sqlite3_reset(stmt); return false; case SQLITE_CONSTRAINT: case SQLITE_ERROR: // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() // to get the specific error message. res = sqlite3_reset(stmt); q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), QSqlError::ConnectionError, res)); q->setAt(QSql::AfterLastRow); return false; case SQLITE_MISUSE: case SQLITE_BUSY: default: // something wrong, don't get col info, but still return false q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), QSqlError::ConnectionError, res)); sqlite3_reset(stmt); q->setAt(QSql::AfterLastRow); return false; } return false; } QSQLiteResult::QSQLiteResult(const QSQLiteDriver *db) : QSqlCachedResult(*new QSQLiteResultPrivate(this, db)) { Q_D(QSQLiteResult); d->access = db->d_func()->access; const_cast(db->d_func())->results.append(this); } QSQLiteResult::~QSQLiteResult() { Q_D(QSQLiteResult); const QSqlDriver *sqlDriver = driver(); if (sqlDriver) { const_cast(qobject_cast(sqlDriver)->d_func())->results.removeOne(this); } d->cleanup(); } void QSQLiteResult::virtual_hook(int id, void *data) { QSqlCachedResult::virtual_hook(id, data); } bool QSQLiteResult::reset(const QString &query) { if (!prepare(query)) { return false; } return exec(); } bool QSQLiteResult::prepare(const QString &query) { Q_D(QSQLiteResult); if (!driver() || !driver()->isOpen() || driver()->isOpenError()) { return false; } d->cleanup(); setSelect(false); const void *pzTail = nullptr; #if (SQLITE_VERSION_NUMBER >= 3003011) // int res = sqlite3_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), // &d->stmt, 0); int res = sqlite3_blocking_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), &d->stmt, &pzTail); #else int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), &d->stmt, &pzTail); #endif if (res != SQLITE_OK) { setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", "Unable to execute statement"), QSqlError::StatementError, res)); d->finalize(); return false; } else if (pzTail && !QString(reinterpret_cast(pzTail)).trimmed().isEmpty()) { setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); d->finalize(); return false; } return true; } bool QSQLiteResult::exec() { Q_D(QSQLiteResult); const QVector values = boundValues(); d->skippedStatus = false; d->skipRow = false; d->rInf.clear(); clearValues(); setLastError(QSqlError()); int res = sqlite3_reset(d->stmt); if (res != SQLITE_OK) { setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", "Unable to reset statement"), QSqlError::StatementError, res)); d->finalize(); return false; } int paramCount = sqlite3_bind_parameter_count(d->stmt); if (paramCount == values.count()) { for (int i = 0; i < paramCount; ++i) { res = SQLITE_OK; const QVariant value = values.at(i); if (value.isNull()) { res = sqlite3_bind_null(d->stmt, i + 1); } else { switch (value.type()) { case QVariant::ByteArray: { const QByteArray *ba = static_cast(value.constData()); res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), ba->size(), SQLITE_STATIC); break; } case QVariant::Int: case QVariant::Bool: res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); break; case QVariant::Double: res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); break; case QVariant::UInt: case QVariant::LongLong: res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); break; case QVariant::DateTime: { const QDateTime dateTime = value.toDateTime(); const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz")); res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), str.size() * sizeof(ushort), SQLITE_TRANSIENT); break; } case QVariant::Time: { const QTime time = value.toTime(); const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz")); res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), str.size() * sizeof(ushort), SQLITE_TRANSIENT); break; } case QVariant::String: { // lifetime of string == lifetime of its qvariant const QString *str = static_cast(value.constData()); res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), (str->size()) * sizeof(QChar), SQLITE_STATIC); break; } default: { QString str = value.toString(); // SQLITE_TRANSIENT makes sure that sqlite buffers the data res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); break; } } } if (res != SQLITE_OK) { setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", "Unable to bind parameters"), QSqlError::StatementError, res)); d->finalize(); return false; } } } else { setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Parameter count mismatch"), QString(), QSqlError::StatementError)); return false; } d->skippedStatus = d->fetchNext(d->firstRow, 0, true); if (lastError().isValid()) { setSelect(false); setActive(false); return false; } setSelect(!d->rInf.isEmpty()); setActive(true); return true; } bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache &row, int idx) { return d_func()->fetchNext(row, idx, false); } int QSQLiteResult::size() { return -1; } int QSQLiteResult::numRowsAffected() { return sqlite3_changes(d_func()->access); } QVariant QSQLiteResult::lastInsertId() const { if (isActive()) { qint64 id = sqlite3_last_insert_rowid(d_func()->access); if (id) { return id; } } return QVariant(); } QSqlRecord QSQLiteResult::record() const { if (!isActive() || !isSelect()) { return QSqlRecord(); } return d_func()->rInf; } void QSQLiteResult::detachFromResultSet() { if (d_func()->stmt) { sqlite3_reset(d_func()->stmt); } } QVariant QSQLiteResult::handle() const { return qVariantFromValue(d_func()->stmt); } ///////////////////////////////////////////////////////// QSQLiteDriver::QSQLiteDriver(QObject *parent) : QSqlDriver(*new QSQLiteDriverPrivate, parent) { } QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) : QSqlDriver(*new QSQLiteDriverPrivate, parent) { Q_D(QSQLiteDriver); d->access = connection; setOpen(true); setOpenError(false); } QSQLiteDriver::~QSQLiteDriver() { } bool QSQLiteDriver::hasFeature(DriverFeature f) const { switch (f) { case BLOB: case Transactions: case Unicode: case LastInsertId: case PreparedQueries: case PositionalPlaceholders: case SimpleLocking: case FinishQuery: case LowPrecisionNumbers: return true; case QuerySize: case NamedPlaceholders: case BatchOperations: case EventNotifications: case MultipleResultSets: case CancelQuery: return false; } return false; } /* SQLite dbs have no user name, passwords, hosts or ports. just file names. */ bool QSQLiteDriver::open(const QString &db, const QString &, const QString &, const QString &, int, const QString &conOpts) { Q_D(QSQLiteDriver); if (isOpen()) { close(); } int timeout = 5000; bool sharedCache = false; bool openReadOnlyOption = false; bool openUriOption = false; const QStringList opts = QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';')); for (const QString &option : opts) { if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { bool ok; const int nt = option.midRef(21).toInt(&ok); if (ok) { timeout = nt; } } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { openReadOnlyOption = true; } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { openUriOption = true; } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { sharedCache = true; } } int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); if (openUriOption) { openMode |= SQLITE_OPEN_URI; } sqlite3_enable_shared_cache(sharedCache); if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, nullptr) == SQLITE_OK) { sqlite3_busy_timeout(d->access, timeout); sqlite3_extended_result_codes(d->access, 1); setOpen(true); setOpenError(false); return true; } else { if (d->access) { sqlite3_close(d->access); d->access = nullptr; } setLastError(qMakeError(d->access, tr("Error opening database"), QSqlError::ConnectionError)); setOpenError(true); return false; } } void QSQLiteDriver::close() { Q_D(QSQLiteDriver); if (isOpen()) { Q_FOREACH (QSQLiteResult *result, d->results) { result->d_func()->finalize(); } if (sqlite3_close(d->access) != SQLITE_OK) setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError)); d->access = nullptr; setOpen(false); setOpenError(false); } } QSqlResult *QSQLiteDriver::createResult() const { return new QSQLiteResult(this); } bool QSQLiteDriver::beginTransaction() { if (!isOpen() || isOpenError()) { return false; } QSqlQuery q(createResult()); if (!q.exec(QStringLiteral("BEGIN"))) { setLastError(QSqlError(tr("Unable to begin transaction"), q.lastError().databaseText(), QSqlError::TransactionError)); return false; } return true; } bool QSQLiteDriver::commitTransaction() { if (!isOpen() || isOpenError()) { return false; } QSqlQuery q(createResult()); if (!q.exec(QStringLiteral("COMMIT"))) { setLastError(QSqlError(tr("Unable to commit transaction"), q.lastError().databaseText(), QSqlError::TransactionError)); return false; } return true; } bool QSQLiteDriver::rollbackTransaction() { if (!isOpen() || isOpenError()) { return false; } QSqlQuery q(createResult()); if (!q.exec(QStringLiteral("ROLLBACK"))) { setLastError(QSqlError(tr("Unable to rollback transaction"), q.lastError().databaseText(), QSqlError::TransactionError)); return false; } return true; } QStringList QSQLiteDriver::tables(QSql::TableType type) const { QStringList res; if (!isOpen()) { return res; } QSqlQuery q(createResult()); q.setForwardOnly(true); QString sql = QStringLiteral("SELECT name FROM sqlite_master WHERE %1 " "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); if ((type & QSql::Tables) && (type & QSql::Views)) { sql = sql.arg(QStringLiteral("type='table' OR type='view'")); } else if (type & QSql::Tables) { sql = sql.arg(QStringLiteral("type='table'")); } else if (type & QSql::Views) { sql = sql.arg(QStringLiteral("type='view'")); } else { sql.clear(); } if (!sql.isEmpty() && q.exec(sql)) { while (q.next()) { res.append(q.value(0).toString()); } } if (type & QSql::SystemTables) { // there are no internal tables beside this one: res.append(QStringLiteral("sqlite_master")); } return res; } static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) { QString schema; QString table(tableName); int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); if (indexOfSeparator > -1) { schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); table = tableName.mid(indexOfSeparator + 1); } q.exec(QStringLiteral("PRAGMA ") + schema + QStringLiteral("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')')); QSqlIndex ind; while (q.next()) { bool isPk = q.value(5).toInt(); if (onlyPIndex && !isPk) { continue; } QString typeName = q.value(2).toString().toLower(); QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); if (isPk && (typeName == QLatin1String("integer"))) // INTEGER PRIMARY KEY fields are auto-generated in sqlite // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! { fld.setAutoValue(true); } fld.setRequired(q.value(3).toInt() != 0); fld.setDefaultValue(q.value(4)); ind.append(fld); } return ind; } QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const { if (!isOpen()) { return QSqlIndex(); } QString table = tblname; if (isIdentifierEscaped(table, QSqlDriver::TableName)) { table = stripDelimiters(table, QSqlDriver::TableName); } QSqlQuery q(createResult()); q.setForwardOnly(true); return qGetTableInfo(q, table, true); } QSqlRecord QSQLiteDriver::record(const QString &tbl) const { if (!isOpen()) { return QSqlRecord(); } QString table = tbl; if (isIdentifierEscaped(table, QSqlDriver::TableName)) { table = stripDelimiters(table, QSqlDriver::TableName); } QSqlQuery q(createResult()); q.setForwardOnly(true); return qGetTableInfo(q, table); } QVariant QSQLiteDriver::handle() const { Q_D(const QSQLiteDriver); return QVariant::fromValue(d->access); } QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const { Q_UNUSED(type); return _q_escapeIdentifier(identifier); } QT_END_NAMESPACE diff --git a/src/server/storage/dbupdater.cpp b/src/server/storage/dbupdater.cpp index ac7d80c7e..ed8680d7e 100644 --- a/src/server/storage/dbupdater.cpp +++ b/src/server/storage/dbupdater.cpp @@ -1,577 +1,577 @@ /* Copyright (c) 2007 - 2012 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 "dbupdater.h" #include "dbtype.h" #include "entities.h" #include "akonadischema.h" #include "querybuilder.h" #include "selectquerybuilder.h" #include "datastore.h" #include "dbconfig.h" #include "dbintrospector.h" #include "dbinitializer_p.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include #include #include #include #include -#include +#include using namespace Akonadi; using namespace Akonadi::Server; DbUpdater::DbUpdater(const QSqlDatabase &database, const QString &filename) : m_database(database) , m_filename(filename) { } bool DbUpdater::run() { Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); // TODO error handling SchemaVersion currentVersion = SchemaVersion::retrieveAll().first(); UpdateSet::Map updates; if (!parseUpdateSets(currentVersion.version(), updates)) { return false; } if (updates.isEmpty()) { return true; } // indicate clients this might take a while // we can ignore unregistration in error cases, that'll kill the server anyway if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::UpgradeIndicator))) { qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message(); } // QMap is sorted, so we should be replaying the changes in correct order for (QMap::ConstIterator it = updates.constBegin(); it != updates.constEnd(); ++it) { Q_ASSERT(it.key() > currentVersion.version()); qCDebug(AKONADISERVER_LOG) << "DbUpdater: update to version:" << it.key() << " mandatory:" << it.value().abortOnFailure; bool success = false; bool hasTransaction = false; if (it.value().complex) { // complex update const QString methodName = QStringLiteral("complexUpdate_%1()").arg(it.value().version); const int index = metaObject()->indexOfMethod(methodName.toLatin1().constData()); if (index == -1) { success = false; qCCritical(AKONADISERVER_LOG) << "Update to version" << it.value().version << "marked as complex, but no implementation is available"; } else { const QMetaMethod method = metaObject()->method(index); method.invoke(this, Q_RETURN_ARG(bool, success)); if (!success) { qCCritical(AKONADISERVER_LOG) << "Update failed"; } } } else { // regular update success = m_database.transaction(); if (success) { hasTransaction = true; for (const QString &statement : qAsConst(it.value().statements)) { QSqlQuery query(m_database); success = query.exec(statement); if (!success) { qCCritical(AKONADISERVER_LOG) << "DBUpdater: query error:" << query.lastError().text() << m_database.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Query was: " << statement; qCCritical(AKONADISERVER_LOG) << "Target version was: " << it.key(); qCCritical(AKONADISERVER_LOG) << "Mandatory: " << it.value().abortOnFailure; } } } } if (success) { currentVersion.setVersion(it.key()); success = currentVersion.update(); } if (!success || (hasTransaction && !m_database.commit())) { qCCritical(AKONADISERVER_LOG) << "Failed to commit transaction for database update"; if (hasTransaction) { m_database.rollback(); } if (it.value().abortOnFailure) { return false; } } } QDBusConnection::sessionBus().unregisterService(DBus::serviceName(DBus::UpgradeIndicator)); return true; } bool DbUpdater::parseUpdateSets(int currentVersion, UpdateSet::Map &updates) const { QFile file(m_filename); if (!file.open(QIODevice::ReadOnly)) { qCCritical(AKONADISERVER_LOG) << "Unable to open update description file" << m_filename; return false; } QDomDocument document; QString errorMsg; int line, column; if (!document.setContent(&file, &errorMsg, &line, &column)) { qCCritical(AKONADISERVER_LOG) << "Unable to parse update description file" << m_filename << ":" << errorMsg << "at line" << line << "column" << column; return false; } const QDomElement documentElement = document.documentElement(); if (documentElement.tagName() != QLatin1String("updates")) { qCCritical(AKONADISERVER_LOG) << "Invalid update description file formant"; return false; } // iterate over the xml document and extract update information into an UpdateSet QDomElement updateElement = documentElement.firstChildElement(); while (!updateElement.isNull()) { if (updateElement.tagName() == QLatin1String("update")) { const int version = updateElement.attribute(QStringLiteral("version"), QStringLiteral("-1")).toInt(); if (version <= 0) { qCCritical(AKONADISERVER_LOG) << "Invalid version attribute in database update description"; return false; } if (updates.contains(version)) { qCCritical(AKONADISERVER_LOG) << "Duplicate version attribute in database update description"; return false; } if (version <= currentVersion) { qCDebug(AKONADISERVER_LOG) << "skipping update" << version; } else { UpdateSet updateSet; updateSet.version = version; updateSet.abortOnFailure = (updateElement.attribute(QStringLiteral("abortOnFailure")) == QLatin1String("true")); QDomElement childElement = updateElement.firstChildElement(); while (!childElement.isNull()) { if (childElement.tagName() == QLatin1String("raw-sql")) { if (updateApplicable(childElement.attribute(QStringLiteral("backends")))) { updateSet.statements << buildRawSqlStatement(childElement); } } else if (childElement.tagName() == QLatin1String("complex-update")) { if (updateApplicable(childElement.attribute(QStringLiteral("backends")))) { updateSet.complex = true; } } //TODO: check for generic tags here in the future childElement = childElement.nextSiblingElement(); } if (!updateSet.statements.isEmpty() || updateSet.complex) { updates.insert(version, updateSet); } } } updateElement = updateElement.nextSiblingElement(); } return true; } bool DbUpdater::updateApplicable(const QString &backends) const { const QStringList matchingBackends = backends.split(QLatin1Char(',')); QString currentBackend; switch (DbType::type(m_database)) { case DbType::MySQL: currentBackend = QStringLiteral("mysql"); break; case DbType::PostgreSQL: currentBackend = QStringLiteral("psql"); break; case DbType::Sqlite: currentBackend = QStringLiteral("sqlite"); break; case DbType::Unknown: return false; } return matchingBackends.contains(currentBackend); } QString DbUpdater::buildRawSqlStatement(const QDomElement &element) const { return element.text().trimmed(); } bool DbUpdater::complexUpdate_25() { qCDebug(AKONADISERVER_LOG) << "Starting database update to version 25"; DbType::Type dbType = DbType::type(DataStore::self()->database()); QTime ttotal; ttotal.start(); // Recover from possibly failed or interrupted update { // We don't care if this fails, it just means that there was no failed update QSqlQuery query(DataStore::self()->database()); query.exec(QStringLiteral("ALTER TABLE PartTable_old RENAME TO PartTable")); } { QSqlQuery query(DataStore::self()->database()); query.exec(QStringLiteral("DROP TABLE IF EXISTS PartTable_new")); } { // Make sure the table is empty, otherwise we get duplicate key error QSqlQuery query(DataStore::self()->database()); if (dbType == DbType::Sqlite) { query.exec(QStringLiteral("DELETE FROM PartTypeTable")); } else { // MySQL, PostgreSQL query.exec(QStringLiteral("TRUNCATE TABLE PartTypeTable")); } } { // It appears that more users than expected have the invalid "GID" part in their // PartTable, which breaks the migration below (see BKO#331867), so we apply this // wanna-be fix to remove the invalid part before we start the actual migration. QueryBuilder qb(QStringLiteral("PartTable"), QueryBuilder::Delete); qb.addValueCondition(QStringLiteral("PartTable.name"), Query::Equals, QLatin1String("GID")); qb.exec(); } qCDebug(AKONADISERVER_LOG) << "Creating a PartTable_new"; { TableDescription description; description.name = QStringLiteral("PartTable_new"); ColumnDescription idColumn; idColumn.name = QStringLiteral("id"); idColumn.type = QStringLiteral("qint64"); idColumn.isAutoIncrement = true; idColumn.isPrimaryKey = true; description.columns << idColumn; ColumnDescription pimItemIdColumn; pimItemIdColumn.name = QStringLiteral("pimItemId"); pimItemIdColumn.type = QStringLiteral("qint64"); pimItemIdColumn.allowNull = false; description.columns << pimItemIdColumn; ColumnDescription partTypeIdColumn; partTypeIdColumn.name = QStringLiteral("partTypeId"); partTypeIdColumn.type = QStringLiteral("qint64"); partTypeIdColumn.allowNull = false; description.columns << partTypeIdColumn; ColumnDescription dataColumn; dataColumn.name = QStringLiteral("data"); dataColumn.type = QStringLiteral("QByteArray"); description.columns << dataColumn; ColumnDescription dataSizeColumn; dataSizeColumn.name = QStringLiteral("datasize"); dataSizeColumn.type = QStringLiteral("qint64"); dataSizeColumn.allowNull = false; description.columns << dataSizeColumn; ColumnDescription versionColumn; versionColumn.name = QStringLiteral("version"); versionColumn.type = QStringLiteral("int"); versionColumn.defaultValue = QStringLiteral("0"); description.columns << versionColumn; ColumnDescription externalColumn; externalColumn.name = QStringLiteral("external"); externalColumn.type = QStringLiteral("bool"); externalColumn.defaultValue = QStringLiteral("false"); description.columns << externalColumn; DbInitializer::Ptr initializer = DbInitializer::createInstance(DataStore::self()->database()); const QString queryString = initializer->buildCreateTableStatement(description); QSqlQuery query(DataStore::self()->database()); if (!query.exec(queryString)) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); return false; } } qCDebug(AKONADISERVER_LOG) << "Migrating part types"; { // Get list of all part names QueryBuilder qb(QStringLiteral("PartTable"), QueryBuilder::Select); qb.setDistinct(true); qb.addColumn(QStringLiteral("PartTable.name")); if (!qb.exec()) { qCCritical(AKONADISERVER_LOG) << qb.query().lastError().text(); return false; } // Process them one by one QSqlQuery query = qb.query(); while (query.next()) { // Split the part name to namespace and name and insert it to PartTypeTable const QString partName = query.value(0).toString(); const QString ns = partName.left(3); const QString name = partName.mid(4); { QueryBuilder qb(QStringLiteral("PartTypeTable"), QueryBuilder::Insert); qb.setColumnValue(QStringLiteral("ns"), ns); qb.setColumnValue(QStringLiteral("name"), name); if (!qb.exec()) { qCCritical(AKONADISERVER_LOG) << qb.query().lastError().text(); return false; } } qCDebug(AKONADISERVER_LOG) << "\t Moved part type" << partName << "to PartTypeTable"; } query.finish(); } qCDebug(AKONADISERVER_LOG) << "Migrating data from PartTable to PartTable_new"; { QSqlQuery query(DataStore::self()->database()); QString queryString; if (dbType == DbType::PostgreSQL) { queryString = QStringLiteral("INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) " "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, " " PartTable.datasize, PartTable.version, PartTable.external " "FROM PartTable " "LEFT JOIN PartTypeTable ON " " PartTable.name = CONCAT(PartTypeTable.ns, ':', PartTypeTable.name)"); } else if (dbType == DbType::MySQL) { queryString = QStringLiteral("INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) " "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, " "PartTable.datasize, PartTable.version, PartTable.external " "FROM PartTable " "LEFT JOIN PartTypeTable ON PartTable.name = CONCAT(PartTypeTable.ns, ':', PartTypeTable.name)"); } else if (dbType == DbType::Sqlite) { queryString = QStringLiteral("INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) " "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, " "PartTable.datasize, PartTable.version, PartTable.external " "FROM PartTable " "LEFT JOIN PartTypeTable ON PartTable.name = PartTypeTable.ns || ':' || PartTypeTable.name"); } if (!query.exec(queryString)) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); return false; } } qCDebug(AKONADISERVER_LOG) << "Swapping PartTable_new for PartTable"; { // Does an atomic swap QSqlQuery query(DataStore::self()->database()); if (dbType == DbType::PostgreSQL || dbType == DbType::Sqlite) { if (dbType == DbType::PostgreSQL) { DataStore::self()->beginTransaction(QStringLiteral("DBUPDATER (r25)")); } if (!query.exec(QStringLiteral("ALTER TABLE PartTable RENAME TO PartTable_old"))) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); DataStore::self()->rollbackTransaction(); return false; } // If this fails in SQLite (i.e. without transaction), we can still recover on next start) if (!query.exec(QStringLiteral("ALTER TABLE PartTable_new RENAME TO PartTable"))) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); if (DataStore::self()->inTransaction()) { DataStore::self()->rollbackTransaction(); } return false; } if (dbType == DbType::PostgreSQL) { DataStore::self()->commitTransaction(); } } else { // MySQL cannot do rename in transaction, but supports atomic renames if (!query.exec(QStringLiteral("RENAME TABLE PartTable TO PartTable_old," " PartTable_new TO PartTable"))) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); return false; } } } qCDebug(AKONADISERVER_LOG) << "Removing PartTable_old"; { QSqlQuery query(DataStore::self()->database()); if (!query.exec(QStringLiteral("DROP TABLE PartTable_old;"))) { // It does not matter when this fails, we are successfully migrated qCDebug(AKONADISERVER_LOG) << query.lastError().text(); qCDebug(AKONADISERVER_LOG) << "Not a fatal problem, continuing..."; } } // Fine tuning for PostgreSQL qCDebug(AKONADISERVER_LOG) << "Final tuning of new PartTable"; { QSqlQuery query(DataStore::self()->database()); if (dbType == DbType::PostgreSQL) { query.exec(QStringLiteral("ALTER TABLE PartTable RENAME CONSTRAINT parttable_new_pkey TO parttable_pkey")); query.exec(QStringLiteral("ALTER SEQUENCE parttable_new_id_seq RENAME TO parttable_id_seq")); query.exec(QStringLiteral("SELECT setval('parttable_id_seq', MAX(id) + 1) FROM PartTable")); } else if (dbType == DbType::MySQL) { // 0 will automatically reset AUTO_INCREMENT to SELECT MAX(id) + 1 FROM PartTable query.exec(QStringLiteral("ALTER TABLE PartTable AUTO_INCREMENT = 0")); } } qCDebug(AKONADISERVER_LOG) << "Update done in" << ttotal.elapsed() << "ms"; // Foreign keys and constraints will be reconstructed automatically once // all updates are done return true; } bool DbUpdater::complexUpdate_36() { qCDebug(AKONADISERVER_LOG, "Starting database update to version 36"); Q_ASSERT(DbType::type(DataStore::self()->database()) == DbType::Sqlite); QSqlQuery query(DataStore::self()->database()); if (!query.exec(QStringLiteral("PRAGMA foreign_key_checks=OFF"))) { qCCritical(AKONADISERVER_LOG, "Failed to disable foreign key checks!"); return false; } const auto hasForeignKeys = [](const TableDescription &desc) { return std::any_of(desc.columns.cbegin(), desc.columns.cend(), [](const ColumnDescription &col) { return !col.refTable.isEmpty() && !col.refColumn.isEmpty(); }); }; const auto recreateTableWithForeignKeys = [](const TableDescription &table) -> QPair { qCDebug(AKONADISERVER_LOG) << "Updating foreign keys in table" << table.name; QSqlQuery query(DataStore::self()->database()); // Recover from possibly failed or interrupted update // We don't care if this fails, it just means that there was no failed update query.exec(QStringLiteral("ALTER TABLE %1_old RENAME TO %1").arg(table.name)); query.exec(QStringLiteral("DROP TABLE %1_new").arg(table.name)); qCDebug(AKONADISERVER_LOG, "\tCreating table %s_new with foreign keys", qUtf8Printable(table.name)); { const auto initializer = DbInitializer::createInstance(DataStore::self()->database()); TableDescription copy = table; copy.name += QStringLiteral("_new"); if (!query.exec(initializer->buildCreateTableStatement(copy))) { // If this fails we will recover on next start return {false, query}; } } qCDebug(AKONADISERVER_LOG, "\tCopying values from %s to %s_new (this may take a very long of time...)", qUtf8Printable(table.name), qUtf8Printable(table.name)); if (!query.exec(QStringLiteral("INSERT INTO %1_new SELECT * FROM %1").arg(table.name))) { // If this fails, we will recover on next start return {false, query}; } qCDebug(AKONADISERVER_LOG, "\tSwapping %s_new for %s", qUtf8Printable(table.name), qUtf8Printable(table.name)); if (!query.exec(QStringLiteral("ALTER TABLE %1 RENAME TO %1_old").arg(table.name))) { // If this fails we will recover on next start return {false, query}; } if (!query.exec(QStringLiteral("ALTER TABLE %1_new RENAME TO %1").arg(table.name))) { // If this fails we will recover on next start return {false, query}; } qCDebug(AKONADISERVER_LOG, "\tRemoving table %s_old", qUtf8Printable(table.name)); if (!query.exec(QStringLiteral("DROP TABLE %1_old").arg(table.name))) { // We don't care if this fails qCWarning(AKONADISERVER_LOG, "Failed to DROP TABLE %s (not fatal, update will continue)", qUtf8Printable(table.name)); qCWarning(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text())); } qCDebug(AKONADISERVER_LOG) << "\tOptimizing table %s", qUtf8Printable(table.name); if (!query.exec(QStringLiteral("ANALYZE %1").arg(table.name))) { // We don't care if this fails qCWarning(AKONADISERVER_LOG, "Failed to ANALYZE %s (not fatal, update will continue)", qUtf8Printable(table.name)); qCWarning(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text())); } qCDebug(AKONADISERVER_LOG) << "\tDone"; return {true, QSqlQuery()}; }; AkonadiSchema schema; const auto tables = schema.tables(); for (const auto &table : tables) { if (!hasForeignKeys(table)) { continue; } const auto result = recreateTableWithForeignKeys(table); if (!result.first) { qCCritical(AKONADISERVER_LOG, "SQL error when updating table %s", qUtf8Printable(table.name)); qCCritical(AKONADISERVER_LOG, "Query: %s", qUtf8Printable(result.second.executedQuery())); qCCritical(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(result.second.lastError().text())); return false; } } const auto relations = schema.relations(); for (const auto &relation : relations) { const RelationTableDescription table(relation); const auto result = recreateTableWithForeignKeys(table); if (!result.first) { qCCritical(AKONADISERVER_LOG, "SQL error when updating relation table %s", qUtf8Printable(table.name)); qCCritical(AKONADISERVER_LOG, "Query: %s", qUtf8Printable(result.second.executedQuery())); qCCritical(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(result.second.lastError().text())); return false; } } qCDebug(AKONADISERVER_LOG) << "Running VACUUM to reduce DB size"; if (!query.exec(QStringLiteral("VACUUM"))) { qCWarning(AKONADISERVER_LOG) << "Vacuum failed (not fatal, update will continue)"; qCWarning(AKONADISERVER_LOG) << "Error:" << query.lastError().text(); } return true; } diff --git a/src/server/storagejanitor.cpp b/src/server/storagejanitor.cpp index f781227a7..03c938726 100644 --- a/src/server/storagejanitor.cpp +++ b/src/server/storagejanitor.cpp @@ -1,880 +1,880 @@ /* 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 "storagejanitor.h" #include "storage/queryhelper.h" #include "storage/transaction.h" #include "storage/datastore.h" #include "storage/selectquerybuilder.h" #include "storage/parthelper.h" #include "storage/dbconfig.h" #include "storage/collectionstatistics.h" #include "search/searchrequest.h" #include "search/searchmanager.h" #include "resourcemanager.h" #include "entities.h" #include "dbusconnectionpool.h" #include "agentmanagerinterface.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include using namespace Akonadi; using namespace Akonadi::Server; StorageJanitor::StorageJanitor(QObject *parent) : AkThread(QStringLiteral("StorageJanitor"), QThread::IdlePriority, parent) , m_lostFoundCollectionId(-1) { } StorageJanitor::~StorageJanitor() { quitThread(); } void StorageJanitor::init() { AkThread::init(); QDBusConnection conn = DBusConnectionPool::threadConnection(); conn.registerService(DBus::serviceName(DBus::StorageJanitor)); conn.registerObject(QStringLiteral(AKONADI_DBUS_STORAGEJANITOR_PATH), this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals); } void StorageJanitor::quit() { QDBusConnection conn = DBusConnectionPool::threadConnection(); conn.unregisterObject(QStringLiteral(AKONADI_DBUS_STORAGEJANITOR_PATH), QDBusConnection::UnregisterTree); conn.unregisterService(DBus::serviceName(DBus::StorageJanitor)); conn.disconnectFromBus(conn.name()); // Make sure all children are deleted within context of this thread qDeleteAll(children()); AkThread::quit(); } void StorageJanitor::check() // implementation of `akonadictl fsck` { m_lostFoundCollectionId = -1; // start with a fresh one each time inform("Looking for resources in the DB not matching a configured resource..."); findOrphanedResources(); inform("Looking for collections not belonging to a valid resource..."); findOrphanedCollections(); inform("Checking collection tree consistency..."); const Collection::List cols = Collection::retrieveAll(); std::for_each(cols.begin(), cols.end(), [this](const Collection & col) { checkPathToRoot(col); }); inform("Looking for items not belonging to a valid collection..."); findOrphanedItems(); inform("Looking for item parts not belonging to a valid item..."); findOrphanedParts(); inform("Looking for item flags not belonging to a valid item..."); findOrphanedPimItemFlags(); inform("Looking for overlapping external parts..."); findOverlappingParts(); inform("Verifying external parts..."); verifyExternalParts(); inform("Checking size treshold changes..."); checkSizeTreshold(); inform("Looking for dirty objects..."); findDirtyObjects(); inform("Looking for rid-duplicates not matching the content mime-type of the parent collection"); findRIDDuplicates(); inform("Migrating parts to new cache hierarchy..."); migrateToLevelledCacheHierarchy(); inform("Checking search index consistency..."); findOrphanSearchIndexEntries(); inform("Flushing collection statistics memory cache..."); CollectionStatistics::self()->expireCache(); inform("Making sure virtual search resource and collections exist"); ensureSearchCollection(); /* TODO some ideas for further checks: * the collection tree is non-cyclic * content type constraints of collections are not violated * find unused flags * find unused mimetypes * check for dead entries in relation tables * check if part size matches file size */ inform("Consistency check done."); Q_EMIT done(); } qint64 StorageJanitor::lostAndFoundCollection() { if (m_lostFoundCollectionId > 0) { return m_lostFoundCollectionId; } Transaction transaction(DataStore::self(), QStringLiteral("JANITOR LOST+FOUND")); Resource lfRes = Resource::retrieveByName(QStringLiteral("akonadi_lost+found_resource")); if (!lfRes.isValid()) { lfRes.setName(QStringLiteral("akonadi_lost+found_resource")); if (!lfRes.insert()) { qCCritical(AKONADISERVER_LOG) << "Failed to create lost+found resource!"; } } Collection lfRoot; SelectQueryBuilder qb; qb.addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, lfRes.id()); qb.addValueCondition(Collection::parentIdFullColumnName(), Query::Is, QVariant()); if (!qb.exec()) { qCCritical(AKONADISERVER_LOG) << "Failed to query top level collections"; return -1; } const Collection::List cols = qb.result(); if (cols.size() > 1) { qCCritical(AKONADISERVER_LOG) << "More than one top-level lost+found collection!?"; } else if (cols.size() == 1) { lfRoot = cols.first(); } else { lfRoot.setName(QStringLiteral("lost+found")); lfRoot.setResourceId(lfRes.id()); lfRoot.setCachePolicyLocalParts(QStringLiteral("ALL")); lfRoot.setCachePolicyCacheTimeout(-1); lfRoot.setCachePolicyInherit(false); if (!lfRoot.insert()) { qCCritical(AKONADISERVER_LOG) << "Failed to create lost+found root."; } DataStore::self()->notificationCollector()->collectionAdded(lfRoot, lfRes.name().toUtf8()); } Collection lfCol; lfCol.setName(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss"))); lfCol.setResourceId(lfRes.id()); lfCol.setParentId(lfRoot.id()); if (!lfCol.insert()) { qCCritical(AKONADISERVER_LOG) << "Failed to create lost+found collection!"; } Q_FOREACH (const MimeType &mt, MimeType::retrieveAll()) { lfCol.addMimeType(mt); } DataStore::self()->notificationCollector()->collectionAdded(lfCol, lfRes.name().toUtf8()); transaction.commit(); m_lostFoundCollectionId = lfCol.id(); return m_lostFoundCollectionId; } void StorageJanitor::findOrphanedResources() { SelectQueryBuilder qbres; OrgFreedesktopAkonadiAgentManagerInterface iface( DBus::serviceName(DBus::Control), QStringLiteral("/AgentManager"), QDBusConnection::sessionBus(), this); if (!iface.isValid()) { inform(QStringLiteral("ERROR: Couldn't talk to %1").arg(DBus::Control)); return; } const QStringList knownResources = iface.agentInstances(); if (knownResources.isEmpty()) { inform(QStringLiteral("ERROR: no known resources. This must be a mistake?")); return; } qbres.addValueCondition(Resource::nameFullColumnName(), Query::NotIn, QVariant(knownResources)); qbres.addValueCondition(Resource::idFullColumnName(), Query::NotEquals, 1); // skip akonadi_search_resource if (!qbres.exec()) { inform("Failed to query known resources, skipping test"); return; } //qCDebug(AKONADISERVER_LOG) << "SQL:" << qbres.query().lastQuery(); const Resource::List orphanResources = qbres.result(); const int orphanResourcesSize(orphanResources.size()); if (orphanResourcesSize > 0) { QStringList resourceNames; resourceNames.reserve(orphanResourcesSize); for (const Resource &resource : orphanResources) { resourceNames.append(resource.name()); } inform(QStringLiteral("Found %1 orphan resources: %2").arg(orphanResourcesSize). arg(resourceNames.join(QLatin1Char(',')))); for (const QString &resourceName : qAsConst(resourceNames)) { inform(QStringLiteral("Removing resource %1").arg(resourceName)); ResourceManager::self()->removeResourceInstance(resourceName); } } } void StorageJanitor::findOrphanedCollections() { SelectQueryBuilder qb; qb.addJoin(QueryBuilder::LeftJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); qb.addValueCondition(Resource::idFullColumnName(), Query::Is, QVariant()); if (!qb.exec()) { inform("Failed to query orphaned collections, skipping test"); return; } const Collection::List orphans = qb.result(); if (!orphans.isEmpty()) { inform(QLatin1Literal("Found ") + QString::number(orphans.size()) + QLatin1Literal(" orphan collections.")); // TODO: attach to lost+found resource } } void StorageJanitor::checkPathToRoot(const Collection &col) { if (col.parentId() == 0) { return; } const Collection parent = col.parent(); if (!parent.isValid()) { inform(QLatin1Literal("Collection \"") + col.name() + QLatin1Literal("\" (id: ") + QString::number(col.id()) + QLatin1Literal(") has no valid parent.")); // TODO fix that by attaching to a top-level lost+found folder return; } if (col.resourceId() != parent.resourceId()) { inform(QLatin1Literal("Collection \"") + col.name() + QLatin1Literal("\" (id: ") + QString::number(col.id()) + QLatin1Literal(") belongs to a different resource than its parent.")); // can/should we actually fix that? } checkPathToRoot(parent); } void StorageJanitor::findOrphanedItems() { SelectQueryBuilder qb; qb.addJoin(QueryBuilder::LeftJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName()); qb.addValueCondition(Collection::idFullColumnName(), Query::Is, QVariant()); if (!qb.exec()) { inform("Failed to query orphaned items, skipping test"); return; } const PimItem::List orphans = qb.result(); if (!orphans.isEmpty()) { inform(QLatin1Literal("Found ") + QString::number(orphans.size()) + QLatin1Literal(" orphan items.")); // Attach to lost+found collection Transaction transaction(DataStore::self(), QStringLiteral("JANITOR ORPHANS")); QueryBuilder qb(PimItem::tableName(), QueryBuilder::Update); qint64 col = lostAndFoundCollection(); if (col == -1) { return; } qb.setColumnValue(PimItem::collectionIdColumn(), col); QVector imapIds; imapIds.reserve(orphans.count()); for (const PimItem &item : qAsConst(orphans)) { imapIds.append(item.id()); } ImapSet set; set.add(imapIds); QueryHelper::setToQuery(set, PimItem::idFullColumnName(), qb); if (qb.exec() && transaction.commit()) { inform(QLatin1Literal("Moved orphan items to collection ") + QString::number(col)); } else { inform(QLatin1Literal("Error moving orphan items to collection ") + QString::number(col) + QLatin1Literal(" : ") + qb.query().lastError().text()); } } } void StorageJanitor::findOrphanedParts() { SelectQueryBuilder qb; qb.addJoin(QueryBuilder::LeftJoin, PimItem::tableName(), Part::pimItemIdFullColumnName(), PimItem::idFullColumnName()); qb.addValueCondition(PimItem::idFullColumnName(), Query::Is, QVariant()); if (!qb.exec()) { inform("Failed to query orphaned parts, skipping test"); return; } const Part::List orphans = qb.result(); if (!orphans.isEmpty()) { inform(QLatin1Literal("Found ") + QString::number(orphans.size()) + QLatin1Literal(" orphan parts.")); // TODO: create lost+found items for those? delete? } } void StorageJanitor:: findOrphanedPimItemFlags() { QueryBuilder sqb(PimItemFlagRelation::tableName(), QueryBuilder::Select); sqb.addColumn(PimItemFlagRelation::leftFullColumnName()); sqb.addJoin(QueryBuilder::LeftJoin, PimItem::tableName(), PimItemFlagRelation::leftFullColumnName(), PimItem::idFullColumnName()); sqb.addValueCondition(PimItem::idFullColumnName(), Query::Is, QVariant()); if (!sqb.exec()) { inform("Failed to query orphaned item flags, skipping test"); return; } QVector imapIds; int count = 0; while (sqb.query().next()) { ++count; imapIds.append(sqb.query().value(0).toInt()); } sqb.query().finish(); if (count > 0) { ImapSet set; set.add(imapIds); QueryBuilder qb(PimItemFlagRelation::tableName(), QueryBuilder::Delete); QueryHelper::setToQuery(set, PimItemFlagRelation::leftFullColumnName(), qb); if (!qb.exec()) { qCCritical(AKONADISERVER_LOG) << "Error:" << qb.query().lastError().text(); return; } inform(QLatin1Literal("Found and deleted ") + QString::number(count) + QLatin1Literal(" orphan pim item flags.")); } } void StorageJanitor::findOverlappingParts() { QueryBuilder qb(Part::tableName(), QueryBuilder::Select); qb.addColumn(Part::dataColumn()); qb.addColumn(QLatin1Literal("count(") + Part::idColumn() + QLatin1Literal(") as cnt")); qb.addValueCondition(Part::storageColumn(), Query::Equals, Part::External); qb.addValueCondition(Part::dataColumn(), Query::IsNot, QVariant()); qb.addGroupColumn(Part::dataColumn()); qb.addValueCondition(QLatin1Literal("count(") + Part::idColumn() + QLatin1Literal(")"), Query::Greater, 1, QueryBuilder::HavingCondition); if (!qb.exec()) { inform("Failed to query overlapping parts, skipping test"); return; } int count = 0; while (qb.query().next()) { ++count; inform(QLatin1Literal("Found overlapping part data: ") + qb.query().value(0).toString()); // TODO: uh oh, this is bad, how do we recover from that? } qb.query().finish(); if (count > 0) { inform(QLatin1Literal("Found ") + QString::number(count) + QLatin1Literal(" overlapping parts - bad.")); } } void StorageJanitor::verifyExternalParts() { QSet existingFiles; QSet usedFiles; // list all files const QString dataDir = StandardDirs::saveDir("data", QStringLiteral("file_db_data")); QDirIterator it(dataDir, QDir::Files, QDirIterator::Subdirectories); while (it.hasNext()) { existingFiles.insert(it.next()); } existingFiles.remove(dataDir + QDir::separator() + QLatin1Char('.')); existingFiles.remove(dataDir + QDir::separator() + QLatin1String("..")); inform(QLatin1Literal("Found ") + QString::number(existingFiles.size()) + QLatin1Literal(" external files.")); // list all parts from the db which claim to have an associated file QueryBuilder qb(Part::tableName(), QueryBuilder::Select); qb.addColumn(Part::dataColumn()); qb.addColumn(Part::pimItemIdColumn()); qb.addColumn(Part::idColumn()); qb.addValueCondition(Part::storageColumn(), Query::Equals, Part::External); qb.addValueCondition(Part::dataColumn(), Query::IsNot, QVariant()); if (!qb.exec()) { inform("Failed to query existing parts, skipping test"); return; } while (qb.query().next()) { const auto filename = qb.query().value(0).toByteArray(); const Entity::Id pimItemId = qb.query().value(1).value(); const Entity::Id partId = qb.query().value(2).value(); QString partPath; if (!filename.isEmpty()) { partPath = ExternalPartStorage::resolveAbsolutePath(filename); } else { partPath = ExternalPartStorage::resolveAbsolutePath(ExternalPartStorage::nameForPartId(partId)); } if (existingFiles.contains(partPath)) { usedFiles.insert(partPath); } else { inform(QLatin1Literal("Cleaning up missing external file: ") + partPath + QLatin1Literal(" for item: ") + QString::number(pimItemId) + QLatin1Literal(" on part: ") + QString::number(partId)); Part part; part.setId(partId); part.setPimItemId(pimItemId); part.setData(QByteArray()); part.setDatasize(0); part.setStorage(Part::Internal); part.update(); } } qb.query().finish(); inform(QLatin1Literal("Found ") + QString::number(usedFiles.size()) + QLatin1Literal(" external parts.")); // see what's left and move it to lost+found const QSet unreferencedFiles = existingFiles - usedFiles; if (!unreferencedFiles.isEmpty()) { const QString lfDir = StandardDirs::saveDir("data", QStringLiteral("file_lost+found")); for (const QString &file : unreferencedFiles) { inform(QLatin1Literal("Found unreferenced external file: ") + file); const QFileInfo f(file); QFile::rename(file, lfDir + QDir::separator() + f.fileName()); } inform(QStringLiteral("Moved %1 unreferenced files to lost+found.").arg(unreferencedFiles.size())); } else { inform("Found no unreferenced external files."); } } void StorageJanitor::findDirtyObjects() { SelectQueryBuilder cqb; cqb.setSubQueryMode(Query::Or); cqb.addValueCondition(Collection::remoteIdColumn(), Query::Is, QVariant()); cqb.addValueCondition(Collection::remoteIdColumn(), Query::Equals, QString()); if (!cqb.exec()) { inform("Failed to query collections without RID, skipping test"); return; } const Collection::List ridLessCols = cqb.result(); for (const Collection &col : ridLessCols) { inform(QLatin1Literal("Collection \"") + col.name() + QLatin1Literal("\" (id: ") + QString::number(col.id()) + QLatin1Literal(") has no RID.")); } inform(QLatin1Literal("Found ") + QString::number(ridLessCols.size()) + QLatin1Literal(" collections without RID.")); SelectQueryBuilder iqb1; iqb1.setSubQueryMode(Query::Or); iqb1.addValueCondition(PimItem::remoteIdColumn(), Query::Is, QVariant()); iqb1.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, QString()); if (!iqb1.exec()) { inform("Failed to query items without RID, skipping test"); return; } const PimItem::List ridLessItems = iqb1.result(); for (const PimItem &item : ridLessItems) { inform(QLatin1Literal("Item \"") + QString::number(item.id()) + QLatin1Literal("\" in collection \"") + QString::number(item.collectionId()) + QLatin1Literal("\" has no RID.")); } inform(QLatin1Literal("Found ") + QString::number(ridLessItems.size()) + QLatin1Literal(" items without RID.")); SelectQueryBuilder iqb2; iqb2.addValueCondition(PimItem::dirtyColumn(), Query::Equals, true); iqb2.addValueCondition(PimItem::remoteIdColumn(), Query::IsNot, QVariant()); iqb2.addSortColumn(PimItem::idFullColumnName()); if (!iqb2.exec()) { inform("Failed to query dirty items, skipping test"); return; } const PimItem::List dirtyItems = iqb2.result(); for (const PimItem &item : dirtyItems) { inform(QLatin1Literal("Item \"") + QString::number(item.id()) + QLatin1Literal("\" has RID and is dirty.")); } inform(QLatin1Literal("Found ") + QString::number(dirtyItems.size()) + QLatin1Literal(" dirty items.")); } void StorageJanitor::findRIDDuplicates() { QueryBuilder qb(Collection::tableName(), QueryBuilder::Select); qb.addColumn(Collection::idColumn()); qb.addColumn(Collection::nameColumn()); qb.exec(); while (qb.query().next()) { const Collection::Id colId = qb.query().value(0).value(); const QString name = qb.query().value(1).toString(); inform(QStringLiteral("Checking ") + name); QueryBuilder duplicates(PimItem::tableName(), QueryBuilder::Select); duplicates.addColumn(PimItem::remoteIdColumn()); duplicates.addColumn(QStringLiteral("count(") + PimItem::idColumn() + QStringLiteral(") as cnt")); duplicates.addValueCondition(PimItem::remoteIdColumn(), Query::IsNot, QVariant()); duplicates.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, colId); duplicates.addGroupColumn(PimItem::remoteIdColumn()); duplicates.addValueCondition(QStringLiteral("count(") + PimItem::idColumn() + QLatin1Char(')'), Query::Greater, 1, QueryBuilder::HavingCondition); duplicates.exec(); Akonadi::Server::Collection col = Akonadi::Server::Collection::retrieveById(colId); const QVector contentMimeTypes = col.mimeTypes(); QVariantList contentMimeTypesVariantList; contentMimeTypesVariantList.reserve(contentMimeTypes.count()); for (const Akonadi::Server::MimeType &mimeType : contentMimeTypes) { contentMimeTypesVariantList << mimeType.id(); } while (duplicates.query().next()) { const QString rid = duplicates.query().value(0).toString(); Query::Condition condition(Query::And); condition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, rid); condition.addValueCondition(PimItem::mimeTypeIdColumn(), Query::NotIn, contentMimeTypesVariantList); condition.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, colId); QueryBuilder items(PimItem::tableName(), QueryBuilder::Select); items.addColumn(PimItem::idColumn()); items.addCondition(condition); if (!items.exec()) { inform(QStringLiteral("Error while deleting duplicates: ") + items.query().lastError().text()); continue; } QVariantList itemsIds; while (items.query().next()) { itemsIds.push_back(items.query().value(0)); } items.query().finish(); if (itemsIds.isEmpty()) { // the mimetype filter may have dropped some entries from the // duplicates query continue; } inform(QStringLiteral("Found duplicates ") + rid); SelectQueryBuilder parts; parts.addValueCondition(Part::pimItemIdFullColumnName(), Query::In, QVariant::fromValue(itemsIds)); parts.addValueCondition(Part::storageFullColumnName(), Query::Equals, (int) Part::External); if (parts.exec()) { const auto partsList = parts.result(); for (const auto &part : partsList) { bool exists = false; const auto filename = ExternalPartStorage::resolveAbsolutePath(part.data(), &exists); if (exists) { QFile::remove(filename); } } } items = QueryBuilder(PimItem::tableName(), QueryBuilder::Delete); items.addCondition(condition); if (!items.exec()) { inform(QStringLiteral("Error while deleting duplicates ") + items.query().lastError().text()); } } duplicates.query().finish(); } qb.query().finish(); } void StorageJanitor::vacuum() { const DbType::Type dbType = DbType::type(DataStore::self()->database()); if (dbType == DbType::MySQL || dbType == DbType::PostgreSQL) { inform("vacuuming database, that'll take some time and require a lot of temporary disk space..."); Q_FOREACH (const QString &table, allDatabaseTables()) { inform(QStringLiteral("optimizing table %1...").arg(table)); QString queryStr; if (dbType == DbType::MySQL) { queryStr = QLatin1Literal("OPTIMIZE TABLE ") + table; } else if (dbType == DbType::PostgreSQL) { queryStr = QLatin1Literal("VACUUM FULL ANALYZE ") + table; } else { continue; } QSqlQuery q(DataStore::self()->database()); if (!q.exec(queryStr)) { qCCritical(AKONADISERVER_LOG) << "failed to optimize table" << table << ":" << q.lastError().text(); } } inform("vacuum done"); } else { inform("Vacuum not supported for this database backend."); } Q_EMIT done(); } void StorageJanitor::checkSizeTreshold() { { QueryBuilder qb(Part::tableName(), QueryBuilder::Select); qb.addColumn(Part::idFullColumnName()); qb.addValueCondition(Part::storageFullColumnName(), Query::Equals, Part::Internal); qb.addValueCondition(Part::datasizeFullColumnName(), Query::Greater, DbConfig::configuredDatabase()->sizeThreshold()); if (!qb.exec()) { inform("Failed to query parts larger than treshold, skipping test"); return; } QSqlQuery query = qb.query(); inform(QStringLiteral("Found %1 parts to be moved to external files").arg(query.size())); while (query.next()) { Transaction transaction(DataStore::self(), QStringLiteral("JANITOR CHECK SIZE THRESHOLD")); Part part = Part::retrieveById(query.value(0).toLongLong()); const QByteArray name = ExternalPartStorage::nameForPartId(part.id()); const QString partPath = ExternalPartStorage::resolveAbsolutePath(name); QFile f(partPath); if (f.exists()) { qCDebug(AKONADISERVER_LOG) << "External payload file" << name << "already exists"; // That however is not a critical issue, since the part is not external, // so we can safely overwrite it } if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCCritical(AKONADISERVER_LOG) << "Failed to open file" << name << "for writing"; continue; } if (f.write(part.data()) != part.datasize()) { qCCritical(AKONADISERVER_LOG) << "Failed to write data to payload file" << name; f.remove(); continue; } part.setData(name); part.setStorage(Part::External); if (!part.update() || !transaction.commit()) { qCCritical(AKONADISERVER_LOG) << "Failed to update database entry of part" << part.id(); f.remove(); continue; } inform(QStringLiteral("Moved part %1 from database into external file %2").arg(part.id()).arg(QString::fromLatin1(name))); } query.finish(); } { QueryBuilder qb(Part::tableName(), QueryBuilder::Select); qb.addColumn(Part::idFullColumnName()); qb.addValueCondition(Part::storageFullColumnName(), Query::Equals, Part::External); qb.addValueCondition(Part::datasizeFullColumnName(), Query::Less, DbConfig::configuredDatabase()->sizeThreshold()); if (!qb.exec()) { inform("Failed to query parts smaller than treshold, skipping test"); return; } QSqlQuery query = qb.query(); inform(QStringLiteral("Found %1 parts to be moved to database").arg(query.size())); while (query.next()) { Transaction transaction(DataStore::self(), QStringLiteral("JANITOR CHECK SIZE THRESHOLD 2")); Part part = Part::retrieveById(query.value(0).toLongLong()); const QString partPath = ExternalPartStorage::resolveAbsolutePath(part.data()); QFile f(partPath); if (!f.exists()) { qCCritical(AKONADISERVER_LOG) << "Part file" << part.data() << "does not exist"; continue; } if (!f.open(QIODevice::ReadOnly)) { qCCritical(AKONADISERVER_LOG) << "Failed to open part file" << part.data() << "for reading"; continue; } part.setStorage(Part::Internal); part.setData(f.readAll()); if (part.data().size() != part.datasize()) { qCCritical(AKONADISERVER_LOG) << "Sizes of" << part.id() << "data don't match"; continue; } if (!part.update() || !transaction.commit()) { qCCritical(AKONADISERVER_LOG) << "Failed to update database entry of part" << part.id(); continue; } f.close(); f.remove(); inform(QStringLiteral("Moved part %1 from external file into database").arg(part.id())); } query.finish(); } } void StorageJanitor::migrateToLevelledCacheHierarchy() { QueryBuilder qb(Part::tableName(), QueryBuilder::Select); qb.addColumn(Part::idColumn()); qb.addColumn(Part::dataColumn()); qb.addValueCondition(Part::storageColumn(), Query::Equals, Part::External); if (!qb.exec()) { inform("Failed to query external payload parts, skipping test"); return; } QSqlQuery query = qb.query(); while (query.next()) { const qint64 id = query.value(0).toLongLong(); const QByteArray data = query.value(1).toByteArray(); const QString fileName = QString::fromUtf8(data); bool oldExists = false, newExists = false; // Resolve the current path const QString currentPath = ExternalPartStorage::resolveAbsolutePath(fileName, &oldExists); // Resolve the new path with legacy fallback disabled, so that it always // returns the new levelled-cache path, even when the old one exists const QString newPath = ExternalPartStorage::resolveAbsolutePath(fileName, &newExists, false); if (!oldExists) { qCCritical(AKONADISERVER_LOG) << "Old payload part does not exist, skipping part" << fileName; continue; } if (currentPath != newPath) { if (newExists) { qCCritical(AKONADISERVER_LOG) << "Part is in legacy location, but the destination file already exists, skipping part" << fileName; continue; } QFile f(currentPath); if (!f.rename(newPath)) { qCCritical(AKONADISERVER_LOG) << "Failed to move part from" << currentPath << " to " << newPath << ":" << f.errorString(); continue; } inform(QStringLiteral("Migrated part %1 to new levelled cache").arg(id)); } } query.finish(); } void StorageJanitor::findOrphanSearchIndexEntries() { QueryBuilder qb(Collection::tableName(), QueryBuilder::Select); qb.addSortColumn(Collection::idColumn(), Query::Ascending); qb.addColumn(Collection::idColumn()); qb.addColumn(Collection::isVirtualColumn()); if (!qb.exec()) { inform("Failed to query collections, skipping test"); return; } QDBusInterface iface(DBus::agentServiceName(QStringLiteral("akonadi_indexing_agent"), DBus::Agent), QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.Indexer"), DBusConnectionPool::threadConnection()); if (!iface.isValid()) { inform("Akonadi Indexing Agent is not running, skipping test"); return; } QSqlQuery query = qb.query(); while (query.next()) { const qint64 colId = query.value(0).toLongLong(); // Skip virtual collections, they are not indexed if (query.value(1).toBool()) { inform(QStringLiteral("Skipping virtual Collection %1").arg(colId)); continue; } inform(QStringLiteral("Checking Collection %1 search index...").arg(colId)); SearchRequest req("StorageJanitor"); req.setStoreResults(true); req.setCollections({ colId }); req.setRemoteSearch(false); req.setQuery(QStringLiteral("{ }")); // empty query to match all QStringList mts; Collection col; col.setId(colId); const auto colMts = col.mimeTypes(); if (colMts.isEmpty()) { // No mimetypes means we don't know which search store to look into, // skip it. continue; } mts.reserve(colMts.count()); for (const auto &mt : colMts) { mts << mt.name(); } req.setMimeTypes(mts); req.exec(); auto searchResults = req.results(); QueryBuilder iqb(PimItem::tableName(), QueryBuilder::Select); iqb.addColumn(PimItem::idColumn()); iqb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, colId); if (!iqb.exec()) { inform(QStringLiteral("Failed to query items in collection %1").arg(colId)); continue; } QSqlQuery itemQuery = iqb.query(); while (itemQuery.next()) { searchResults.remove(itemQuery.value(0).toLongLong()); } itemQuery.finish(); if (!searchResults.isEmpty()) { inform(QStringLiteral("Collection %1 search index contains %2 orphan items. Scheduling reindexing").arg(colId).arg(searchResults.count())); iface.call(QDBus::NoBlock, QStringLiteral("reindexCollection"), colId); } } query.finish(); } void StorageJanitor::ensureSearchCollection() { static const auto searchResourceName = QStringLiteral("akonadi_search_resource"); auto searchResource = Resource::retrieveByName(searchResourceName); if (!searchResource.isValid()) { searchResource.setName(searchResourceName); searchResource.setIsVirtual(true); if (!searchResource.insert()) { inform(QStringLiteral("Failed to create Search resource.")); return; } } auto searchCols = Collection::retrieveFiltered(Collection::resourceIdColumn(), searchResource.id()); if (searchCols.isEmpty()) { Collection searchCol; searchCol.setId(1); searchCol.setName(QStringLiteral("Search")); searchCol.setResource(searchResource); searchCol.setIndexPref(Collection::False); searchCol.setIsVirtual(true); if (!searchCol.insert()) { inform(QStringLiteral("Failed to create Search Collection")); return; } } } void StorageJanitor::inform(const char *msg) { inform(QLatin1String(msg)); } void StorageJanitor::inform(const QString &msg) { qCDebug(AKONADISERVER_LOG) << msg; Q_EMIT information(msg); } diff --git a/src/widgets/collectionpropertiesdialog.cpp b/src/widgets/collectionpropertiesdialog.cpp index b81b513ed..b3428b752 100644 --- a/src/widgets/collectionpropertiesdialog.cpp +++ b/src/widgets/collectionpropertiesdialog.cpp @@ -1,233 +1,233 @@ /* 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 "collectionpropertiesdialog.h" #include "cachepolicy.h" #include "cachepolicypage.h" #include "collection.h" #include "collectiongeneralpropertiespage_p.h" #include "collectionmodifyjob.h" #include "akonadiwidgets_debug.h" #include #include -#include +#include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN CollectionPropertiesDialog::Private { public: Private(CollectionPropertiesDialog *parent, const Akonadi::Collection &collection, const QStringList &pageNames); void init(); static void registerBuiltinPages(); void save() { const int numberOfTab(mTabWidget->count()); for (int i = 0; i < numberOfTab; ++i) { CollectionPropertiesPage *page = static_cast(mTabWidget->widget(i)); page->save(mCollection); } // We use WA_DeleteOnClose => Don't use dialog as parent otherwise we can't save modified collection. CollectionModifyJob *job = new CollectionModifyJob(mCollection); connect(job, &CollectionModifyJob::result, q, [this](KJob *job) { saveResult(job); }); } void saveResult(KJob *job) { if (job->error()) { // TODO qCWarning(AKONADIWIDGETS_LOG) << job->errorString(); } } void setCurrentPage(const QString &name) { const int numberOfTab(mTabWidget->count()); for (int i = 0; i < numberOfTab; ++i) { QWidget *w = mTabWidget->widget(i); if (w->objectName() == name) { mTabWidget->setCurrentIndex(i); break; } } } CollectionPropertiesDialog *q = nullptr; Collection mCollection; QStringList mPageNames; QTabWidget *mTabWidget = nullptr; }; typedef QList CollectionPropertiesPageFactoryList; Q_GLOBAL_STATIC(CollectionPropertiesPageFactoryList, s_pages) static bool s_defaultPage = true; CollectionPropertiesDialog::Private::Private(CollectionPropertiesDialog *qq, const Akonadi::Collection &collection, const QStringList &pageNames) : q(qq) , mCollection(collection) , mPageNames(pageNames) , mTabWidget(nullptr) { if (s_defaultPage) { registerBuiltinPages(); } } void CollectionPropertiesDialog::Private::registerBuiltinPages() { static bool registered = false; if (registered) { return; } s_pages->append(new CollectionGeneralPropertiesPageFactory()); s_pages->append(new CachePolicyPageFactory()); registered = true; } void CollectionPropertiesDialog::Private::init() { QVBoxLayout *mainLayout = new QVBoxLayout(q); q->setAttribute(Qt::WA_DeleteOnClose); mTabWidget = new QTabWidget(q); mainLayout->addWidget(mTabWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, q); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); q->connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); q->connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); mainLayout->addWidget(buttonBox); if (mPageNames.isEmpty()) { // default loading foreach (CollectionPropertiesPageFactory *factory, *s_pages) { CollectionPropertiesPage *page = factory->createWidget(mTabWidget); if (page->canHandle(mCollection)) { mTabWidget->addTab(page, page->pageTitle()); page->load(mCollection); } else { delete page; } } } else { // custom loading QHash pages; foreach (CollectionPropertiesPageFactory *factory, *s_pages) { CollectionPropertiesPage *page = factory->createWidget(mTabWidget); const QString pageName = page->objectName(); if (page->canHandle(mCollection) && mPageNames.contains(pageName) && !pages.contains(pageName)) { pages.insert(page->objectName(), page); } else { delete page; } } for (const QString &pageName : qAsConst(mPageNames)) { CollectionPropertiesPage *page = pages.value(pageName); if (page) { mTabWidget->addTab(page, page->pageTitle()); page->load(mCollection); } } } q->connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, q, [this]() { save(); }); q->connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, q, &QObject::deleteLater); KConfigGroup group(KSharedConfig::openConfig(), "CollectionPropertiesDialog"); const QSize size = group.readEntry("Size", QSize()); if (size.isValid()) { q->resize(size); } else { q->resize(q->sizeHint().width(), q->sizeHint().height()); } } CollectionPropertiesDialog::CollectionPropertiesDialog(const Collection &collection, QWidget *parent) : QDialog(parent) , d(new Private(this, collection, QStringList())) { d->init(); } CollectionPropertiesDialog::CollectionPropertiesDialog(const Collection &collection, const QStringList &pages, QWidget *parent) : QDialog(parent) , d(new Private(this, collection, pages)) { d->init(); } CollectionPropertiesDialog::~CollectionPropertiesDialog() { KConfigGroup group(KSharedConfig::openConfig(), "CollectionPropertiesDialog"); group.writeEntry("Size", size()); delete d; } void CollectionPropertiesDialog::registerPage(CollectionPropertiesPageFactory *factory) { if (s_pages->isEmpty() && s_defaultPage) { Private::registerBuiltinPages(); } s_pages->append(factory); } void CollectionPropertiesDialog::useDefaultPage(bool defaultPage) { s_defaultPage = defaultPage; } QString CollectionPropertiesDialog::defaultPageObjectName(DefaultPage page) { switch (page) { case GeneralPage: return QStringLiteral("Akonadi::CollectionGeneralPropertiesPage"); case CachePage: return QStringLiteral("Akonadi::CachePolicyPage"); } return QString(); } void CollectionPropertiesDialog::setCurrentPage(const QString &name) { d->setCurrentPage(name); } #include "moc_collectionpropertiesdialog.cpp" diff --git a/src/widgets/conflictresolvedialog.cpp b/src/widgets/conflictresolvedialog.cpp index 4e87705f9..b8150a431 100644 --- a/src/widgets/conflictresolvedialog.cpp +++ b/src/widgets/conflictresolvedialog.cpp @@ -1,327 +1,327 @@ /* Copyright (c) 2010 KDAB Author: Tobias Koenig 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 "conflictresolvedialog_p.h" #include "abstractdifferencesreporter.h" #include "differencesalgorithminterface.h" #include "typepluginloader_p.h" #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include using namespace Akonadi; static inline QString textToHTML(const QString &text) { return Qt::convertFromPlainText(text); } class HtmlDifferencesReporter : public AbstractDifferencesReporter { public: HtmlDifferencesReporter() { } QString toHtml() const { return header() + mContent + footer(); } QString plainText() const { return mTextContent; } void setPropertyNameTitle(const QString &title) override { mNameTitle = title; } void setLeftPropertyValueTitle(const QString &title) override { mLeftTitle = title; } void setRightPropertyValueTitle(const QString &title) override { mRightTitle = title; } void addProperty(Mode mode, const QString &name, const QString &leftValue, const QString &rightValue) override { switch (mode) { case NormalMode: mContent.append(QStringLiteral("%1:%2%3") .arg(name, textToHTML(leftValue), textToHTML(rightValue))); mTextContent.append(QStringLiteral("%1:\n%2\n%3\n\n").arg(name, leftValue, rightValue)); break; case ConflictMode: mContent.append(QStringLiteral("%1:%2%3") .arg(name, textToHTML(leftValue), textToHTML(rightValue))); mTextContent.append(QStringLiteral("%1:\n%2\n%3\n\n").arg(name, leftValue, rightValue)); break; case AdditionalLeftMode: mContent.append(QStringLiteral("%1:%2") .arg(name, textToHTML(leftValue))); mTextContent.append(QStringLiteral("%1:\n%2\n\n").arg(name, leftValue)); break; case AdditionalRightMode: mContent.append(QStringLiteral("%1:%2") .arg(name, textToHTML(rightValue))); mTextContent.append(QStringLiteral("%1:\n%2\n\n").arg(name, rightValue)); break; } } private: QString header() const { QString header = QStringLiteral(""); header += QStringLiteral("") .arg(KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name(), KColorScheme(QPalette::Active, KColorScheme::View).background().color().name()); header += QLatin1String("
"); header += QStringLiteral("") .arg(mNameTitle, mLeftTitle, mRightTitle); return header; } QString footer() const { return QStringLiteral("
%1%2 %3
" "" ""); } QString mContent; QString mNameTitle; QString mLeftTitle; QString mRightTitle; QString mTextContent; }; static void compareItems(AbstractDifferencesReporter *reporter, const Akonadi::Item &localItem, const Akonadi::Item &otherItem) { if (localItem.modificationTime() != otherItem.modificationTime()) { reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Modification Time"), QLocale().toString(localItem.modificationTime(), QLocale::ShortFormat), QLocale().toString(otherItem.modificationTime(), QLocale::ShortFormat)); } if (localItem.flags() != otherItem.flags()) { QStringList localFlags; localFlags.reserve(localItem.flags().count()); foreach (const QByteArray &localFlag, localItem.flags()) { localFlags.append(QString::fromUtf8(localFlag)); } QStringList otherFlags; otherFlags.reserve(otherItem.flags().count()); foreach (const QByteArray &otherFlag, otherItem.flags()) { otherFlags.append(QString::fromUtf8(otherFlag)); } reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Flags"), localFlags.join(QStringLiteral(", ")), otherFlags.join(QStringLiteral(", "))); } QHash localAttributes; foreach (Akonadi::Attribute *attribute, localItem.attributes()) { localAttributes.insert(attribute->type(), attribute->serialized()); } QHash otherAttributes; foreach (Akonadi::Attribute *attribute, otherItem.attributes()) { otherAttributes.insert(attribute->type(), attribute->serialized()); } if (localAttributes != otherAttributes) { foreach (const QByteArray &localKey, localAttributes) { if (!otherAttributes.contains(localKey)) { reporter->addProperty(AbstractDifferencesReporter::AdditionalLeftMode, i18n("Attribute: %1", QString::fromUtf8(localKey)), QString::fromUtf8(localAttributes.value(localKey)), QString()); } else { const QByteArray localValue = localAttributes.value(localKey); const QByteArray otherValue = otherAttributes.value(localKey); if (localValue != otherValue) { reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Attribute: %1", QString::fromUtf8(localKey)), QString::fromUtf8(localValue), QString::fromUtf8(otherValue)); } } } foreach (const QByteArray &otherKey, otherAttributes) { if (!localAttributes.contains(otherKey)) { reporter->addProperty(AbstractDifferencesReporter::AdditionalRightMode, i18n("Attribute: %1", QString::fromUtf8(otherKey)), QString(), QString::fromUtf8(otherAttributes.value(otherKey))); } } } } ConflictResolveDialog::ConflictResolveDialog(QWidget *parent) : QDialog(parent), mResolveStrategy(ConflictHandler::UseBothItems) { setWindowTitle(i18nc("@title:window", "Conflict Resolution")); QVBoxLayout *mainLayout = new QVBoxLayout(this); // Don't use QDialogButtonBox, order is very important (left on the left, right on the right) QHBoxLayout *buttonLayout = new QHBoxLayout(); QPushButton *takeLeftButton = new QPushButton(this); takeLeftButton->setText(i18nc("@action:button", "Take my version")); connect(takeLeftButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseLocalItemChoosen); buttonLayout->addWidget(takeLeftButton); takeLeftButton->setObjectName(QStringLiteral("takeLeftButton")); QPushButton *takeRightButton = new QPushButton(this); takeRightButton->setText(i18nc("@action:button", "Take their version")); takeRightButton->setObjectName(QStringLiteral("takeRightButton")); connect(takeRightButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseOtherItemChoosen); buttonLayout->addWidget(takeRightButton); QPushButton *keepBothButton = new QPushButton(this); keepBothButton->setText(i18nc("@action:button", "Keep both versions")); keepBothButton->setObjectName(QStringLiteral("keepBothButton")); buttonLayout->addWidget(keepBothButton); connect(keepBothButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseBothItemsChoosen); keepBothButton->setDefault(true); mView = new QTextBrowser(this); mView->setObjectName(QStringLiteral("view")); mView->setOpenLinks(false); QLabel *docuLabel = new QLabel(i18n("Your changes conflict with those made by someone else meanwhile.
" "Unless one version can just be thrown away, you will have to integrate those changes manually.
" "Click on \"Open text editor\" to keep a copy of the texts, then select which version is most correct, then re-open it and modify it again to add what's missing.")); connect(docuLabel, &QLabel::linkActivated, this, &ConflictResolveDialog::slotOpenEditor); docuLabel->setContextMenuPolicy(Qt::NoContextMenu); docuLabel->setWordWrap(true); docuLabel->setObjectName(QStringLiteral("doculabel")); mainLayout->addWidget(mView); mainLayout->addWidget(docuLabel); mainLayout->addLayout(buttonLayout); // default size is tiny, and there's usually lots of text, so make it much bigger create(); // ensure a window is created const QSize availableSize = windowHandle()->screen()->availableSize(); windowHandle()->resize(availableSize.width() * 0.7, availableSize.height() * 0.5); KWindowConfig::restoreWindowSize(windowHandle(), KSharedConfig::openConfig()->group("ConflictResolveDialog")); resize(windowHandle()->size()); // workaround for QTBUG-40584 } ConflictResolveDialog::~ConflictResolveDialog() { KConfigGroup group(KSharedConfig::openConfig()->group("ConflictResolveDialog")); KWindowConfig::saveWindowSize(windowHandle(), group); } void ConflictResolveDialog::setConflictingItems(const Akonadi::Item &localItem, const Akonadi::Item &otherItem) { mLocalItem = localItem; mOtherItem = otherItem; HtmlDifferencesReporter reporter; compareItems(&reporter, localItem, otherItem); if (mLocalItem.hasPayload() && mOtherItem.hasPayload()) { QObject *object = TypePluginLoader::objectForMimeTypeAndClass(localItem.mimeType(), localItem.availablePayloadMetaTypeIds()); if (object) { DifferencesAlgorithmInterface *algorithm = qobject_cast(object); if (algorithm) { algorithm->compare(&reporter, localItem, otherItem); mView->setHtml(reporter.toHtml()); mTextContent = reporter.plainText(); return; } } reporter.addProperty(HtmlDifferencesReporter::NormalMode, i18n("Data"), QString::fromUtf8(mLocalItem.payloadData()), QString::fromUtf8(mOtherItem.payloadData())); } mView->setHtml(reporter.toHtml()); mTextContent = reporter.plainText(); } void ConflictResolveDialog::slotOpenEditor() { QTemporaryFile file(QDir::tempPath() + QStringLiteral("/akonadi-XXXXXX.txt")); if (file.open()) { file.setAutoRemove(false); file.write(mTextContent.toLocal8Bit()); const QString fileName = file.fileName(); file.close(); QDesktopServices::openUrl(QUrl::fromLocalFile(fileName)); } } ConflictHandler::ResolveStrategy ConflictResolveDialog::resolveStrategy() const { return mResolveStrategy; } void ConflictResolveDialog::slotUseLocalItemChoosen() { mResolveStrategy = ConflictHandler::UseLocalItem; accept(); } void ConflictResolveDialog::slotUseOtherItemChoosen() { mResolveStrategy = ConflictHandler::UseOtherItem; accept(); } void ConflictResolveDialog::slotUseBothItemsChoosen() { mResolveStrategy = ConflictHandler::UseBothItems; accept(); } #include "moc_conflictresolvedialog_p.cpp" diff --git a/src/xml/autotests/collectiontest.cpp b/src/xml/autotests/collectiontest.cpp index 9f181710b..b31a770d3 100644 --- a/src/xml/autotests/collectiontest.cpp +++ b/src/xml/autotests/collectiontest.cpp @@ -1,107 +1,107 @@ /* Copyright (c) Igor Trindade Oliveira 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 "collectiontest.h" #include #include #include "entitydisplayattribute.h" -#include -#include +#include +#include #include using namespace Akonadi; QTEST_MAIN(CollectionTest) // NOTE: XML element attributes are stored in QHash, which means that there are // always in random order when converting to string. This test has QT_HASH_SEED // always set to 1, but it appears that it has different effect on my computer // and on Jenkins. This order of attributes is the order that passes on Jenkis, // so if it fails for you locally because of different order of arguments, // please make sure that your fix won't break the test on Jenkins. QByteArray collection1( "\n" " \n" " (\"Posteingang\" \"mail-folder-inbox\" \"\" ())\n" " \n" "\n"); QByteArray collection2( " \ \ (\"Posteingang\" \"mail-folder-inbox\" false) \ \ \ \ wcW \ \ \ "); void CollectionTest::testBuildCollection() { QDomDocument mDocument; mDocument.setContent(collection1, true, nullptr); Collection::List colist = XmlReader::readCollections(mDocument.documentElement()); const QStringList mimeType {QStringLiteral("inode/directory"),QStringLiteral("message/rfc822")}; QCOMPARE(colist.size(), 1); verifyCollection(colist, 0, QStringLiteral("c11"), QStringLiteral("Inbox"), mimeType); mDocument.setContent(collection2, true, nullptr); colist = XmlReader::readCollections(mDocument.documentElement()); QCOMPARE(colist.size(), 3); verifyCollection(colist, 0, QStringLiteral("c11"), QStringLiteral("Inbox"), mimeType); verifyCollection(colist, 1, QStringLiteral("c111"), QStringLiteral("KDE PIM"), mimeType); verifyCollection(colist, 2, QStringLiteral("c112"), QStringLiteral("Akonadi"), mimeType); QVERIFY(colist.at(0).hasAttribute()); EntityDisplayAttribute *attr = colist.at(0).attribute(); QCOMPARE(attr->displayName(), QStringLiteral("Posteingang")); } void CollectionTest::serializeCollection() { Collection c; c.setRemoteId(QStringLiteral("c11")); c.setName(QStringLiteral("Inbox")); c.setContentMimeTypes(QStringList() << Collection::mimeType() << QStringLiteral("message/rfc822")); c.attribute(Collection::AddIfMissing)->setDisplayName(QStringLiteral("Posteingang")); c.attribute()->setIconName(QStringLiteral("mail-folder-inbox")); QDomDocument doc; QDomElement root = doc.createElement(QStringLiteral("test")); doc.appendChild(root); XmlWriter::writeCollection(c, root); QCOMPARE(doc.toString(), QString::fromUtf8(collection1)); } void CollectionTest::verifyCollection(const Collection::List &colist, int listPosition, const QString &remoteId, const QString &name, const QStringList &mimeType) { QVERIFY(colist.at(listPosition).name() == name); QVERIFY(colist.at(listPosition).remoteId() == remoteId); QVERIFY(colist.at(listPosition).contentMimeTypes() == mimeType); } diff --git a/src/xml/autotests/xmldocumenttest.cpp b/src/xml/autotests/xmldocumenttest.cpp index c4ce167db..8bbf2915c 100644 --- a/src/xml/autotests/xmldocumenttest.cpp +++ b/src/xml/autotests/xmldocumenttest.cpp @@ -1,61 +1,61 @@ /* 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 "xmldocument.h" #include -#include +#include using namespace Akonadi; class XmlDocumentTest : public QObject { Q_OBJECT private Q_SLOTS: void testDocumentLoad() { XmlDocument doc(QStringLiteral(KDESRCDIR "/knutdemo.xml")); QVERIFY(doc.isValid()); QVERIFY(doc.lastError().isEmpty()); QCOMPARE(doc.collections().count(), 9); Collection col = doc.collectionByRemoteId(QStringLiteral("c11")); QCOMPARE(col.name(), QStringLiteral("Inbox")); QCOMPARE(col.attributes().count(), 1); QCOMPARE(col.parentCollection().remoteId(), QStringLiteral("c1")); QCOMPARE(doc.childCollections(col).count(), 2); Item item = doc.itemByRemoteId(QStringLiteral("contact1")); QCOMPARE(item.mimeType(), QStringLiteral("text/directory")); QVERIFY(item.hasPayload()); Item::List items = doc.items(col); QCOMPARE(items.count(), 1); item = items.first(); QVERIFY(item.hasPayload()); QCOMPARE(item.flags().count(), 1); QVERIFY(item.hasFlag("\\SEEN")); } }; QTEST_MAIN(XmlDocumentTest) #include "xmldocumenttest.moc" diff --git a/src/xml/xmldocument.cpp b/src/xml/xmldocument.cpp index da389d222..3a1037a99 100644 --- a/src/xml/xmldocument.cpp +++ b/src/xml/xmldocument.cpp @@ -1,332 +1,332 @@ /* 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 "xmldocument.h" #include "format_p.h" #include "xmlreader.h" #include #include -#include +#include #ifdef HAVE_LIBXML2 #include #include #include #include #endif using namespace Akonadi; // helper class for dealing with libxml resource management template class XmlPtr { public: XmlPtr(const T &t) : p(t) {} ~XmlPtr() { FreeFunc(p); } operator T() const { return p; } operator bool() const { return p != nullptr; } private: Q_DISABLE_COPY(XmlPtr) T p; }; static QDomElement findElementByRidHelper(const QDomElement &elem, const QString &rid, const QString &elemName) { if (elem.isNull()) { return QDomElement(); } if (elem.tagName() == elemName && elem.attribute(Format::Attr::remoteId()) == rid) { return elem; } const QDomNodeList children = elem.childNodes(); for (int i = 0; i < children.count(); ++i) { const QDomElement child = children.at(i).toElement(); if (child.isNull()) { continue; } const QDomElement rv = findElementByRidHelper(child, rid, elemName); if (!rv.isNull()) { return rv; } } return QDomElement(); } namespace Akonadi { class XmlDocumentPrivate { public: XmlDocumentPrivate() : valid(false) { lastError = i18n("No data loaded."); } QDomElement findElementByRid(const QString &rid, const QString &elemName) const { return findElementByRidHelper(document.documentElement(), rid, elemName); } QDomDocument document; QString lastError; bool valid; }; } XmlDocument::XmlDocument() : d(new XmlDocumentPrivate) { const QDomElement rootElem = d->document.createElement(Format::Tag::root()); d->document.appendChild(rootElem); } XmlDocument::XmlDocument(const QString &fileName) : d(new XmlDocumentPrivate) { loadFile(fileName); } XmlDocument::~XmlDocument() { delete d; } bool Akonadi::XmlDocument::loadFile(const QString &fileName) { d->valid = false; d->document = QDomDocument(); if (fileName.isEmpty()) { d->lastError = i18n("No filename specified"); return false; } QFile file(fileName); QByteArray data; if (file.exists()) { if (!file.open(QIODevice::ReadOnly)) { d->lastError = i18n("Unable to open data file '%1'.", fileName); return false; } data = file.readAll(); } else { d->lastError = i18n("File %1 does not exist.", fileName); return false; } #ifdef HAVE_LIBXML2 // schema validation XmlPtr sourceDoc(xmlParseMemory(data.constData(), data.length())); if (!sourceDoc) { d->lastError = i18n("Unable to parse data file '%1'.", fileName); return false; } const QString &schemaFileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/akonadi/akonadi-xml.xsd")); XmlPtr schemaDoc(xmlReadFile(schemaFileName.toLocal8Bit().constData(), nullptr, XML_PARSE_NONET)); if (!schemaDoc) { d->lastError = i18n("Schema definition could not be loaded and parsed."); return false; } XmlPtr parserContext(xmlSchemaNewDocParserCtxt(schemaDoc)); if (!parserContext) { d->lastError = i18n("Unable to create schema parser context."); return false; } XmlPtr schema(xmlSchemaParse(parserContext)); if (!schema) { d->lastError = i18n("Unable to create schema."); return false; } XmlPtr validationContext(xmlSchemaNewValidCtxt(schema)); if (!validationContext) { d->lastError = i18n("Unable to create schema validation context."); return false; } if (xmlSchemaValidateDoc(validationContext, sourceDoc) != 0) { d->lastError = i18n("Invalid file format."); return false; } #endif // DOM loading QString errMsg; if (!d->document.setContent(data, true, &errMsg)) { d->lastError = i18n("Unable to parse data file: %1", errMsg); return false; } d->valid = true; d->lastError.clear(); return true; } bool XmlDocument::writeToFile(const QString &fileName) const { QFile f(fileName); if (!f.open(QFile::WriteOnly)) { d->lastError = f.errorString(); return false; } f.write(d->document.toByteArray(2)); d->lastError.clear(); return true; } bool XmlDocument::isValid() const { return d->valid; } QString XmlDocument::lastError() const { return d->lastError; } QDomDocument &XmlDocument::document() const { return d->document; } QDomElement XmlDocument::collectionElement(const Collection &collection) const { if (collection == Collection::root()) { return d->document.documentElement(); } if (collection.remoteId().isEmpty()) { return QDomElement(); } if (collection.parentCollection().remoteId().isEmpty() && collection.parentCollection() != Collection::root()) { return d->findElementByRid(collection.remoteId(), Format::Tag::collection()); } QDomElement parent = collectionElement(collection.parentCollection()); if (parent.isNull()) { return QDomElement(); } const QDomNodeList children = parent.childNodes(); for (int i = 0; i < children.count(); ++i) { const QDomElement child = children.at(i).toElement(); if (child.isNull()) { continue; } if (child.tagName() == Format::Tag::collection() && child.attribute(Format::Attr::remoteId()) == collection.remoteId()) { return child; } } return QDomElement(); } QDomElement XmlDocument::itemElementByRemoteId(const QString &rid) const { return d->findElementByRid(rid, Format::Tag::item()); } QDomElement XmlDocument::collectionElementByRemoteId(const QString &rid) const { return d->findElementByRid(rid, Format::Tag::collection()); } Collection XmlDocument::collectionByRemoteId(const QString &rid) const { const QDomElement elem = d->findElementByRid(rid, Format::Tag::collection()); return XmlReader::elementToCollection(elem); } Item XmlDocument::itemByRemoteId(const QString &rid, bool includePayload) const { return XmlReader::elementToItem(itemElementByRemoteId(rid), includePayload); } Collection::List XmlDocument::collections() const { return XmlReader::readCollections(d->document.documentElement()); } Tag::List XmlDocument::tags() const { return XmlReader::readTags(d->document.documentElement()); } Collection::List XmlDocument::childCollections(const Collection &parentCollection) const { QDomElement parentElem = collectionElement(parentCollection); if (parentElem.isNull()) { d->lastError = QStringLiteral("Parent node not found."); return Collection::List(); } Collection::List rv; const QDomNodeList children = parentElem.childNodes(); for (int i = 0; i < children.count(); ++i) { const QDomElement childElem = children.at(i).toElement(); if (childElem.isNull() || childElem.tagName() != Format::Tag::collection()) { continue; } Collection c = XmlReader::elementToCollection(childElem); c.setParentCollection(parentCollection); rv.append(c); } return rv; } Item::List XmlDocument::items(const Akonadi::Collection &collection, bool includePayload) const { const QDomElement colElem = collectionElement(collection); if (colElem.isNull()) { d->lastError = i18n("Unable to find collection %1", collection.name()); return Item::List(); } else { d->lastError.clear(); } Item::List items; const QDomNodeList children = colElem.childNodes(); for (int i = 0; i < children.count(); ++i) { const QDomElement itemElem = children.at(i).toElement(); if (itemElem.isNull() || itemElem.tagName() != Format::Tag::item()) { continue; } items += XmlReader::elementToItem(itemElem, includePayload); } return items; } diff --git a/tests/libs/collectiondialog.cpp b/tests/libs/collectiondialog.cpp index 5c89540a6..d52a7359c 100644 --- a/tests/libs/collectiondialog.cpp +++ b/tests/libs/collectiondialog.cpp @@ -1,59 +1,59 @@ /* Copyright (c) 2010 Tobias Koenig 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 "collectiondialog.h" #include #include -#include +#include #include #include using namespace Akonadi; int main(int argc, char **argv) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("test"), i18n("Test Application"), QStringLiteral("1.0")); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); CollectionDialog dlg; dlg.setMimeTypeFilter( {QStringLiteral("text/directory")} ); dlg.setAccessRightsFilter(Collection::CanCreateItem); dlg.setDescription(i18n("Select an address book for saving:")); dlg.setSelectionMode(QAbstractItemView::ExtendedSelection); dlg.changeCollectionDialogOptions(CollectionDialog::AllowToCreateNewChildCollection); dlg.exec(); foreach (const Collection &collection, dlg.selectedCollections()) { qDebug() << "Selected collection:" << collection.name(); } return 0; }