Add high-level dbus signal class 91/152691/7 submit/tizen/20171019.011823
authorsangwan.kwon <sangwan.kwon@samsung.com>
Mon, 25 Sep 2017 09:10:08 +0000 (05:10 -0400)
committersangwan.kwon <sangwan.kwon@samsung.com>
Tue, 10 Oct 2017 02:49:33 +0000 (22:49 -0400)
Change-Id: Ib7247c440a331607f57bac4ab795e2efe52861cd
Signed-off-by: sangwan.kwon <sangwan.kwon@samsung.com>
include/klay/dbus/connection.h
include/klay/dbus/introspection.h
include/klay/dbus/signal.h [new file with mode: 0644]
src/CMakeLists.txt
src/dbus/introspection.cpp
src/dbus/signal.cpp [new file with mode: 0644]
test/dbus.cpp

index 11cf298ec3907f5bdd73df8f5cb52fec717d4e82..1cbfff35375bb63ba6268880c3bc83f3241005c8 100644 (file)
@@ -27,6 +27,8 @@
 
 namespace dbus {
 
+typedef std::function<void(Variant parameters)> SignalCallback;
+
 class Connection {
 public:
        typedef unsigned int NameId;
@@ -35,7 +37,6 @@ public:
 
        typedef std::function<void()> VoidCallback;
 
-       typedef std::function<void(Variant parameters)> SignalCallback;
 
        typedef std::function<void(const std::string& name)> ClientVanishedCallback;
 
index 5960876b1655db4973e6cba6c7964920747d3d90..711611e276e12da4cc489e720777bb68b4babd9b 100644 (file)
@@ -20,6 +20,8 @@
 #include <gio/gio.h>
 
 #include <string>
+#include <vector>
+#include <utility>
 
 namespace dbus {
 
@@ -29,6 +31,8 @@ using Method = GDBusMethodInfo*;
 using Signal = GDBusSignalInfo*;
 using Property = GDBusPropertyInfo*;
 
+using XmlProperties = std::vector<std::pair<std::string, std::string>>;
+
 class Introspection {
 public:
        explicit Introspection(const std::string &xmlData);
@@ -48,18 +52,27 @@ public:
                                                 const std::string &propertyName) const;
 
        std::string getXmlData(unsigned int indent = 0);
+       static std::string createXmlDataFromFile(const std::string &path);
+       static void writeXmlDataToFile(const std::string &path, const std::string &xmlData);
 
        void addInterface(const std::string &name);
        void addMethod(const std::string &interfaceName, const std::string &methodData);
        void addSignal(const std::string &interfaceName, const std::string &signalData);
        void addProperty(const std::string &interfaceName, const std::string &propertyData);
 
+       void addSignal(const std::string &interfaceName,
+                                  const std::string &signalName,
+                                  const std::string &argumentType);
+
 private:
        BusNode getBusNode(const std::string &xmlData);
        void update(void);
 
-       std::string getInterfaceBeginTag(const std::string &name) const;
-       std::string getInterfaceEndTag(void) const;
+       std::string getXmlBeginTag(const std::string &node,
+                                                          const XmlProperties &properties) const;
+       std::string getXmlEndTag(const std::string &node) const;
+
+       std::string parseXmlProperties(const XmlProperties &properties) const;
 
        void addInternalData(const std::string &interfaceName, const std::string &data);
 
diff --git a/include/klay/dbus/signal.h b/include/klay/dbus/signal.h
new file mode 100644 (file)
index 0000000..b180918
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#ifndef __RUNTIME_DBUS_SIGNAL_H__
+#define __RUNTIME_DBUS_SIGNAL_H__
+
+#include <string>
+#include <utility>
+
+#include <klay/dbus/connection.h>
+
+namespace dbus {
+namespace signal {
+
+class Sender {
+public:
+       explicit Sender(const std::string &objectPath, const std::string &interfaceName);
+       virtual ~Sender(void) = default;
+
+       void setBusName(const std::string &name);
+       void setInterfaceName(const std::string &name);
+
+       void addSignal(const std::string &manifestPath,
+                                  const std::string &signalName,
+                                  const std::string &argumentType) const;
+
+       template<typename... Arguments>
+       void emit(const std::string &signalName,
+                         const std::string &argumentType,
+                         Arguments&&... argumnets) const;
+
+private:
+       std::string busName;
+       std::string objectPath;
+       std::string interfaceName;
+};
+
+class Receiver {
+public:
+       explicit Receiver(const std::string &objectPath, const std::string &interfaceName);
+       virtual ~Receiver(void) = default;
+
+       void setInterfaceName(const std::string &name);
+       void setSenderName(const std::string &name);
+
+       unsigned int subscribe(const std::string &signalName,
+                                                  const SignalCallback& signalCallback) const;
+       void unsubscribe(unsigned int id) const;
+
+private:
+       std::string objectPath;
+       std::string interfaceName;
+       std::string senderName;
+};
+
+template<typename... Arguments>
+void Sender::emit(const std::string &signalName,
+                                 const std::string &argumentType,
+                                 Arguments&&... argumnets) const
+{
+       dbus::Connection &conn = dbus::Connection::getSystem();
+       conn.emitSignal(busName,
+                                       objectPath,
+                                       interfaceName,
+                                       signalName,
+                                       argumentType,
+                                       std::forward<Arguments>(argumnets)...);
+}
+
+} // namespace signal
+} // namespace dbus
+
+#endif //! __RUNTIME_DBUS_SIGNAL_H__
index 6073882b5ff136af042e68e1695d5eadbaabd71b..832c6bafb4ae473d9e3750b95fb7150ead90fd9f 100755 (executable)
@@ -47,6 +47,7 @@ SET (KLAY_SOURCES             ${KLAY_SRC}/error.cpp
                                                ${KLAY_SRC}/dbus/variant.cpp
                                                ${KLAY_SRC}/dbus/connection.cpp
                                                ${KLAY_SRC}/dbus/introspection.cpp
+                                               ${KLAY_SRC}/dbus/signal.cpp
                                                ${KLAY_SRC}/audit/logger.cpp
                                                ${KLAY_SRC}/audit/logger-core.cpp
                                                ${KLAY_SRC}/audit/null-sink.cpp
index 182fe13f52e941e220739432d7c3e7d2cc4797ae..4bc1a9563e133c3fab11a8523dd9d31a9957587a 100644 (file)
 
 #include <klay/dbus/introspection.h>
 #include <klay/dbus/error.h>
+#include <klay/filesystem.h>
 #include <klay/exception.h>
 
+#include <unistd.h>
+#include <fcntl.h>
+
 #include <memory>
 #include <functional>
 
 namespace dbus {
 
+namespace {
+
+const std::string MANIFEST_TEMPLATE = "<node></node>";
+
+const std::string INTERFACE_NODE = "interface";
+const std::string SIGNAL_NODE = "signal";
+const std::string ARGUMENT_NODE = "arg";
+
+}
+
 Introspection::Introspection(const std::string &xmlData) :
        busNode(getBusNode(xmlData)), xmlData(getXmlData())
 {
@@ -99,6 +113,32 @@ std::string Introspection::getXmlData(unsigned int indent)
        return std::string(buf->str);
 }
 
+std::string Introspection::createXmlDataFromFile(const std::string &path)
+{
+       runtime::File manifest(path);
+       if (!manifest.exists()) {
+               manifest.create(644);
+               manifest.lock();
+               manifest.write(MANIFEST_TEMPLATE.c_str(), MANIFEST_TEMPLATE.length());
+               manifest.unlock();
+       }
+
+       manifest.open(O_RDONLY);
+       std::vector<char> buf(manifest.size() + 1);
+       manifest.read(buf.data(), manifest.size());
+       return std::string(buf.data());
+}
+
+void Introspection::writeXmlDataToFile(const std::string &path,
+                                                                          const std::string &xmlData)
+{
+       runtime::File manifest(path);
+       manifest.open(O_WRONLY | O_TRUNC);
+       manifest.lock();
+       manifest.write(xmlData.c_str(), xmlData.length());
+       manifest.unlock();
+}
+
 void Introspection::addInterface(const std::string &name)
 {
        if (getInterface(name) != nullptr)
@@ -108,23 +148,40 @@ void Introspection::addInterface(const std::string &name)
        if (offset == std::string::npos)
                throw runtime::Exception("Failed to find </node>.");
 
-       xmlData.insert(offset, getInterfaceBeginTag(name) + getInterfaceEndTag());
+       XmlProperties properties;
+       properties.emplace_back(std::make_pair("name", name));
+
+       xmlData.insert(offset, getXmlBeginTag(INTERFACE_NODE, properties) +
+                                                  getXmlEndTag(INTERFACE_NODE));
        update();
 }
 
-std::string Introspection::getInterfaceBeginTag(const std::string &name) const
+std::string Introspection::parseXmlProperties(const XmlProperties &properties) const
 {
-       const std::string token = "@NAME@";
-       std::string iTemplate = "<interface name=\"@NAME@\">";
+       std::string parsed = "";
+       for (const auto &property : properties)
+               parsed.append(property.first + "=\"" + property.second + "\" ");
 
-       iTemplate.replace(iTemplate.find(token), token.length(), name);
+       return parsed.substr(0, parsed.length() - 1);
+};
 
-       return iTemplate;
+std::string Introspection::getXmlBeginTag(const std::string &node,
+                                                                                 const XmlProperties &properties) const
+{
+       const std::string nameToken = "@NODE@";
+       const std::string propertyToken = "@PROPERTIES@";
+       std::string tagTemplate = "<@NODE@ @PROPERTIES@>";
+
+       std::string parsed = parseXmlProperties(properties);
+       tagTemplate.replace(tagTemplate.find(nameToken), nameToken.length(), node);
+       tagTemplate.replace(tagTemplate.find(propertyToken), propertyToken.length(), parsed);
+
+       return tagTemplate;
 }
 
-std::string Introspection::getInterfaceEndTag(void) const
+std::string Introspection::getXmlEndTag(const std::string &node) const
 {
-       return "</interface>";
+       return std::string("</" + node + ">");
 }
 
 // TODO: Check more strict.
@@ -149,6 +206,33 @@ void Introspection::addMethod(const std::string &interfaceName,
        addInternalData(interfaceName, methodData);
 }
 
+void Introspection::addSignal(const std::string &interfaceName,
+                                                         const std::string &signalName,
+                                                         const std::string &argumentType)
+{
+       XmlProperties properties;
+       properties.emplace_back(std::make_pair("name", signalName));
+       std::string xmlData = getXmlBeginTag(SIGNAL_NODE, properties);
+
+       int index = 1;
+       for (const auto &type : argumentType) {
+               if (type == '(' || type == ')')
+                       continue;
+
+               properties.clear();
+               properties.emplace_back(std::make_pair("type", std::string(1, type)));
+               properties.emplace_back(std::make_pair("name",
+                                                                                          "argument" + std::to_string(index++)));
+
+               xmlData.append(getXmlBeginTag(ARGUMENT_NODE, properties));
+               xmlData.append(getXmlEndTag(ARGUMENT_NODE));
+       }
+
+       xmlData.append(getXmlEndTag(SIGNAL_NODE));
+
+       addInternalData(interfaceName, xmlData);
+}
+
 void Introspection::addSignal(const std::string &interfaceName,
                                                          const std::string &signalData)
 {
@@ -166,7 +250,10 @@ void Introspection::addInternalData(const std::string &interfaceName,
 {
        checkDataFormat(data);
 
-       std::string iTemplate = getInterfaceBeginTag(interfaceName);
+       XmlProperties properties;
+       properties.emplace_back(std::make_pair("name", interfaceName));
+
+       std::string iTemplate = getXmlBeginTag(INTERFACE_NODE, properties);
        std::size_t offset = xmlData.find(iTemplate);
        if (offset == std::string::npos)
                throw runtime::Exception("Failed to find interface xml node: " + interfaceName);
diff --git a/src/dbus/signal.cpp b/src/dbus/signal.cpp
new file mode 100644 (file)
index 0000000..2498ed8
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include <klay/dbus/signal.h>
+#include <klay/dbus/introspection.h>
+#include <klay/exception.h>
+
+namespace dbus {
+namespace signal {
+
+Sender::Sender(const std::string &objectPath, const std::string &interfaceName) :
+       busName(""), objectPath(objectPath), interfaceName(interfaceName)
+{
+}
+
+void Sender::addSignal(const std::string &manifestPath,
+                                          const std::string &signalName,
+                                          const std::string &argumentType) const
+{
+       std::string xmlData = Introspection::createXmlDataFromFile(manifestPath);
+       Introspection introspect(xmlData);
+       if (introspect.getInterface(interfaceName) == nullptr)
+               introspect.addInterface(interfaceName);
+
+       if (introspect.getSignal(interfaceName, signalName) == nullptr)
+               introspect.addSignal(interfaceName, signalName, argumentType);
+       else
+               return;
+
+       Introspection::writeXmlDataToFile(manifestPath, introspect.getXmlData());
+}
+
+void Sender::setBusName(const std::string &name)
+{
+       busName = name;
+}
+
+void Sender::setInterfaceName(const std::string &name)
+{
+       interfaceName = name;
+}
+
+Receiver::Receiver(const std::string &objectPath, const std::string &interfaceName) :
+       objectPath(objectPath), interfaceName(interfaceName), senderName("")
+{
+}
+
+void Receiver::setInterfaceName(const std::string &name)
+{
+       interfaceName = name;
+}
+
+void Receiver::setSenderName(const std::string &name)
+{
+       senderName = name;
+}
+
+unsigned int Receiver::subscribe(const std::string &signalName,
+                                                                const SignalCallback& signalCallback) const
+{
+       dbus::Connection &conn = dbus::Connection::getSystem();
+       return conn.subscribeSignal(senderName,
+                                                               objectPath,
+                                                               interfaceName,
+                                                               signalName,
+                                                               signalCallback);
+}
+
+void Receiver::unsubscribe(unsigned int id) const
+{
+       dbus::Connection &conn = dbus::Connection::getSystem();
+       return conn.unsubscribeSignal(id);
+}
+
+} // namespace signal
+} // namespace dbus
index a10f3095d0f71fca46121f5a32ee607d31af503f..f6516d6bd17ccf4538368a8f0f32b753b0bf416c 100644 (file)
@@ -20,6 +20,7 @@
 #include <klay/exception.h>
 #include <klay/audit/logger.h>
 #include <klay/dbus/variant.h>
+#include <klay/dbus/signal.h>
 #include <klay/dbus/connection.h>
 #include <klay/dbus/introspection.h>
 #include <klay/latch.h>
@@ -36,10 +37,11 @@ const std::string TESTSVC_METHOD_THROW   = "Throw";
 const std::string TESTSVC_SIGNAL_NOTIFY  = "Notify";
 const std::string TESTSVC_NOT_EXIST      = "None";
 
-const std::string TESTSVC_INTERFACE_NEW_NAME = "NewInterface";
-const std::string TESTSVC_METHOD_NEW_NAME    = "NewMethod";
-const std::string TESTSVC_SIGNAL_NEW_NAME    = "NewSignal";
-const std::string TESTSVC_PROPERTY_NEW_NAME  = "NewProperty";
+const std::string TESTSVC_RUNTIME_OBJECT_PATH = "/org/tizen/klay/runtime";
+const std::string TESTSVC_INTERFACE_NEW_NAME  = "interface.api";
+const std::string TESTSVC_METHOD_NEW_NAME     = "NewMethod";
+const std::string TESTSVC_SIGNAL_NEW_NAME     = "NewSignal";
+const std::string TESTSVC_PROPERTY_NEW_NAME   = "NewProperty";
 
 const std::string TESTSVC_METHOD_NEW_DATA =
        "<method name='" + TESTSVC_METHOD_NEW_NAME + "'>"
@@ -56,6 +58,8 @@ const std::string TESTSVC_PROPERTY_NEW_DATA =
 const std::string TESTSVC_WRONG_DATA_TYPE1 = "signal'/>";
 const std::string TESTSVC_WRONG_DATA_TYPE2 = "<signal>";
 
+const std::string TESTSVC_MANIFEST_PATH = TEST_DATA_DIR "/manifest";
+
 const std::string manifest =
        "<node>"
        "       <interface name='" + TESTSVC_INTERFACE + "'>"
@@ -325,3 +329,61 @@ TESTCASE(DBusIntrospectionCheckDataFormat)
                TEST_EXPECT(true, true);
        }
 }
+
+TESTCASE(DBusSignalAddToNotExistManifest)
+{
+       try {
+               dbus::signal::Sender sender(TESTSVC_OBJECT_PATH, TESTSVC_INTERFACE_NEW_NAME);
+               sender.addSignal(TESTSVC_MANIFEST_PATH, TESTSVC_SIGNAL_NEW_NAME, "(ss)");
+               TEST_EXPECT(true, true);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusSignalAddToExistManifest)
+{
+       try {
+               dbus::signal::Sender sender(TESTSVC_OBJECT_PATH, TESTSVC_INTERFACE_NEW_NAME);
+               sender.addSignal(TESTSVC_MANIFEST_PATH, TESTSVC_SIGNAL_NEW_NAME, "(ss)");
+               TEST_EXPECT(true, true);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusSignalEmitTest)
+{
+       ScopedGMainLoop mainloop;
+
+       try {
+               std::string manifest = dbus::Introspection::createXmlDataFromFile(TESTSVC_MANIFEST_PATH);
+               dbus::Connection& svc = dbus::Connection::getSystem();
+               svc.registerObject(TESTSVC_RUNTIME_OBJECT_PATH, manifest, nullptr, nullptr);
+
+               std::string arg1 = "arg1";
+               std::string arg2 = "arg2";
+               auto onSignal = [&](dbus::Variant variant)
+                       {
+                               char *ret1 = NULL;
+                               char *ret2 = NULL;
+                               variant.get("(ss)", &ret1, &ret2);
+
+                               TEST_EXPECT(true, arg1.compare(ret1) == 0);
+                               TEST_EXPECT(true, arg2.compare(ret2) == 0);
+                       };
+
+               dbus::signal::Receiver receiver(TESTSVC_RUNTIME_OBJECT_PATH, TESTSVC_INTERFACE_NEW_NAME);
+               receiver.subscribe(TESTSVC_SIGNAL_NEW_NAME, onSignal);
+
+               dbus::signal::Sender sender(TESTSVC_RUNTIME_OBJECT_PATH, TESTSVC_INTERFACE_NEW_NAME);
+               sender.emit(TESTSVC_SIGNAL_NEW_NAME, "(ss)", arg1.c_str(), arg2.c_str());
+
+               TEST_EXPECT(true, true);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}