diff options
59 files changed, 1124 insertions, 305 deletions
diff --git a/akonadish/CMakeLists.txt b/akonadish/CMakeLists.txt index 6761a32..eaedf9a 100644 --- a/akonadish/CMakeLists.txt +++ b/akonadish/CMakeLists.txt | |||
@@ -18,7 +18,8 @@ set(akonadi2_cli_SRCS | |||
18 | akonadish_utils.cpp | 18 | akonadish_utils.cpp |
19 | repl/repl.cpp | 19 | repl/repl.cpp |
20 | repl/replStates.cpp | 20 | repl/replStates.cpp |
21 | state.cpp) | 21 | state.cpp |
22 | utils.cpp) | ||
22 | 23 | ||
23 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | 24 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) |
24 | 25 | ||
diff --git a/akonadish/akonadish_utils.cpp b/akonadish/akonadish_utils.cpp index 070d788..27a863d 100644 --- a/akonadish/akonadish_utils.cpp +++ b/akonadish/akonadish_utils.cpp | |||
@@ -22,6 +22,8 @@ | |||
22 | 22 | ||
23 | #include "common/clientapi.h" | 23 | #include "common/clientapi.h" |
24 | 24 | ||
25 | #include "utils.h" | ||
26 | |||
25 | namespace AkonadishUtils | 27 | namespace AkonadishUtils |
26 | { | 28 | { |
27 | 29 | ||
@@ -75,8 +77,6 @@ QStringList resourceIds(State &state) | |||
75 | { | 77 | { |
76 | QStringList resources; | 78 | QStringList resources; |
77 | Akonadi2::Query query; | 79 | Akonadi2::Query query; |
78 | query.syncOnDemand = false; | ||
79 | query.processAll = false; | ||
80 | query.liveQuery = false; | 80 | query.liveQuery = false; |
81 | auto model = AkonadishUtils::loadModel("resource", query); | 81 | auto model = AkonadishUtils::loadModel("resource", query); |
82 | 82 | ||
@@ -98,40 +98,24 @@ QStringList resourceIds(State &state) | |||
98 | return resources; | 98 | return resources; |
99 | } | 99 | } |
100 | 100 | ||
101 | QStringList filtered(const QStringList &list, const QString &fragment) | ||
102 | { | ||
103 | if (fragment.isEmpty()) { | ||
104 | return list; | ||
105 | } | ||
106 | |||
107 | QStringList filtered; | ||
108 | for (auto item: list) { | ||
109 | if (item.startsWith(fragment)) { | ||
110 | filtered << item; | ||
111 | } | ||
112 | } | ||
113 | |||
114 | return filtered; | ||
115 | } | ||
116 | |||
117 | QStringList resourceCompleter(const QStringList &, const QString &fragment, State &state) | 101 | QStringList resourceCompleter(const QStringList &, const QString &fragment, State &state) |
118 | { | 102 | { |
119 | return filtered(resourceIds(state), fragment); | 103 | return Utils::filteredCompletions(resourceIds(state), fragment); |
120 | } | 104 | } |
121 | 105 | ||
122 | QStringList resourceOrTypeCompleter(const QStringList &commands, const QString &fragment, State &state) | 106 | QStringList resourceOrTypeCompleter(const QStringList &commands, const QString &fragment, State &state) |
123 | { | 107 | { |
124 | static QStringList types = QStringList() << "resource" << "folder" << "mail" << "event"; | 108 | static QStringList types = QStringList() << "resource" << "folder" << "mail" << "event"; |
125 | if (commands.count() == 1) { | 109 | if (commands.count() == 1) { |
126 | return filtered(s_types, fragment); | 110 | return Utils::filteredCompletions(s_types, fragment); |
127 | } | 111 | } |
128 | 112 | ||
129 | return filtered(resourceIds(state), fragment); | 113 | return Utils::filteredCompletions(resourceIds(state), fragment); |
130 | } | 114 | } |
131 | 115 | ||
132 | QStringList typeCompleter(const QStringList &commands, const QString &fragment, State &state) | 116 | QStringList typeCompleter(const QStringList &commands, const QString &fragment, State &state) |
133 | { | 117 | { |
134 | return filtered(s_types, fragment); | 118 | return Utils::filteredCompletions(s_types, fragment); |
135 | } | 119 | } |
136 | 120 | ||
137 | QMap<QString, QString> keyValueMapFromArgs(const QStringList &args) | 121 | QMap<QString, QString> keyValueMapFromArgs(const QStringList &args) |
diff --git a/akonadish/main.cpp b/akonadish/main.cpp index f3cbcac..4c00b9b 100644 --- a/akonadish/main.cpp +++ b/akonadish/main.cpp | |||
@@ -31,71 +31,82 @@ | |||
31 | /* | 31 | /* |
32 | * modes of operation: | 32 | * modes of operation: |
33 | * | 33 | * |
34 | * 1. called with no commands: start the REPL and listen for JSON on stin | 34 | * 1. called with no commands: start the REPL |
35 | * 2. called with -: listen for JSON on stdin | 35 | * 2. called with -: listen for commands on stdin |
36 | * 3. called with commands: try to match to syntx | 36 | * 3. called with a filename: try to run it as a script |
37 | * 4. called with commands: try to match to syntax and run the result | ||
37 | */ | 38 | */ |
38 | 39 | ||
40 | int enterRepl() | ||
41 | { | ||
42 | if (State::hasEventLoop()) { | ||
43 | return 0; | ||
44 | } | ||
45 | |||
46 | Repl *repl = new Repl; | ||
47 | QObject::connect(repl, &QStateMachine::finished, | ||
48 | repl, &QObject::deleteLater); | ||
49 | QObject::connect(repl, &QStateMachine::finished, | ||
50 | QCoreApplication::instance(), &QCoreApplication::quit); | ||
51 | |||
52 | State::setHasEventLoop(true); | ||
53 | int rv = QCoreApplication::instance()->exec(); | ||
54 | State::setHasEventLoop(false); | ||
55 | return rv; | ||
56 | } | ||
57 | |||
58 | bool goInteractive(const QStringList &, State &) | ||
59 | { | ||
60 | enterRepl(); | ||
61 | return true; | ||
62 | } | ||
63 | |||
64 | Syntax::List goInteractiveSyntax() | ||
65 | { | ||
66 | Syntax interactive("go_interactive", QString(), &goInteractive); | ||
67 | return Syntax::List() << interactive; | ||
68 | } | ||
69 | |||
70 | void processCommandStream(QTextStream &stream) | ||
71 | { | ||
72 | SyntaxTree::self()->registerSyntax(&goInteractiveSyntax); | ||
73 | QString line = stream.readLine(); | ||
74 | while (!line.isEmpty()) { | ||
75 | line = line.trimmed(); | ||
76 | |||
77 | if (!line.isEmpty() && !line.startsWith('#')) { | ||
78 | SyntaxTree::self()->run(SyntaxTree::tokenize(line)); | ||
79 | } | ||
80 | |||
81 | line = stream.readLine(); | ||
82 | } | ||
83 | } | ||
84 | |||
39 | int main(int argc, char *argv[]) | 85 | int main(int argc, char *argv[]) |
40 | { | 86 | { |
41 | const bool interactive = isatty(fileno(stdin)); | 87 | const bool interactive = isatty(fileno(stdin)); |
42 | const bool startRepl = (argc == 1) && interactive; | 88 | const bool startRepl = (argc == 1) && interactive; |
43 | //TODO: make a json command parse cause that would be awesomesauce | 89 | //TODO: make a json command parse cause that would be awesomesauce |
44 | const bool startJsonListener = !startRepl && | ||
45 | (argc == 2 && qstrcmp(argv[1], "-") == 0); | ||
46 | const bool fromScript = !startRepl && QFile::exists(argv[1]); | 90 | const bool fromScript = !startRepl && QFile::exists(argv[1]); |
47 | 91 | ||
48 | //qDebug() << "state at startup is" << interactive << startRepl << startJsonListener << fromScript; | 92 | //qDebug() << "state at startup is" << interactive << startRepl << fromScript; |
49 | 93 | ||
50 | QCoreApplication app(argc, argv); | 94 | QCoreApplication app(argc, argv); |
51 | app.setApplicationName(fromScript ? "interactive-app-shell" : argv[0]); | 95 | app.setApplicationName(fromScript ? "interactive-app-shell" : argv[0]); |
52 | //app.setApplicationName(argv[0]); | ||
53 | |||
54 | if (startRepl || startJsonListener) { | ||
55 | if (startRepl) { | ||
56 | Repl *repl = new Repl; | ||
57 | QObject::connect(repl, &QStateMachine::finished, | ||
58 | repl, &QObject::deleteLater); | ||
59 | QObject::connect(repl, &QStateMachine::finished, | ||
60 | &app, &QCoreApplication::quit); | ||
61 | } | ||
62 | |||
63 | if (startJsonListener) { | ||
64 | // JsonListener listener(syntax); | ||
65 | } | ||
66 | 96 | ||
67 | State::setHasEventLoop(true); | 97 | if (startRepl) { |
68 | return app.exec(); | 98 | return enterRepl(); |
69 | } else if (fromScript) { | 99 | } else if (fromScript) { |
70 | QFile f(argv[1]); | 100 | QFile f(argv[1]); |
71 | if (!f.open(QIODevice::ReadOnly)) { | 101 | if (!f.open(QIODevice::ReadOnly)) { |
72 | return 1; | 102 | return 1; |
73 | } | 103 | } |
74 | 104 | ||
75 | QString line = f.readLine(); | 105 | QTextStream inputStream(&f); |
76 | while (!line.isEmpty()) { | 106 | processCommandStream(inputStream); |
77 | line = line.trimmed(); | ||
78 | |||
79 | if (!line.isEmpty() && !line.startsWith('#')) { | ||
80 | SyntaxTree::self()->run(SyntaxTree::tokenize(line)); | ||
81 | } | ||
82 | |||
83 | line = f.readLine(); | ||
84 | } | ||
85 | exit(0); | ||
86 | } else if (!interactive) { | 107 | } else if (!interactive) { |
87 | QTextStream inputStream(stdin); | 108 | QTextStream inputStream(stdin); |
88 | 109 | processCommandStream(inputStream); | |
89 | QString line = inputStream.readLine(); | ||
90 | while (!line.isEmpty()) { | ||
91 | line = line.trimmed(); | ||
92 | |||
93 | if (!line.isEmpty() && !line.startsWith('#')) { | ||
94 | SyntaxTree::self()->run(SyntaxTree::tokenize(line)); | ||
95 | } | ||
96 | |||
97 | line = inputStream.readLine(); | ||
98 | } | ||
99 | } else { | 110 | } else { |
100 | QStringList commands = app.arguments(); | 111 | QStringList commands = app.arguments(); |
101 | commands.removeFirst(); | 112 | commands.removeFirst(); |
diff --git a/akonadish/state.cpp b/akonadish/state.cpp index f3f5975..9fb5bcc 100644 --- a/akonadish/state.cpp +++ b/akonadish/state.cpp | |||
@@ -24,6 +24,8 @@ | |||
24 | #include <QEventLoop> | 24 | #include <QEventLoop> |
25 | #include <QTextStream> | 25 | #include <QTextStream> |
26 | 26 | ||
27 | #include "common/log.h" | ||
28 | |||
27 | static bool s_hasEventLoop = false; | 29 | static bool s_hasEventLoop = false; |
28 | 30 | ||
29 | class State::Private | 31 | class State::Private |
@@ -112,6 +114,11 @@ void State::setHasEventLoop(bool evented) | |||
112 | s_hasEventLoop = evented; | 114 | s_hasEventLoop = evented; |
113 | } | 115 | } |
114 | 116 | ||
117 | bool State::hasEventLoop() | ||
118 | { | ||
119 | return s_hasEventLoop; | ||
120 | } | ||
121 | |||
115 | void State::setCommandTiming(bool time) | 122 | void State::setCommandTiming(bool time) |
116 | { | 123 | { |
117 | d->timing = time; | 124 | d->timing = time; |
@@ -122,4 +129,16 @@ bool State::commandTiming() const | |||
122 | return d->timing; | 129 | return d->timing; |
123 | } | 130 | } |
124 | 131 | ||
132 | void State::setLoggingLevel(const QString &level) const | ||
133 | { | ||
134 | Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::debugLevelFromName(level.toLatin1())); | ||
135 | } | ||
136 | |||
137 | QString State::loggingLevel() const | ||
138 | { | ||
139 | // do not turn this into a single line return: that core dumps due to allocation of | ||
140 | // the byte array in Akonadi2::Log | ||
141 | QByteArray rv = Akonadi2::Log::debugLevelName(Akonadi2::Log::debugOutputLevel()); | ||
142 | return rv.toLower(); | ||
143 | } | ||
125 | 144 | ||
diff --git a/akonadish/state.h b/akonadish/state.h index 9c1ab6f..3c4c2c7 100644 --- a/akonadish/state.h +++ b/akonadish/state.h | |||
@@ -39,7 +39,11 @@ public: | |||
39 | int commandStarted() const; | 39 | int commandStarted() const; |
40 | void commandFinished(int returnCode = 0) const; | 40 | void commandFinished(int returnCode = 0) const; |
41 | 41 | ||
42 | void setLoggingLevel(const QString &level) const; | ||
43 | QString loggingLevel() const; | ||
44 | |||
42 | static void setHasEventLoop(bool evented); | 45 | static void setHasEventLoop(bool evented); |
46 | static bool hasEventLoop(); | ||
43 | 47 | ||
44 | private: | 48 | private: |
45 | class Private; | 49 | class Private; |
diff --git a/akonadish/syntax_modules/akonadi_count.cpp b/akonadish/syntax_modules/akonadi_count.cpp index 5acdcdd..bb1cd19 100644 --- a/akonadish/syntax_modules/akonadi_count.cpp +++ b/akonadish/syntax_modules/akonadi_count.cpp | |||
@@ -53,8 +53,6 @@ bool count(const QStringList &args, State &state) | |||
53 | for (const auto &res : resources) { | 53 | for (const auto &res : resources) { |
54 | query.resources << res.toLatin1(); | 54 | query.resources << res.toLatin1(); |
55 | } | 55 | } |
56 | query.syncOnDemand = false; | ||
57 | query.processAll = false; | ||
58 | query.liveQuery = false; | 56 | query.liveQuery = false; |
59 | 57 | ||
60 | auto model = AkonadishUtils::loadModel(type, query); | 58 | auto model = AkonadishUtils::loadModel(type, query); |
diff --git a/akonadish/syntax_modules/akonadi_list.cpp b/akonadish/syntax_modules/akonadi_list.cpp index 82f13b5..7709d3b 100644 --- a/akonadish/syntax_modules/akonadi_list.cpp +++ b/akonadish/syntax_modules/akonadi_list.cpp | |||
@@ -58,8 +58,6 @@ bool list(const QStringList &args, State &state) | |||
58 | for (const auto &res : resources) { | 58 | for (const auto &res : resources) { |
59 | query.resources << res.toLatin1(); | 59 | query.resources << res.toLatin1(); |
60 | } | 60 | } |
61 | query.syncOnDemand = false; | ||
62 | query.processAll = false; | ||
63 | query.liveQuery = false; | 61 | query.liveQuery = false; |
64 | 62 | ||
65 | QTime time; | 63 | QTime time; |
diff --git a/akonadish/syntax_modules/akonadi_stat.cpp b/akonadish/syntax_modules/akonadi_stat.cpp index 9270f9d..d10556f 100644 --- a/akonadish/syntax_modules/akonadi_stat.cpp +++ b/akonadish/syntax_modules/akonadi_stat.cpp | |||
@@ -69,8 +69,6 @@ void statResources(const QStringList &resources, const State &state) | |||
69 | bool statAllResources(State &state) | 69 | bool statAllResources(State &state) |
70 | { | 70 | { |
71 | Akonadi2::Query query; | 71 | Akonadi2::Query query; |
72 | query.syncOnDemand = false; | ||
73 | query.processAll = false; | ||
74 | query.liveQuery = false; | 72 | query.liveQuery = false; |
75 | auto model = AkonadishUtils::loadModel("resource", query); | 73 | auto model = AkonadishUtils::loadModel("resource", query); |
76 | 74 | ||
diff --git a/akonadish/syntax_modules/akonadi_sync.cpp b/akonadish/syntax_modules/akonadi_sync.cpp index 03abbb4..0c994d0 100644 --- a/akonadish/syntax_modules/akonadi_sync.cpp +++ b/akonadish/syntax_modules/akonadi_sync.cpp | |||
@@ -43,8 +43,6 @@ bool sync(const QStringList &args, State &state) | |||
43 | for (const auto &res : args) { | 43 | for (const auto &res : args) { |
44 | query.resources << res.toLatin1(); | 44 | query.resources << res.toLatin1(); |
45 | } | 45 | } |
46 | query.syncOnDemand = true; | ||
47 | query.processAll = true; | ||
48 | 46 | ||
49 | QTimer::singleShot(0, [query, state]() { | 47 | QTimer::singleShot(0, [query, state]() { |
50 | Akonadi2::Store::synchronize(query).then<void>([state]() { | 48 | Akonadi2::Store::synchronize(query).then<void>([state]() { |
diff --git a/akonadish/syntax_modules/core_syntax.cpp b/akonadish/syntax_modules/core_syntax.cpp index b4812df..ccf96c1 100644 --- a/akonadish/syntax_modules/core_syntax.cpp +++ b/akonadish/syntax_modules/core_syntax.cpp | |||
@@ -24,6 +24,7 @@ | |||
24 | 24 | ||
25 | #include "state.h" | 25 | #include "state.h" |
26 | #include "syntaxtree.h" | 26 | #include "syntaxtree.h" |
27 | #include "utils.h" | ||
27 | 28 | ||
28 | namespace CoreSyntax | 29 | namespace CoreSyntax |
29 | { | 30 | { |
@@ -145,6 +146,24 @@ bool printSyntaxTree(const QStringList &, State &state) | |||
145 | return true; | 146 | return true; |
146 | } | 147 | } |
147 | 148 | ||
149 | bool setLoggingLevel(const QStringList &commands, State &state) | ||
150 | { | ||
151 | if (commands.count() != 1) { | ||
152 | state.printError(QObject::tr("Wrong number of arguments; expected 1 got %1").arg(commands.count())); | ||
153 | return false; | ||
154 | } | ||
155 | |||
156 | state.setLoggingLevel(commands.at(0)); | ||
157 | return true; | ||
158 | } | ||
159 | |||
160 | bool printLoggingLevel(const QStringList &commands, State &state) | ||
161 | { | ||
162 | const QString level = state.loggingLevel(); | ||
163 | state.printLine(level); | ||
164 | return true; | ||
165 | } | ||
166 | |||
148 | Syntax::List syntax() | 167 | Syntax::List syntax() |
149 | { | 168 | { |
150 | Syntax::List syntax; | 169 | Syntax::List syntax; |
@@ -158,15 +177,22 @@ Syntax::List syntax() | |||
158 | 177 | ||
159 | Syntax set("set", QObject::tr("Sets settings for the session")); | 178 | Syntax set("set", QObject::tr("Sets settings for the session")); |
160 | set.children << Syntax("debug", QObject::tr("Set the debug level from 0 to 6"), &CoreSyntax::setDebugLevel); | 179 | set.children << Syntax("debug", QObject::tr("Set the debug level from 0 to 6"), &CoreSyntax::setDebugLevel); |
180 | |||
161 | Syntax setTiming = Syntax("timing", QObject::tr("Whether or not to print the time commands take to complete")); | 181 | Syntax setTiming = Syntax("timing", QObject::tr("Whether or not to print the time commands take to complete")); |
162 | setTiming.children << Syntax("on", QString(), [](const QStringList &, State &state) -> bool { state.setCommandTiming(true); return true; }); | 182 | setTiming.children << Syntax("on", QString(), [](const QStringList &, State &state) -> bool { state.setCommandTiming(true); return true; }); |
163 | setTiming.children << Syntax("off", QString(), [](const QStringList &, State &state) -> bool { state.setCommandTiming(false); return true; }); | 183 | setTiming.children << Syntax("off", QString(), [](const QStringList &, State &state) -> bool { state.setCommandTiming(false); return true; }); |
164 | set.children << setTiming; | 184 | set.children << setTiming; |
185 | |||
186 | Syntax logging("logging", QObject::tr("Set the logging level to one of Trace, Log, Warning or Error"), &CoreSyntax::setLoggingLevel); | ||
187 | logging.completer = [](const QStringList &, const QString &fragment, State &state) -> QStringList { return Utils::filteredCompletions(QStringList() << "trace" << "log" << "warning" << "error", fragment, Qt::CaseInsensitive); }; | ||
188 | set.children << logging; | ||
189 | |||
165 | syntax << set; | 190 | syntax << set; |
166 | 191 | ||
167 | Syntax get("get", QObject::tr("Gets settings for the session")); | 192 | Syntax get("get", QObject::tr("Gets settings for the session")); |
168 | get.children << Syntax("debug", QObject::tr("The current debug level from 0 to 6"), &CoreSyntax::printDebugLevel); | 193 | get.children << Syntax("debug", QObject::tr("The current debug level from 0 to 6"), &CoreSyntax::printDebugLevel); |
169 | get.children << Syntax("timing", QObject::tr("Whether or not to print the time commands take to complete"), &CoreSyntax::printCommandTiming); | 194 | get.children << Syntax("timing", QObject::tr("Whether or not to print the time commands take to complete"), &CoreSyntax::printCommandTiming); |
195 | get.children << Syntax("logging", QObject::tr("The current logging level"), &CoreSyntax::printLoggingLevel); | ||
170 | syntax << get; | 196 | syntax << get; |
171 | 197 | ||
172 | return syntax; | 198 | return syntax; |
diff --git a/akonadish/utils.cpp b/akonadish/utils.cpp new file mode 100644 index 0000000..d2a28ed --- /dev/null +++ b/akonadish/utils.cpp | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "utils.h" | ||
21 | |||
22 | namespace Utils | ||
23 | { | ||
24 | |||
25 | QStringList filteredCompletions(const QStringList &possibleCompletions, const QString &commandFragment, Qt::CaseSensitivity cs) | ||
26 | { | ||
27 | if (commandFragment.isEmpty()) { | ||
28 | return possibleCompletions; | ||
29 | } | ||
30 | |||
31 | QStringList filtered; | ||
32 | for (auto item: possibleCompletions) { | ||
33 | if (item.startsWith(commandFragment, cs)) { | ||
34 | filtered << item; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | return filtered; | ||
39 | } | ||
40 | |||
41 | } // namespace Utils | ||
42 | |||
diff --git a/akonadish/utils.h b/akonadish/utils.h new file mode 100644 index 0000000..82be8d5 --- /dev/null +++ b/akonadish/utils.h | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QStringList> | ||
23 | |||
24 | namespace Utils | ||
25 | { | ||
26 | |||
27 | QStringList filteredCompletions(const QStringList &possibleCompletions, const QString &commandFragment, Qt::CaseSensitivity cs = Qt::CaseSensitive); | ||
28 | |||
29 | } // namespace Utils | ||
30 | |||
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 87f4898..85cd621 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt | |||
@@ -75,6 +75,7 @@ generate_flatbuffers( | |||
75 | commands/synchronize | 75 | commands/synchronize |
76 | commands/notification | 76 | commands/notification |
77 | commands/revisionreplayed | 77 | commands/revisionreplayed |
78 | commands/inspection | ||
78 | domain/event | 79 | domain/event |
79 | domain/mail | 80 | domain/mail |
80 | domain/folder | 81 | domain/folder |
diff --git a/common/bufferutils.h b/common/bufferutils.h new file mode 100644 index 0000000..b0fb75a --- /dev/null +++ b/common/bufferutils.h | |||
@@ -0,0 +1,26 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <flatbuffers/flatbuffers.h> | ||
4 | #include <QByteArray> | ||
5 | |||
6 | namespace Akonadi2 { | ||
7 | namespace BufferUtils { | ||
8 | template<typename T> | ||
9 | static QByteArray extractBuffer(const T *data) | ||
10 | { | ||
11 | return QByteArray::fromRawData(reinterpret_cast<char const *>(data->Data()), data->size()); | ||
12 | } | ||
13 | |||
14 | template<typename T> | ||
15 | static QByteArray extractBufferCopy(const T *data) | ||
16 | { | ||
17 | return QByteArray(reinterpret_cast<char const *>(data->Data()), data->size()); | ||
18 | } | ||
19 | |||
20 | static QByteArray extractBuffer(const flatbuffers::FlatBufferBuilder &fbb) | ||
21 | { | ||
22 | return QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()); | ||
23 | } | ||
24 | } | ||
25 | } | ||
26 | |||
diff --git a/common/clientapi.cpp b/common/clientapi.cpp index e7ca99d..824ef19 100644 --- a/common/clientapi.cpp +++ b/common/clientapi.cpp | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <QEventLoop> | 25 | #include <QEventLoop> |
26 | #include <QAbstractItemModel> | 26 | #include <QAbstractItemModel> |
27 | #include <QDir> | 27 | #include <QDir> |
28 | #include <QUuid> | ||
28 | #include <functional> | 29 | #include <functional> |
29 | #include <memory> | 30 | #include <memory> |
30 | 31 | ||
@@ -204,7 +205,7 @@ KAsync::Job<void> Store::synchronize(const Akonadi2::Query &query) | |||
204 | Trace() << "Synchronizing " << resource; | 205 | Trace() << "Synchronizing " << resource; |
205 | auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource); | 206 | auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource); |
206 | resourceAccess->open(); | 207 | resourceAccess->open(); |
207 | resourceAccess->synchronizeResource(query.syncOnDemand, query.processAll).then<void>([&future, resourceAccess]() { | 208 | resourceAccess->synchronizeResource(true, false).then<void>([&future, resourceAccess]() { |
208 | future.setFinished(); | 209 | future.setFinished(); |
209 | }).exec(); | 210 | }).exec(); |
210 | }) | 211 | }) |
@@ -212,10 +213,148 @@ KAsync::Job<void> Store::synchronize(const Akonadi2::Query &query) | |||
212 | .template then<void>([](){}); | 213 | .template then<void>([](){}); |
213 | } | 214 | } |
214 | 215 | ||
216 | KAsync::Job<void> Store::flushMessageQueue(const QByteArrayList &resourceIdentifier) | ||
217 | { | ||
218 | Trace() << "flushMessageQueue" << resourceIdentifier; | ||
219 | return KAsync::iterate(resourceIdentifier) | ||
220 | .template each<void, QByteArray>([](const QByteArray &resource, KAsync::Future<void> &future) { | ||
221 | Trace() << "Flushing message queue " << resource; | ||
222 | auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource); | ||
223 | resourceAccess->open(); | ||
224 | resourceAccess->synchronizeResource(false, true).then<void>([&future, resourceAccess]() { | ||
225 | future.setFinished(); | ||
226 | }).exec(); | ||
227 | }) | ||
228 | //FIXME JOBAPI this is only required because we don't care about the return value of each (and each shouldn't even have a return value) | ||
229 | .template then<void>([](){}); | ||
230 | } | ||
231 | |||
232 | KAsync::Job<void> Store::flushReplayQueue(const QByteArrayList &resourceIdentifier) | ||
233 | { | ||
234 | return flushMessageQueue(resourceIdentifier); | ||
235 | } | ||
236 | |||
237 | template <class DomainType> | ||
238 | KAsync::Job<DomainType> Store::fetchOne(const Akonadi2::Query &query) | ||
239 | { | ||
240 | return KAsync::start<DomainType>([query](KAsync::Future<DomainType> &future) { | ||
241 | //FIXME We could do this more elegantly if composed jobs would have the correct type (In that case we'd simply return the value from then continuation, and could avoid the outer job entirely) | ||
242 | fetch<DomainType>(query, 1) | ||
243 | .template then<void, QList<typename DomainType::Ptr> >([&future](const QList<typename DomainType::Ptr> &list){ | ||
244 | future.setValue(*list.first()); | ||
245 | future.setFinished(); | ||
246 | }, [&future](int errorCode, const QString &errorMessage) { | ||
247 | future.setError(errorCode, errorMessage); | ||
248 | future.setFinished(); | ||
249 | }).exec(); | ||
250 | }); | ||
251 | } | ||
252 | |||
253 | template <class DomainType> | ||
254 | KAsync::Job<QList<typename DomainType::Ptr> > Store::fetchAll(const Akonadi2::Query &query) | ||
255 | { | ||
256 | return fetch<DomainType>(query); | ||
257 | } | ||
258 | |||
259 | template <class DomainType> | ||
260 | KAsync::Job<QList<typename DomainType::Ptr> > Store::fetch(const Akonadi2::Query &query, int minimumAmount) | ||
261 | { | ||
262 | auto model = loadModel<DomainType>(query); | ||
263 | auto list = QSharedPointer<QList<typename DomainType::Ptr> >::create(); | ||
264 | auto context = QSharedPointer<QObject>::create(); | ||
265 | return KAsync::start<QList<typename DomainType::Ptr> >([model, list, context, minimumAmount](KAsync::Future<QList<typename DomainType::Ptr> > &future) { | ||
266 | if (model->rowCount() >= 1) { | ||
267 | for (int i = 0; i < model->rowCount(); i++) { | ||
268 | list->append(model->index(i, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).template value<typename DomainType::Ptr>()); | ||
269 | } | ||
270 | } else { | ||
271 | QObject::connect(model.data(), &QAbstractItemModel::rowsInserted, context.data(), [model, &future, list](const QModelIndex &index, int start, int end) { | ||
272 | for (int i = start; i <= end; i++) { | ||
273 | list->append(model->index(i, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).template value<typename DomainType::Ptr>()); | ||
274 | } | ||
275 | }); | ||
276 | QObject::connect(model.data(), &QAbstractItemModel::dataChanged, context.data(), [model, &future, list, minimumAmount](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) { | ||
277 | if (roles.contains(ModelResult<DomainType, typename DomainType::Ptr>::ChildrenFetchedRole)) { | ||
278 | if (list->size() < minimumAmount) { | ||
279 | future.setError(1, "Not enough values."); | ||
280 | } else { | ||
281 | future.setValue(*list); | ||
282 | } | ||
283 | future.setFinished(); | ||
284 | } | ||
285 | }); | ||
286 | } | ||
287 | if (model->data(QModelIndex(), ModelResult<DomainType, typename DomainType::Ptr>::ChildrenFetchedRole).toBool()) { | ||
288 | if (list->size() < minimumAmount) { | ||
289 | future.setError(1, "Not enough values."); | ||
290 | } else { | ||
291 | future.setValue(*list); | ||
292 | } | ||
293 | future.setFinished(); | ||
294 | } | ||
295 | }); | ||
296 | } | ||
297 | |||
298 | template <class DomainType> | ||
299 | KAsync::Job<void> Resources::inspect(const Inspection &inspectionCommand) | ||
300 | { | ||
301 | auto resource = inspectionCommand.resourceIdentifier; | ||
302 | |||
303 | Trace() << "Sending inspection " << resource; | ||
304 | auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource); | ||
305 | resourceAccess->open(); | ||
306 | auto notifier = QSharedPointer<Akonadi2::Notifier>::create(resourceAccess); | ||
307 | auto id = QUuid::createUuid().toByteArray(); | ||
308 | return resourceAccess->sendInspectionCommand(id, ApplicationDomain::getTypeName<DomainType>(), inspectionCommand.entityIdentifier, inspectionCommand.property, inspectionCommand.expectedValue) | ||
309 | .template then<void>([resourceAccess, notifier, id](KAsync::Future<void> &future) { | ||
310 | notifier->registerHandler([&future, id](const Notification ¬ification) { | ||
311 | if (notification.id == id) { | ||
312 | if (notification.code) { | ||
313 | future.setError(-1, "Inspection returned an error: " + notification.message); | ||
314 | } else { | ||
315 | future.setFinished(); | ||
316 | } | ||
317 | } | ||
318 | }); | ||
319 | }); | ||
320 | } | ||
321 | |||
322 | class Akonadi2::Notifier::Private { | ||
323 | public: | ||
324 | Private() | ||
325 | : context(new QObject) | ||
326 | { | ||
327 | |||
328 | } | ||
329 | QList<QSharedPointer<ResourceAccess> > resourceAccess; | ||
330 | QList<std::function<void(const Notification &)> > handler; | ||
331 | QSharedPointer<QObject> context; | ||
332 | }; | ||
333 | |||
334 | Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess) | ||
335 | : d(new Akonadi2::Notifier::Private) | ||
336 | { | ||
337 | QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification ¬ification) { | ||
338 | for (const auto &handler : d->handler) { | ||
339 | handler(notification); | ||
340 | } | ||
341 | }); | ||
342 | d->resourceAccess << resourceAccess; | ||
343 | } | ||
344 | |||
345 | void Notifier::registerHandler(std::function<void(const Notification &)> handler) | ||
346 | { | ||
347 | d->handler << handler; | ||
348 | } | ||
349 | |||
215 | #define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \ | 350 | #define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \ |
216 | template KAsync::Job<void> Store::create<T>(const T &domainObject); \ | 351 | template KAsync::Job<void> Store::create<T>(const T &domainObject); \ |
217 | template KAsync::Job<void> Store::modify<T>(const T &domainObject); \ | 352 | template KAsync::Job<void> Store::modify<T>(const T &domainObject); \ |
218 | template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \ | 353 | template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \ |
354 | template KAsync::Job<void> Resources::inspect<T>(const Inspection &); \ | ||
355 | template KAsync::Job<T> Store::fetchOne<T>(const Query &); \ | ||
356 | template KAsync::Job<QList<T::Ptr> > Store::fetchAll<T>(const Query &); \ | ||
357 | template KAsync::Job<QList<T::Ptr> > Store::fetch<T>(const Query &, int); \ | ||
219 | 358 | ||
220 | REGISTER_TYPE(ApplicationDomain::Event); | 359 | REGISTER_TYPE(ApplicationDomain::Event); |
221 | REGISTER_TYPE(ApplicationDomain::Mail); | 360 | REGISTER_TYPE(ApplicationDomain::Mail); |
diff --git a/common/clientapi.h b/common/clientapi.h index 4e55432..06376c2 100644 --- a/common/clientapi.h +++ b/common/clientapi.h | |||
@@ -26,11 +26,14 @@ | |||
26 | #include <Async/Async> | 26 | #include <Async/Async> |
27 | 27 | ||
28 | #include "query.h" | 28 | #include "query.h" |
29 | #include "inspection.h" | ||
29 | #include "applicationdomaintype.h" | 30 | #include "applicationdomaintype.h" |
30 | 31 | ||
31 | class QAbstractItemModel; | 32 | class QAbstractItemModel; |
32 | 33 | ||
33 | namespace Akonadi2 { | 34 | namespace Akonadi2 { |
35 | class ResourceAccess; | ||
36 | class Notification; | ||
34 | 37 | ||
35 | /** | 38 | /** |
36 | * Store interface used in the client API. | 39 | * Store interface used in the client API. |
@@ -73,6 +76,11 @@ public: | |||
73 | static KAsync::Job<void> remove(const DomainType &domainObject); | 76 | static KAsync::Job<void> remove(const DomainType &domainObject); |
74 | 77 | ||
75 | /** | 78 | /** |
79 | * Synchronize data to local cache. | ||
80 | */ | ||
81 | static KAsync::Job<void> synchronize(const Akonadi2::Query &query); | ||
82 | |||
83 | /** | ||
76 | * Shutdown resource. | 84 | * Shutdown resource. |
77 | */ | 85 | */ |
78 | static KAsync::Job<void> shutdown(const QByteArray &resourceIdentifier); | 86 | static KAsync::Job<void> shutdown(const QByteArray &resourceIdentifier); |
@@ -87,16 +95,46 @@ public: | |||
87 | static KAsync::Job<void> start(const QByteArray &resourceIdentifier); | 95 | static KAsync::Job<void> start(const QByteArray &resourceIdentifier); |
88 | 96 | ||
89 | /** | 97 | /** |
90 | * Synchronize data to local cache. | 98 | * Flushes any pending messages to disk |
91 | */ | 99 | */ |
92 | static KAsync::Job<void> synchronize(const Akonadi2::Query &query); | 100 | static KAsync::Job<void> flushMessageQueue(const QByteArrayList &resourceIdentifier); |
101 | |||
102 | /** | ||
103 | * Flushes any pending messages that haven't been replayed to the source. | ||
104 | */ | ||
105 | static KAsync::Job<void> flushReplayQueue(const QByteArrayList &resourceIdentifier); | ||
93 | 106 | ||
94 | /** | 107 | /** |
95 | * Removes a resource from disk. | 108 | * Removes a resource from disk. |
96 | */ | 109 | */ |
97 | static void removeFromDisk(const QByteArray &resourceIdentifier); | 110 | static void removeFromDisk(const QByteArray &resourceIdentifier); |
111 | |||
112 | template <class DomainType> | ||
113 | static KAsync::Job<DomainType> fetchOne(const Akonadi2::Query &query); | ||
114 | |||
115 | template <class DomainType> | ||
116 | static KAsync::Job<QList<typename DomainType::Ptr> > fetchAll(const Akonadi2::Query &query); | ||
117 | |||
118 | template <class DomainType> | ||
119 | static KAsync::Job<QList<typename DomainType::Ptr> > fetch(const Akonadi2::Query &query, int minimumAmount = 0); | ||
98 | }; | 120 | }; |
99 | 121 | ||
122 | namespace Resources { | ||
123 | template <class DomainType> | ||
124 | KAsync::Job<void> inspect(const Inspection &inspectionCommand); | ||
125 | } | ||
126 | |||
127 | class Notifier { | ||
128 | public: | ||
129 | Notifier(const QSharedPointer<ResourceAccess> &resourceAccess); | ||
130 | // Notifier(const QByteArray &resource); | ||
131 | // Notifier(const QByteArrayList &resource); | ||
132 | void registerHandler(std::function<void(const Notification &)>); | ||
133 | |||
134 | private: | ||
135 | class Private; | ||
136 | QScopedPointer<Private> d; | ||
137 | }; | ||
100 | 138 | ||
101 | } | 139 | } |
102 | 140 | ||
diff --git a/common/commands.cpp b/common/commands.cpp index 7a0ae23..35dfb13 100644 --- a/common/commands.cpp +++ b/common/commands.cpp | |||
@@ -33,7 +33,7 @@ QByteArray name(int commandId) | |||
33 | switch(commandId) { | 33 | switch(commandId) { |
34 | case UnknownCommand: | 34 | case UnknownCommand: |
35 | return "Unknown"; | 35 | return "Unknown"; |
36 | case CommandCompletion: | 36 | case CommandCompletionCommand: |
37 | return "Completion"; | 37 | return "Completion"; |
38 | case HandshakeCommand: | 38 | case HandshakeCommand: |
39 | return "Handshake"; | 39 | return "Handshake"; |
@@ -59,6 +59,8 @@ QByteArray name(int commandId) | |||
59 | return "Ping"; | 59 | return "Ping"; |
60 | case RevisionReplayedCommand: | 60 | case RevisionReplayedCommand: |
61 | return "RevisionReplayed"; | 61 | return "RevisionReplayed"; |
62 | case InspectionCommand: | ||
63 | return "Inspection"; | ||
62 | case CustomCommand: | 64 | case CustomCommand: |
63 | return "Custom"; | 65 | return "Custom"; |
64 | }; | 66 | }; |
diff --git a/common/commands.h b/common/commands.h index c68ef90..33d5cd7 100644 --- a/common/commands.h +++ b/common/commands.h | |||
@@ -34,7 +34,7 @@ namespace Commands | |||
34 | 34 | ||
35 | enum CommandIds { | 35 | enum CommandIds { |
36 | UnknownCommand = 0, | 36 | UnknownCommand = 0, |
37 | CommandCompletion, | 37 | CommandCompletionCommand, |
38 | HandshakeCommand, | 38 | HandshakeCommand, |
39 | RevisionUpdateCommand, | 39 | RevisionUpdateCommand, |
40 | SynchronizeCommand, | 40 | SynchronizeCommand, |
@@ -47,6 +47,7 @@ enum CommandIds { | |||
47 | NotificationCommand, | 47 | NotificationCommand, |
48 | PingCommand, | 48 | PingCommand, |
49 | RevisionReplayedCommand, | 49 | RevisionReplayedCommand, |
50 | InspectionCommand, | ||
50 | CustomCommand = 0xffff | 51 | CustomCommand = 0xffff |
51 | }; | 52 | }; |
52 | 53 | ||
diff --git a/common/commands/commandcompletion.fbs b/common/commands/commandcompletion.fbs index 5330b4f..de7ec14 100644 --- a/common/commands/commandcompletion.fbs +++ b/common/commands/commandcompletion.fbs | |||
@@ -1,4 +1,4 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2.Commands; |
2 | 2 | ||
3 | table CommandCompletion { | 3 | table CommandCompletion { |
4 | id: ulong; | 4 | id: ulong; |
diff --git a/common/commands/fetchentity.fbs b/common/commands/fetchentity.fbs index ddca275..7a1d74d 100644 --- a/common/commands/fetchentity.fbs +++ b/common/commands/fetchentity.fbs | |||
@@ -1,4 +1,4 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2.Commands; |
2 | 2 | ||
3 | table FetchEntity { | 3 | table FetchEntity { |
4 | revision: ulong; | 4 | revision: ulong; |
diff --git a/common/commands/handshake.fbs b/common/commands/handshake.fbs index 52a883a..e824715 100644 --- a/common/commands/handshake.fbs +++ b/common/commands/handshake.fbs | |||
@@ -1,4 +1,4 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2.Commands; |
2 | 2 | ||
3 | table Handshake { | 3 | table Handshake { |
4 | name: string; | 4 | name: string; |
diff --git a/common/commands/inspection.fbs b/common/commands/inspection.fbs new file mode 100644 index 0000000..aaae1ae --- /dev/null +++ b/common/commands/inspection.fbs | |||
@@ -0,0 +1,12 @@ | |||
1 | namespace Akonadi2.Commands; | ||
2 | |||
3 | table Inspection { | ||
4 | id: string; | ||
5 | type: int; | ||
6 | entityId: string; | ||
7 | domainType: string; | ||
8 | property: string; | ||
9 | expectedValue: string; | ||
10 | } | ||
11 | |||
12 | root_type Inspection; | ||
diff --git a/common/commands/notification.fbs b/common/commands/notification.fbs index 6684472..89687cf 100644 --- a/common/commands/notification.fbs +++ b/common/commands/notification.fbs | |||
@@ -1,9 +1,13 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2.Commands; |
2 | 2 | ||
3 | enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress } | 3 | enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection } |
4 | enum NotificationCode : byte { Success = 0, Failure = 1, UserCode } | ||
4 | 5 | ||
5 | table Notification { | 6 | table Notification { |
6 | type: NotificationType = Status; | 7 | type: NotificationType = Status; |
8 | identifier: string; //An identifier that links back to the something related to the notification (e.g. an entity id or a command id) | ||
9 | message: string; | ||
10 | code: int = 0; //Of type NotificationCode | ||
7 | } | 11 | } |
8 | 12 | ||
9 | root_type Notification; | 13 | root_type Notification; |
diff --git a/common/commands/revisionupdate.fbs b/common/commands/revisionupdate.fbs index 634bcd0..93fbe34 100644 --- a/common/commands/revisionupdate.fbs +++ b/common/commands/revisionupdate.fbs | |||
@@ -1,4 +1,4 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2.Commands; |
2 | 2 | ||
3 | table RevisionUpdate { | 3 | table RevisionUpdate { |
4 | revision: ulong; | 4 | revision: ulong; |
diff --git a/common/commands/synchronize.fbs b/common/commands/synchronize.fbs index d2d0364..7c3ae9a 100644 --- a/common/commands/synchronize.fbs +++ b/common/commands/synchronize.fbs | |||
@@ -1,4 +1,4 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2.Commands; |
2 | 2 | ||
3 | table Synchronize { | 3 | table Synchronize { |
4 | sourceSync: bool; //Synchronize with source | 4 | sourceSync: bool; //Synchronize with source |
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h index cff0172..44d8743 100644 --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h | |||
@@ -82,29 +82,34 @@ inline bool operator==(const ApplicationDomainType& lhs, const ApplicationDomain | |||
82 | && lhs.resourceInstanceIdentifier() == rhs.resourceInstanceIdentifier(); | 82 | && lhs.resourceInstanceIdentifier() == rhs.resourceInstanceIdentifier(); |
83 | } | 83 | } |
84 | 84 | ||
85 | struct Event : public ApplicationDomainType { | 85 | struct Entity : public ApplicationDomainType { |
86 | typedef QSharedPointer<Event> Ptr; | 86 | typedef QSharedPointer<Entity> Ptr; |
87 | using ApplicationDomainType::ApplicationDomainType; | 87 | using ApplicationDomainType::ApplicationDomainType; |
88 | }; | 88 | }; |
89 | 89 | ||
90 | struct Todo : public ApplicationDomainType { | 90 | struct Event : public Entity { |
91 | typedef QSharedPointer<Event> Ptr; | ||
92 | using Entity::Entity; | ||
93 | }; | ||
94 | |||
95 | struct Todo : public Entity { | ||
91 | typedef QSharedPointer<Todo> Ptr; | 96 | typedef QSharedPointer<Todo> Ptr; |
92 | using ApplicationDomainType::ApplicationDomainType; | 97 | using Entity::Entity; |
93 | }; | 98 | }; |
94 | 99 | ||
95 | struct Calendar : public ApplicationDomainType { | 100 | struct Calendar : public Entity { |
96 | typedef QSharedPointer<Calendar> Ptr; | 101 | typedef QSharedPointer<Calendar> Ptr; |
97 | using ApplicationDomainType::ApplicationDomainType; | 102 | using Entity::Entity; |
98 | }; | 103 | }; |
99 | 104 | ||
100 | struct Mail : public ApplicationDomainType { | 105 | struct Mail : public Entity { |
101 | typedef QSharedPointer<Mail> Ptr; | 106 | typedef QSharedPointer<Mail> Ptr; |
102 | using ApplicationDomainType::ApplicationDomainType; | 107 | using Entity::Entity; |
103 | }; | 108 | }; |
104 | 109 | ||
105 | struct Folder : public ApplicationDomainType { | 110 | struct Folder : public Entity { |
106 | typedef QSharedPointer<Folder> Ptr; | 111 | typedef QSharedPointer<Folder> Ptr; |
107 | using ApplicationDomainType::ApplicationDomainType; | 112 | using Entity::Entity; |
108 | }; | 113 | }; |
109 | 114 | ||
110 | /** | 115 | /** |
@@ -155,6 +160,8 @@ class TypeImplementation; | |||
155 | 160 | ||
156 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType) | 161 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType) |
157 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr) | 162 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr) |
163 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Entity) | ||
164 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Entity::Ptr) | ||
158 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event) | 165 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event) |
159 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event::Ptr) | 166 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event::Ptr) |
160 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail) | 167 | Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail) |
diff --git a/common/facade.cpp b/common/facade.cpp index 22ef84a..91021db 100644 --- a/common/facade.cpp +++ b/common/facade.cpp | |||
@@ -25,6 +25,7 @@ | |||
25 | #include "definitions.h" | 25 | #include "definitions.h" |
26 | #include "domainadaptor.h" | 26 | #include "domainadaptor.h" |
27 | #include "queryrunner.h" | 27 | #include "queryrunner.h" |
28 | #include "bufferutils.h" | ||
28 | 29 | ||
29 | using namespace Akonadi2; | 30 | using namespace Akonadi2; |
30 | 31 | ||
@@ -113,7 +114,7 @@ KAsync::Job<void> GenericFacade<DomainType>::create(const DomainType &domainObje | |||
113 | } | 114 | } |
114 | flatbuffers::FlatBufferBuilder entityFbb; | 115 | flatbuffers::FlatBufferBuilder entityFbb; |
115 | mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb); | 116 | mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb); |
116 | return mResourceAccess->sendCreateCommand(bufferTypeForDomainType(), QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize())); | 117 | return mResourceAccess->sendCreateCommand(bufferTypeForDomainType(), BufferUtils::extractBuffer(entityFbb)); |
117 | } | 118 | } |
118 | 119 | ||
119 | template<class DomainType> | 120 | template<class DomainType> |
@@ -125,7 +126,7 @@ KAsync::Job<void> GenericFacade<DomainType>::modify(const DomainType &domainObje | |||
125 | } | 126 | } |
126 | flatbuffers::FlatBufferBuilder entityFbb; | 127 | flatbuffers::FlatBufferBuilder entityFbb; |
127 | mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb); | 128 | mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb); |
128 | return mResourceAccess->sendModifyCommand(domainObject.identifier(), domainObject.revision(), bufferTypeForDomainType(), QByteArrayList(), QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize())); | 129 | return mResourceAccess->sendModifyCommand(domainObject.identifier(), domainObject.revision(), bufferTypeForDomainType(), QByteArrayList(), BufferUtils::extractBuffer(entityFbb)); |
129 | } | 130 | } |
130 | 131 | ||
131 | template<class DomainType> | 132 | template<class DomainType> |
diff --git a/common/genericresource.cpp b/common/genericresource.cpp index 29acce4..c7f323a 100644 --- a/common/genericresource.cpp +++ b/common/genericresource.cpp | |||
@@ -6,13 +6,17 @@ | |||
6 | #include "createentity_generated.h" | 6 | #include "createentity_generated.h" |
7 | #include "modifyentity_generated.h" | 7 | #include "modifyentity_generated.h" |
8 | #include "deleteentity_generated.h" | 8 | #include "deleteentity_generated.h" |
9 | #include "inspection_generated.h" | ||
10 | #include "notification_generated.h" | ||
9 | #include "domainadaptor.h" | 11 | #include "domainadaptor.h" |
10 | #include "commands.h" | 12 | #include "commands.h" |
11 | #include "index.h" | 13 | #include "index.h" |
12 | #include "log.h" | 14 | #include "log.h" |
13 | #include "definitions.h" | 15 | #include "definitions.h" |
16 | #include "bufferutils.h" | ||
14 | 17 | ||
15 | #include <QUuid> | 18 | #include <QUuid> |
19 | #include <QDataStream> | ||
16 | 20 | ||
17 | static int sBatchSize = 100; | 21 | static int sBatchSize = 100; |
18 | 22 | ||
@@ -112,6 +116,7 @@ private: | |||
112 | class CommandProcessor : public QObject | 116 | class CommandProcessor : public QObject |
113 | { | 117 | { |
114 | Q_OBJECT | 118 | Q_OBJECT |
119 | typedef std::function<KAsync::Job<void>(void const *, size_t)> InspectionFunction; | ||
115 | public: | 120 | public: |
116 | CommandProcessor(Akonadi2::Pipeline *pipeline, QList<MessageQueue*> commandQueues) | 121 | CommandProcessor(Akonadi2::Pipeline *pipeline, QList<MessageQueue*> commandQueues) |
117 | : QObject(), | 122 | : QObject(), |
@@ -135,6 +140,11 @@ public: | |||
135 | mLowerBoundRevision = revision; | 140 | mLowerBoundRevision = revision; |
136 | } | 141 | } |
137 | 142 | ||
143 | void setInspectionCommand(const InspectionFunction &f) | ||
144 | { | ||
145 | mInspect = f; | ||
146 | } | ||
147 | |||
138 | 148 | ||
139 | signals: | 149 | signals: |
140 | void error(int errorCode, const QString &errorMessage); | 150 | void error(int errorCode, const QString &errorMessage); |
@@ -176,6 +186,14 @@ private slots: | |||
176 | return mPipeline->modifiedEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); | 186 | return mPipeline->modifiedEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); |
177 | case Akonadi2::Commands::CreateEntityCommand: | 187 | case Akonadi2::Commands::CreateEntityCommand: |
178 | return mPipeline->newEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); | 188 | return mPipeline->newEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); |
189 | case Akonadi2::Commands::InspectionCommand: | ||
190 | if (mInspect) { | ||
191 | return mInspect(queuedCommand->command()->Data(), queuedCommand->command()->size()).then<qint64>([]() { | ||
192 | return -1; | ||
193 | }); | ||
194 | } else { | ||
195 | return KAsync::error<qint64>(-1, "Missing inspection command."); | ||
196 | } | ||
179 | default: | 197 | default: |
180 | return KAsync::error<qint64>(-1, "Unhandled command"); | 198 | return KAsync::error<qint64>(-1, "Unhandled command"); |
181 | } | 199 | } |
@@ -266,6 +284,7 @@ private: | |||
266 | bool mProcessingLock; | 284 | bool mProcessingLock; |
267 | //The lowest revision we no longer need | 285 | //The lowest revision we no longer need |
268 | qint64 mLowerBoundRevision; | 286 | qint64 mLowerBoundRevision; |
287 | InspectionFunction mInspect; | ||
269 | }; | 288 | }; |
270 | 289 | ||
271 | 290 | ||
@@ -279,6 +298,38 @@ GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, c | |||
279 | mClientLowerBoundRevision(std::numeric_limits<qint64>::max()) | 298 | mClientLowerBoundRevision(std::numeric_limits<qint64>::max()) |
280 | { | 299 | { |
281 | mProcessor = new CommandProcessor(mPipeline.data(), QList<MessageQueue*>() << &mUserQueue << &mSynchronizerQueue); | 300 | mProcessor = new CommandProcessor(mPipeline.data(), QList<MessageQueue*>() << &mUserQueue << &mSynchronizerQueue); |
301 | mProcessor->setInspectionCommand([this](void const *command, size_t size) { | ||
302 | flatbuffers::Verifier verifier((const uint8_t *)command, size); | ||
303 | if (Akonadi2::Commands::VerifyInspectionBuffer(verifier)) { | ||
304 | auto buffer = Akonadi2::Commands::GetInspection(command); | ||
305 | int inspectionType = buffer->type(); | ||
306 | |||
307 | QByteArray inspectionId = BufferUtils::extractBuffer(buffer->id()); | ||
308 | QByteArray entityId = BufferUtils::extractBuffer(buffer->entityId()); | ||
309 | QByteArray domainType = BufferUtils::extractBuffer(buffer->domainType()); | ||
310 | QByteArray property = BufferUtils::extractBuffer(buffer->property()); | ||
311 | QByteArray expectedValueString = BufferUtils::extractBuffer(buffer->expectedValue()); | ||
312 | QDataStream s(expectedValueString); | ||
313 | QVariant expectedValue; | ||
314 | s >> expectedValue; | ||
315 | inspect(inspectionType, inspectionId, domainType, entityId, property, expectedValue).then<void>([=]() { | ||
316 | Akonadi2::Notification n; | ||
317 | n.type = Akonadi2::Commands::NotificationType_Inspection; | ||
318 | n.id = inspectionId; | ||
319 | n.code = Akonadi2::Commands::NotificationCode_Success; | ||
320 | emit notify(n); | ||
321 | }, [=](int code, const QString &message) { | ||
322 | Akonadi2::Notification n; | ||
323 | n.type = Akonadi2::Commands::NotificationType_Inspection; | ||
324 | n.message = message; | ||
325 | n.id = inspectionId; | ||
326 | n.code = Akonadi2::Commands::NotificationCode_Failure; | ||
327 | emit notify(n); | ||
328 | }).exec(); | ||
329 | return KAsync::null<void>(); | ||
330 | } | ||
331 | return KAsync::error<void>(-1, "Invalid inspection command."); | ||
332 | }); | ||
282 | QObject::connect(mProcessor, &CommandProcessor::error, [this](int errorCode, const QString &msg) { onProcessorError(errorCode, msg); }); | 333 | QObject::connect(mProcessor, &CommandProcessor::error, [this](int errorCode, const QString &msg) { onProcessorError(errorCode, msg); }); |
283 | QObject::connect(mPipeline.data(), &Pipeline::revisionUpdated, this, &Resource::revisionUpdated); | 334 | QObject::connect(mPipeline.data(), &Pipeline::revisionUpdated, this, &Resource::revisionUpdated); |
284 | mSourceChangeReplay = new ChangeReplay(resourceInstanceIdentifier, [this](const QByteArray &type, const QByteArray &key, const QByteArray &value) { | 335 | mSourceChangeReplay = new ChangeReplay(resourceInstanceIdentifier, [this](const QByteArray &type, const QByteArray &key, const QByteArray &value) { |
@@ -301,6 +352,12 @@ GenericResource::~GenericResource() | |||
301 | delete mSourceChangeReplay; | 352 | delete mSourceChangeReplay; |
302 | } | 353 | } |
303 | 354 | ||
355 | KAsync::Job<void> GenericResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
356 | { | ||
357 | Warning() << "Inspection not implemented"; | ||
358 | return KAsync::null<void>(); | ||
359 | } | ||
360 | |||
304 | void GenericResource::enableChangeReplay(bool enable) | 361 | void GenericResource::enableChangeReplay(bool enable) |
305 | { | 362 | { |
306 | if (enable) { | 363 | if (enable) { |
@@ -464,7 +521,7 @@ void GenericResource::createEntity(const QByteArray &akonadiId, const QByteArray | |||
464 | auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize()); | 521 | auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize()); |
465 | auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta, replayToSource); | 522 | auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta, replayToSource); |
466 | Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); | 523 | Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); |
467 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | 524 | callback(BufferUtils::extractBuffer(fbb)); |
468 | } | 525 | } |
469 | 526 | ||
470 | void GenericResource::modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) | 527 | void GenericResource::modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) |
@@ -481,7 +538,7 @@ void GenericResource::modifyEntity(const QByteArray &akonadiId, qint64 revision, | |||
481 | //TODO removals | 538 | //TODO removals |
482 | auto location = Akonadi2::Commands::CreateModifyEntity(fbb, revision, entityId, 0, type, delta, replayToSource); | 539 | auto location = Akonadi2::Commands::CreateModifyEntity(fbb, revision, entityId, 0, type, delta, replayToSource); |
483 | Akonadi2::Commands::FinishModifyEntityBuffer(fbb, location); | 540 | Akonadi2::Commands::FinishModifyEntityBuffer(fbb, location); |
484 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | 541 | callback(BufferUtils::extractBuffer(fbb)); |
485 | } | 542 | } |
486 | 543 | ||
487 | void GenericResource::deleteEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, std::function<void(const QByteArray &)> callback) | 544 | void GenericResource::deleteEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, std::function<void(const QByteArray &)> callback) |
@@ -494,7 +551,7 @@ void GenericResource::deleteEntity(const QByteArray &akonadiId, qint64 revision, | |||
494 | auto type = fbb.CreateString(bufferType.toStdString()); | 551 | auto type = fbb.CreateString(bufferType.toStdString()); |
495 | auto location = Akonadi2::Commands::CreateDeleteEntity(fbb, revision, entityId, type, replayToSource); | 552 | auto location = Akonadi2::Commands::CreateDeleteEntity(fbb, revision, entityId, type, replayToSource); |
496 | Akonadi2::Commands::FinishDeleteEntityBuffer(fbb, location); | 553 | Akonadi2::Commands::FinishDeleteEntityBuffer(fbb, location); |
497 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | 554 | callback(BufferUtils::extractBuffer(fbb)); |
498 | } | 555 | } |
499 | 556 | ||
500 | void GenericResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Akonadi2::Storage::Transaction &transaction) | 557 | void GenericResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Akonadi2::Storage::Transaction &transaction) |
diff --git a/common/genericresource.h b/common/genericresource.h index f47c6f8..d71061c 100644 --- a/common/genericresource.h +++ b/common/genericresource.h | |||
@@ -48,6 +48,7 @@ public: | |||
48 | virtual KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore); | 48 | virtual KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore); |
49 | virtual KAsync::Job<void> processAllMessages() Q_DECL_OVERRIDE; | 49 | virtual KAsync::Job<void> processAllMessages() Q_DECL_OVERRIDE; |
50 | virtual void setLowerBoundRevision(qint64 revision) Q_DECL_OVERRIDE; | 50 | virtual void setLowerBoundRevision(qint64 revision) Q_DECL_OVERRIDE; |
51 | virtual KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue); | ||
51 | 52 | ||
52 | int error() const; | 53 | int error() const; |
53 | 54 | ||
diff --git a/common/inspection.h b/common/inspection.h new file mode 100644 index 0000000..ecf5b3d --- /dev/null +++ b/common/inspection.h | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
3 | * | ||
4 | * This library is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) version 3, or any | ||
8 | * later version accepted by the membership of KDE e.V. (or its | ||
9 | * successor approved by the membership of KDE e.V.), which shall | ||
10 | * act as a proxy defined in Section 6 of version 3 of the license. | ||
11 | * | ||
12 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | #pragma once | ||
21 | |||
22 | #include <QByteArray> | ||
23 | #include <QVariant> | ||
24 | #include "applicationdomaintype.h" | ||
25 | |||
26 | namespace Akonadi2 { | ||
27 | namespace Resources { | ||
28 | |||
29 | struct Inspection { | ||
30 | static Inspection PropertyInspection(const Akonadi2::ApplicationDomain::Entity &entity, const QByteArray &property, const QVariant &expectedValue) | ||
31 | { | ||
32 | Inspection inspection; | ||
33 | inspection.resourceIdentifier = entity.resourceInstanceIdentifier(); | ||
34 | inspection.entityIdentifier = entity.identifier(); | ||
35 | inspection.property = property; | ||
36 | inspection.expectedValue = expectedValue; | ||
37 | return inspection; | ||
38 | } | ||
39 | |||
40 | static Inspection ExistenceInspection(const Akonadi2::ApplicationDomain::Entity &entity, bool exists) | ||
41 | { | ||
42 | Inspection inspection; | ||
43 | inspection.resourceIdentifier = entity.resourceInstanceIdentifier(); | ||
44 | inspection.entityIdentifier = entity.identifier(); | ||
45 | inspection.expectedValue = exists; | ||
46 | return inspection; | ||
47 | } | ||
48 | |||
49 | enum Type { | ||
50 | PropertyInspectionType, | ||
51 | ExistenceInspectionType | ||
52 | }; | ||
53 | QByteArray resourceIdentifier; | ||
54 | QByteArray entityIdentifier; | ||
55 | QByteArray property; | ||
56 | QVariant expectedValue; | ||
57 | }; | ||
58 | |||
59 | } | ||
60 | } | ||
diff --git a/common/listener.cpp b/common/listener.cpp index 1b78f01..fa08472 100644 --- a/common/listener.cpp +++ b/common/listener.cpp | |||
@@ -31,11 +31,13 @@ | |||
31 | #include "common/synchronize_generated.h" | 31 | #include "common/synchronize_generated.h" |
32 | #include "common/notification_generated.h" | 32 | #include "common/notification_generated.h" |
33 | #include "common/revisionreplayed_generated.h" | 33 | #include "common/revisionreplayed_generated.h" |
34 | #include "common/inspection_generated.h" | ||
34 | 35 | ||
35 | #include <QLocalServer> | 36 | #include <QLocalServer> |
36 | #include <QLocalSocket> | 37 | #include <QLocalSocket> |
37 | #include <QTimer> | 38 | #include <QTimer> |
38 | #include <QTime> | 39 | #include <QTime> |
40 | #include <QDataStream> | ||
39 | 41 | ||
40 | Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) | 42 | Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) |
41 | : QObject(parent), | 43 | : QObject(parent), |
@@ -203,13 +205,14 @@ void Listener::processClientBuffers() | |||
203 | } | 205 | } |
204 | } | 206 | } |
205 | 207 | ||
206 | void Listener::processCommand(int commandId, uint messageId, const QByteArray &commandBuffer, Client &client, const std::function<void()> &callback) | 208 | void Listener::processCommand(int commandId, uint messageId, const QByteArray &commandBuffer, Client &client, const std::function<void(bool)> &callback) |
207 | { | 209 | { |
210 | bool success = true; | ||
208 | switch (commandId) { | 211 | switch (commandId) { |
209 | case Akonadi2::Commands::HandshakeCommand: { | 212 | case Akonadi2::Commands::HandshakeCommand: { |
210 | flatbuffers::Verifier verifier((const uint8_t *)commandBuffer.constData(), commandBuffer.size()); | 213 | flatbuffers::Verifier verifier((const uint8_t *)commandBuffer.constData(), commandBuffer.size()); |
211 | if (Akonadi2::VerifyHandshakeBuffer(verifier)) { | 214 | if (Akonadi2::Commands::VerifyHandshakeBuffer(verifier)) { |
212 | auto buffer = Akonadi2::GetHandshake(commandBuffer.constData()); | 215 | auto buffer = Akonadi2::Commands::GetHandshake(commandBuffer.constData()); |
213 | client.name = buffer->name()->c_str(); | 216 | client.name = buffer->name()->c_str(); |
214 | } else { | 217 | } else { |
215 | Warning() << "received invalid command"; | 218 | Warning() << "received invalid command"; |
@@ -218,8 +221,8 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c | |||
218 | } | 221 | } |
219 | case Akonadi2::Commands::SynchronizeCommand: { | 222 | case Akonadi2::Commands::SynchronizeCommand: { |
220 | flatbuffers::Verifier verifier((const uint8_t *)commandBuffer.constData(), commandBuffer.size()); | 223 | flatbuffers::Verifier verifier((const uint8_t *)commandBuffer.constData(), commandBuffer.size()); |
221 | if (Akonadi2::VerifySynchronizeBuffer(verifier)) { | 224 | if (Akonadi2::Commands::VerifySynchronizeBuffer(verifier)) { |
222 | auto buffer = Akonadi2::GetSynchronize(commandBuffer.constData()); | 225 | auto buffer = Akonadi2::Commands::GetSynchronize(commandBuffer.constData()); |
223 | Log() << QString("\tSynchronize request (id %1) from %2").arg(messageId).arg(client.name); | 226 | Log() << QString("\tSynchronize request (id %1) from %2").arg(messageId).arg(client.name); |
224 | auto timer = QSharedPointer<QTime>::create(); | 227 | auto timer = QSharedPointer<QTime>::create(); |
225 | timer->start(); | 228 | timer->start(); |
@@ -232,7 +235,7 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c | |||
232 | } | 235 | } |
233 | job.then<void>([callback, timer]() { | 236 | job.then<void>([callback, timer]() { |
234 | Trace() << "Sync took " << timer->elapsed(); | 237 | Trace() << "Sync took " << timer->elapsed(); |
235 | callback(); | 238 | callback(true); |
236 | }).exec(); | 239 | }).exec(); |
237 | return; | 240 | return; |
238 | } else { | 241 | } else { |
@@ -240,6 +243,7 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c | |||
240 | } | 243 | } |
241 | break; | 244 | break; |
242 | } | 245 | } |
246 | case Akonadi2::Commands::InspectionCommand: | ||
243 | case Akonadi2::Commands::FetchEntityCommand: | 247 | case Akonadi2::Commands::FetchEntityCommand: |
244 | case Akonadi2::Commands::DeleteEntityCommand: | 248 | case Akonadi2::Commands::DeleteEntityCommand: |
245 | case Akonadi2::Commands::ModifyEntityCommand: | 249 | case Akonadi2::Commands::ModifyEntityCommand: |
@@ -273,11 +277,12 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c | |||
273 | Log() << QString("\tReceived custom command from %1: ").arg(client.name) << commandId; | 277 | Log() << QString("\tReceived custom command from %1: ").arg(client.name) << commandId; |
274 | loadResource()->processCommand(commandId, commandBuffer); | 278 | loadResource()->processCommand(commandId, commandBuffer); |
275 | } else { | 279 | } else { |
280 | success = false; | ||
276 | ErrorMsg() << QString("\tReceived invalid command from %1: ").arg(client.name) << commandId; | 281 | ErrorMsg() << QString("\tReceived invalid command from %1: ").arg(client.name) << commandId; |
277 | } | 282 | } |
278 | break; | 283 | break; |
279 | } | 284 | } |
280 | callback(); | 285 | callback(success); |
281 | } | 286 | } |
282 | 287 | ||
283 | qint64 Listener::lowerBoundRevision() | 288 | qint64 Listener::lowerBoundRevision() |
@@ -298,8 +303,8 @@ qint64 Listener::lowerBoundRevision() | |||
298 | void Listener::quit() | 303 | void Listener::quit() |
299 | { | 304 | { |
300 | //Broadcast shutdown notifications to open clients, so they don't try to restart the resource | 305 | //Broadcast shutdown notifications to open clients, so they don't try to restart the resource |
301 | auto command = Akonadi2::CreateNotification(m_fbb, Akonadi2::NotificationType::NotificationType_Shutdown); | 306 | auto command = Akonadi2::Commands::CreateNotification(m_fbb, Akonadi2::Commands::NotificationType::NotificationType_Shutdown); |
302 | Akonadi2::FinishNotificationBuffer(m_fbb, command); | 307 | Akonadi2::Commands::FinishNotificationBuffer(m_fbb, command); |
303 | for (Client &client : m_connections) { | 308 | for (Client &client : m_connections) { |
304 | if (client.socket && client.socket->isOpen()) { | 309 | if (client.socket && client.socket->isOpen()) { |
305 | Akonadi2::Commands::write(client.socket, ++m_messageId, Akonadi2::Commands::NotificationCommand, m_fbb); | 310 | Akonadi2::Commands::write(client.socket, ++m_messageId, Akonadi2::Commands::NotificationCommand, m_fbb); |
@@ -333,10 +338,10 @@ bool Listener::processClientBuffer(Client &client) | |||
333 | auto clientName = client.name; | 338 | auto clientName = client.name; |
334 | const QByteArray commandBuffer = client.commandBuffer.left(size); | 339 | const QByteArray commandBuffer = client.commandBuffer.left(size); |
335 | client.commandBuffer.remove(0, size); | 340 | client.commandBuffer.remove(0, size); |
336 | processCommand(commandId, messageId, commandBuffer, client, [this, messageId, commandId, socket, clientName]() { | 341 | processCommand(commandId, messageId, commandBuffer, client, [this, messageId, commandId, socket, clientName](bool success) { |
337 | Log() << QString("\tCompleted command messageid %1 of type \"%2\" from %3").arg(messageId).arg(QString(Akonadi2::Commands::name(commandId))).arg(clientName); | 342 | Log() << QString("\tCompleted command messageid %1 of type \"%2\" from %3").arg(messageId).arg(QString(Akonadi2::Commands::name(commandId))).arg(clientName); |
338 | if (socket) { | 343 | if (socket) { |
339 | sendCommandCompleted(socket.data(), messageId); | 344 | sendCommandCompleted(socket.data(), messageId, success); |
340 | } else { | 345 | } else { |
341 | Log() << QString("Socket became invalid before we could send a response. client: %1").arg(clientName); | 346 | Log() << QString("Socket became invalid before we could send a response. client: %1").arg(clientName); |
342 | } | 347 | } |
@@ -348,15 +353,15 @@ bool Listener::processClientBuffer(Client &client) | |||
348 | return false; | 353 | return false; |
349 | } | 354 | } |
350 | 355 | ||
351 | void Listener::sendCommandCompleted(QLocalSocket *socket, uint messageId) | 356 | void Listener::sendCommandCompleted(QLocalSocket *socket, uint messageId, bool success) |
352 | { | 357 | { |
353 | if (!socket || !socket->isValid()) { | 358 | if (!socket || !socket->isValid()) { |
354 | return; | 359 | return; |
355 | } | 360 | } |
356 | 361 | ||
357 | auto command = Akonadi2::CreateCommandCompletion(m_fbb, messageId); | 362 | auto command = Akonadi2::Commands::CreateCommandCompletion(m_fbb, messageId, success); |
358 | Akonadi2::FinishCommandCompletionBuffer(m_fbb, command); | 363 | Akonadi2::Commands::FinishCommandCompletionBuffer(m_fbb, command); |
359 | Akonadi2::Commands::write(socket, ++m_messageId, Akonadi2::Commands::CommandCompletion, m_fbb); | 364 | Akonadi2::Commands::write(socket, ++m_messageId, Akonadi2::Commands::CommandCompletionCommand, m_fbb); |
360 | m_fbb.Clear(); | 365 | m_fbb.Clear(); |
361 | } | 366 | } |
362 | 367 | ||
@@ -367,8 +372,8 @@ void Listener::refreshRevision(qint64 revision) | |||
367 | 372 | ||
368 | void Listener::updateClientsWithRevision(qint64 revision) | 373 | void Listener::updateClientsWithRevision(qint64 revision) |
369 | { | 374 | { |
370 | auto command = Akonadi2::CreateRevisionUpdate(m_fbb, revision); | 375 | auto command = Akonadi2::Commands::CreateRevisionUpdate(m_fbb, revision); |
371 | Akonadi2::FinishRevisionUpdateBuffer(m_fbb, command); | 376 | Akonadi2::Commands::FinishRevisionUpdateBuffer(m_fbb, command); |
372 | 377 | ||
373 | for (const Client &client: m_connections) { | 378 | for (const Client &client: m_connections) { |
374 | if (!client.socket || !client.socket->isValid()) { | 379 | if (!client.socket || !client.socket->isValid()) { |
@@ -381,6 +386,25 @@ void Listener::updateClientsWithRevision(qint64 revision) | |||
381 | m_fbb.Clear(); | 386 | m_fbb.Clear(); |
382 | } | 387 | } |
383 | 388 | ||
389 | void Listener::notify(const Akonadi2::Notification ¬ification) | ||
390 | { | ||
391 | auto messageString = m_fbb.CreateString(notification.message.toUtf8().constData(), notification.message.toUtf8().size()); | ||
392 | auto idString = m_fbb.CreateString(notification.id.constData(), notification.id.size()); | ||
393 | Akonadi2::Commands::NotificationBuilder builder(m_fbb); | ||
394 | builder.add_type(static_cast<Akonadi2::Commands::NotificationType>(notification.type)); | ||
395 | builder.add_code(notification.code); | ||
396 | builder.add_identifier(idString); | ||
397 | builder.add_message(messageString); | ||
398 | auto command = builder.Finish(); | ||
399 | Akonadi2::Commands::FinishNotificationBuffer(m_fbb, command); | ||
400 | for (Client &client : m_connections) { | ||
401 | if (client.socket && client.socket->isOpen()) { | ||
402 | Akonadi2::Commands::write(client.socket, ++m_messageId, Akonadi2::Commands::NotificationCommand, m_fbb); | ||
403 | } | ||
404 | } | ||
405 | m_fbb.Clear(); | ||
406 | } | ||
407 | |||
384 | Akonadi2::Resource *Listener::loadResource() | 408 | Akonadi2::Resource *Listener::loadResource() |
385 | { | 409 | { |
386 | if (!m_resource) { | 410 | if (!m_resource) { |
@@ -390,6 +414,8 @@ Akonadi2::Resource *Listener::loadResource() | |||
390 | Trace() << QString("\tResource: %1").arg((qlonglong)m_resource); | 414 | Trace() << QString("\tResource: %1").arg((qlonglong)m_resource); |
391 | connect(m_resource, &Akonadi2::Resource::revisionUpdated, | 415 | connect(m_resource, &Akonadi2::Resource::revisionUpdated, |
392 | this, &Listener::refreshRevision); | 416 | this, &Listener::refreshRevision); |
417 | connect(m_resource, &Akonadi2::Resource::notify, | ||
418 | this, &Listener::notify); | ||
393 | } else { | 419 | } else { |
394 | ErrorMsg() << "Failed to load resource " << m_resourceName; | 420 | ErrorMsg() << "Failed to load resource " << m_resourceName; |
395 | m_resource = new Akonadi2::Resource; | 421 | m_resource = new Akonadi2::Resource; |
diff --git a/common/listener.h b/common/listener.h index 248a190..4112a6a 100644 --- a/common/listener.h +++ b/common/listener.h | |||
@@ -28,6 +28,7 @@ | |||
28 | namespace Akonadi2 | 28 | namespace Akonadi2 |
29 | { | 29 | { |
30 | class Resource; | 30 | class Resource; |
31 | class Notification; | ||
31 | } | 32 | } |
32 | 33 | ||
33 | class QTimer; | 34 | class QTimer; |
@@ -76,12 +77,13 @@ private Q_SLOTS: | |||
76 | void onDataAvailable(); | 77 | void onDataAvailable(); |
77 | void processClientBuffers(); | 78 | void processClientBuffers(); |
78 | void refreshRevision(qint64); | 79 | void refreshRevision(qint64); |
80 | void notify(const Akonadi2::Notification &); | ||
79 | void quit(); | 81 | void quit(); |
80 | 82 | ||
81 | private: | 83 | private: |
82 | void processCommand(int commandId, uint messageId, const QByteArray &commandBuffer, Client &client, const std::function<void()> &callback); | 84 | void processCommand(int commandId, uint messageId, const QByteArray &commandBuffer, Client &client, const std::function<void(bool)> &callback); |
83 | bool processClientBuffer(Client &client); | 85 | bool processClientBuffer(Client &client); |
84 | void sendCommandCompleted(QLocalSocket *socket, uint messageId); | 86 | void sendCommandCompleted(QLocalSocket *socket, uint messageId, bool success); |
85 | void updateClientsWithRevision(qint64); | 87 | void updateClientsWithRevision(qint64); |
86 | Akonadi2::Resource *loadResource(); | 88 | Akonadi2::Resource *loadResource(); |
87 | void readFromSocket(QLocalSocket *socket); | 89 | void readFromSocket(QLocalSocket *socket); |
diff --git a/common/log.cpp b/common/log.cpp index c33c700..489e1bd 100644 --- a/common/log.cpp +++ b/common/log.cpp | |||
@@ -96,7 +96,7 @@ static QString colorCommand(QList<int> colorCodes) | |||
96 | return string; | 96 | return string; |
97 | } | 97 | } |
98 | 98 | ||
99 | QByteArray debugLevelName(DebugLevel debugLevel) | 99 | QByteArray Akonadi2::Log::debugLevelName(DebugLevel debugLevel) |
100 | { | 100 | { |
101 | switch (debugLevel) { | 101 | switch (debugLevel) { |
102 | case DebugLevel::Trace: | 102 | case DebugLevel::Trace: |
@@ -114,15 +114,16 @@ QByteArray debugLevelName(DebugLevel debugLevel) | |||
114 | return QByteArray(); | 114 | return QByteArray(); |
115 | } | 115 | } |
116 | 116 | ||
117 | DebugLevel debugLevelFromName(const QByteArray &name) | 117 | DebugLevel Akonadi2::Log::debugLevelFromName(const QByteArray &name) |
118 | { | 118 | { |
119 | if (name.toLower() == "trace") | 119 | const QByteArray lowercaseName = name.toLower(); |
120 | if (lowercaseName == "trace") | ||
120 | return DebugLevel::Trace; | 121 | return DebugLevel::Trace; |
121 | if (name.toLower() == "log") | 122 | if (lowercaseName == "log") |
122 | return DebugLevel::Log; | 123 | return DebugLevel::Log; |
123 | if (name.toLower() == "warning") | 124 | if (lowercaseName == "warning") |
124 | return DebugLevel::Warning; | 125 | return DebugLevel::Warning; |
125 | if (name.toLower() == "error") | 126 | if (lowercaseName == "error") |
126 | return DebugLevel::Error; | 127 | return DebugLevel::Error; |
127 | return DebugLevel::Log; | 128 | return DebugLevel::Log; |
128 | } | 129 | } |
@@ -132,6 +133,11 @@ void Akonadi2::Log::setDebugOutputLevel(DebugLevel debugLevel) | |||
132 | qputenv("AKONADI2DEBUGLEVEL", debugLevelName(debugLevel)); | 133 | qputenv("AKONADI2DEBUGLEVEL", debugLevelName(debugLevel)); |
133 | } | 134 | } |
134 | 135 | ||
136 | Akonadi2::Log::DebugLevel Akonadi2::Log::debugOutputLevel() | ||
137 | { | ||
138 | return debugLevelFromName(qgetenv("AKONADI2DEBUGLEVEL")); | ||
139 | } | ||
140 | |||
135 | QDebug Akonadi2::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea) | 141 | QDebug Akonadi2::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea) |
136 | { | 142 | { |
137 | DebugLevel debugOutputLevel = debugLevelFromName(qgetenv("AKONADI2DEBUGLEVEL")); | 143 | DebugLevel debugOutputLevel = debugLevelFromName(qgetenv("AKONADI2DEBUGLEVEL")); |
diff --git a/common/log.h b/common/log.h index 9db9e8e..e531348 100644 --- a/common/log.h +++ b/common/log.h | |||
@@ -12,7 +12,11 @@ enum DebugLevel { | |||
12 | Error | 12 | Error |
13 | }; | 13 | }; |
14 | 14 | ||
15 | QByteArray debugLevelName(DebugLevel debugLevel); | ||
16 | DebugLevel debugLevelFromName(const QByteArray &name); | ||
17 | |||
15 | void setDebugOutputLevel(DebugLevel); | 18 | void setDebugOutputLevel(DebugLevel); |
19 | DebugLevel debugOutputLevel(); | ||
16 | 20 | ||
17 | QDebug debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0); | 21 | QDebug debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0); |
18 | 22 | ||
diff --git a/common/notification.h b/common/notification.h new file mode 100644 index 0000000..e1b5bff --- /dev/null +++ b/common/notification.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | * | ||
4 | * This library is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) version 3, or any | ||
8 | * later version accepted by the membership of KDE e.V. (or its | ||
9 | * successor approved by the membership of KDE e.V.), which shall | ||
10 | * act as a proxy defined in Section 6 of version 3 of the license. | ||
11 | * | ||
12 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | #pragma once | ||
21 | |||
22 | #include <akonadi2common_export.h> | ||
23 | #include <QString> | ||
24 | |||
25 | namespace Akonadi2 | ||
26 | { | ||
27 | |||
28 | /** | ||
29 | * A notification | ||
30 | */ | ||
31 | class AKONADI2COMMON_EXPORT Notification | ||
32 | { | ||
33 | public: | ||
34 | QByteArray id; | ||
35 | int type; | ||
36 | QString message; | ||
37 | int code; | ||
38 | }; | ||
39 | |||
40 | } | ||
diff --git a/common/pipeline.cpp b/common/pipeline.cpp index 06d8114..a087def 100644 --- a/common/pipeline.cpp +++ b/common/pipeline.cpp | |||
@@ -34,6 +34,7 @@ | |||
34 | #include "log.h" | 34 | #include "log.h" |
35 | #include "domain/applicationdomaintype.h" | 35 | #include "domain/applicationdomaintype.h" |
36 | #include "definitions.h" | 36 | #include "definitions.h" |
37 | #include "bufferutils.h" | ||
37 | 38 | ||
38 | namespace Akonadi2 | 39 | namespace Akonadi2 |
39 | { | 40 | { |
@@ -119,7 +120,7 @@ Storage &Pipeline::storage() const | |||
119 | 120 | ||
120 | void Pipeline::storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid) | 121 | void Pipeline::storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid) |
121 | { | 122 | { |
122 | d->transaction.openDatabase(bufferType + ".main").write(Akonadi2::Storage::assembleKey(uid, newRevision), QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()), | 123 | d->transaction.openDatabase(bufferType + ".main").write(Akonadi2::Storage::assembleKey(uid, newRevision), BufferUtils::extractBuffer(fbb), |
123 | [](const Akonadi2::Storage::Error &error) { | 124 | [](const Akonadi2::Storage::Error &error) { |
124 | Warning() << "Failed to write entity"; | 125 | Warning() << "Failed to write entity"; |
125 | } | 126 | } |
@@ -285,7 +286,7 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size) | |||
285 | //Remove deletions | 286 | //Remove deletions |
286 | if (modifyEntity->deletions()) { | 287 | if (modifyEntity->deletions()) { |
287 | for (const auto &property : *modifyEntity->deletions()) { | 288 | for (const auto &property : *modifyEntity->deletions()) { |
288 | newObject->setProperty(QByteArray::fromRawData(property->data(), property->size()), QVariant()); | 289 | newObject->setProperty(BufferUtils::extractBuffer(property), QVariant()); |
289 | } | 290 | } |
290 | } | 291 | } |
291 | 292 | ||
diff --git a/common/query.h b/common/query.h index 0d0f382..1df32da 100644 --- a/common/query.h +++ b/common/query.h | |||
@@ -22,42 +22,120 @@ | |||
22 | #include <QByteArrayList> | 22 | #include <QByteArrayList> |
23 | #include <QHash> | 23 | #include <QHash> |
24 | #include <QSet> | 24 | #include <QSet> |
25 | #include "applicationdomaintype.h" | ||
25 | 26 | ||
26 | namespace Akonadi2 { | 27 | namespace Akonadi2 { |
27 | 28 | ||
28 | /** | 29 | /** |
29 | * A query that matches a set of objects | 30 | * A query that matches a set of entities. |
30 | * | ||
31 | * The query will have to be updated regularly similary to the domain objects. | ||
32 | * It probably also makes sense to have a domain specific part of the query, | ||
33 | * such as what properties we're interested in (necessary information for on-demand | ||
34 | * loading of data). | ||
35 | * | ||
36 | * The query defines: | ||
37 | * * what resources to search | ||
38 | * * filters on various properties (parent collection, startDate range, ....) | ||
39 | * * properties we need (for on-demand querying) | ||
40 | * | ||
41 | * syncOnDemand: Execute a source sync before executing the query | ||
42 | * processAll: Ensure all local messages are processed before querying to guarantee an up-to date dataset. | ||
43 | */ | 31 | */ |
44 | class Query | 32 | class Query |
45 | { | 33 | { |
46 | public: | 34 | public: |
47 | Query() : syncOnDemand(true), processAll(false), liveQuery(false) {} | 35 | enum Flag { |
48 | //Could also be a propertyFilter | 36 | /** Leave the query running an contiously update the result set. */ |
37 | LiveQuery | ||
38 | }; | ||
39 | Q_DECLARE_FLAGS(Flags, Flag) | ||
40 | |||
41 | static Query PropertyFilter(const QByteArray &key, const QVariant &value) | ||
42 | { | ||
43 | Query query; | ||
44 | query.propertyFilter.insert(key, value); | ||
45 | return query; | ||
46 | } | ||
47 | |||
48 | static Query PropertyFilter(const QByteArray &key, const ApplicationDomain::Entity &entity) | ||
49 | { | ||
50 | return PropertyFilter(key, QVariant::fromValue(entity.identifier())); | ||
51 | } | ||
52 | |||
53 | static Query ResourceFilter(const QByteArray &identifier) | ||
54 | { | ||
55 | Query query; | ||
56 | query.resources.append(identifier); | ||
57 | return query; | ||
58 | } | ||
59 | |||
60 | static Query ResourceFilter(const QByteArrayList &identifier) | ||
61 | { | ||
62 | Query query; | ||
63 | query.resources = identifier; | ||
64 | return query; | ||
65 | } | ||
66 | |||
67 | static Query ResourceFilter(const ApplicationDomain::AkonadiResource &entity) | ||
68 | { | ||
69 | return ResourceFilter(entity.identifier()); | ||
70 | } | ||
71 | |||
72 | static Query IdentityFilter(const QByteArray &identifier) | ||
73 | { | ||
74 | Query query; | ||
75 | query.ids << identifier; | ||
76 | return query; | ||
77 | } | ||
78 | |||
79 | static Query IdentityFilter(const QByteArrayList &identifier) | ||
80 | { | ||
81 | Query query; | ||
82 | query.ids = identifier; | ||
83 | return query; | ||
84 | } | ||
85 | |||
86 | static Query IdentityFilter(const ApplicationDomain::Entity &entity) | ||
87 | { | ||
88 | return IdentityFilter(entity.identifier()); | ||
89 | } | ||
90 | |||
91 | static Query RequestedProperties(const QByteArrayList &properties) | ||
92 | { | ||
93 | Query query; | ||
94 | query.requestedProperties = properties; | ||
95 | return query; | ||
96 | } | ||
97 | |||
98 | static Query RequestTree(const QByteArray &parentProperty) | ||
99 | { | ||
100 | Query query; | ||
101 | query.parentProperty = parentProperty; | ||
102 | return query; | ||
103 | } | ||
104 | |||
105 | Query(Flags flags = Flags()) | ||
106 | {} | ||
107 | |||
108 | Query& operator+=(const Query& rhs) | ||
109 | { | ||
110 | resources += rhs.resources; | ||
111 | ids += rhs.ids; | ||
112 | for (auto it = rhs.propertyFilter.constBegin(); it != rhs.propertyFilter.constEnd(); it++) { | ||
113 | propertyFilter.insert(it.key(), it.value()); | ||
114 | } | ||
115 | requestedProperties += rhs.requestedProperties; | ||
116 | parentProperty = rhs.parentProperty; | ||
117 | liveQuery = rhs.liveQuery; | ||
118 | syncOnDemand = rhs.syncOnDemand; | ||
119 | processAll = rhs.processAll; | ||
120 | return *this; | ||
121 | } | ||
122 | |||
123 | friend Query operator+(Query lhs, const Query& rhs) | ||
124 | { | ||
125 | lhs += rhs; | ||
126 | return lhs; | ||
127 | } | ||
128 | |||
49 | QByteArrayList resources; | 129 | QByteArrayList resources; |
50 | //Could also be a propertyFilter | ||
51 | QByteArrayList ids; | 130 | QByteArrayList ids; |
52 | //Filters to apply | ||
53 | QHash<QByteArray, QVariant> propertyFilter; | 131 | QHash<QByteArray, QVariant> propertyFilter; |
54 | //Properties to retrieve | ||
55 | QByteArrayList requestedProperties; | 132 | QByteArrayList requestedProperties; |
56 | QByteArray parentProperty; | 133 | QByteArray parentProperty; |
134 | bool liveQuery; | ||
57 | bool syncOnDemand; | 135 | bool syncOnDemand; |
58 | bool processAll; | 136 | bool processAll; |
59 | //If live query is false, this query will not continuously be updated | ||
60 | bool liveQuery; | ||
61 | }; | 137 | }; |
62 | 138 | ||
63 | } | 139 | } |
140 | |||
141 | Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi2::Query::Flags) | ||
diff --git a/common/resource.h b/common/resource.h index 4ed21b5..2ae71a0 100644 --- a/common/resource.h +++ b/common/resource.h | |||
@@ -17,10 +17,12 @@ | |||
17 | * You should have received a copy of the GNU Lesser General Public | 17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | 18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
19 | */ | 19 | */ |
20 | #pragma once | ||
20 | 21 | ||
21 | #include <akonadi2common_export.h> | 22 | #include <akonadi2common_export.h> |
22 | 23 | ||
23 | #include <Async/Async> | 24 | #include <Async/Async> |
25 | #include "notification.h" | ||
24 | 26 | ||
25 | namespace Akonadi2 | 27 | namespace Akonadi2 |
26 | { | 28 | { |
@@ -55,6 +57,7 @@ public: | |||
55 | 57 | ||
56 | Q_SIGNALS: | 58 | Q_SIGNALS: |
57 | void revisionUpdated(qint64); | 59 | void revisionUpdated(qint64); |
60 | void notify(Notification); | ||
58 | 61 | ||
59 | private: | 62 | private: |
60 | class Private; | 63 | class Private; |
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp index 7be1259..6592699 100644 --- a/common/resourceaccess.cpp +++ b/common/resourceaccess.cpp | |||
@@ -30,19 +30,33 @@ | |||
30 | #include "common/modifyentity_generated.h" | 30 | #include "common/modifyentity_generated.h" |
31 | #include "common/deleteentity_generated.h" | 31 | #include "common/deleteentity_generated.h" |
32 | #include "common/revisionreplayed_generated.h" | 32 | #include "common/revisionreplayed_generated.h" |
33 | #include "common/inspection_generated.h" | ||
33 | #include "common/entitybuffer.h" | 34 | #include "common/entitybuffer.h" |
35 | #include "common/bufferutils.h" | ||
34 | #include "log.h" | 36 | #include "log.h" |
35 | 37 | ||
36 | #include <QCoreApplication> | 38 | #include <QCoreApplication> |
37 | #include <QDebug> | 39 | #include <QDebug> |
38 | #include <QDir> | 40 | #include <QDir> |
39 | #include <QProcess> | 41 | #include <QProcess> |
42 | #include <QDataStream> | ||
43 | #include <QBuffer> | ||
40 | 44 | ||
41 | #undef Trace | 45 | #undef Trace |
42 | #define Trace() Akonadi2::Log::debugStream(Akonadi2::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess") | 46 | #define Trace() Akonadi2::Log::debugStream(Akonadi2::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess") |
43 | #undef Log | 47 | #undef Log |
44 | #define Log(IDENTIFIER) Akonadi2::Log::debugStream(Akonadi2::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess("+IDENTIFIER+")") | 48 | #define Log(IDENTIFIER) Akonadi2::Log::debugStream(Akonadi2::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess("+IDENTIFIER+")") |
45 | 49 | ||
50 | static void queuedInvoke(const std::function<void()> &f, QObject *context = 0) | ||
51 | { | ||
52 | auto timer = QSharedPointer<QTimer>::create(); | ||
53 | timer->setSingleShot(true); | ||
54 | QObject::connect(timer.data(), &QTimer::timeout, context, [f, timer]() { | ||
55 | f(); | ||
56 | }); | ||
57 | timer->start(0); | ||
58 | } | ||
59 | |||
46 | namespace Akonadi2 | 60 | namespace Akonadi2 |
47 | { | 61 | { |
48 | 62 | ||
@@ -284,8 +298,8 @@ KAsync::Job<void> ResourceAccess::synchronizeResource(bool sourceSync, bool loca | |||
284 | { | 298 | { |
285 | Trace() << "Sending synchronize command: " << sourceSync << localSync; | 299 | Trace() << "Sending synchronize command: " << sourceSync << localSync; |
286 | flatbuffers::FlatBufferBuilder fbb; | 300 | flatbuffers::FlatBufferBuilder fbb; |
287 | auto command = Akonadi2::CreateSynchronize(fbb, sourceSync, localSync); | 301 | auto command = Akonadi2::Commands::CreateSynchronize(fbb, sourceSync, localSync); |
288 | Akonadi2::FinishSynchronizeBuffer(fbb, command); | 302 | Akonadi2::Commands::FinishSynchronizeBuffer(fbb, command); |
289 | open(); | 303 | open(); |
290 | return sendCommand(Commands::SynchronizeCommand, fbb); | 304 | return sendCommand(Commands::SynchronizeCommand, fbb); |
291 | } | 305 | } |
@@ -338,6 +352,25 @@ KAsync::Job<void> ResourceAccess::sendRevisionReplayedCommand(qint64 revision) | |||
338 | return sendCommand(Akonadi2::Commands::RevisionReplayedCommand, fbb); | 352 | return sendCommand(Akonadi2::Commands::RevisionReplayedCommand, fbb); |
339 | } | 353 | } |
340 | 354 | ||
355 | KAsync::Job<void> ResourceAccess::sendInspectionCommand(const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
356 | { | ||
357 | flatbuffers::FlatBufferBuilder fbb; | ||
358 | auto id = fbb.CreateString(inspectionId.toStdString()); | ||
359 | auto domain = fbb.CreateString(domainType.toStdString()); | ||
360 | auto entity = fbb.CreateString(entityId.toStdString()); | ||
361 | auto prop = fbb.CreateString(property.toStdString()); | ||
362 | |||
363 | QByteArray array; | ||
364 | QDataStream s(&array, QIODevice::WriteOnly); | ||
365 | s << expectedValue; | ||
366 | |||
367 | auto expected = fbb.CreateString(array.toStdString()); | ||
368 | auto location = Akonadi2::Commands::CreateInspection (fbb, id, 0, entity, domain, prop, expected); | ||
369 | Akonadi2::Commands::FinishInspectionBuffer(fbb, location); | ||
370 | open(); | ||
371 | return sendCommand(Akonadi2::Commands::InspectionCommand, fbb); | ||
372 | } | ||
373 | |||
341 | void ResourceAccess::open() | 374 | void ResourceAccess::open() |
342 | { | 375 | { |
343 | if (d->socket && d->socket->isValid()) { | 376 | if (d->socket && d->socket->isValid()) { |
@@ -424,8 +457,8 @@ void ResourceAccess::connected() | |||
424 | { | 457 | { |
425 | flatbuffers::FlatBufferBuilder fbb; | 458 | flatbuffers::FlatBufferBuilder fbb; |
426 | auto name = fbb.CreateString(QString("PID: %1 ResourceAccess: %2").arg(QCoreApplication::applicationPid()).arg(reinterpret_cast<qlonglong>(this)).toLatin1()); | 459 | auto name = fbb.CreateString(QString("PID: %1 ResourceAccess: %2").arg(QCoreApplication::applicationPid()).arg(reinterpret_cast<qlonglong>(this)).toLatin1()); |
427 | auto command = Akonadi2::CreateHandshake(fbb, name); | 460 | auto command = Akonadi2::Commands::CreateHandshake(fbb, name); |
428 | Akonadi2::FinishHandshakeBuffer(fbb, command); | 461 | Akonadi2::Commands::FinishHandshakeBuffer(fbb, command); |
429 | Commands::write(d->socket.data(), ++d->messageId, Commands::HandshakeCommand, fbb); | 462 | Commands::write(d->socket.data(), ++d->messageId, Commands::HandshakeCommand, fbb); |
430 | } | 463 | } |
431 | 464 | ||
@@ -490,28 +523,49 @@ bool ResourceAccess::processMessageBuffer() | |||
490 | 523 | ||
491 | switch (commandId) { | 524 | switch (commandId) { |
492 | case Commands::RevisionUpdateCommand: { | 525 | case Commands::RevisionUpdateCommand: { |
493 | auto buffer = GetRevisionUpdate(d->partialMessageBuffer.constData() + headerSize); | 526 | auto buffer = Commands::GetRevisionUpdate(d->partialMessageBuffer.constData() + headerSize); |
494 | log(QString("Revision updated to: %1").arg(buffer->revision())); | 527 | log(QString("Revision updated to: %1").arg(buffer->revision())); |
495 | emit revisionChanged(buffer->revision()); | 528 | emit revisionChanged(buffer->revision()); |
496 | 529 | ||
497 | break; | 530 | break; |
498 | } | 531 | } |
499 | case Commands::CommandCompletion: { | 532 | case Commands::CommandCompletionCommand: { |
500 | auto buffer = GetCommandCompletion(d->partialMessageBuffer.constData() + headerSize); | 533 | auto buffer = Commands::GetCommandCompletion(d->partialMessageBuffer.constData() + headerSize); |
501 | log(QString("Command with messageId %1 completed %2").arg(buffer->id()).arg(buffer->success() ? "sucessfully" : "unsuccessfully")); | 534 | log(QString("Command with messageId %1 completed %2").arg(buffer->id()).arg(buffer->success() ? "sucessfully" : "unsuccessfully")); |
502 | 535 | ||
503 | d->completeCommands << buffer->id(); | 536 | d->completeCommands << buffer->id(); |
504 | //The callbacks can result in this object getting destroyed directly, so we need to ensure we finish our work first | 537 | //The callbacks can result in this object getting destroyed directly, so we need to ensure we finish our work first |
505 | QMetaObject::invokeMethod(this, "callCallbacks", Qt::QueuedConnection); | 538 | queuedInvoke([=]() { |
539 | d->callCallbacks(); | ||
540 | }, this); | ||
506 | break; | 541 | break; |
507 | } | 542 | } |
508 | case Commands::NotificationCommand: { | 543 | case Commands::NotificationCommand: { |
509 | auto buffer = GetNotification(d->partialMessageBuffer.constData() + headerSize); | 544 | auto buffer = Commands::GetNotification(d->partialMessageBuffer.constData() + headerSize); |
510 | switch (buffer->type()) { | 545 | switch (buffer->type()) { |
511 | case Akonadi2::NotificationType::NotificationType_Shutdown: | 546 | case Akonadi2::Commands::NotificationType::NotificationType_Shutdown: |
512 | Log(d->resourceInstanceIdentifier) << "Received shutdown notification."; | 547 | Log(d->resourceInstanceIdentifier) << "Received shutdown notification."; |
513 | close(); | 548 | close(); |
514 | break; | 549 | break; |
550 | case Akonadi2::Commands::NotificationType::NotificationType_Inspection: { | ||
551 | Log(d->resourceInstanceIdentifier) << "Received inspection notification."; | ||
552 | Notification n; | ||
553 | if (buffer->identifier()) { | ||
554 | //Don't use fromRawData, the buffer is gone once we invoke emit notification | ||
555 | n.id = BufferUtils::extractBufferCopy(buffer->identifier()); | ||
556 | } | ||
557 | if (buffer->message()) { | ||
558 | //Don't use fromRawData, the buffer is gone once we invoke emit notification | ||
559 | n.message = BufferUtils::extractBufferCopy(buffer->message()); | ||
560 | } | ||
561 | n.type = buffer->type(); | ||
562 | n.code = buffer->code(); | ||
563 | //The callbacks can result in this object getting destroyed directly, so we need to ensure we finish our work first | ||
564 | queuedInvoke([=]() { | ||
565 | emit notification(n); | ||
566 | }, this); | ||
567 | } | ||
568 | break; | ||
515 | default: | 569 | default: |
516 | Warning() << "Received unknown notification: " << buffer->type(); | 570 | Warning() << "Received unknown notification: " << buffer->type(); |
517 | break; | 571 | break; |
@@ -526,11 +580,6 @@ bool ResourceAccess::processMessageBuffer() | |||
526 | return d->partialMessageBuffer.size() >= headerSize; | 580 | return d->partialMessageBuffer.size() >= headerSize; |
527 | } | 581 | } |
528 | 582 | ||
529 | void ResourceAccess::callCallbacks() | ||
530 | { | ||
531 | d->callCallbacks(); | ||
532 | } | ||
533 | |||
534 | void ResourceAccess::log(const QString &message) | 583 | void ResourceAccess::log(const QString &message) |
535 | { | 584 | { |
536 | Log(d->resourceInstanceIdentifier) << this << message; | 585 | Log(d->resourceInstanceIdentifier) << this << message; |
diff --git a/common/resourceaccess.h b/common/resourceaccess.h index 7f61b30..2fe83ed 100644 --- a/common/resourceaccess.h +++ b/common/resourceaccess.h | |||
@@ -27,6 +27,7 @@ | |||
27 | #include <Async/Async> | 27 | #include <Async/Async> |
28 | 28 | ||
29 | #include <flatbuffers/flatbuffers.h> | 29 | #include <flatbuffers/flatbuffers.h> |
30 | #include "notification.h" | ||
30 | 31 | ||
31 | namespace Akonadi2 | 32 | namespace Akonadi2 |
32 | { | 33 | { |
@@ -49,10 +50,12 @@ public: | |||
49 | virtual KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) { return KAsync::null<void>(); }; | 50 | virtual KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) { return KAsync::null<void>(); }; |
50 | virtual KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) { return KAsync::null<void>(); }; | 51 | virtual KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) { return KAsync::null<void>(); }; |
51 | virtual KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) {return KAsync::null<void>(); }; | 52 | virtual KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) {return KAsync::null<void>(); }; |
53 | virtual KAsync::Job<void> sendInspectionCommand(const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expecedValue) {return KAsync::null<void>(); }; | ||
52 | 54 | ||
53 | Q_SIGNALS: | 55 | Q_SIGNALS: |
54 | void ready(bool isReady); | 56 | void ready(bool isReady); |
55 | void revisionChanged(qint64 revision); | 57 | void revisionChanged(qint64 revision); |
58 | void notification(Notification revision); | ||
56 | 59 | ||
57 | public Q_SLOTS: | 60 | public Q_SLOTS: |
58 | virtual void open() = 0; | 61 | virtual void open() = 0; |
@@ -78,6 +81,7 @@ public: | |||
78 | KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) Q_DECL_OVERRIDE; | 81 | KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) Q_DECL_OVERRIDE; |
79 | KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) Q_DECL_OVERRIDE; | 82 | KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) Q_DECL_OVERRIDE; |
80 | KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) Q_DECL_OVERRIDE; | 83 | KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) Q_DECL_OVERRIDE; |
84 | KAsync::Job<void> sendInspectionCommand(const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expecedValue) Q_DECL_OVERRIDE; | ||
81 | /** | 85 | /** |
82 | * Tries to connect to server, and returns a connected socket on success. | 86 | * Tries to connect to server, and returns a connected socket on success. |
83 | */ | 87 | */ |
@@ -93,7 +97,6 @@ private Q_SLOTS: | |||
93 | void connectionError(QLocalSocket::LocalSocketError error); | 97 | void connectionError(QLocalSocket::LocalSocketError error); |
94 | void readResourceMessage(); | 98 | void readResourceMessage(); |
95 | bool processMessageBuffer(); | 99 | bool processMessageBuffer(); |
96 | void callCallbacks(); | ||
97 | 100 | ||
98 | private: | 101 | private: |
99 | void connected(); | 102 | void connected(); |
diff --git a/common/resourcefacade.h b/common/resourcefacade.h index 38e0c0e..ae3037a 100644 --- a/common/resourcefacade.h +++ b/common/resourcefacade.h | |||
@@ -27,6 +27,7 @@ | |||
27 | 27 | ||
28 | namespace Akonadi2 { | 28 | namespace Akonadi2 { |
29 | class Query; | 29 | class Query; |
30 | class Inspection; | ||
30 | } | 31 | } |
31 | 32 | ||
32 | class ResourceFacade : public Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::AkonadiResource> | 33 | class ResourceFacade : public Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::AkonadiResource> |
@@ -38,5 +39,9 @@ public: | |||
38 | KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; | 39 | KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; |
39 | KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; | 40 | KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; |
40 | QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<Akonadi2::ApplicationDomain::AkonadiResource::Ptr>::Ptr > load(const Akonadi2::Query &query) Q_DECL_OVERRIDE; | 41 | QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<Akonadi2::ApplicationDomain::AkonadiResource::Ptr>::Ptr > load(const Akonadi2::Query &query) Q_DECL_OVERRIDE; |
42 | KAsync::Job<void> inspect(const Akonadi2::Inspection &domainObject) Q_DECL_OVERRIDE | ||
43 | { | ||
44 | return KAsync::error<void>(-1, "Failed to inspect."); | ||
45 | } | ||
41 | }; | 46 | }; |
42 | 47 | ||
diff --git a/common/resultprovider.h b/common/resultprovider.h index 7f090e9..a064ab5 100644 --- a/common/resultprovider.h +++ b/common/resultprovider.h | |||
@@ -346,8 +346,14 @@ public: | |||
346 | emitter->onRemoved([this](const DomainType &value) { | 346 | emitter->onRemoved([this](const DomainType &value) { |
347 | this->remove(value); | 347 | this->remove(value); |
348 | }); | 348 | }); |
349 | emitter->onInitialResultSetComplete([this](const DomainType &value) { | 349 | auto ptr = emitter.data(); |
350 | this->initialResultSetComplete(value); | 350 | emitter->onInitialResultSetComplete([this, ptr](const DomainType &parent) { |
351 | auto hashValue = qHash(parent); | ||
352 | mInitialResultSetInProgress.remove(hashValue, ptr); | ||
353 | //Normally a parent is only in a single resource, except the toplevel (invalid) parent | ||
354 | if (!mInitialResultSetInProgress.contains(hashValue)) { | ||
355 | this->initialResultSetComplete(parent); | ||
356 | } | ||
351 | }); | 357 | }); |
352 | emitter->onComplete([this]() { | 358 | emitter->onComplete([this]() { |
353 | this->complete(); | 359 | this->complete(); |
@@ -365,6 +371,7 @@ public: | |||
365 | this->initialResultSetComplete(parent); | 371 | this->initialResultSetComplete(parent); |
366 | } else { | 372 | } else { |
367 | for (const auto &emitter : mEmitter) { | 373 | for (const auto &emitter : mEmitter) { |
374 | mInitialResultSetInProgress.insert(qHash(parent), emitter.data()); | ||
368 | emitter->fetch(parent); | 375 | emitter->fetch(parent); |
369 | } | 376 | } |
370 | } | 377 | } |
@@ -372,6 +379,7 @@ public: | |||
372 | 379 | ||
373 | private: | 380 | private: |
374 | QList<typename ResultEmitter<DomainType>::Ptr> mEmitter; | 381 | QList<typename ResultEmitter<DomainType>::Ptr> mEmitter; |
382 | QMultiMap<qint64, ResultEmitter<DomainType>*> mInitialResultSetInProgress; | ||
375 | }; | 383 | }; |
376 | 384 | ||
377 | 385 | ||
diff --git a/docs/resource.md b/docs/resource.md index c8f58e9..0988535 100644 --- a/docs/resource.md +++ b/docs/resource.md | |||
@@ -141,3 +141,10 @@ The remoteid mapping has to be updated in two places: | |||
141 | 141 | ||
142 | * New entities that are synchronized immediately get a localid assinged, that is then recorded together with the remoteid. This is required to be able to reference other entities directly in the command queue (i.e. for parent folders). | 142 | * New entities that are synchronized immediately get a localid assinged, that is then recorded together with the remoteid. This is required to be able to reference other entities directly in the command queue (i.e. for parent folders). |
143 | * Entities created by clients get a remoteid assigned during change replay, so the entity can be recognized during the next sync. | 143 | * Entities created by clients get a remoteid assigned during change replay, so the entity can be recognized during the next sync. |
144 | |||
145 | # Testing / Inspection | ||
146 | Resources new to be tested, which often requires inspections into the current state of the resource. This is difficult in an asynchronous system where the whole backend logic is encapsulated in a separate process without running tests in a vastly different setup from how it will be run in production. | ||
147 | |||
148 | To alleviate this inspection commands are introduced. Inspection commands are special commands that the resource processes just like all other commands, and that have the sole purpose of inspecting the current resource state. Because the command is processed with the same mechanism as other commands we can rely on ordering of commands in a way that a prior command is guaranteed to be executed once the inspection command is processed. | ||
149 | |||
150 | A typical inspection command could i.e. verify that a file has been created in the expected path after a create command. | ||
diff --git a/examples/client/main.cpp b/examples/client/main.cpp index 127fd2f..6660f86 100644 --- a/examples/client/main.cpp +++ b/examples/client/main.cpp | |||
@@ -120,7 +120,6 @@ public: | |||
120 | QObject::connect(syncButton, &QPushButton::pressed, []() { | 120 | QObject::connect(syncButton, &QPushButton::pressed, []() { |
121 | Akonadi2::Query query; | 121 | Akonadi2::Query query; |
122 | query.resources << "org.kde.dummy.instance1"; | 122 | query.resources << "org.kde.dummy.instance1"; |
123 | query.syncOnDemand = true; | ||
124 | Akonadi2::Store::synchronize(query).exec(); | 123 | Akonadi2::Store::synchronize(query).exec(); |
125 | }); | 124 | }); |
126 | 125 | ||
@@ -198,8 +197,6 @@ int main(int argc, char *argv[]) | |||
198 | for (const auto &res : resources) { | 197 | for (const auto &res : resources) { |
199 | query.resources << res.toLatin1(); | 198 | query.resources << res.toLatin1(); |
200 | } | 199 | } |
201 | query.syncOnDemand = false; | ||
202 | query.processAll = false; | ||
203 | query.liveQuery = true; | 200 | query.liveQuery = true; |
204 | if (type == "folder") { | 201 | if (type == "folder") { |
205 | query.parentProperty = "parent"; | 202 | query.parentProperty = "parent"; |
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp index a984097..c43b5e6 100644 --- a/examples/dummyresource/resourcefactory.cpp +++ b/examples/dummyresource/resourcefactory.cpp | |||
@@ -134,6 +134,22 @@ void DummyResource::removeFromDisk(const QByteArray &instanceIdentifier) | |||
134 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); | 134 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); |
135 | } | 135 | } |
136 | 136 | ||
137 | KAsync::Job<void> DummyResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
138 | { | ||
139 | |||
140 | Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; | ||
141 | if (property == "testInspection") { | ||
142 | if (expectedValue.toBool()) { | ||
143 | //Success | ||
144 | return KAsync::null<void>(); | ||
145 | } else { | ||
146 | //Failure | ||
147 | return KAsync::error<void>(1, "Failed."); | ||
148 | } | ||
149 | } | ||
150 | return KAsync::null<void>(); | ||
151 | } | ||
152 | |||
137 | DummyResourceFactory::DummyResourceFactory(QObject *parent) | 153 | DummyResourceFactory::DummyResourceFactory(QObject *parent) |
138 | : Akonadi2::ResourceFactory(parent) | 154 | : Akonadi2::ResourceFactory(parent) |
139 | { | 155 | { |
diff --git a/examples/dummyresource/resourcefactory.h b/examples/dummyresource/resourcefactory.h index 2ed4c5d..634829e 100644 --- a/examples/dummyresource/resourcefactory.h +++ b/examples/dummyresource/resourcefactory.h | |||
@@ -40,6 +40,7 @@ public: | |||
40 | KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore) Q_DECL_OVERRIDE; | 40 | KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore) Q_DECL_OVERRIDE; |
41 | using GenericResource::synchronizeWithSource; | 41 | using GenericResource::synchronizeWithSource; |
42 | static void removeFromDisk(const QByteArray &instanceIdentifier); | 42 | static void removeFromDisk(const QByteArray &instanceIdentifier); |
43 | KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE; | ||
43 | private: | 44 | private: |
44 | KAsync::Job<void> replay(Akonadi2::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; | 45 | KAsync::Job<void> replay(Akonadi2::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; |
45 | Akonadi2::ApplicationDomain::Event::Ptr createEvent(const QByteArray &rid, const QMap<QString, QVariant> &data, Akonadi2::Storage::Transaction &); | 46 | Akonadi2::ApplicationDomain::Event::Ptr createEvent(const QByteArray &rid, const QMap<QString, QVariant> &data, Akonadi2::Storage::Transaction &); |
diff --git a/examples/maildirresource/libmaildir/maildir.cpp b/examples/maildirresource/libmaildir/maildir.cpp index 94363b8..28cf46e 100644 --- a/examples/maildirresource/libmaildir/maildir.cpp +++ b/examples/maildirresource/libmaildir/maildir.cpp | |||
@@ -770,7 +770,7 @@ bool Maildir::removeEntry(const QString& key) | |||
770 | // return newUniqueKey; | 770 | // return newUniqueKey; |
771 | // } | 771 | // } |
772 | // | 772 | // |
773 | Maildir::Flags Maildir::readEntryFlags(const QString& key) const | 773 | Maildir::Flags Maildir::readEntryFlags(const QString& key) |
774 | { | 774 | { |
775 | Flags flags; | 775 | Flags flags; |
776 | const QRegExp rx = *(statusSeparatorRx()); | 776 | const QRegExp rx = *(statusSeparatorRx()); |
diff --git a/examples/maildirresource/libmaildir/maildir.h b/examples/maildirresource/libmaildir/maildir.h index f80ba5d..fefd5a7 100644 --- a/examples/maildirresource/libmaildir/maildir.h +++ b/examples/maildirresource/libmaildir/maildir.h | |||
@@ -178,7 +178,7 @@ public: | |||
178 | /** | 178 | /** |
179 | * Return the flags encoded in the maildir file name for an entry | 179 | * Return the flags encoded in the maildir file name for an entry |
180 | **/ | 180 | **/ |
181 | Flags readEntryFlags( const QString& key ) const; | 181 | static Flags readEntryFlags( const QString& key ); |
182 | 182 | ||
183 | /** | 183 | /** |
184 | * Return the contents of the headers section of the file the maildir with the given @p file, that | 184 | * Return the contents of the headers section of the file the maildir with the given @p file, that |
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 70f6ae5..9280bdc 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp | |||
@@ -35,6 +35,7 @@ | |||
35 | #include "facadefactory.h" | 35 | #include "facadefactory.h" |
36 | #include "indexupdater.h" | 36 | #include "indexupdater.h" |
37 | #include "libmaildir/maildir.h" | 37 | #include "libmaildir/maildir.h" |
38 | #include "inspection.h" | ||
38 | #include <QDate> | 39 | #include <QDate> |
39 | #include <QUuid> | 40 | #include <QUuid> |
40 | #include <QDir> | 41 | #include <QDir> |
@@ -292,6 +293,35 @@ void MaildirResource::removeFromDisk(const QByteArray &instanceIdentifier) | |||
292 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); | 293 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); |
293 | } | 294 | } |
294 | 295 | ||
296 | KAsync::Job<void> MaildirResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
297 | { | ||
298 | auto synchronizationStore = QSharedPointer<Akonadi2::Storage>::create(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadOnly); | ||
299 | auto synchronizationTransaction = synchronizationStore->createTransaction(Akonadi2::Storage::ReadOnly); | ||
300 | Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; | ||
301 | if (domainType == ENTITY_TYPE_MAIL) { | ||
302 | if (inspectionType == Akonadi2::Resources::Inspection::PropertyInspectionType) { | ||
303 | if (property == "unread") { | ||
304 | const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction); | ||
305 | const auto flags = KPIM::Maildir::readEntryFlags(remoteId.split('/').last()); | ||
306 | if (expectedValue.toBool() && !(flags & KPIM::Maildir::Seen)) { | ||
307 | return KAsync::error<void>(1, "Expected seen but couldn't find it."); | ||
308 | } | ||
309 | if (!expectedValue.toBool() && (flags & KPIM::Maildir::Seen)) { | ||
310 | return KAsync::error<void>(1, "Expected seen but couldn't find it."); | ||
311 | } | ||
312 | return KAsync::null<void>(); | ||
313 | } | ||
314 | } | ||
315 | if (inspectionType == Akonadi2::Resources::Inspection::ExistenceInspectionType) { | ||
316 | const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction); | ||
317 | if (QFileInfo(remoteId).exists() != expectedValue.toBool()) { | ||
318 | return KAsync::error<void>(1, "Wrong file existence: " + remoteId); | ||
319 | } | ||
320 | } | ||
321 | } | ||
322 | return KAsync::null<void>(); | ||
323 | } | ||
324 | |||
295 | MaildirResourceFactory::MaildirResourceFactory(QObject *parent) | 325 | MaildirResourceFactory::MaildirResourceFactory(QObject *parent) |
296 | : Akonadi2::ResourceFactory(parent) | 326 | : Akonadi2::ResourceFactory(parent) |
297 | { | 327 | { |
diff --git a/examples/maildirresource/maildirresource.h b/examples/maildirresource/maildirresource.h index 21ee637..5f7795b 100644 --- a/examples/maildirresource/maildirresource.h +++ b/examples/maildirresource/maildirresource.h | |||
@@ -44,6 +44,7 @@ class MaildirResource : public Akonadi2::GenericResource | |||
44 | public: | 44 | public: |
45 | MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline = QSharedPointer<Akonadi2::Pipeline>()); | 45 | MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline = QSharedPointer<Akonadi2::Pipeline>()); |
46 | KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore) Q_DECL_OVERRIDE; | 46 | KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore) Q_DECL_OVERRIDE; |
47 | KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE; | ||
47 | static void removeFromDisk(const QByteArray &instanceIdentifier); | 48 | static void removeFromDisk(const QByteArray &instanceIdentifier); |
48 | private: | 49 | private: |
49 | KAsync::Job<void> replay(Akonadi2::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; | 50 | KAsync::Job<void> replay(Akonadi2::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; |
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e0f6b5..38e5512 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt | |||
@@ -51,12 +51,14 @@ auto_tests ( | |||
51 | databasepopulationandfacadequerybenchmark | 51 | databasepopulationandfacadequerybenchmark |
52 | dummyresourcewritebenchmark | 52 | dummyresourcewritebenchmark |
53 | modelinteractivitytest | 53 | modelinteractivitytest |
54 | inspectiontest | ||
54 | ) | 55 | ) |
55 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) | 56 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) |
56 | target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) | 57 | target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) |
57 | target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy) | 58 | target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy) |
58 | target_link_libraries(querytest akonadi2_resource_dummy) | 59 | target_link_libraries(querytest akonadi2_resource_dummy) |
59 | target_link_libraries(modelinteractivitytest akonadi2_resource_dummy) | 60 | target_link_libraries(modelinteractivitytest akonadi2_resource_dummy) |
61 | target_link_libraries(inspectiontest akonadi2_resource_dummy) | ||
60 | 62 | ||
61 | if (BUILD_MAILDIR) | 63 | if (BUILD_MAILDIR) |
62 | auto_tests ( | 64 | auto_tests ( |
diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp index 78e1d1e..86150ee 100644 --- a/tests/clientapitest.cpp +++ b/tests/clientapitest.cpp | |||
@@ -33,6 +33,7 @@ public: | |||
33 | KAsync::Job<void> create(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; | 33 | KAsync::Job<void> create(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; |
34 | KAsync::Job<void> modify(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; | 34 | KAsync::Job<void> modify(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; |
35 | KAsync::Job<void> remove(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; | 35 | KAsync::Job<void> remove(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; |
36 | KAsync::Job<void> inspect(const Akonadi2::Inspection &) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; | ||
36 | QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<typename T::Ptr>::Ptr > load(const Akonadi2::Query &query) Q_DECL_OVERRIDE | 37 | QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<typename T::Ptr>::Ptr > load(const Akonadi2::Query &query) Q_DECL_OVERRIDE |
37 | { | 38 | { |
38 | auto resultProvider = new Akonadi2::ResultProvider<typename T::Ptr>(); | 39 | auto resultProvider = new Akonadi2::ResultProvider<typename T::Ptr>(); |
@@ -265,9 +266,38 @@ private Q_SLOTS: | |||
265 | Akonadi2::Query query; | 266 | Akonadi2::Query query; |
266 | query.liveQuery = false; | 267 | query.liveQuery = false; |
267 | 268 | ||
269 | int childrenFetchedCount = 0; | ||
268 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); | 270 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); |
271 | QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [&childrenFetchedCount](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) { | ||
272 | if (roles.contains(Akonadi2::Store::ChildrenFetchedRole)) { | ||
273 | childrenFetchedCount++; | ||
274 | } | ||
275 | }); | ||
269 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 276 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
270 | QCOMPARE(model->rowCount(QModelIndex()), 2); | 277 | QCOMPARE(model->rowCount(QModelIndex()), 2); |
278 | //Ensure children fetched is only emitted once (when all resources are done) | ||
279 | QTest::qWait(50); | ||
280 | QCOMPARE(childrenFetchedCount, 1); | ||
281 | } | ||
282 | |||
283 | void testImperativeLoad() | ||
284 | { | ||
285 | auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Event>::registerFacade(); | ||
286 | facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create()); | ||
287 | ResourceConfig::addResource("dummyresource.instance1", "dummyresource"); | ||
288 | |||
289 | Akonadi2::Query query; | ||
290 | query.resources << "dummyresource.instance1"; | ||
291 | query.liveQuery = false; | ||
292 | |||
293 | bool gotValue = false; | ||
294 | auto result = Akonadi2::Store::fetchOne<Akonadi2::ApplicationDomain::Event>(query) | ||
295 | .then<void, Akonadi2::ApplicationDomain::Event>([&gotValue](const Akonadi2::ApplicationDomain::Event &event) { | ||
296 | gotValue = true; | ||
297 | }).exec(); | ||
298 | result.waitForFinished(); | ||
299 | QVERIFY(!result.errorCode()); | ||
300 | QVERIFY(gotValue); | ||
271 | } | 301 | } |
272 | 302 | ||
273 | 303 | ||
diff --git a/tests/dummyresourcebenchmark.cpp b/tests/dummyresourcebenchmark.cpp index aad86c0..c52eee3 100644 --- a/tests/dummyresourcebenchmark.cpp +++ b/tests/dummyresourcebenchmark.cpp | |||
@@ -92,9 +92,7 @@ private Q_SLOTS: | |||
92 | { | 92 | { |
93 | Akonadi2::Query query; | 93 | Akonadi2::Query query; |
94 | query.resources << "org.kde.dummy.instance1"; | 94 | query.resources << "org.kde.dummy.instance1"; |
95 | query.syncOnDemand = false; | 95 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
96 | query.processAll = true; | ||
97 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | ||
98 | } | 96 | } |
99 | auto allProcessedTime = time.elapsed(); | 97 | auto allProcessedTime = time.elapsed(); |
100 | 98 | ||
@@ -123,8 +121,6 @@ private Q_SLOTS: | |||
123 | time.start(); | 121 | time.start(); |
124 | Akonadi2::Query query; | 122 | Akonadi2::Query query; |
125 | query.resources << "org.kde.dummy.instance1"; | 123 | query.resources << "org.kde.dummy.instance1"; |
126 | query.syncOnDemand = false; | ||
127 | query.processAll = false; | ||
128 | 124 | ||
129 | query.propertyFilter.insert("uid", "testuid"); | 125 | query.propertyFilter.insert("uid", "testuid"); |
130 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); | 126 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); |
diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp index 0e1f382..72a24b6 100644 --- a/tests/dummyresourcetest.cpp +++ b/tests/dummyresourcetest.cpp | |||
@@ -62,11 +62,9 @@ private Q_SLOTS: | |||
62 | 62 | ||
63 | Akonadi2::Query query; | 63 | Akonadi2::Query query; |
64 | query.resources << "org.kde.dummy.instance1"; | 64 | query.resources << "org.kde.dummy.instance1"; |
65 | query.syncOnDemand = false; | ||
66 | query.processAll = true; | ||
67 | 65 | ||
68 | //Ensure all local data is processed | 66 | //Ensure all local data is processed |
69 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 67 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
70 | 68 | ||
71 | query.propertyFilter.insert("uid", "testuid"); | 69 | query.propertyFilter.insert("uid", "testuid"); |
72 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); | 70 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); |
@@ -88,11 +86,9 @@ private Q_SLOTS: | |||
88 | 86 | ||
89 | Akonadi2::Query query; | 87 | Akonadi2::Query query; |
90 | query.resources << "org.kde.dummy.instance1"; | 88 | query.resources << "org.kde.dummy.instance1"; |
91 | query.syncOnDemand = false; | ||
92 | query.processAll = true; | ||
93 | 89 | ||
94 | //Ensure all local data is processed | 90 | //Ensure all local data is processed |
95 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 91 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
96 | 92 | ||
97 | query.propertyFilter.insert("uid", "testuid"); | 93 | query.propertyFilter.insert("uid", "testuid"); |
98 | 94 | ||
@@ -118,11 +114,9 @@ private Q_SLOTS: | |||
118 | 114 | ||
119 | Akonadi2::Query query; | 115 | Akonadi2::Query query; |
120 | query.resources << "org.kde.dummy.instance1"; | 116 | query.resources << "org.kde.dummy.instance1"; |
121 | query.syncOnDemand = false; | ||
122 | query.processAll = true; | ||
123 | 117 | ||
124 | //Ensure all local data is processed | 118 | //Ensure all local data is processed |
125 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 119 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
126 | 120 | ||
127 | query.propertyFilter.insert("summary", "summaryValue2"); | 121 | query.propertyFilter.insert("summary", "summaryValue2"); |
128 | 122 | ||
@@ -153,11 +147,10 @@ private Q_SLOTS: | |||
153 | { | 147 | { |
154 | Akonadi2::Query query; | 148 | Akonadi2::Query query; |
155 | query.resources << "org.kde.dummy.instance1"; | 149 | query.resources << "org.kde.dummy.instance1"; |
156 | query.syncOnDemand = true; | ||
157 | query.processAll = true; | ||
158 | 150 | ||
159 | //Ensure all local data is processed | 151 | //Ensure all local data is processed |
160 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 152 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
153 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
161 | 154 | ||
162 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); | 155 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query); |
163 | QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1); | 156 | QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1); |
@@ -171,11 +164,10 @@ private Q_SLOTS: | |||
171 | { | 164 | { |
172 | Akonadi2::Query query; | 165 | Akonadi2::Query query; |
173 | query.resources << "org.kde.dummy.instance1"; | 166 | query.resources << "org.kde.dummy.instance1"; |
174 | query.syncOnDemand = true; | ||
175 | query.processAll = true; | ||
176 | 167 | ||
177 | //Ensure all local data is processed | 168 | //Ensure all local data is processed |
178 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 169 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
170 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
179 | 171 | ||
180 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 172 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
181 | QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1); | 173 | QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1); |
@@ -195,12 +187,10 @@ private Q_SLOTS: | |||
195 | 187 | ||
196 | Akonadi2::Query query; | 188 | Akonadi2::Query query; |
197 | query.resources << "org.kde.dummy.instance1"; | 189 | query.resources << "org.kde.dummy.instance1"; |
198 | query.syncOnDemand = false; | ||
199 | query.processAll = true; | ||
200 | query.propertyFilter.insert("uid", "testuid"); | 190 | query.propertyFilter.insert("uid", "testuid"); |
201 | 191 | ||
202 | //Ensure all local data is processed | 192 | //Ensure all local data is processed |
203 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 193 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
204 | 194 | ||
205 | //Test create | 195 | //Test create |
206 | Akonadi2::ApplicationDomain::Event event2; | 196 | Akonadi2::ApplicationDomain::Event event2; |
@@ -219,7 +209,7 @@ private Q_SLOTS: | |||
219 | Akonadi2::Store::modify<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished(); | 209 | Akonadi2::Store::modify<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished(); |
220 | 210 | ||
221 | //Ensure all local data is processed | 211 | //Ensure all local data is processed |
222 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 212 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
223 | 213 | ||
224 | //Test modify | 214 | //Test modify |
225 | { | 215 | { |
@@ -234,7 +224,7 @@ private Q_SLOTS: | |||
234 | Akonadi2::Store::remove<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished(); | 224 | Akonadi2::Store::remove<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished(); |
235 | 225 | ||
236 | //Ensure all local data is processed | 226 | //Ensure all local data is processed |
237 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 227 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
238 | 228 | ||
239 | //Test remove | 229 | //Test remove |
240 | { | 230 | { |
@@ -249,8 +239,6 @@ private Q_SLOTS: | |||
249 | 239 | ||
250 | Akonadi2::Query query; | 240 | Akonadi2::Query query; |
251 | query.resources << "org.kde.dummy.instance1"; | 241 | query.resources << "org.kde.dummy.instance1"; |
252 | query.syncOnDemand = false; | ||
253 | query.processAll = true; | ||
254 | query.liveQuery = true; | 242 | query.liveQuery = true; |
255 | query.propertyFilter.insert("uid", "testuid"); | 243 | query.propertyFilter.insert("uid", "testuid"); |
256 | 244 | ||
diff --git a/tests/inspectiontest.cpp b/tests/inspectiontest.cpp new file mode 100644 index 0000000..29cce6c --- /dev/null +++ b/tests/inspectiontest.cpp | |||
@@ -0,0 +1,65 @@ | |||
1 | #include <QtTest> | ||
2 | |||
3 | #include <QString> | ||
4 | |||
5 | #include "dummyresource/resourcefactory.h" | ||
6 | #include "clientapi.h" | ||
7 | #include "resourceconfig.h" | ||
8 | #include "log.h" | ||
9 | |||
10 | /** | ||
11 | * Test of inspection system using the dummy resource. | ||
12 | * | ||
13 | * This test requires the dummy resource installed. | ||
14 | */ | ||
15 | class InspectionTest : public QObject | ||
16 | { | ||
17 | Q_OBJECT | ||
18 | private Q_SLOTS: | ||
19 | void initTestCase() | ||
20 | { | ||
21 | Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Trace); | ||
22 | auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy"); | ||
23 | QVERIFY(factory); | ||
24 | DummyResource::removeFromDisk("org.kde.dummy.instance1"); | ||
25 | ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy"); | ||
26 | } | ||
27 | |||
28 | void cleanup() | ||
29 | { | ||
30 | Akonadi2::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); | ||
31 | DummyResource::removeFromDisk("org.kde.dummy.instance1"); | ||
32 | auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy"); | ||
33 | QVERIFY(factory); | ||
34 | Akonadi2::Store::start(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); | ||
35 | } | ||
36 | |||
37 | void testInspection_data() | ||
38 | { | ||
39 | QTest::addColumn<bool>("success"); | ||
40 | QTest::newRow("success") << true; | ||
41 | QTest::newRow("fail") << false; | ||
42 | } | ||
43 | |||
44 | void testInspection() | ||
45 | { | ||
46 | QFETCH(bool, success); | ||
47 | using namespace Akonadi2; | ||
48 | using namespace Akonadi2::ApplicationDomain; | ||
49 | |||
50 | Mail mail(QByteArray("org.kde.dummy.instance1"), QByteArray("identifier"), 0, QSharedPointer<MemoryBufferAdaptor::MemoryBufferAdaptor>::create()); | ||
51 | |||
52 | //testInspection is a magic property that the dummyresource supports | ||
53 | auto inspectionCommand = Resources::Inspection::PropertyInspection(mail, "testInspection", success); | ||
54 | auto result = Resources::inspect<Mail>(inspectionCommand).exec(); | ||
55 | result.waitForFinished(); | ||
56 | if (success) { | ||
57 | QVERIFY(!result.errorCode()); | ||
58 | } else { | ||
59 | QVERIFY(result.errorCode()); | ||
60 | } | ||
61 | } | ||
62 | }; | ||
63 | |||
64 | QTEST_MAIN(InspectionTest) | ||
65 | #include "inspectiontest.moc" | ||
diff --git a/tests/maildirresourcetest.cpp b/tests/maildirresourcetest.cpp index 6e7818a..ec4f6a4 100644 --- a/tests/maildirresourcetest.cpp +++ b/tests/maildirresourcetest.cpp | |||
@@ -87,11 +87,10 @@ private Q_SLOTS: | |||
87 | { | 87 | { |
88 | Akonadi2::Query query; | 88 | Akonadi2::Query query; |
89 | query.resources << "org.kde.maildir.instance1"; | 89 | query.resources << "org.kde.maildir.instance1"; |
90 | query.syncOnDemand = true; | ||
91 | query.processAll = true; | ||
92 | 90 | ||
93 | //Ensure all local data is processed | 91 | //Ensure all local data is processed |
94 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 92 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
93 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
95 | 94 | ||
96 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 95 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); |
97 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 96 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -102,12 +101,11 @@ private Q_SLOTS: | |||
102 | { | 101 | { |
103 | Akonadi2::Query query; | 102 | Akonadi2::Query query; |
104 | query.resources << "org.kde.maildir.instance1"; | 103 | query.resources << "org.kde.maildir.instance1"; |
105 | query.syncOnDemand = true; | ||
106 | query.processAll = true; | ||
107 | query.parentProperty = "parent"; | 104 | query.parentProperty = "parent"; |
108 | 105 | ||
109 | //Ensure all local data is processed | 106 | //Ensure all local data is processed |
110 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 107 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
108 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
111 | 109 | ||
112 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 110 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); |
113 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 111 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -120,34 +118,27 @@ private Q_SLOTS: | |||
120 | 118 | ||
121 | void testListMailsOfFolder() | 119 | void testListMailsOfFolder() |
122 | { | 120 | { |
123 | { | 121 | using namespace Akonadi2; |
124 | Akonadi2::Query query; | 122 | using namespace Akonadi2::ApplicationDomain; |
125 | query.resources << "org.kde.maildir.instance1"; | 123 | //Ensure all local data is processed |
126 | query.syncOnDemand = true; | 124 | auto query = Query::ResourceFilter("org.kde.maildir.instance1"); |
127 | query.processAll = true; | 125 | Store::synchronize(query).exec().waitForFinished(); |
128 | 126 | Store::flushMessageQueue(query.resources).exec().waitForFinished(); | |
129 | //Ensure all local data is processed | 127 | auto result = Store::fetchOne<Folder>( |
130 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 128 | Query::ResourceFilter("org.kde.maildir.instance1") + Query::RequestedProperties(QByteArrayList() << "name") |
131 | } | 129 | ) |
132 | QByteArray folderIdentifier; | 130 | .then<QList<Mail::Ptr>, Folder>([](const Folder &folder) { |
133 | { | 131 | Trace() << "Found a folder" << folder.identifier(); |
134 | Akonadi2::Query query; | 132 | return Store::fetchAll<Mail>( |
135 | query.resources << "org.kde.maildir.instance1"; | 133 | Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject") |
136 | query.requestedProperties << "folder" << "name"; | 134 | ); |
137 | 135 | }) | |
138 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 136 | .then<void, QList<Mail::Ptr> >([](const QList<Mail::Ptr> &mails) { |
139 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 137 | QVERIFY(mails.size() >= 1); |
140 | QVERIFY(model->rowCount(QModelIndex()) > 1); | 138 | }) |
141 | folderIdentifier = model->index(1, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder::Ptr>()->identifier(); | 139 | .exec(); |
142 | } | 140 | result.waitForFinished(); |
143 | 141 | QVERIFY(!result.errorCode()); | |
144 | Akonadi2::Query query; | ||
145 | query.resources << "org.kde.maildir.instance1"; | ||
146 | query.requestedProperties << "folder" << "subject"; | ||
147 | query.propertyFilter.insert("folder", folderIdentifier); | ||
148 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | ||
149 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | ||
150 | QVERIFY(mailModel->rowCount(QModelIndex()) >= 1); | ||
151 | } | 142 | } |
152 | 143 | ||
153 | void testMailContent() | 144 | void testMailContent() |
@@ -155,11 +146,10 @@ private Q_SLOTS: | |||
155 | Akonadi2::Query query; | 146 | Akonadi2::Query query; |
156 | query.resources << "org.kde.maildir.instance1"; | 147 | query.resources << "org.kde.maildir.instance1"; |
157 | query.requestedProperties << "folder" << "subject" << "mimeMessage" << "date"; | 148 | query.requestedProperties << "folder" << "subject" << "mimeMessage" << "date"; |
158 | query.syncOnDemand = true; | ||
159 | query.processAll = true; | ||
160 | 149 | ||
161 | //Ensure all local data is processed | 150 | //Ensure all local data is processed |
162 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 151 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
152 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
163 | 153 | ||
164 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 154 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
165 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 155 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -175,12 +165,11 @@ private Q_SLOTS: | |||
175 | { | 165 | { |
176 | Akonadi2::Query query; | 166 | Akonadi2::Query query; |
177 | query.resources << "org.kde.maildir.instance1"; | 167 | query.resources << "org.kde.maildir.instance1"; |
178 | query.syncOnDemand = true; | ||
179 | query.processAll = true; | ||
180 | query.requestedProperties << "name"; | 168 | query.requestedProperties << "name"; |
181 | 169 | ||
182 | //Ensure all local data is processed | 170 | //Ensure all local data is processed |
183 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 171 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
172 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
184 | 173 | ||
185 | auto targetPath = tempDir.path() + "/maildir1/"; | 174 | auto targetPath = tempDir.path() + "/maildir1/"; |
186 | QDir dir(targetPath); | 175 | QDir dir(targetPath); |
@@ -188,6 +177,7 @@ private Q_SLOTS: | |||
188 | 177 | ||
189 | //Ensure all local data is processed | 178 | //Ensure all local data is processed |
190 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 179 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
180 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
191 | 181 | ||
192 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 182 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); |
193 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 183 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -199,15 +189,15 @@ private Q_SLOTS: | |||
199 | { | 189 | { |
200 | Akonadi2::Query query; | 190 | Akonadi2::Query query; |
201 | query.resources << "org.kde.maildir.instance1"; | 191 | query.resources << "org.kde.maildir.instance1"; |
202 | query.syncOnDemand = true; | ||
203 | query.processAll = true; | ||
204 | query.requestedProperties << "folder" << "subject"; | 192 | query.requestedProperties << "folder" << "subject"; |
205 | 193 | ||
206 | //Ensure all local data is processed | 194 | //Ensure all local data is processed |
207 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 195 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
196 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
208 | 197 | ||
209 | //Ensure all local data is processed | 198 | //Ensure all local data is processed |
210 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 199 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
200 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
211 | 201 | ||
212 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 202 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
213 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 203 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -218,12 +208,11 @@ private Q_SLOTS: | |||
218 | { | 208 | { |
219 | Akonadi2::Query query; | 209 | Akonadi2::Query query; |
220 | query.resources << "org.kde.maildir.instance1"; | 210 | query.resources << "org.kde.maildir.instance1"; |
221 | query.syncOnDemand = true; | ||
222 | query.processAll = true; | ||
223 | query.requestedProperties << "folder" << "subject"; | 211 | query.requestedProperties << "folder" << "subject"; |
224 | 212 | ||
225 | //Ensure all local data is processed | 213 | //Ensure all local data is processed |
226 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 214 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
215 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
227 | 216 | ||
228 | auto targetPath = tempDir.path() + "/maildir1/cur/1365777830.R28.localhost.localdomain:2,S"; | 217 | auto targetPath = tempDir.path() + "/maildir1/cur/1365777830.R28.localhost.localdomain:2,S"; |
229 | QFile file(targetPath); | 218 | QFile file(targetPath); |
@@ -231,6 +220,7 @@ private Q_SLOTS: | |||
231 | 220 | ||
232 | //Ensure all local data is processed | 221 | //Ensure all local data is processed |
233 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 222 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); |
223 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
234 | 224 | ||
235 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 225 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
236 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 226 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -241,11 +231,9 @@ private Q_SLOTS: | |||
241 | { | 231 | { |
242 | Akonadi2::Query query; | 232 | Akonadi2::Query query; |
243 | query.resources << "org.kde.maildir.instance1"; | 233 | query.resources << "org.kde.maildir.instance1"; |
244 | query.syncOnDemand = false; | ||
245 | query.processAll = true; | ||
246 | 234 | ||
247 | //Ensure all local data is processed | 235 | //Ensure all local data is processed |
248 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 236 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
249 | 237 | ||
250 | Akonadi2::ApplicationDomain::Folder folder("org.kde.maildir.instance1"); | 238 | Akonadi2::ApplicationDomain::Folder folder("org.kde.maildir.instance1"); |
251 | folder.setProperty("name", "testCreateFolder"); | 239 | folder.setProperty("name", "testCreateFolder"); |
@@ -253,7 +241,7 @@ private Q_SLOTS: | |||
253 | Akonadi2::Store::create(folder).exec().waitForFinished(); | 241 | Akonadi2::Store::create(folder).exec().waitForFinished(); |
254 | 242 | ||
255 | //Ensure all local data is processed | 243 | //Ensure all local data is processed |
256 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 244 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
257 | 245 | ||
258 | auto targetPath = tempDir.path() + "/maildir1/testCreateFolder"; | 246 | auto targetPath = tempDir.path() + "/maildir1/testCreateFolder"; |
259 | QFileInfo file(targetPath); | 247 | QFileInfo file(targetPath); |
@@ -265,15 +253,13 @@ private Q_SLOTS: | |||
265 | { | 253 | { |
266 | Akonadi2::Query query; | 254 | Akonadi2::Query query; |
267 | query.resources << "org.kde.maildir.instance1"; | 255 | query.resources << "org.kde.maildir.instance1"; |
268 | query.syncOnDemand = false; | ||
269 | query.processAll = true; | ||
270 | 256 | ||
271 | auto targetPath = tempDir.path() + "/maildir1/testCreateFolder"; | 257 | auto targetPath = tempDir.path() + "/maildir1/testCreateFolder"; |
272 | 258 | ||
273 | Akonadi2::ApplicationDomain::Folder folder("org.kde.maildir.instance1"); | 259 | Akonadi2::ApplicationDomain::Folder folder("org.kde.maildir.instance1"); |
274 | folder.setProperty("name", "testCreateFolder"); | 260 | folder.setProperty("name", "testCreateFolder"); |
275 | Akonadi2::Store::create(folder).exec().waitForFinished(); | 261 | Akonadi2::Store::create(folder).exec().waitForFinished(); |
276 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 262 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
277 | QTRY_VERIFY(QFileInfo(targetPath).exists()); | 263 | QTRY_VERIFY(QFileInfo(targetPath).exists()); |
278 | 264 | ||
279 | Akonadi2::Query folderQuery; | 265 | Akonadi2::Query folderQuery; |
@@ -285,7 +271,7 @@ private Q_SLOTS: | |||
285 | auto createdFolder = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder::Ptr>(); | 271 | auto createdFolder = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder::Ptr>(); |
286 | 272 | ||
287 | Akonadi2::Store::remove(*createdFolder).exec().waitForFinished(); | 273 | Akonadi2::Store::remove(*createdFolder).exec().waitForFinished(); |
288 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 274 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
289 | QTRY_VERIFY(!QFileInfo(targetPath).exists()); | 275 | QTRY_VERIFY(!QFileInfo(targetPath).exists()); |
290 | } | 276 | } |
291 | 277 | ||
@@ -293,11 +279,9 @@ private Q_SLOTS: | |||
293 | { | 279 | { |
294 | Akonadi2::Query query; | 280 | Akonadi2::Query query; |
295 | query.resources << "org.kde.maildir.instance1"; | 281 | query.resources << "org.kde.maildir.instance1"; |
296 | query.syncOnDemand = false; | ||
297 | query.processAll = true; | ||
298 | 282 | ||
299 | //Ensure all local data is processed | 283 | //Ensure all local data is processed |
300 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 284 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
301 | 285 | ||
302 | Akonadi2::ApplicationDomain::Mail mail("org.kde.maildir.instance1"); | 286 | Akonadi2::ApplicationDomain::Mail mail("org.kde.maildir.instance1"); |
303 | mail.setProperty("name", "testCreateMail"); | 287 | mail.setProperty("name", "testCreateMail"); |
@@ -305,7 +289,7 @@ private Q_SLOTS: | |||
305 | Akonadi2::Store::create(mail).exec().waitForFinished(); | 289 | Akonadi2::Store::create(mail).exec().waitForFinished(); |
306 | 290 | ||
307 | //Ensure all local data is processed | 291 | //Ensure all local data is processed |
308 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 292 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
309 | 293 | ||
310 | auto targetPath = tempDir.path() + "/maildir1/new"; | 294 | auto targetPath = tempDir.path() + "/maildir1/new"; |
311 | QDir dir(targetPath); | 295 | QDir dir(targetPath); |
@@ -315,32 +299,72 @@ private Q_SLOTS: | |||
315 | 299 | ||
316 | void testRemoveMail() | 300 | void testRemoveMail() |
317 | { | 301 | { |
318 | Akonadi2::Query query; | 302 | using namespace Akonadi2; |
319 | query.resources << "org.kde.maildir.instance1"; | 303 | using namespace Akonadi2::ApplicationDomain; |
320 | query.syncOnDemand = true; | 304 | |
321 | query.processAll = true; | 305 | auto query = Query::ResourceFilter("org.kde.maildir.instance1"); |
322 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 306 | Store::synchronize(query).exec().waitForFinished(); |
323 | 307 | Store::flushMessageQueue(query.resources).exec().waitForFinished(); | |
324 | Akonadi2::Query folderQuery; | 308 | |
325 | folderQuery.resources << "org.kde.maildir.instance1"; | 309 | auto result = Store::fetchOne<Folder>( |
326 | folderQuery.propertyFilter.insert("name", "maildir1"); | 310 | Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name") |
327 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(folderQuery); | 311 | ) |
328 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 312 | .then<void, KAsync::Job<void>, Folder>([query](const Folder &folder) { |
329 | QCOMPARE(model->rowCount(QModelIndex()), 1); | 313 | return Store::fetchAll<Mail>( |
330 | auto folder = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder::Ptr>(); | 314 | Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject") |
331 | 315 | ) | |
332 | Akonadi2::Query mailQuery; | 316 | .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([query](const QList<Mail::Ptr> &mails) { |
333 | mailQuery.resources << "org.kde.maildir.instance1"; | 317 | //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE |
334 | mailQuery.propertyFilter.insert("folder", folder->identifier()); | 318 | if (mails.size() != 1) { |
335 | auto mailModel = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(mailQuery); | 319 | return KAsync::error<void>(1, "Wrong number of mails."); |
336 | QTRY_VERIFY(mailModel->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 320 | } |
337 | QCOMPARE(mailModel->rowCount(QModelIndex()), 1); | 321 | auto mail = mails.first(); |
338 | auto mail = mailModel->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Mail::Ptr>(); | 322 | |
339 | 323 | return Store::remove(*mail) | |
340 | Akonadi2::Store::remove(*mail).exec().waitForFinished(); | 324 | .then(Store::flushReplayQueue(query.resources)) //The change needs to be replayed already |
341 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 325 | .then(Resources::inspect<Mail>(Resources::Inspection::ExistenceInspection(*mail, false))); |
326 | }) | ||
327 | .then<void>([](){}); | ||
328 | }) | ||
329 | .exec(); | ||
330 | result.waitForFinished(); | ||
331 | QVERIFY(!result.errorCode()); | ||
332 | } | ||
342 | 333 | ||
343 | QTRY_COMPARE(QDir(tempDir.path() + "/maildir1/cur", QString(), QDir::NoSort, QDir::Files).count(), static_cast<unsigned int>(0)); | 334 | void testMarkMailAsRead() |
335 | { | ||
336 | using namespace Akonadi2; | ||
337 | using namespace Akonadi2::ApplicationDomain; | ||
338 | |||
339 | auto query = Query::ResourceFilter("org.kde.maildir.instance1"); | ||
340 | Store::synchronize(query).exec().waitForFinished(); | ||
341 | Store::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
342 | |||
343 | auto result = Store::fetchOne<Folder>( | ||
344 | Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name") | ||
345 | ) | ||
346 | .then<void, KAsync::Job<void>, Folder>([query](const Folder &folder) { | ||
347 | Trace() << "Found a folder" << folder.identifier(); | ||
348 | return Store::fetchAll<Mail>( | ||
349 | Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject") | ||
350 | ) | ||
351 | .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([query](const QList<Mail::Ptr> &mails) { | ||
352 | //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE | ||
353 | if (mails.size() != 1) { | ||
354 | return KAsync::error<void>(1, "Wrong number of mails."); | ||
355 | } | ||
356 | auto mail = mails.first(); | ||
357 | mail->setProperty("unread", true); | ||
358 | auto inspectionCommand = Resources::Inspection::PropertyInspection(*mail, "unread", true); | ||
359 | return Store::modify(*mail) | ||
360 | .then<void>(Store::flushReplayQueue(query.resources)) //The change needs to be replayed already | ||
361 | .then(Resources::inspect<Mail>(inspectionCommand)); | ||
362 | }) | ||
363 | .then<void>([](){}); | ||
364 | }) | ||
365 | .exec(); | ||
366 | result.waitForFinished(); | ||
367 | QVERIFY(!result.errorCode()); | ||
344 | } | 368 | } |
345 | 369 | ||
346 | }; | 370 | }; |
diff --git a/tests/modelinteractivitytest.cpp b/tests/modelinteractivitytest.cpp index 52db932..59c2c6f 100644 --- a/tests/modelinteractivitytest.cpp +++ b/tests/modelinteractivitytest.cpp | |||
@@ -73,11 +73,9 @@ private Q_SLOTS: | |||
73 | 73 | ||
74 | Akonadi2::Query query; | 74 | Akonadi2::Query query; |
75 | query.resources << "org.kde.dummy.instance1"; | 75 | query.resources << "org.kde.dummy.instance1"; |
76 | query.syncOnDemand = false; | ||
77 | query.processAll = true; | ||
78 | query.liveQuery = true; | 76 | query.liveQuery = true; |
79 | 77 | ||
80 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 78 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
81 | 79 | ||
82 | //Test | 80 | //Test |
83 | QTime time; | 81 | QTime time; |
diff --git a/tests/querytest.cpp b/tests/querytest.cpp index e09f7a4..f9344cd 100644 --- a/tests/querytest.cpp +++ b/tests/querytest.cpp | |||
@@ -48,8 +48,6 @@ private Q_SLOTS: | |||
48 | //Test | 48 | //Test |
49 | Akonadi2::Query query; | 49 | Akonadi2::Query query; |
50 | query.resources << "foobar"; | 50 | query.resources << "foobar"; |
51 | query.syncOnDemand = false; | ||
52 | query.processAll = false; | ||
53 | query.liveQuery = true; | 51 | query.liveQuery = true; |
54 | 52 | ||
55 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data | 53 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data |
@@ -70,8 +68,6 @@ private Q_SLOTS: | |||
70 | //Test | 68 | //Test |
71 | Akonadi2::Query query; | 69 | Akonadi2::Query query; |
72 | query.resources << "org.kde.dummy.instance1"; | 70 | query.resources << "org.kde.dummy.instance1"; |
73 | query.syncOnDemand = false; | ||
74 | query.processAll = false; | ||
75 | query.liveQuery = true; | 71 | query.liveQuery = true; |
76 | 72 | ||
77 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data | 73 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data |
@@ -91,12 +87,10 @@ private Q_SLOTS: | |||
91 | //Test | 87 | //Test |
92 | Akonadi2::Query query; | 88 | Akonadi2::Query query; |
93 | query.resources << "org.kde.dummy.instance1"; | 89 | query.resources << "org.kde.dummy.instance1"; |
94 | query.syncOnDemand = false; | ||
95 | query.processAll = true; | ||
96 | query.liveQuery = false; | 90 | query.liveQuery = false; |
97 | 91 | ||
98 | //Ensure all local data is processed | 92 | //Ensure all local data is processed |
99 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 93 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
100 | 94 | ||
101 | //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data | 95 | //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data |
102 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 96 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
@@ -150,8 +144,6 @@ private Q_SLOTS: | |||
150 | //Test | 144 | //Test |
151 | Akonadi2::Query query; | 145 | Akonadi2::Query query; |
152 | query.resources << "org.kde.dummy.instance1"; | 146 | query.resources << "org.kde.dummy.instance1"; |
153 | query.syncOnDemand = false; | ||
154 | query.processAll = false; | ||
155 | query.liveQuery = true; | 147 | query.liveQuery = true; |
156 | 148 | ||
157 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data | 149 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data |
@@ -171,11 +163,9 @@ private Q_SLOTS: | |||
171 | 163 | ||
172 | Akonadi2::Query query; | 164 | Akonadi2::Query query; |
173 | query.resources << "org.kde.dummy.instance1"; | 165 | query.resources << "org.kde.dummy.instance1"; |
174 | query.syncOnDemand = false; | ||
175 | query.processAll = true; | ||
176 | 166 | ||
177 | //Ensure all local data is processed | 167 | //Ensure all local data is processed |
178 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 168 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
179 | 169 | ||
180 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 170 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); |
181 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 171 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -192,12 +182,10 @@ private Q_SLOTS: | |||
192 | //Test | 182 | //Test |
193 | Akonadi2::Query query; | 183 | Akonadi2::Query query; |
194 | query.resources << "org.kde.dummy.instance1"; | 184 | query.resources << "org.kde.dummy.instance1"; |
195 | query.syncOnDemand = false; | ||
196 | query.processAll = true; | ||
197 | query.parentProperty = "parent"; | 185 | query.parentProperty = "parent"; |
198 | 186 | ||
199 | //Ensure all local data is processed | 187 | //Ensure all local data is processed |
200 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 188 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
201 | 189 | ||
202 | //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data | 190 | //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data |
203 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 191 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); |
@@ -222,13 +210,11 @@ private Q_SLOTS: | |||
222 | //Test | 210 | //Test |
223 | Akonadi2::Query query; | 211 | Akonadi2::Query query; |
224 | query.resources << "org.kde.dummy.instance1"; | 212 | query.resources << "org.kde.dummy.instance1"; |
225 | query.syncOnDemand = false; | ||
226 | query.processAll = true; | ||
227 | query.liveQuery = false; | 213 | query.liveQuery = false; |
228 | query.propertyFilter.insert("uid", "test1"); | 214 | query.propertyFilter.insert("uid", "test1"); |
229 | 215 | ||
230 | //Ensure all local data is processed | 216 | //Ensure all local data is processed |
231 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 217 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
232 | 218 | ||
233 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data | 219 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data |
234 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 220 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
@@ -246,11 +232,9 @@ private Q_SLOTS: | |||
246 | 232 | ||
247 | Akonadi2::Query query; | 233 | Akonadi2::Query query; |
248 | query.resources << "org.kde.dummy.instance1"; | 234 | query.resources << "org.kde.dummy.instance1"; |
249 | query.syncOnDemand = false; | ||
250 | query.processAll = true; | ||
251 | 235 | ||
252 | //Ensure all local data is processed | 236 | //Ensure all local data is processed |
253 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 237 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
254 | 238 | ||
255 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); | 239 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query); |
256 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); | 240 | QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()); |
@@ -268,13 +252,10 @@ private Q_SLOTS: | |||
268 | //Test | 252 | //Test |
269 | Akonadi2::Query query; | 253 | Akonadi2::Query query; |
270 | query.resources << "org.kde.dummy.instance1"; | 254 | query.resources << "org.kde.dummy.instance1"; |
271 | query.syncOnDemand = false; | ||
272 | query.processAll = true; | ||
273 | query.liveQuery = false; | ||
274 | query.propertyFilter.insert("folder", folderEntity->identifier()); | 255 | query.propertyFilter.insert("folder", folderEntity->identifier()); |
275 | 256 | ||
276 | //Ensure all local data is processed | 257 | //Ensure all local data is processed |
277 | Akonadi2::Store::synchronize(query).exec().waitForFinished(); | 258 | Akonadi2::Store::flushMessageQueue(query.resources).exec().waitForFinished(); |
278 | 259 | ||
279 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data | 260 | //We fetch before the data is available and rely on the live query mechanism to deliver the actual data |
280 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); | 261 | auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query); |
diff --git a/tests/resourcecommunicationtest.cpp b/tests/resourcecommunicationtest.cpp index 18e9223..1b09c5f 100644 --- a/tests/resourcecommunicationtest.cpp +++ b/tests/resourcecommunicationtest.cpp | |||
@@ -32,8 +32,8 @@ private Q_SLOTS: | |||
32 | 32 | ||
33 | flatbuffers::FlatBufferBuilder fbb; | 33 | flatbuffers::FlatBufferBuilder fbb; |
34 | auto name = fbb.CreateString("test"); | 34 | auto name = fbb.CreateString("test"); |
35 | auto command = Akonadi2::CreateHandshake(fbb, name); | 35 | auto command = Akonadi2::Commands::CreateHandshake(fbb, name); |
36 | Akonadi2::FinishHandshakeBuffer(fbb, command); | 36 | Akonadi2::Commands::FinishHandshakeBuffer(fbb, command); |
37 | auto result = resourceAccess.sendCommand(Akonadi2::Commands::HandshakeCommand, fbb).exec(); | 37 | auto result = resourceAccess.sendCommand(Akonadi2::Commands::HandshakeCommand, fbb).exec(); |
38 | result.waitForFinished(); | 38 | result.waitForFinished(); |
39 | QVERIFY(!result.errorCode()); | 39 | QVERIFY(!result.errorCode()); |