From d1d0d5b160e4a07c76ef8139910dc120e4f9b29a Mon Sep 17 00:00:00 2001 From: Ralf Ramsauer Date: Thu, 21 Jul 2016 22:48:56 +0200 Subject: [PATCH] Fix QT quirk So far, we used QT interfaces in a wrong way. QT is not thread safe, so use its signal/slot concept and move network communication stuff to a separate QThread. Signed-off-by: Ralf Ramsauer --- doorlockd/CMakeLists.txt | 2 + doorlockd/client/doorlock-client.cpp | 182 ++++----------------------- doorlockd/client/mainwindow.cpp | 11 +- doorlockd/client/mainwindow.h | 6 +- doorlockd/client/network.cpp | 94 ++++++++++++++ doorlockd/client/network.h | 37 ++++++ 6 files changed, 171 insertions(+), 161 deletions(-) create mode 100644 doorlockd/client/network.cpp create mode 100644 doorlockd/client/network.h diff --git a/doorlockd/CMakeLists.txt b/doorlockd/CMakeLists.txt index 5b5d21c..ae5af39 100644 --- a/doorlockd/CMakeLists.txt +++ b/doorlockd/CMakeLists.txt @@ -100,6 +100,8 @@ set(_DOORLOCK_CLIENT_SRCS client/mainwindow.h client/mainwindow.cpp client/mainwindow.ui + client/network.cpp + client/network.h client/wave.h client/wave.cpp) diff --git a/doorlockd/client/doorlock-client.cpp b/doorlockd/client/doorlock-client.cpp index ac25157..eb11ebf 100644 --- a/doorlockd/client/doorlock-client.cpp +++ b/doorlockd/client/doorlock-client.cpp @@ -3,17 +3,11 @@ #include #include -#include #include #include #include "config.h" - -#include "../lib/clientmessage.h" -#include "../lib/logger.h" -#include "../lib/response.h" - #include "mainwindow.h" // Info about doorlock-client version @@ -22,117 +16,25 @@ const static std::string version = const static std::string gitversion = DOORLOCK_GIT_BRANCH "-" DOORLOCK_GIT_COMMIT_HASH; -static Logger &l = Logger::get(); - namespace po = boost::program_options; -namespace ba = boost::asio; -using ba::ip::tcp; - -static ba::io_service io_service; - -const static std::string subscriptionCommand = - "{ \"command\": \"subscribe\"}"; - -// The receive buffer length of the TCP socket -constexpr static int SOCKET_BUFFERLENGTH = 2048; - -static volatile bool app_run = true; - -static std::unique_ptr mainWindow = nullptr; - -static void onDoorlockUpdate(const Clientmessage &msg) -{ - const auto& doormessage = msg.doormessage(); - l("Received message", LogLevel::info); - l((std::string)" token: " + msg.web_address(), - LogLevel::info); - l((std::string)" open: " + std::to_string(msg.isOpen()), - LogLevel::info); - l((std::string)" button lock: " + std::to_string(doormessage.isLockButton), - LogLevel::info); - l((std::string)" button unlock: " + std::to_string(doormessage.isUnlockButton), - LogLevel::info); - l((std::string)" emergency open: " + std::to_string(doormessage.isEmergencyUnlock), - LogLevel::info); - if (mainWindow) { - mainWindow->setClientmessage(msg); - l(LogLevel::info, "Clientmessage successfully set"); - } else { - l(LogLevel::error, "No valid UI object!"); - } -} - -static int doorlock_client(const std::string &hostname, - const unsigned short port) -{ - int retval = 0; - - try { - tcp::resolver resolver(io_service); - tcp::socket socket(io_service); - tcp::resolver::query query(hostname, std::to_string(port)); - tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); - tcp::resolver::iterator end; - boost::system::error_code error = ba::error::host_not_found; - std::vector data; - - while (error && endpoint_iterator != end) { - socket.close(); - socket.connect(*endpoint_iterator++, error); - } - if (error) - throw boost::system::system_error(error); - - // After connection is established, send the subscription command - socket.write_some(ba::buffer(subscriptionCommand), error); - if (error) - throw boost::system::system_error(error); - - data.resize(SOCKET_BUFFERLENGTH); - - std::function receiveMessage = [&] () { - socket.async_read_some(ba::buffer(data), - [&] (const boost::system::error_code &ec, - const size_t length) - { - if (ec) { - throw boost::system::system_error(ec); - } - - const auto message = Clientmessage::fromString( - std::string(data.begin(), data.begin()+length)); - onDoorlockUpdate(message); - - receiveMessage(); - }); - }; - - receiveMessage(); - io_service.run(); - } - catch(const Response &err) { - l(err.message, LogLevel::error); - retval = -1; - } - catch(const std::exception &err) { - l(LogLevel::error, err.what()); - retval = -1; - } - - io_service.reset(); - - return retval; -} int main(int argc, char** argv) { - std::string hostname; - unsigned short port; - + int retval = 0; + Logger &l = Logger::get(); l((std::string)"Hello, this is " + version + " built on " + gitversion, LogLevel::info); try { + QApplication app(argc, argv); + std::string hostname; + unsigned short port; + po::variables_map vm; + + qRegisterMetaType("Clientmessage"); + app.setOrganizationName("Binary Kitchen"); + app.setApplicationName("doorlock-client"); + po::options_description desc("doorlockd (" + version + " built on " + gitversion + ")"); desc.add_options() ("help,h", @@ -144,7 +46,6 @@ int main(int argc, char** argv) po::value(&hostname)->default_value("localhost"), "IP or name of host running doorlockd"); - po::variables_map vm; po::store(po::command_line_parser(argc, argv).options(desc).run(), vm); if (vm.count("help")) @@ -154,60 +55,23 @@ int main(int argc, char** argv) } po::notify(vm); + l(LogLevel::notice, "Starting doorlock-client"); + + // Start main GUI + MainWindow mainWindow(hostname, port); + + // This routine will never return under normal conditions + retval = app.exec(); + + mainWindow.hide(); + mainWindow.close(); } catch(const std::exception &e) { l(LogLevel::error, e.what()); - exit(-1); + retval = -1; } - l(LogLevel::notice, "Starting doorlock-client"); - - QApplication app(argc, argv); - app.setOrganizationName("Binary Kitchen"); - app.setApplicationName("doorlock-client"); - - try { - mainWindow = std::unique_ptr(new MainWindow); - mainWindow->showFullScreen(); - } - catch(const std::exception &e) - { - l(LogLevel::error, e.what()); - exit(-1); - } - - // Start the TCP client as thread - std::thread clientThread = std::thread([&] () { - // If the TCP client returns, an error has occured - // In normal operation, it never returns - while (app_run) { - doorlock_client(hostname, port); - if (app_run) { - l(LogLevel::error, "client aborted, retrying in 5 seconds"); - // Todo: Write message to QT frontend - - sleep(5); - } - } - - mainWindow->hide(); - mainWindow->close(); - }); - - // This routine will never return in normal operation - app.exec(); - - app_run = false; - - // Stop the IO service - io_service.stop(); - - clientThread.join(); - - if (mainWindow) - mainWindow.reset(); - l(LogLevel::notice, "Stopping doorlock-client"); - return 0; + return retval; } diff --git a/doorlockd/client/mainwindow.cpp b/doorlockd/client/mainwindow.cpp index 1e42d16..38214ef 100644 --- a/doorlockd/client/mainwindow.cpp +++ b/doorlockd/client/mainwindow.cpp @@ -2,8 +2,11 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +#include "network.h" -MainWindow::MainWindow(QWidget *parent) : +MainWindow::MainWindow(const std::string &hostname, + const unsigned short port, + QWidget* parent) : QWidget(parent), ui(new Ui::MainWindow), _soundLock(Wave(SOUND_LOCK)), @@ -15,6 +18,12 @@ MainWindow::MainWindow(QWidget *parent) : { ui->setupUi(this); _LED(false); + NetworkThread* nw = new NetworkThread(hostname, port); + connect(nw, SIGNAL(new_clientmessage(Clientmessage)), + SLOT(setClientmessage(Clientmessage))); + connect(nw, SIGNAL(finished()), + nw, SLOT(deleteLater())); + nw->start(); } MainWindow::~MainWindow() diff --git a/doorlockd/client/mainwindow.h b/doorlockd/client/mainwindow.h index 6d0612e..437ee8c 100644 --- a/doorlockd/client/mainwindow.h +++ b/doorlockd/client/mainwindow.h @@ -4,6 +4,7 @@ #include #include "../lib/clientmessage.h" +#include "../lib/logger.h" #include "wave.h" namespace Ui { @@ -15,13 +16,16 @@ class MainWindow : public QWidget Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(const std::string &hostname, + const unsigned short port, + QWidget *parent = 0); MainWindow(const MainWindow &rhs); MainWindow &operator =(const MainWindow &rhs); ~MainWindow(); +public slots: void setClientmessage(const Clientmessage &msg); private: diff --git a/doorlockd/client/network.cpp b/doorlockd/client/network.cpp new file mode 100644 index 0000000..59db568 --- /dev/null +++ b/doorlockd/client/network.cpp @@ -0,0 +1,94 @@ +#include "network.h" + +#include "../lib/response.h" + +namespace ba = boost::asio; +using ba::ip::tcp; + +const std::string NetworkThread::_subscription_command = "{ \"command\": \"subscribe\"}"; + +NetworkThread::NetworkThread(const std::string &hostname, + const unsigned short port) : + QThread(), + _l(Logger::get()), + _hostname(hostname), + _port(port), + _io_service() +{ +} + +void NetworkThread::run() { + do { + try { + tcp::resolver resolver(_io_service); + tcp::socket socket(_io_service); + tcp::resolver::query query(_hostname, std::to_string(_port)); + tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); + tcp::resolver::iterator end; + boost::system::error_code error = ba::error::host_not_found; + std::vector data; + + while (error && endpoint_iterator != end) { + socket.close(); + socket.connect(*endpoint_iterator++, error); + } + if (error) + throw boost::system::system_error(error); + + // After connection is established, send the subscription command + socket.write_some(ba::buffer(_subscription_command), error); + if (error) + throw boost::system::system_error(error); + + data.resize(_SOCKET_BUFFERLENGTH); + + std::function receiveMessage = [&] () { + socket.async_read_some(ba::buffer(data), + [&] (const boost::system::error_code &ec, + const size_t length) + { + if (ec) { + throw boost::system::system_error(ec); + } + + const auto msg = Clientmessage::fromString( + std::string(data.begin(), data.begin()+length)); + + // Received valid Clientmessage + // Log it! + const auto& doormessage = msg.doormessage(); + _l("Received message", LogLevel::info); + _l((std::string)" token: " + msg.web_address(), + LogLevel::info); + _l((std::string)" open: " + std::to_string(msg.isOpen()), + LogLevel::info); + _l((std::string)" button lock: " + std::to_string(doormessage.isLockButton), + LogLevel::info); + _l((std::string)" button unlock: " + std::to_string(doormessage.isUnlockButton), + LogLevel::info); + _l((std::string)" emergency open: " + std::to_string(doormessage.isEmergencyUnlock), + LogLevel::info); + + // Emit the new message + emit new_clientmessage(msg); + + // Wait for new message + receiveMessage(); + }); + }; + + receiveMessage(); + _io_service.run(); + } + catch(const Response &err) { + _l(err.message, LogLevel::error); + } + catch(const std::exception &err) { + _l(LogLevel::error, err.what()); + } + + sleep(1); + } while(1); + + _io_service.reset(); +} diff --git a/doorlockd/client/network.h b/doorlockd/client/network.h new file mode 100644 index 0000000..c4e980b --- /dev/null +++ b/doorlockd/client/network.h @@ -0,0 +1,37 @@ +#ifndef NETWORK_H +#define NETWORK_H + +#include + +#include +#include + +#include "../lib/clientmessage.h" +#include "../lib/logger.h" + +class NetworkThread : public QThread { + Q_OBJECT + +public: + NetworkThread(const std::string &hostname, + const unsigned short port); + +private: + void run(); + + Logger &_l; + const std::string _hostname; + const unsigned short _port; + + boost::asio::io_service _io_service; + + const static std::string _subscription_command; + + // The receive buffer length of the TCP socket + constexpr static int _SOCKET_BUFFERLENGTH = {2048}; + +signals: + void new_clientmessage(Clientmessage msg); +}; + +#endif