Mercurial > irccd
diff irccdctl/connection.h @ 0:1158cffe5a5e
Initial import
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 08 Feb 2016 16:43:14 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/irccdctl/connection.h Mon Feb 08 16:43:14 2016 +0100 @@ -0,0 +1,227 @@ +/* + * connection.h -- value wrapper for connecting to irccd + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _IRCCD_CONNECTION_H_ +#define _IRCCD_CONNECTION_H_ + +#include <cassert> +#include <stdexcept> + +#include <elapsed-timer.h> +#include <json.h> +#include <sockets.h> +#include <system.h> +#include <util.h> + +namespace irccd { + +/** + * @class Connection + * @brief Abstract class for connecting to irccd from Ip or Local addresses. + */ +class Connection { +protected: + ElapsedTimer m_timer; + + /** + * Clamp the time to wait to be sure that it will be never less than 0. + */ + inline int clamp(int timeout) + { + return timeout < 0 ? -1 : (timeout - (int)m_timer.elapsed() < 0) ? 0 : (timeout - m_timer.elapsed()); + } + +public: + /** + * Wait for the next requested response. + * + * @param name the response name + * @param timeout the optional timeout + * @return the object + * @throw net::Error on errors or on timeout + */ + json::Value next(const std::string &name, int timeout = 30000); + + /** + * Just wait if the operation succeeded. + * + * @param name the response name + * @param timeout the timeout + */ + void verify(const std::string &name, int timeout = 30000); + + /** + * Check if the socket is still connected. + * + * @return true if connected + */ + virtual bool isConnected() const noexcept = 0; + + /** + * Try to connect to the host. + * + * @param timeout the maximum time in milliseconds + * @throw net::Error on errors or timeout + */ + virtual void connect(int timeout = 30000) = 0; + + /** + * Try to send the message in 30 seconds. The message must not end with \r\n\r\n, it is added automatically. + * + * @pre msg must not be empty + * @param msg the message to send + * @param timeout the maximum time in milliseconds + * @throw net::Error on errors + */ + virtual void send(std::string msg, int timeout = 30000) = 0; + + /** + * Get the next event from irccd. + * + * This functions throws if the connection is lost. + * + * @param timeout the maximum time in milliseconds + * @return the next event + * @throw net::Error on errors or disconnection + */ + virtual json::Value next(int timeout = 30000) = 0; +}; + +/** + * @class ConnectionBase + * @brief Implementation for Ip or Local. + */ +template <typename Address> +class ConnectionBase : public Connection { +private: + net::SocketTcp<Address> m_socket; + net::Listener<> m_listener; + Address m_address; + + /* Input buffer */ + std::string m_input; + +public: + /** + * Construct the socket but do not connect immediately. + * + * @param address the address + */ + ConnectionBase(Address address) + : m_address(std::move(address)) + { + m_socket.set(net::option::SockBlockMode{false}); + m_listener.set(m_socket.handle(), net::Condition::Readable); + } + + /** + * @copydoc Connection::isConnected + */ + bool isConnected() const noexcept override + { + return m_socket.state() == net::State::Connected; + } + + /** + * @copydoc Connection::connect + */ + void connect(int timeout) override; + + /** + * @copydoc Connection::send + */ + void send(std::string msg, int timeout) override; + + /** + * @copydoc Connection::next + */ + json::Value next(int timeout) override; +}; + +template <typename Address> +void ConnectionBase<Address>::connect(int timeout) +{ + m_socket.connect(m_address); + + if (m_socket.state() == net::State::Connecting) { + m_listener.set(m_socket.handle(), net::Condition::Writable); + m_listener.wait(timeout); + m_socket.connect(); + m_listener.unset(m_socket.handle(), net::Condition::Writable); + } +} + +template <typename Address> +void ConnectionBase<Address>::send(std::string msg, int timeout) +{ + assert(!msg.empty()); + + /* Add termination */ + msg += "\r\n\r\n"; + + m_listener.remove(m_socket.handle()); + m_listener.set(m_socket.handle(), net::Condition::Writable); + m_timer.reset(); + + while (!msg.empty()) { + /* Do not wait the time that is already passed */ + m_listener.wait(clamp(timeout)); + + /* Try to send at most as possible */ + msg.erase(0, m_socket.send(msg)); + } + + /* Timeout? */ + if (!msg.empty()) + throw std::runtime_error("operation timed out while sending to irccd"); +} + +template <typename Address> +json::Value ConnectionBase<Address>::next(int timeout) +{ + /* Maybe there is already something */ + std::string buffer = util::nextNetwork(m_input); + + m_listener.remove(m_socket.handle()); + m_listener.set(m_socket.handle(), net::Condition::Readable); + m_timer.reset(); + + /* Read if there is nothing */ + while (buffer.empty() && isConnected()) { + /* Wait and read */ + m_listener.wait(clamp(timeout)); + m_input += m_socket.recv(512); + + /* Finally try */ + buffer = util::nextNetwork(m_input); + } + + if (!isConnected()) + throw std::runtime_error("connection lost"); + + json::Value value(json::Buffer{buffer}); + + if (!value.isObject()) + throw std::invalid_argument("invalid message received"); + + return value; +} + +} // !irccd + +#endif // !_IRCCD_CONNECTION_H_