diff options
-rw-r--r-- | common/resourcefacade.cpp | 242 | ||||
-rw-r--r-- | common/resourcefacade.h | 71 | ||||
-rw-r--r-- | tests/accountstest.cpp | 34 |
3 files changed, 207 insertions, 140 deletions
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp index 2969c44..935c613 100644 --- a/common/resourcefacade.cpp +++ b/common/resourcefacade.cpp | |||
@@ -24,6 +24,136 @@ | |||
24 | #include "storage.h" | 24 | #include "storage.h" |
25 | #include <QDir> | 25 | #include <QDir> |
26 | 26 | ||
27 | template <typename DomainType> | ||
28 | ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier; | ||
29 | |||
30 | template <typename DomainType> | ||
31 | LocalStorageFacade<DomainType>::LocalStorageFacade(const QByteArray &identifier) : Sink::StoreFacade<DomainType>(), mConfigStore(identifier) | ||
32 | { | ||
33 | } | ||
34 | |||
35 | template <typename DomainType> | ||
36 | LocalStorageFacade<DomainType>::~LocalStorageFacade() | ||
37 | { | ||
38 | } | ||
39 | |||
40 | template <typename DomainType> | ||
41 | typename DomainType::Ptr LocalStorageFacade<DomainType>::readFromConfig(const QByteArray &id, const QByteArray &type) | ||
42 | { | ||
43 | auto object = DomainType::Ptr::create(id); | ||
44 | object->setProperty("type", type); | ||
45 | const auto configurationValues = mConfigStore.get(id); | ||
46 | for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) { | ||
47 | object->setProperty(it.key(), it.value()); | ||
48 | } | ||
49 | return object; | ||
50 | } | ||
51 | |||
52 | template <typename DomainType> | ||
53 | KAsync::Job<void> LocalStorageFacade<DomainType>::create(const DomainType &domainObject) | ||
54 | { | ||
55 | return KAsync::start<void>([domainObject, this]() { | ||
56 | const QByteArray type = domainObject.getProperty("type").toByteArray(); | ||
57 | //FIXME use .identifier() instead | ||
58 | const QByteArray providedIdentifier = domainObject.getProperty("identifier").toByteArray(); | ||
59 | const QByteArray identifier = providedIdentifier.isEmpty() ? ResourceConfig::newIdentifier(type) : providedIdentifier; | ||
60 | mConfigStore.add(identifier, type); | ||
61 | auto changedProperties = domainObject.changedProperties(); | ||
62 | changedProperties.removeOne("identifier"); | ||
63 | changedProperties.removeOne("type"); | ||
64 | if (!changedProperties.isEmpty()) { | ||
65 | // We have some configuration values | ||
66 | QMap<QByteArray, QVariant> configurationValues; | ||
67 | for (const auto &property : changedProperties) { | ||
68 | configurationValues.insert(property, domainObject.getProperty(property)); | ||
69 | } | ||
70 | mConfigStore.modify(identifier, configurationValues); | ||
71 | } | ||
72 | sConfigNotifier.add(readFromConfig(identifier, type)); | ||
73 | }); | ||
74 | } | ||
75 | |||
76 | template <typename DomainType> | ||
77 | KAsync::Job<void> LocalStorageFacade<DomainType>::modify(const DomainType &domainObject) | ||
78 | { | ||
79 | return KAsync::start<void>([domainObject, this]() { | ||
80 | const QByteArray identifier = domainObject.identifier(); | ||
81 | if (identifier.isEmpty()) { | ||
82 | Warning() << "We need an \"identifier\" property to identify the entity to configure."; | ||
83 | return; | ||
84 | } | ||
85 | auto changedProperties = domainObject.changedProperties(); | ||
86 | changedProperties.removeOne("identifier"); | ||
87 | changedProperties.removeOne("type"); | ||
88 | if (!changedProperties.isEmpty()) { | ||
89 | // We have some configuration values | ||
90 | QMap<QByteArray, QVariant> configurationValues; | ||
91 | for (const auto &property : changedProperties) { | ||
92 | configurationValues.insert(property, domainObject.getProperty(property)); | ||
93 | } | ||
94 | mConfigStore.modify(identifier, configurationValues); | ||
95 | } | ||
96 | |||
97 | const auto type = mConfigStore.getEntries().value(identifier); | ||
98 | sConfigNotifier.modify(readFromConfig(identifier, type)); | ||
99 | }); | ||
100 | } | ||
101 | |||
102 | template <typename DomainType> | ||
103 | KAsync::Job<void> LocalStorageFacade<DomainType>::remove(const DomainType &domainObject) | ||
104 | { | ||
105 | return KAsync::start<void>([domainObject, this]() { | ||
106 | const QByteArray identifier = domainObject.identifier(); | ||
107 | if (identifier.isEmpty()) { | ||
108 | Warning() << "We need an \"identifier\" property to identify the entity to configure"; | ||
109 | return; | ||
110 | } | ||
111 | mConfigStore.remove(identifier); | ||
112 | sConfigNotifier.remove(QSharedPointer<DomainType>::create(domainObject)); | ||
113 | }); | ||
114 | } | ||
115 | |||
116 | template <typename DomainType> | ||
117 | QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr> LocalStorageFacade<DomainType>::load(const Sink::Query &query) | ||
118 | { | ||
119 | QObject *guard = new QObject; | ||
120 | auto resultProvider = new Sink::ResultProvider<typename DomainType::Ptr>(); | ||
121 | auto emitter = resultProvider->emitter(); | ||
122 | resultProvider->setFetcher([](const QSharedPointer<DomainType> &) {}); | ||
123 | resultProvider->onDone([=]() { delete resultProvider; delete guard; }); | ||
124 | auto job = KAsync::start<void>([=]() { | ||
125 | const auto entries = mConfigStore.getEntries(); | ||
126 | for (const auto &res : entries.keys()) { | ||
127 | const auto type = entries.value(res); | ||
128 | if (!query.ids.isEmpty() && !query.ids.contains(res)) { | ||
129 | continue; | ||
130 | } | ||
131 | resultProvider->add(readFromConfig(res, type)); | ||
132 | } | ||
133 | if (query.liveQuery) { | ||
134 | QObject::connect(&sConfigNotifier, &ConfigNotifier::modified, guard, [resultProvider](const Sink::ApplicationDomain::ApplicationDomainType::Ptr &entry) { | ||
135 | resultProvider->modify(entry.staticCast<DomainType>()); | ||
136 | }); | ||
137 | QObject::connect(&sConfigNotifier, &ConfigNotifier::added, guard, [resultProvider](const Sink::ApplicationDomain::ApplicationDomainType::Ptr &entry) { | ||
138 | resultProvider->add(entry.staticCast<DomainType>()); | ||
139 | }); | ||
140 | QObject::connect(&sConfigNotifier, &ConfigNotifier::removed, guard,[resultProvider](const Sink::ApplicationDomain::ApplicationDomainType::Ptr &entry) { | ||
141 | resultProvider->remove(entry.staticCast<DomainType>()); | ||
142 | }); | ||
143 | } | ||
144 | // TODO initialResultSetComplete should be implicit | ||
145 | resultProvider->initialResultSetComplete(typename DomainType::Ptr()); | ||
146 | resultProvider->complete(); | ||
147 | }); | ||
148 | return qMakePair(job, emitter); | ||
149 | } | ||
150 | |||
151 | |||
152 | |||
153 | |||
154 | |||
155 | |||
156 | |||
27 | ResourceFacade::ResourceFacade(const QByteArray &) : Sink::StoreFacade<Sink::ApplicationDomain::SinkResource>() | 157 | ResourceFacade::ResourceFacade(const QByteArray &) : Sink::StoreFacade<Sink::ApplicationDomain::SinkResource>() |
28 | { | 158 | { |
29 | } | 159 | } |
@@ -145,22 +275,7 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<Sink::ApplicationDomain::S | |||
145 | 275 | ||
146 | 276 | ||
147 | 277 | ||
148 | Q_GLOBAL_STATIC(ConfigNotifier, sConfig); | 278 | AccountFacade::AccountFacade(const QByteArray &) : LocalStorageFacade<Sink::ApplicationDomain::SinkAccount>("accounts") |
149 | |||
150 | static Sink::ApplicationDomain::SinkAccount::Ptr readAccountFromConfig(const QByteArray &id, const QByteArray &type) | ||
151 | { | ||
152 | auto account = Sink::ApplicationDomain::SinkAccount::Ptr::create(id); | ||
153 | |||
154 | account->setProperty("type", type); | ||
155 | const auto configurationValues = ConfigStore("accounts").get(id); | ||
156 | for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) { | ||
157 | account->setProperty(it.key(), it.value()); | ||
158 | } | ||
159 | return account; | ||
160 | } | ||
161 | |||
162 | |||
163 | AccountFacade::AccountFacade(const QByteArray &) : Sink::StoreFacade<Sink::ApplicationDomain::SinkAccount>(), mConfigStore("accounts") | ||
164 | { | 279 | { |
165 | } | 280 | } |
166 | 281 | ||
@@ -168,98 +283,3 @@ AccountFacade::~AccountFacade() | |||
168 | { | 283 | { |
169 | } | 284 | } |
170 | 285 | ||
171 | KAsync::Job<void> AccountFacade::create(const Sink::ApplicationDomain::SinkAccount &account) | ||
172 | { | ||
173 | return KAsync::start<void>([account, this]() { | ||
174 | const QByteArray type = account.getProperty("type").toByteArray(); | ||
175 | const QByteArray providedIdentifier = account.getProperty("identifier").toByteArray(); | ||
176 | // It is currently a requirement that the account starts with the type | ||
177 | const QByteArray identifier = providedIdentifier.isEmpty() ? ResourceConfig::newIdentifier(type) : providedIdentifier; | ||
178 | mConfigStore.add(identifier, type); | ||
179 | auto changedProperties = account.changedProperties(); | ||
180 | changedProperties.removeOne("identifier"); | ||
181 | changedProperties.removeOne("type"); | ||
182 | if (!changedProperties.isEmpty()) { | ||
183 | // We have some configuration values | ||
184 | QMap<QByteArray, QVariant> configurationValues; | ||
185 | for (const auto &property : changedProperties) { | ||
186 | configurationValues.insert(property, account.getProperty(property)); | ||
187 | } | ||
188 | mConfigStore.modify(identifier, configurationValues); | ||
189 | } | ||
190 | sConfig->add(readAccountFromConfig(identifier, type)); | ||
191 | }); | ||
192 | } | ||
193 | |||
194 | KAsync::Job<void> AccountFacade::modify(const Sink::ApplicationDomain::SinkAccount &account) | ||
195 | { | ||
196 | return KAsync::start<void>([account, this]() { | ||
197 | const QByteArray identifier = account.identifier(); | ||
198 | if (identifier.isEmpty()) { | ||
199 | Warning() << "We need an \"identifier\" property to identify the account to configure."; | ||
200 | return; | ||
201 | } | ||
202 | auto changedProperties = account.changedProperties(); | ||
203 | changedProperties.removeOne("identifier"); | ||
204 | changedProperties.removeOne("type"); | ||
205 | if (!changedProperties.isEmpty()) { | ||
206 | // We have some configuration values | ||
207 | QMap<QByteArray, QVariant> configurationValues; | ||
208 | for (const auto &property : changedProperties) { | ||
209 | configurationValues.insert(property, account.getProperty(property)); | ||
210 | } | ||
211 | mConfigStore.modify(identifier, configurationValues); | ||
212 | } | ||
213 | |||
214 | const auto type = mConfigStore.getEntries().value(identifier); | ||
215 | sConfig->modify(readAccountFromConfig(identifier, type)); | ||
216 | }); | ||
217 | } | ||
218 | |||
219 | KAsync::Job<void> AccountFacade::remove(const Sink::ApplicationDomain::SinkAccount &account) | ||
220 | { | ||
221 | return KAsync::start<void>([account, this]() { | ||
222 | const QByteArray identifier = account.identifier(); | ||
223 | if (identifier.isEmpty()) { | ||
224 | Warning() << "We need an \"identifier\" property to identify the account to configure"; | ||
225 | return; | ||
226 | } | ||
227 | mConfigStore.remove(identifier); | ||
228 | sConfig->remove(Sink::ApplicationDomain::SinkAccount::Ptr::create(account)); | ||
229 | }); | ||
230 | } | ||
231 | |||
232 | QPair<KAsync::Job<void>, typename Sink::ResultEmitter<Sink::ApplicationDomain::SinkAccount::Ptr>::Ptr> AccountFacade::load(const Sink::Query &query) | ||
233 | { | ||
234 | QObject *guard = new QObject; | ||
235 | auto resultProvider = new Sink::ResultProvider<typename Sink::ApplicationDomain::SinkAccount::Ptr>(); | ||
236 | auto emitter = resultProvider->emitter(); | ||
237 | resultProvider->setFetcher([](const QSharedPointer<Sink::ApplicationDomain::SinkAccount> &) {}); | ||
238 | resultProvider->onDone([=]() { delete resultProvider; delete guard; }); | ||
239 | auto job = KAsync::start<void>([=]() { | ||
240 | const auto configuredAccounts = mConfigStore.getEntries(); | ||
241 | for (const auto &res : configuredAccounts.keys()) { | ||
242 | const auto type = configuredAccounts.value(res); | ||
243 | if (!query.ids.isEmpty() && !query.ids.contains(res)) { | ||
244 | continue; | ||
245 | } | ||
246 | |||
247 | resultProvider->add(readAccountFromConfig(res, type)); | ||
248 | } | ||
249 | if (query.liveQuery) { | ||
250 | QObject::connect(sConfig, &ConfigNotifier::modified, guard, [resultProvider](const Sink::ApplicationDomain::SinkAccount::Ptr &account) { | ||
251 | resultProvider->modify(account); | ||
252 | }); | ||
253 | QObject::connect(sConfig, &ConfigNotifier::added, guard, [resultProvider](const Sink::ApplicationDomain::SinkAccount::Ptr &account) { | ||
254 | resultProvider->add(account); | ||
255 | }); | ||
256 | QObject::connect(sConfig, &ConfigNotifier::removed, guard,[resultProvider](const Sink::ApplicationDomain::SinkAccount::Ptr &account) { | ||
257 | resultProvider->remove(account); | ||
258 | }); | ||
259 | } | ||
260 | // TODO initialResultSetComplete should be implicit | ||
261 | resultProvider->initialResultSetComplete(Sink::ApplicationDomain::SinkAccount::Ptr()); | ||
262 | resultProvider->complete(); | ||
263 | }); | ||
264 | return qMakePair(job, emitter); | ||
265 | } | ||
diff --git a/common/resourcefacade.h b/common/resourcefacade.h index 0deb017..e7e39d3 100644 --- a/common/resourcefacade.h +++ b/common/resourcefacade.h | |||
@@ -31,6 +31,47 @@ class Query; | |||
31 | class Inspection; | 31 | class Inspection; |
32 | } | 32 | } |
33 | 33 | ||
34 | class ConfigNotifier : public QObject | ||
35 | { | ||
36 | Q_OBJECT | ||
37 | public: | ||
38 | void add(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account) | ||
39 | { | ||
40 | emit added(account); | ||
41 | } | ||
42 | |||
43 | void remove(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account) | ||
44 | { | ||
45 | emit removed(account); | ||
46 | } | ||
47 | |||
48 | void modify(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account) | ||
49 | { | ||
50 | emit modified(account); | ||
51 | } | ||
52 | signals: | ||
53 | void added(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account); | ||
54 | void removed(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account); | ||
55 | void modified(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account); | ||
56 | }; | ||
57 | |||
58 | template <typename DomainType> | ||
59 | class LocalStorageFacade : public Sink::StoreFacade<DomainType> | ||
60 | { | ||
61 | public: | ||
62 | LocalStorageFacade(const QByteArray &instanceIdentifier); | ||
63 | virtual ~LocalStorageFacade(); | ||
64 | virtual KAsync::Job<void> create(const DomainType &resource) Q_DECL_OVERRIDE; | ||
65 | virtual KAsync::Job<void> modify(const DomainType &resource) Q_DECL_OVERRIDE; | ||
66 | virtual KAsync::Job<void> remove(const DomainType &resource) Q_DECL_OVERRIDE; | ||
67 | virtual QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE; | ||
68 | private: | ||
69 | typename DomainType::Ptr readFromConfig(const QByteArray &id, const QByteArray &type); | ||
70 | |||
71 | ConfigStore mConfigStore; | ||
72 | static ConfigNotifier sConfigNotifier; | ||
73 | }; | ||
74 | |||
34 | class ResourceFacade : public Sink::StoreFacade<Sink::ApplicationDomain::SinkResource> | 75 | class ResourceFacade : public Sink::StoreFacade<Sink::ApplicationDomain::SinkResource> |
35 | { | 76 | { |
36 | public: | 77 | public: |
@@ -42,39 +83,11 @@ public: | |||
42 | QPair<KAsync::Job<void>, typename Sink::ResultEmitter<Sink::ApplicationDomain::SinkResource::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE; | 83 | QPair<KAsync::Job<void>, typename Sink::ResultEmitter<Sink::ApplicationDomain::SinkResource::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE; |
43 | }; | 84 | }; |
44 | 85 | ||
45 | class AccountFacade : public Sink::StoreFacade<Sink::ApplicationDomain::SinkAccount> | 86 | class AccountFacade : public LocalStorageFacade<Sink::ApplicationDomain::SinkAccount> |
46 | { | 87 | { |
47 | public: | 88 | public: |
48 | AccountFacade(const QByteArray &instanceIdentifier); | 89 | AccountFacade(const QByteArray &instanceIdentifier); |
49 | virtual ~AccountFacade(); | 90 | virtual ~AccountFacade(); |
50 | KAsync::Job<void> create(const Sink::ApplicationDomain::SinkAccount &resource) Q_DECL_OVERRIDE; | ||
51 | KAsync::Job<void> modify(const Sink::ApplicationDomain::SinkAccount &resource) Q_DECL_OVERRIDE; | ||
52 | KAsync::Job<void> remove(const Sink::ApplicationDomain::SinkAccount &resource) Q_DECL_OVERRIDE; | ||
53 | QPair<KAsync::Job<void>, typename Sink::ResultEmitter<Sink::ApplicationDomain::SinkAccount::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE; | ||
54 | private: | ||
55 | ConfigStore mConfigStore; | ||
56 | }; | 91 | }; |
57 | 92 | ||
58 | class ConfigNotifier : public QObject | ||
59 | { | ||
60 | Q_OBJECT | ||
61 | public: | ||
62 | void add(const Sink::ApplicationDomain::SinkAccount::Ptr &account) | ||
63 | { | ||
64 | emit added(account); | ||
65 | } | ||
66 | |||
67 | void remove(const Sink::ApplicationDomain::SinkAccount::Ptr &account) | ||
68 | { | ||
69 | emit removed(account); | ||
70 | } | ||
71 | 93 | ||
72 | void modify(const Sink::ApplicationDomain::SinkAccount::Ptr &account) | ||
73 | { | ||
74 | emit modified(account); | ||
75 | } | ||
76 | signals: | ||
77 | void added(const Sink::ApplicationDomain::SinkAccount::Ptr &account); | ||
78 | void removed(const Sink::ApplicationDomain::SinkAccount::Ptr &account); | ||
79 | void modified(const Sink::ApplicationDomain::SinkAccount::Ptr &account); | ||
80 | }; | ||
diff --git a/tests/accountstest.cpp b/tests/accountstest.cpp index dc74d15..8062bc3 100644 --- a/tests/accountstest.cpp +++ b/tests/accountstest.cpp | |||
@@ -1,11 +1,13 @@ | |||
1 | #include <QTest> | 1 | #include <QTest> |
2 | #include <QDebug> | 2 | #include <QDebug> |
3 | #include <QSignalSpy> | 3 | #include <QSignalSpy> |
4 | #include <QAbstractItemModel> | ||
4 | #include <functional> | 5 | #include <functional> |
5 | 6 | ||
6 | #include <test.h> | 7 | #include <test.h> |
7 | #include <store.h> | 8 | #include <store.h> |
8 | #include <log.h> | 9 | #include <log.h> |
10 | #include <configstore.h> | ||
9 | 11 | ||
10 | class AccountsTest : public QObject | 12 | class AccountsTest : public QObject |
11 | { | 13 | { |
@@ -18,6 +20,12 @@ private slots: | |||
18 | Sink::Log::setDebugOutputLevel(Sink::Log::Trace); | 20 | Sink::Log::setDebugOutputLevel(Sink::Log::Trace); |
19 | } | 21 | } |
20 | 22 | ||
23 | void init() | ||
24 | { | ||
25 | ConfigStore("accounts").clear(); | ||
26 | ConfigStore("resources").clear(); | ||
27 | } | ||
28 | |||
21 | void testLoad() | 29 | void testLoad() |
22 | { | 30 | { |
23 | using namespace Sink; | 31 | using namespace Sink; |
@@ -64,6 +72,32 @@ private slots: | |||
64 | }) | 72 | }) |
65 | .exec().waitForFinished(); | 73 | .exec().waitForFinished(); |
66 | } | 74 | } |
75 | |||
76 | void testLiveQuery() | ||
77 | { | ||
78 | using namespace Sink; | ||
79 | using namespace Sink::ApplicationDomain; | ||
80 | |||
81 | auto account = ApplicationDomainType::createEntity<SinkAccount>(); | ||
82 | account.setProperty("type", "maildir"); | ||
83 | account.setProperty("name", "name"); | ||
84 | Store::create(account).exec().waitForFinished(); | ||
85 | |||
86 | Query query; | ||
87 | query.liveQuery = true; | ||
88 | auto model = Store::loadModel<SinkAccount>(query); | ||
89 | QSignalSpy spy(model.data(), &QAbstractItemModel::rowsInserted); | ||
90 | QTRY_COMPARE(spy.count(), 1); | ||
91 | Store::create(account).exec().waitForFinished(); | ||
92 | QTRY_COMPARE(spy.count(), 2); | ||
93 | |||
94 | //Ensure the notifier only affects one type | ||
95 | auto resource = ApplicationDomainType::createEntity<SinkResource>(); | ||
96 | resource.setProperty("type", "org.kde.mailtransport"); | ||
97 | Store::create(resource).exec().waitForFinished(); | ||
98 | QTRY_COMPARE(spy.count(), 2); | ||
99 | } | ||
100 | |||
67 | }; | 101 | }; |
68 | 102 | ||
69 | QTEST_GUILESS_MAIN(AccountsTest) | 103 | QTEST_GUILESS_MAIN(AccountsTest) |