summaryrefslogtreecommitdiffstats
path: root/framework/actions
diff options
context:
space:
mode:
Diffstat (limited to 'framework/actions')
-rw-r--r--framework/actions/CMakeLists.txt4
-rw-r--r--framework/actions/actionbroker.cpp5
-rw-r--r--framework/actions/actionbroker.h1
-rw-r--r--framework/actions/actionhandler.cpp17
-rw-r--r--framework/actions/actionhandler.h55
-rw-r--r--framework/actions/context.cpp34
-rw-r--r--framework/actions/context.h27
-rw-r--r--framework/actions/tests/CMakeLists.txt6
-rw-r--r--framework/actions/tests/actiontest.cpp102
9 files changed, 238 insertions, 13 deletions
diff --git a/framework/actions/CMakeLists.txt b/framework/actions/CMakeLists.txt
index 9cf0acd1..9fc43b9b 100644
--- a/framework/actions/CMakeLists.txt
+++ b/framework/actions/CMakeLists.txt
@@ -9,8 +9,10 @@ set(SRCS
9 9
10add_library(actionplugin SHARED ${SRCS}) 10add_library(actionplugin SHARED ${SRCS})
11 11
12target_link_libraries(actionplugin KF5::Async) 12target_link_libraries(actionplugin KF5::Async sink)
13qt5_use_modules(actionplugin Core Quick Qml) 13qt5_use_modules(actionplugin Core Quick Qml)
14 14
15install(TARGETS actionplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) 15install(TARGETS actionplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions)
16install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) 16install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions)
17
18add_subdirectory(tests)
diff --git a/framework/actions/actionbroker.cpp b/framework/actions/actionbroker.cpp
index 17145440..f6bfdd8e 100644
--- a/framework/actions/actionbroker.cpp
+++ b/framework/actions/actionbroker.cpp
@@ -94,3 +94,8 @@ void ActionBroker::registerHandler(const QByteArray &actionId, ActionHandler *ha
94{ 94{
95 mHandler.insert(actionId, handler); 95 mHandler.insert(actionId, handler);
96} 96}
97
98void ActionBroker::unregisterHandler(const QByteArray &actionId, ActionHandler *handler)
99{
100 mHandler.remove(actionId, handler);
101}
diff --git a/framework/actions/actionbroker.h b/framework/actions/actionbroker.h
index 84678c16..d893a3e7 100644
--- a/framework/actions/actionbroker.h
+++ b/framework/actions/actionbroker.h
@@ -36,6 +36,7 @@ public:
36 ActionResult executeAction(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler, const QList<QPointer<ActionHandler>> &postHandler); 36 ActionResult executeAction(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler, const QList<QPointer<ActionHandler>> &postHandler);
37 37
38 void registerHandler(const QByteArray &actionId, ActionHandler *handler); 38 void registerHandler(const QByteArray &actionId, ActionHandler *handler);
39 void unregisterHandler(const QByteArray &actionId, ActionHandler *handler);
39 40
40Q_SIGNALS: 41Q_SIGNALS:
41 void readyChanged(); 42 void readyChanged();
diff --git a/framework/actions/actionhandler.cpp b/framework/actions/actionhandler.cpp
index dc9edeca..eb7b3224 100644
--- a/framework/actions/actionhandler.cpp
+++ b/framework/actions/actionhandler.cpp
@@ -31,6 +31,11 @@ ActionHandler::ActionHandler(QObject *parent)
31 31
32} 32}
33 33
34ActionHandler::~ActionHandler()
35{
36 ActionBroker::instance().unregisterHandler(mActionId, this);
37}
38
34bool ActionHandler::isActionReady(Context *context) 39bool ActionHandler::isActionReady(Context *context)
35{ 40{
36 if (context) { 41 if (context) {
@@ -67,6 +72,8 @@ ActionResult ActionHandler::execute(Context *context)
67 72
68void ActionHandler::setActionId(const QByteArray &actionId) 73void ActionHandler::setActionId(const QByteArray &actionId)
69{ 74{
75 //Reassigning the id is not supported
76 Q_ASSERT(mActionId.isEmpty());
70 mActionId = actionId; 77 mActionId = actionId;
71 ActionBroker::instance().registerHandler(actionId, this); 78 ActionBroker::instance().registerHandler(actionId, this);
72} 79}
@@ -76,6 +83,16 @@ QByteArray ActionHandler::actionId() const
76 return mActionId; 83 return mActionId;
77} 84}
78 85
86void ActionHandler::setRequiredProperties(const QSet<QByteArray> &requiredProperties)
87{
88 mRequiredProperties = requiredProperties;
89}
90
91QSet<QByteArray> ActionHandler::requiredProperties() const
92{
93 return mRequiredProperties;
94}
95
79 96
80ActionHandlerHelper::ActionHandlerHelper(const Handler &handler) 97ActionHandlerHelper::ActionHandlerHelper(const Handler &handler)
81 : ActionHandler(nullptr), 98 : ActionHandler(nullptr),
diff --git a/framework/actions/actionhandler.h b/framework/actions/actionhandler.h
index 09ed13c6..5ccf0ac7 100644
--- a/framework/actions/actionhandler.h
+++ b/framework/actions/actionhandler.h
@@ -24,9 +24,9 @@
24#include <Async/Async> 24#include <Async/Async>
25 25
26#include "actionresult.h" 26#include "actionresult.h"
27#include "context.h"
27 28
28namespace Kube { 29namespace Kube {
29class Context;
30 30
31class ActionHandler : public QObject 31class ActionHandler : public QObject
32{ 32{
@@ -35,6 +35,7 @@ class ActionHandler : public QObject
35 35
36public: 36public:
37 ActionHandler(QObject *parent = 0); 37 ActionHandler(QObject *parent = 0);
38 virtual ~ActionHandler();
38 39
39 virtual bool isActionReady(Context *context); 40 virtual bool isActionReady(Context *context);
40 41
@@ -43,25 +44,65 @@ public:
43 void setActionId(const QByteArray &); 44 void setActionId(const QByteArray &);
44 QByteArray actionId() const; 45 QByteArray actionId() const;
45 46
47 void setRequiredProperties(const QSet<QByteArray> &requiredProperties);
48 QSet<QByteArray> requiredProperties() const;
49
46private: 50private:
47 QByteArray mActionId; 51 QByteArray mActionId;
52 QSet<QByteArray> mRequiredProperties;
53};
54
55template <typename ContextType>
56class ActionHandlerBase : public ActionHandler
57{
58public:
59 ActionHandlerBase(const QByteArray &actionId)
60 : ActionHandler{}
61 {
62 setActionId(actionId);
63 }
64
65 bool isActionReady(Context *c) Q_DECL_OVERRIDE
66 {
67 auto wrapper = ContextType{*c};
68 return isActionReady(wrapper);
69 }
70
71 ActionResult execute(Context *c) Q_DECL_OVERRIDE
72 {
73 ActionResult result;
74 auto wrapper = ContextType{*c};
75 execute(wrapper)
76 .template syncThen<void>([=](const KAsync::Error &error) {
77 auto modifyableResult = result;
78 if (error) {
79 qWarning() << "Job failed: " << error.errorCode << error.errorMessage;
80 modifyableResult.setError(1);
81 }
82 modifyableResult.setDone();
83 }).exec();
84 return result;
85 }
86protected:
87
88 virtual bool isActionReady(ContextType &) { return true; }
89 virtual KAsync::Job<void> execute(ContextType &) = 0;
48}; 90};
49 91
50class ActionHandlerHelper : public ActionHandler 92class ActionHandlerHelper : public ActionHandler
51{ 93{
52 Q_OBJECT
53public: 94public:
54 typedef std::function<bool(Context*)> IsReadyFunction; 95 typedef std::function<bool(Context *)> IsReadyFunction;
55 typedef std::function<void(Context*)> Handler; 96 typedef std::function<void(Context *)> Handler;
56 typedef std::function<KAsync::Job<void>(Context*)> JobHandler; 97 typedef std::function<KAsync::Job<void>(Context *)> JobHandler;
57 98
58 ActionHandlerHelper(const Handler &); 99 ActionHandlerHelper(const Handler &);
59 ActionHandlerHelper(const IsReadyFunction &, const Handler &); 100 ActionHandlerHelper(const IsReadyFunction &, const Handler &);
60 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const Handler &); 101 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const Handler &);
61 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const JobHandler &); 102 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const JobHandler &);
62 103
63 bool isActionReady(Context *context) Q_DECL_OVERRIDE; 104 bool isActionReady(Context *) Q_DECL_OVERRIDE;
64 ActionResult execute(Context *context) Q_DECL_OVERRIDE; 105 ActionResult execute(Context *) Q_DECL_OVERRIDE;
65private: 106private:
66 const IsReadyFunction isReadyFunction; 107 const IsReadyFunction isReadyFunction;
67 const Handler handlerFunction; 108 const Handler handlerFunction;
diff --git a/framework/actions/context.cpp b/framework/actions/context.cpp
index 8f370a0b..45b660a9 100644
--- a/framework/actions/context.cpp
+++ b/framework/actions/context.cpp
@@ -29,6 +29,20 @@ Context::Context(QObject *parent)
29 29
30} 30}
31 31
32Context::Context(const Context &other)
33 : QObject()
34{
35 *this = other;
36}
37
38Context &Context::operator=(const Context &other)
39{
40 for (const auto &p : other.availableProperties()) {
41 setProperty(p, other.property(p));
42 }
43 return *this;
44}
45
32void Context::clear() 46void Context::clear()
33{ 47{
34 auto meta = metaObject(); 48 auto meta = metaObject();
@@ -41,6 +55,20 @@ void Context::clear()
41 } 55 }
42} 56}
43 57
58QSet<QByteArray> Context::availableProperties() const
59{
60 QSet<QByteArray> names;
61 auto meta = metaObject();
62 for (auto i = meta->propertyOffset(); i < meta->propertyCount(); i++) {
63 auto property = meta->property(i);
64 names << property.name();
65 }
66 for (const auto &p : dynamicPropertyNames()) {
67 names << p;
68 }
69 return names;
70}
71
44QDebug operator<<(QDebug dbg, const Kube::Context &context) 72QDebug operator<<(QDebug dbg, const Kube::Context &context)
45{ 73{
46 dbg << "Kube::Context {\n"; 74 dbg << "Kube::Context {\n";
@@ -55,3 +83,9 @@ QDebug operator<<(QDebug dbg, const Kube::Context &context)
55 dbg << "\n}"; 83 dbg << "\n}";
56 return dbg; 84 return dbg;
57} 85}
86
87QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &context)
88{
89 dbg << context.context;
90 return dbg;
91}
diff --git a/framework/actions/context.h b/framework/actions/context.h
index 42ae3a93..4207fe12 100644
--- a/framework/actions/context.h
+++ b/framework/actions/context.h
@@ -19,17 +19,20 @@
19#pragma once 19#pragma once
20 20
21#include <QObject> 21#include <QObject>
22
23#define KUBE_CONTEXT_PROPERTY(TYPE, NAME, LOWERCASENAME) \ 22#define KUBE_CONTEXT_PROPERTY(TYPE, NAME, LOWERCASENAME) \
24 public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \ 23 public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \
24 Q_SIGNALS: void LOWERCASENAME##Changed(); \
25 private: TYPE m##NAME;
26
27#define KUBE_CONTEXTWRAPPER_PROPERTY(TYPE, NAME, LOWERCASENAME) \
28 public: \
25 struct NAME { \ 29 struct NAME { \
26 static constexpr const char *name = #LOWERCASENAME; \ 30 static constexpr const char *name = #LOWERCASENAME; \
27 typedef TYPE Type; \ 31 typedef TYPE Type; \
28 }; \ 32 }; \
29 void set##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \ 33 void set##NAME(const TYPE &value) { context.setProperty(NAME::name, QVariant::fromValue(value)); } \
30 TYPE get##NAME() const { return m##NAME; } \ 34 void clear##NAME() { context.setProperty(NAME::name, QVariant{}); } \
31 Q_SIGNALS: void LOWERCASENAME##Changed(); \ 35 TYPE get##NAME() const { return context.property(NAME::name).value<TYPE>(); } \
32 private: TYPE m##NAME;
33 36
34 37
35namespace Kube { 38namespace Kube {
@@ -38,13 +41,27 @@ class Context : public QObject {
38 Q_OBJECT 41 Q_OBJECT
39public: 42public:
40 Context(QObject *parent = 0); 43 Context(QObject *parent = 0);
44 Context(const Context &);
45
41 virtual ~Context(){}; 46 virtual ~Context(){};
47
48 Context &operator=(const Context &);
49
42 virtual void clear(); 50 virtual void clear();
51
52 QSet<QByteArray> availableProperties() const;
53};
54
55class ContextWrapper {
56public:
57 ContextWrapper(Context &c) : context{c} {}
58 Context &context;
43}; 59};
44 60
45} 61}
46 62
47QDebug operator<<(QDebug dbg, const Kube::Context &); 63QDebug operator<<(QDebug dbg, const Kube::Context &);
64QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &);
48 65
49Q_DECLARE_METATYPE(Kube::Context*); 66Q_DECLARE_METATYPE(Kube::Context*);
50 67
diff --git a/framework/actions/tests/CMakeLists.txt b/framework/actions/tests/CMakeLists.txt
new file mode 100644
index 00000000..af872a3b
--- /dev/null
+++ b/framework/actions/tests/CMakeLists.txt
@@ -0,0 +1,6 @@
1include_directories(${CMAKE_CURRENT_BINARY_DIR})
2cmake_policy(SET CMP0063 NEW)
3add_executable(actiontest actiontest.cpp)
4add_test(actiontest sinkactiontest)
5qt5_use_modules(actiontest Core Test)
6target_link_libraries(actiontest actionplugin)
diff --git a/framework/actions/tests/actiontest.cpp b/framework/actions/tests/actiontest.cpp
new file mode 100644
index 00000000..a4ec4432
--- /dev/null
+++ b/framework/actions/tests/actiontest.cpp
@@ -0,0 +1,102 @@
1#include <QTest>
2#include <QDebug>
3#include <QSignalSpy>
4
5#include <actions/action.h>
6#include <actions/context.h>
7#include <actions/actionhandler.h>
8
9#include <sink/log.h>
10
11SINK_DEBUG_AREA("actiontest")
12
13class HandlerContext : public Kube::Context {
14 Q_OBJECT
15 KUBE_CONTEXT_PROPERTY(QString, Property1, property1)
16 KUBE_CONTEXT_PROPERTY(QString, Property2, property2)
17};
18
19class HandlerContextWrapper : public Kube::ContextWrapper {
20 using Kube::ContextWrapper::ContextWrapper;
21 KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property1, property1)
22 KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property2, property2)
23};
24
25
26
27class Handler : public Kube::ActionHandlerBase<HandlerContextWrapper>
28{
29public:
30 Handler() : Kube::ActionHandlerBase<HandlerContextWrapper>{"org.kde.kube.test.action1"}
31 {}
32
33 //TODO default implementation checks that all defined properties are available in the context
34 // bool isReady() override {
35 // auto accountId = context->property("accountId").value<QByteArray>();
36 // return !accountId.isEmpty();
37 // }
38
39 KAsync::Job<void> execute(HandlerContextWrapper &context)
40 {
41 SinkLog() << "Executing action1";
42 SinkLog() << context;
43 executions.append(context.context);
44 return KAsync::null<void>();
45 }
46 mutable QList<Kube::Context> executions;
47};
48
49class Context1 : public Kube::ContextWrapper {
50 using Kube::ContextWrapper::ContextWrapper;
51 KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property1, property1)
52 KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2)
53};
54
55class Context2 : public Kube::ContextWrapper {
56 using Kube::ContextWrapper::ContextWrapper;
57 KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2)
58};
59
60
61class ActionTest : public QObject
62{
63 Q_OBJECT
64private slots:
65
66 void initTestCase()
67 {
68 }
69
70 void testActionExecution()
71 {
72 Handler actionHandler;
73
74 HandlerContext context;
75 //Kube::Context context;
76 HandlerContextWrapper{context}.setProperty1(QString("property1"));
77 context.setProperty("property2", QVariant::fromValue(QString("property2")));
78 auto future = Kube::Action("org.kde.kube.test.action1", context).executeWithResult();
79
80 QTRY_VERIFY(future.isDone());
81 QVERIFY(!future.error());
82
83 QCOMPARE(actionHandler.executions.size(), 1);
84 QCOMPARE(actionHandler.executions.first().availableProperties().size(), 2);
85 }
86
87 void testContextCasting()
88 {
89 Kube::Context c;
90
91 Context1 context1{c};
92 context1.setProperty1("property1");
93 context1.setProperty2("property2");
94
95 auto context2 = Context2{c};
96 QCOMPARE(context2.getProperty2(), QByteArray("property2"));
97 }
98
99};
100
101QTEST_GUILESS_MAIN(ActionTest)
102#include "actiontest.moc"