From ca992f4f76d09965e4e62c805daa02b23266a224 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 29 Nov 2018 18:29:34 +0100 Subject: control: begin control interface implementation --- .vscode/settings.json | 50 +++++++++++++++- CMakeLists.txt | 8 +++ cmake/Modules/SystemDependencies.cmake | 6 +- conanfile.py | 1 + src/control_connection.cpp | 86 ++++++++++++++++++++++++++ src/control_connection.hpp | 76 +++++++++++++++++++++++ src/control_interface.cpp | 106 +++++++++++++++++++++++++++++++++ src/control_interface.hpp | 55 +++++++++++++++++ src/keyed.hpp | 20 +++++++ src/main.cpp | 8 +++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 src/control_connection.cpp create mode 100644 src/control_connection.hpp create mode 100644 src/control_interface.cpp create mode 100644 src/control_interface.hpp create mode 100644 src/keyed.hpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 7192bf1..4b30768 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,54 @@ "string": "cpp", "variant": "cpp", "string_view": "cpp", - "ostream": "cpp" + "ostream": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "chrono": "cpp", + "codecvt": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "fstream": "cpp", + "functional": "cpp", + "future": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "__config": "cpp", + "__nullptr": "cpp" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e82b59e..d90fecc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,9 +24,14 @@ add_executable("wanda" "src/main.cpp" # Components + "src/control_connection.cpp" + "src/control_connection.hpp" + "src/control_interface.cpp" + "src/control_interface.hpp" "src/deferred_failure.hpp" "src/filesystem.cpp" "src/filesystem.hpp" + "src/keyed.hpp" "src/optional.hpp" "src/setting.cpp" "src/setting.hpp" @@ -34,7 +39,10 @@ add_executable("wanda" ) target_link_libraries("wanda" + "CONAN_PKG::boost_asio" "CONAN_PKG::boost_config" + "CONAN_PKG::boost_program_options" "SYSTEM::C++FS" "SYSTEM::GIO" + "Threads::Threads" ) diff --git a/cmake/Modules/SystemDependencies.cmake b/cmake/Modules/SystemDependencies.cmake index 8200824..c970bfe 100644 --- a/cmake/Modules/SystemDependencies.cmake +++ b/cmake/Modules/SystemDependencies.cmake @@ -11,4 +11,8 @@ pkg_check_modules("GIO" "gio-2.0" REQUIRED) add_library("SYSTEM::GIO" INTERFACE IMPORTED) set_property(TARGET "SYSTEM::GIO" PROPERTY INTERFACE_LINK_LIBRARIES ${GIO_LIBRARIES}) set_property(TARGET "SYSTEM::GIO" PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${GIO_INCLUDE_DIRS}) -set_property(TARGET "SYSTEM::GIO" PROPERTY INTERFACE_COMPILE_OPTIONS ${GIO_CFLAGS} ${GIO_CFLAGS_OTHER}) \ No newline at end of file +set_property(TARGET "SYSTEM::GIO" PROPERTY INTERFACE_COMPILE_OPTIONS ${GIO_CFLAGS} ${GIO_CFLAGS_OTHER}) + +set(CMAKE_THREAD_PREFER_PTHREAD ON) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package("Threads") \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index a77ff14..26b6e47 100644 --- a/conanfile.py +++ b/conanfile.py @@ -25,6 +25,7 @@ class Wanda(ConanFile): requires = ( "boost_program_options/1.67.0@bincrafters/stable", "boost_iterator/1.67.0@bincrafters/stable", + "boost_asio/1.67.0@bincrafters/stable", ) def build(self): diff --git a/src/control_connection.cpp b/src/control_connection.cpp new file mode 100644 index 0000000..88a136e --- /dev/null +++ b/src/control_connection.cpp @@ -0,0 +1,86 @@ +#include "control_connection.hpp" + +namespace wanda +{ + +control_connection::pointer make_control_connection(control_connection::protocol::socket &&socket) +{ + return std::make_shared(control_connection::key{}, std::move(socket)); +} + +control_connection::control_connection(control_connection::key key, control_connection::protocol::socket socket) + : keyed{key}, + m_socket{std::move(socket)} +{ +} + +bool control_connection::add(std::shared_ptr listener) +{ + auto [_, inserted] = m_listeners.insert(listener); + return inserted; +} + +bool control_connection::remove(std::shared_ptr listener) +{ + return m_listeners.erase(listener); +} + +void control_connection::start() +{ + if (!m_running) + { + m_running = true; + perform_read(); + } +} + +void control_connection::close() +{ + if (auto error = boost::system::error_code{}; m_socket.cancel(error)) + { + for (auto &listener : m_listeners) + { + listener->on_error(shared_from_this(), error); + } + } + + if (auto error = boost::system::error_code{}; m_socket.close(error)) + { + for (auto &listener : m_listeners) + { + listener->on_error(shared_from_this(), error); + } + } + + for (auto &listener : m_listeners) + { + listener->on_close(shared_from_this()); + } + m_listeners.clear(); +} + +void control_connection::perform_read() +{ + boost::asio::async_read_until(m_socket, m_in, '\n', [that = shared_from_this(), this](auto const &error, auto const length) { + if (error) + { + for (auto &listener : m_listeners) + { + listener->on_error(shared_from_this(), error); + } + close(); + } + else + { + std::string message{}; + std::getline(m_input, message); + for (auto &listener : m_listeners) + { + listener->on_received(shared_from_this(), message); + } + perform_read(); + } + }); +} + +} // namespace wanda \ No newline at end of file diff --git a/src/control_connection.hpp b/src/control_connection.hpp new file mode 100644 index 0000000..e868f99 --- /dev/null +++ b/src/control_connection.hpp @@ -0,0 +1,76 @@ +#ifndef WANDA_CONTROL_CONNECTION_HPP +#define WANDA_CONTROL_CONNECTION_HPP + +#include "keyed.hpp" + +#include + +#include +#include +#include + +namespace wanda +{ + +struct control_connection : keyed, std::enable_shared_from_this +{ + using protocol = boost::asio::local::stream_protocol; + using pointer = std::shared_ptr; + + struct listener + { + virtual void on_close(pointer connection) {} + virtual void on_received(pointer connection, std::string message) {} + virtual void on_error(pointer connection, boost::system::error_code) {} + }; + + /** + * @internal + * @brief Construct a new control connection object + * + * @param socket The socket connected to the remote control endpoint + */ + control_connection(key, protocol::socket socket); + + /** + * @brief Add the given listener to this control connection's listener set + * + * @returns true iff. the listener was not already in the listener set + */ + bool add(std::shared_ptr listener); + + /** + * @brief Remove the given listener from this control connection's listener set + * + * @return true iff. the listener was previously registered with this control connection + */ + bool remove(std::shared_ptr listener); + + /** + * @brief Start I/O processing for this control connection + */ + void start(); + + /** + * @brief Close this control connection + */ + void close(); + + private: + friend pointer make_control_connection(protocol::socket &&socket); + + void perform_read(); + + protocol::socket m_socket; + boost::asio::streambuf m_in{}; + boost::asio::streambuf m_out{}; + std::istream m_input{&m_in}; + std::set> m_listeners{}; + bool m_running{}; +}; + +control_connection::pointer make_control_connection(control_connection::protocol::socket &&socket); + +} // namespace wanda + +#endif \ No newline at end of file diff --git a/src/control_interface.cpp b/src/control_interface.cpp new file mode 100644 index 0000000..b662da5 --- /dev/null +++ b/src/control_interface.cpp @@ -0,0 +1,106 @@ +#include "control_interface.hpp" + +#include + +#include +#include +#include +#include + +#include + +namespace wanda +{ + +// 'socket_deleter' implementation + +socket_deleter::~socket_deleter() +{ + if (std::filesystem::exists(path)) + { + std::filesystem::remove(path); + } +} + +// 'control_interface' implementation + +control_interface::control_interface(control_interface::key key, boost::asio::io_service &service, control_interface::protocol::endpoint endpoint) + : keyed{key}, + m_service{service}, + m_endpoint{std::move(endpoint)}, + m_socket{m_service}, + m_acceptor{m_service} +{ +} + +boost::system::error_code control_interface::start() +{ + if (auto error = boost::system::error_code{}; m_acceptor.open(m_endpoint.protocol(), error)) + { + return error; + } + + if (auto error = boost::system::error_code{}; m_acceptor.set_option(boost::asio::socket_base::reuse_address(true), error)) + { + return error; + } + + if (auto error = boost::system::error_code{}; m_acceptor.bind(m_endpoint, error)) + { + return error; + } + + if (auto error = boost::system::error_code{}; m_acceptor.listen(128, error)) + { + return error; + } + else + { + perform_accept(); + return error; + } +} + +void control_interface::perform_accept() +{ + m_acceptor.async_accept(m_socket, [that = shared_from_this(), this](auto const &error) { + if (error) + { + // TODO: Handle error + } + else + { + auto [connection, inserted] = m_connections.insert(make_control_connection(std::move(m_socket))); + if(inserted) + { + std::cout << "Accepted a new connection\n"; + (*connection)->add(shared_from_this()); + (*connection)->start(); + } + perform_accept(); + } + }); +} + +void control_interface::on_received(control_connection::pointer, std::string message) +{ + std::cout << "Received '" << message << "'\n"; +} + +void control_interface::on_close(control_connection::pointer connection) +{ + std::cout << "Connection closed\n"; + m_connections.erase(connection); +} + +control_interface::pointer make_interface(boost::asio::io_service &service, std::filesystem::path file) +{ + if (std::filesystem::exists(file)) + { + std::filesystem::remove(file); + } + control_interface::protocol::endpoint endpoint{file.string()}; + return std::make_shared(control_interface::key{}, service, std::move(endpoint)); +} + +} // namespace wanda \ No newline at end of file diff --git a/src/control_interface.hpp b/src/control_interface.hpp new file mode 100644 index 0000000..f95362c --- /dev/null +++ b/src/control_interface.hpp @@ -0,0 +1,55 @@ +#ifndef WANDA_CONTROL_INTERFACE_HPP +#define WANDA_CONTROL_INTERFACE_HPP + +#include "control_connection.hpp" +#include "keyed.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace wanda +{ + +struct socket_deleter +{ + ~socket_deleter(); + + std::filesystem::path path; +}; + +struct control_interface : control_connection::listener, keyed, std::enable_shared_from_this +{ + using protocol = boost::asio::local::stream_protocol; + using pointer = std::shared_ptr; + + control_interface(key, boost::asio::io_service &service, protocol::endpoint endpoint); + + boost::system::error_code start(); + + void on_received(control_connection::pointer connection, std::string message) override; + void on_close(control_connection::pointer) override; + + private: + void perform_accept(); + + friend pointer make_interface(boost::asio::io_service &service, std::filesystem::path file); + + boost::asio::io_service &m_service; + protocol::endpoint m_endpoint; + protocol::socket m_socket; + protocol::acceptor m_acceptor; + socket_deleter m_deleter{m_endpoint.path()}; + std::set m_connections; +}; + +control_interface::pointer make_interface(boost::asio::io_service &service, std::filesystem::path file); + +} // namespace wanda + +#endif \ No newline at end of file diff --git a/src/keyed.hpp b/src/keyed.hpp new file mode 100644 index 0000000..de3500e --- /dev/null +++ b/src/keyed.hpp @@ -0,0 +1,20 @@ +#ifndef WANDA_KEYED_HPP +#define WANDA_KEYED_HPP + +namespace wanda +{ + +template +struct keyed +{ + protected: + struct key + { + }; + + explicit keyed(key) {} +}; + +} // namespace wanda + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6d1353d..1d6f2bd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "filesystem.hpp" #include "setting.hpp" #include "optional.hpp" +#include "control_interface.hpp" #include #include @@ -46,5 +47,12 @@ int main() auto wallpaper = wanda::random_pick(list); std::cout << "changing wallpaper to " << wallpaper << '\n'; set_wallpaper(wallpaper); + + auto service = boost::asio::io_service{}; + auto interface = wanda::make_interface(service, ".wanda_interface"); + std::cout << interface.use_count() << '\n'; + auto status = interface->start(); + std::cout << status << ' ' << status.message() << '\n'; + service.run(); }) || [] { std::cerr << "Directory does not exist\n"; }; } -- cgit v1.2.3