summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--akonadish/CMakeLists.txt3
-rw-r--r--akonadish/akonadish_utils.cpp28
-rw-r--r--akonadish/main.cpp99
-rw-r--r--akonadish/state.cpp19
-rw-r--r--akonadish/state.h4
-rw-r--r--akonadish/syntax_modules/akonadi_count.cpp2
-rw-r--r--akonadish/syntax_modules/akonadi_list.cpp2
-rw-r--r--akonadish/syntax_modules/akonadi_stat.cpp2
-rw-r--r--akonadish/syntax_modules/akonadi_sync.cpp2
-rw-r--r--akonadish/syntax_modules/core_syntax.cpp26
-rw-r--r--akonadish/utils.cpp42
-rw-r--r--akonadish/utils.h30
-rw-r--r--common/CMakeLists.txt1
-rw-r--r--common/bufferutils.h26
-rw-r--r--common/clientapi.cpp141
-rw-r--r--common/clientapi.h42
-rw-r--r--common/commands.cpp4
-rw-r--r--common/commands.h3
-rw-r--r--common/commands/commandcompletion.fbs2
-rw-r--r--common/commands/fetchentity.fbs2
-rw-r--r--common/commands/handshake.fbs2
-rw-r--r--common/commands/inspection.fbs12
-rw-r--r--common/commands/notification.fbs8
-rw-r--r--common/commands/revisionupdate.fbs2
-rw-r--r--common/commands/synchronize.fbs2
-rw-r--r--common/domain/applicationdomaintype.h27
-rw-r--r--common/facade.cpp5
-rw-r--r--common/genericresource.cpp63
-rw-r--r--common/genericresource.h1
-rw-r--r--common/inspection.h60
-rw-r--r--common/listener.cpp60
-rw-r--r--common/listener.h6
-rw-r--r--common/log.cpp18
-rw-r--r--common/log.h4
-rw-r--r--common/notification.h40
-rw-r--r--common/pipeline.cpp5
-rw-r--r--common/query.h120
-rw-r--r--common/resource.h3
-rw-r--r--common/resourceaccess.cpp79
-rw-r--r--common/resourceaccess.h5
-rw-r--r--common/resourcefacade.h5
-rw-r--r--common/resultprovider.h12
-rw-r--r--docs/resource.md7
-rw-r--r--examples/client/main.cpp3
-rw-r--r--examples/dummyresource/resourcefactory.cpp16
-rw-r--r--examples/dummyresource/resourcefactory.h1
-rw-r--r--examples/maildirresource/libmaildir/maildir.cpp2
-rw-r--r--examples/maildirresource/libmaildir/maildir.h2
-rw-r--r--examples/maildirresource/maildirresource.cpp30
-rw-r--r--examples/maildirresource/maildirresource.h1
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/clientapitest.cpp30
-rw-r--r--tests/dummyresourcebenchmark.cpp6
-rw-r--r--tests/dummyresourcetest.cpp28
-rw-r--r--tests/inspectiontest.cpp65
-rw-r--r--tests/maildirresourcetest.cpp178
-rw-r--r--tests/modelinteractivitytest.cpp4
-rw-r--r--tests/querytest.cpp31
-rw-r--r--tests/resourcecommunicationtest.cpp4
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
23include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 24include_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
25namespace AkonadishUtils 27namespace 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
101QStringList 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
117QStringList resourceCompleter(const QStringList &, const QString &fragment, State &state) 101QStringList 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
122QStringList resourceOrTypeCompleter(const QStringList &commands, const QString &fragment, State &state) 106QStringList 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
132QStringList typeCompleter(const QStringList &commands, const QString &fragment, State &state) 116QStringList 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
137QMap<QString, QString> keyValueMapFromArgs(const QStringList &args) 121QMap<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
40int 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
58bool goInteractive(const QStringList &, State &)
59{
60 enterRepl();
61 return true;
62}
63
64Syntax::List goInteractiveSyntax()
65{
66 Syntax interactive("go_interactive", QString(), &goInteractive);
67 return Syntax::List() << interactive;
68}
69
70void 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
39int main(int argc, char *argv[]) 85int 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
27static bool s_hasEventLoop = false; 29static bool s_hasEventLoop = false;
28 30
29class State::Private 31class State::Private
@@ -112,6 +114,11 @@ void State::setHasEventLoop(bool evented)
112 s_hasEventLoop = evented; 114 s_hasEventLoop = evented;
113} 115}
114 116
117bool State::hasEventLoop()
118{
119 return s_hasEventLoop;
120}
121
115void State::setCommandTiming(bool time) 122void 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
132void State::setLoggingLevel(const QString &level) const
133{
134 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::debugLevelFromName(level.toLatin1()));
135}
136
137QString 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
44private: 48private:
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)
69bool statAllResources(State &state) 69bool 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
28namespace CoreSyntax 29namespace CoreSyntax
29{ 30{
@@ -145,6 +146,24 @@ bool printSyntaxTree(const QStringList &, State &state)
145 return true; 146 return true;
146} 147}
147 148
149bool 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
160bool printLoggingLevel(const QStringList &commands, State &state)
161{
162 const QString level = state.loggingLevel();
163 state.printLine(level);
164 return true;
165}
166
148Syntax::List syntax() 167Syntax::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
22namespace Utils
23{
24
25QStringList 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
24namespace Utils
25{
26
27QStringList 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
6namespace Akonadi2 {
7namespace 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
216KAsync::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
232KAsync::Job<void> Store::flushReplayQueue(const QByteArrayList &resourceIdentifier)
233{
234 return flushMessageQueue(resourceIdentifier);
235}
236
237template <class DomainType>
238KAsync::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
253template <class DomainType>
254KAsync::Job<QList<typename DomainType::Ptr> > Store::fetchAll(const Akonadi2::Query &query)
255{
256 return fetch<DomainType>(query);
257}
258
259template <class DomainType>
260KAsync::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
298template <class DomainType>
299KAsync::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 &notification) {
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
322class Akonadi2::Notifier::Private {
323public:
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
334Notifier::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 &notification) {
338 for (const auto &handler : d->handler) {
339 handler(notification);
340 }
341 });
342 d->resourceAccess << resourceAccess;
343}
344
345void 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
220REGISTER_TYPE(ApplicationDomain::Event); 359REGISTER_TYPE(ApplicationDomain::Event);
221REGISTER_TYPE(ApplicationDomain::Mail); 360REGISTER_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
31class QAbstractItemModel; 32class QAbstractItemModel;
32 33
33namespace Akonadi2 { 34namespace Akonadi2 {
35class ResourceAccess;
36class 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
122namespace Resources {
123 template <class DomainType>
124 KAsync::Job<void> inspect(const Inspection &inspectionCommand);
125}
126
127class Notifier {
128public:
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
134private:
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
35enum CommandIds { 35enum 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 @@
1namespace Akonadi2; 1namespace Akonadi2.Commands;
2 2
3table CommandCompletion { 3table 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 @@
1namespace Akonadi2; 1namespace Akonadi2.Commands;
2 2
3table FetchEntity { 3table 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 @@
1namespace Akonadi2; 1namespace Akonadi2.Commands;
2 2
3table Handshake { 3table 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 @@
1namespace Akonadi2.Commands;
2
3table Inspection {
4 id: string;
5 type: int;
6 entityId: string;
7 domainType: string;
8 property: string;
9 expectedValue: string;
10}
11
12root_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 @@
1namespace Akonadi2; 1namespace Akonadi2.Commands;
2 2
3enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress } 3enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection }
4enum NotificationCode : byte { Success = 0, Failure = 1, UserCode }
4 5
5table Notification { 6table 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
9root_type Notification; 13root_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 @@
1namespace Akonadi2; 1namespace Akonadi2.Commands;
2 2
3table RevisionUpdate { 3table 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 @@
1namespace Akonadi2; 1namespace Akonadi2.Commands;
2 2
3table Synchronize { 3table 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
85struct Event : public ApplicationDomainType { 85struct 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
90struct Todo : public ApplicationDomainType { 90struct Event : public Entity {
91 typedef QSharedPointer<Event> Ptr;
92 using Entity::Entity;
93};
94
95struct 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
95struct Calendar : public ApplicationDomainType { 100struct 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
100struct Mail : public ApplicationDomainType { 105struct 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
105struct Folder : public ApplicationDomainType { 110struct 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
156Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType) 161Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType)
157Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr) 162Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr)
163Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Entity)
164Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Entity::Ptr)
158Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event) 165Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event)
159Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event::Ptr) 166Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event::Ptr)
160Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail) 167Q_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
29using namespace Akonadi2; 30using 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
119template<class DomainType> 120template<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
131template<class DomainType> 132template<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
17static int sBatchSize = 100; 21static int sBatchSize = 100;
18 22
@@ -112,6 +116,7 @@ private:
112class CommandProcessor : public QObject 116class CommandProcessor : public QObject
113{ 117{
114 Q_OBJECT 118 Q_OBJECT
119 typedef std::function<KAsync::Job<void>(void const *, size_t)> InspectionFunction;
115public: 120public:
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
139signals: 149signals:
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
355KAsync::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
304void GenericResource::enableChangeReplay(bool enable) 361void 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
470void GenericResource::modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) 527void 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
487void GenericResource::deleteEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, std::function<void(const QByteArray &)> callback) 544void 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
500void GenericResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Akonadi2::Storage::Transaction &transaction) 557void 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
26namespace Akonadi2 {
27 namespace Resources {
28
29struct 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
40Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) 42Listener::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
206void Listener::processCommand(int commandId, uint messageId, const QByteArray &commandBuffer, Client &client, const std::function<void()> &callback) 208void 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
283qint64 Listener::lowerBoundRevision() 288qint64 Listener::lowerBoundRevision()
@@ -298,8 +303,8 @@ qint64 Listener::lowerBoundRevision()
298void Listener::quit() 303void 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
351void Listener::sendCommandCompleted(QLocalSocket *socket, uint messageId) 356void 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
368void Listener::updateClientsWithRevision(qint64 revision) 373void 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
389void Listener::notify(const Akonadi2::Notification &notification)
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
384Akonadi2::Resource *Listener::loadResource() 408Akonadi2::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 @@
28namespace Akonadi2 28namespace Akonadi2
29{ 29{
30 class Resource; 30 class Resource;
31 class Notification;
31} 32}
32 33
33class QTimer; 34class 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
81private: 83private:
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
99QByteArray debugLevelName(DebugLevel debugLevel) 99QByteArray 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
117DebugLevel debugLevelFromName(const QByteArray &name) 117DebugLevel 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
136Akonadi2::Log::DebugLevel Akonadi2::Log::debugOutputLevel()
137{
138 return debugLevelFromName(qgetenv("AKONADI2DEBUGLEVEL"));
139}
140
135QDebug Akonadi2::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea) 141QDebug 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
15QByteArray debugLevelName(DebugLevel debugLevel);
16DebugLevel debugLevelFromName(const QByteArray &name);
17
15void setDebugOutputLevel(DebugLevel); 18void setDebugOutputLevel(DebugLevel);
19DebugLevel debugOutputLevel();
16 20
17QDebug debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0); 21QDebug 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
25namespace Akonadi2
26{
27
28/**
29 * A notification
30 */
31class AKONADI2COMMON_EXPORT Notification
32{
33public:
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
38namespace Akonadi2 39namespace Akonadi2
39{ 40{
@@ -119,7 +120,7 @@ Storage &Pipeline::storage() const
119 120
120void Pipeline::storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid) 121void 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
26namespace Akonadi2 { 27namespace 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 */
44class Query 32class Query
45{ 33{
46public: 34public:
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
141Q_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
25namespace Akonadi2 27namespace Akonadi2
26{ 28{
@@ -55,6 +57,7 @@ public:
55 57
56Q_SIGNALS: 58Q_SIGNALS:
57 void revisionUpdated(qint64); 59 void revisionUpdated(qint64);
60 void notify(Notification);
58 61
59private: 62private:
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
50static 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
46namespace Akonadi2 60namespace 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
355KAsync::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
341void ResourceAccess::open() 374void 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
529void ResourceAccess::callCallbacks()
530{
531 d->callCallbacks();
532}
533
534void ResourceAccess::log(const QString &message) 583void 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
31namespace Akonadi2 32namespace 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
53Q_SIGNALS: 55Q_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
57public Q_SLOTS: 60public 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
98private: 101private:
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
28namespace Akonadi2 { 28namespace Akonadi2 {
29 class Query; 29 class Query;
30 class Inspection;
30} 31}
31 32
32class ResourceFacade : public Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::AkonadiResource> 33class 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
373private: 380private:
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
146Resources 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
148To 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
150A 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
137KAsync::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
137DummyResourceFactory::DummyResourceFactory(QObject *parent) 153DummyResourceFactory::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;
43private: 44private:
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//
773Maildir::Flags Maildir::readEntryFlags(const QString& key) const 773Maildir::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
296KAsync::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
295MaildirResourceFactory::MaildirResourceFactory(QObject *parent) 325MaildirResourceFactory::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
44public: 44public:
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);
48private: 49private:
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)
55target_link_libraries(dummyresourcetest akonadi2_resource_dummy) 56target_link_libraries(dummyresourcetest akonadi2_resource_dummy)
56target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) 57target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy)
57target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy) 58target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy)
58target_link_libraries(querytest akonadi2_resource_dummy) 59target_link_libraries(querytest akonadi2_resource_dummy)
59target_link_libraries(modelinteractivitytest akonadi2_resource_dummy) 60target_link_libraries(modelinteractivitytest akonadi2_resource_dummy)
61target_link_libraries(inspectiontest akonadi2_resource_dummy)
60 62
61if (BUILD_MAILDIR) 63if (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 */
15class InspectionTest : public QObject
16{
17 Q_OBJECT
18private 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
64QTEST_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());