<commit_before>/*
 *   Copyright (c) 2022 Project CHIP Authors
 *   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 "Decoder.h"
#include "DecoderCustomLog.h"

#include <app/MessageDef/InvokeRequestMessage.h>
#include <app/MessageDef/InvokeResponseMessage.h>
#include <app/MessageDef/ReadRequestMessage.h>
#include <app/MessageDef/ReportDataMessage.h>
#include <app/MessageDef/StatusResponseMessage.h>
#include <app/MessageDef/SubscribeRequestMessage.h>
#include <app/MessageDef/SubscribeResponseMessage.h>
#include <app/MessageDef/TimedRequestMessage.h>
#include <app/MessageDef/WriteRequestMessage.h>
#include <app/MessageDef/WriteResponseMessage.h>

namespace {
constexpr const char * kProtocolName = "Interaction Model";

constexpr const char * kUnknown               = "Unknown";
constexpr const char * kStatusResponse        = "Status Response";
constexpr const char * kReadRequest           = "Read Request";
constexpr const char * kSubscribeRequest      = "Subscribe Request";
constexpr const char * kSubscribeResponse     = "Subscribe Response";
constexpr const char * kReportData            = "Report Data";
constexpr const char * kWriteRequest          = "Write Request";
constexpr const char * kWriteResponse         = "Write Response";
constexpr const char * kInvokeCommandRequest  = "InvokeCommandRequest";
constexpr const char * kInvokeCommandResponse = "InvokeCommandResponse";
constexpr const char * kTimedRequest          = "Timed Request";

} // namespace

using MessageType = chip::Protocols::InteractionModel::MsgType;

namespace chip {
namespace trace {
namespace interaction_model {

CHIP_ERROR DecodeStatusResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeReadRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeSubscribeRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeSubscribeResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeReportData(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeWriteRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeWriteResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeInvokeCommandRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeInvokeCommandResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeTimedRequest(TLV::TLVReader & reader, bool decode);

const char * ToProtocolName()
{
    return kProtocolName;
}

const char * ToProtocolMessageTypeName(uint8_t protocolCode)
{
    switch (protocolCode)
    {
    case to_underlying(MessageType::StatusResponse):
        return kStatusResponse;
    case to_underlying(MessageType::ReadRequest):
        return kReadRequest;
    case to_underlying(MessageType::SubscribeRequest):
        return kSubscribeRequest;
    case to_underlying(MessageType::SubscribeResponse):
        return kSubscribeResponse;
    case to_underlying(MessageType::ReportData):
        return kReportData;
    case to_underlying(MessageType::WriteRequest):
        return kWriteRequest;
    case to_underlying(MessageType::WriteResponse):
        return kWriteResponse;
    case to_underlying(MessageType::InvokeCommandRequest):
        return kInvokeCommandRequest;
    case to_underlying(MessageType::InvokeCommandResponse):
        return kInvokeCommandResponse;
    case to_underlying(MessageType::TimedRequest):
        return kTimedRequest;
    default:
        return kUnknown;
    }
}

CHIP_ERROR LogAsProtocolMessage(uint8_t protocolCode, const uint8_t * data, size_t len, bool decodeResponse)
{
    TLV::TLVReader reader;
    reader.Init(data, len);

    switch (protocolCode)
    {
    case to_underlying(MessageType::StatusResponse):
        return DecodeStatusResponse(reader, decodeResponse);
    case to_underlying(MessageType::ReadRequest):
        return DecodeReadRequest(reader, decodeResponse);
    case to_underlying(MessageType::SubscribeRequest):
        return DecodeSubscribeRequest(reader, decodeResponse);
    case to_underlying(MessageType::SubscribeResponse):
        return DecodeSubscribeResponse(reader, decodeResponse);
    case to_underlying(MessageType::ReportData):
        return DecodeReportData(reader, decodeResponse);
    case to_underlying(MessageType::WriteRequest):
        return DecodeWriteRequest(reader, decodeResponse);
    case to_underlying(MessageType::WriteResponse):
        return DecodeWriteResponse(reader, decodeResponse);
    case to_underlying(MessageType::InvokeCommandRequest):
        return DecodeInvokeCommandRequest(reader, decodeResponse);
    case to_underlying(MessageType::InvokeCommandResponse):
        return DecodeInvokeCommandResponse(reader, decodeResponse);
    case to_underlying(MessageType::TimedRequest):
        return DecodeTimedRequest(reader, decodeResponse);
    default:
        return CHIP_ERROR_NOT_IMPLEMENTED;
    }
}

CHIP_ERROR DecodeStatusResponse(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::InvokeRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeReadRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::ReadRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeSubscribeRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::SubscribeRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeSubscribeResponse(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::SubscribeResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeReportData(TLV::TLVReader & reader, bool decode)
{
    ReturnErrorOnFailure(MaybeDecodeNestedReadResponse(reader.GetReadPoint(), reader.GetTotalLength()));

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::ReportDataMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeWriteRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::WriteRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeWriteResponse(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::WriteResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeInvokeCommandRequest(TLV::TLVReader & reader, bool decode)
{
    ReturnErrorOnFailure(MaybeDecodeNestedCommandRequest(reader.GetReadPoint(), reader.GetTotalLength()));

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::InvokeRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeInvokeCommandResponse(TLV::TLVReader & reader, bool decode)
{
    ReturnErrorOnFailure(MaybeDecodeNestedCommandResponse(reader.GetReadPoint(), reader.GetTotalLength()));

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::InvokeResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeTimedRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::TimedRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

} // namespace interaction_model
} // namespace trace
} // namespace chip
<commit_msg>[chip-trace-decoder] Use the right message parser for status response (#19307)<commit_after>/*
 *   Copyright (c) 2022 Project CHIP Authors
 *   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 "Decoder.h"
#include "DecoderCustomLog.h"

#include <app/MessageDef/InvokeRequestMessage.h>
#include <app/MessageDef/InvokeResponseMessage.h>
#include <app/MessageDef/ReadRequestMessage.h>
#include <app/MessageDef/ReportDataMessage.h>
#include <app/MessageDef/StatusResponseMessage.h>
#include <app/MessageDef/SubscribeRequestMessage.h>
#include <app/MessageDef/SubscribeResponseMessage.h>
#include <app/MessageDef/TimedRequestMessage.h>
#include <app/MessageDef/WriteRequestMessage.h>
#include <app/MessageDef/WriteResponseMessage.h>

namespace {
constexpr const char * kProtocolName = "Interaction Model";

constexpr const char * kUnknown               = "Unknown";
constexpr const char * kStatusResponse        = "Status Response";
constexpr const char * kReadRequest           = "Read Request";
constexpr const char * kSubscribeRequest      = "Subscribe Request";
constexpr const char * kSubscribeResponse     = "Subscribe Response";
constexpr const char * kReportData            = "Report Data";
constexpr const char * kWriteRequest          = "Write Request";
constexpr const char * kWriteResponse         = "Write Response";
constexpr const char * kInvokeCommandRequest  = "InvokeCommandRequest";
constexpr const char * kInvokeCommandResponse = "InvokeCommandResponse";
constexpr const char * kTimedRequest          = "Timed Request";

} // namespace

using MessageType = chip::Protocols::InteractionModel::MsgType;

namespace chip {
namespace trace {
namespace interaction_model {

CHIP_ERROR DecodeStatusResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeReadRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeSubscribeRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeSubscribeResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeReportData(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeWriteRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeWriteResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeInvokeCommandRequest(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeInvokeCommandResponse(TLV::TLVReader & reader, bool decode);
CHIP_ERROR DecodeTimedRequest(TLV::TLVReader & reader, bool decode);

const char * ToProtocolName()
{
    return kProtocolName;
}

const char * ToProtocolMessageTypeName(uint8_t protocolCode)
{
    switch (protocolCode)
    {
    case to_underlying(MessageType::StatusResponse):
        return kStatusResponse;
    case to_underlying(MessageType::ReadRequest):
        return kReadRequest;
    case to_underlying(MessageType::SubscribeRequest):
        return kSubscribeRequest;
    case to_underlying(MessageType::SubscribeResponse):
        return kSubscribeResponse;
    case to_underlying(MessageType::ReportData):
        return kReportData;
    case to_underlying(MessageType::WriteRequest):
        return kWriteRequest;
    case to_underlying(MessageType::WriteResponse):
        return kWriteResponse;
    case to_underlying(MessageType::InvokeCommandRequest):
        return kInvokeCommandRequest;
    case to_underlying(MessageType::InvokeCommandResponse):
        return kInvokeCommandResponse;
    case to_underlying(MessageType::TimedRequest):
        return kTimedRequest;
    default:
        return kUnknown;
    }
}

CHIP_ERROR LogAsProtocolMessage(uint8_t protocolCode, const uint8_t * data, size_t len, bool decodeResponse)
{
    TLV::TLVReader reader;
    reader.Init(data, len);

    switch (protocolCode)
    {
    case to_underlying(MessageType::StatusResponse):
        return DecodeStatusResponse(reader, decodeResponse);
    case to_underlying(MessageType::ReadRequest):
        return DecodeReadRequest(reader, decodeResponse);
    case to_underlying(MessageType::SubscribeRequest):
        return DecodeSubscribeRequest(reader, decodeResponse);
    case to_underlying(MessageType::SubscribeResponse):
        return DecodeSubscribeResponse(reader, decodeResponse);
    case to_underlying(MessageType::ReportData):
        return DecodeReportData(reader, decodeResponse);
    case to_underlying(MessageType::WriteRequest):
        return DecodeWriteRequest(reader, decodeResponse);
    case to_underlying(MessageType::WriteResponse):
        return DecodeWriteResponse(reader, decodeResponse);
    case to_underlying(MessageType::InvokeCommandRequest):
        return DecodeInvokeCommandRequest(reader, decodeResponse);
    case to_underlying(MessageType::InvokeCommandResponse):
        return DecodeInvokeCommandResponse(reader, decodeResponse);
    case to_underlying(MessageType::TimedRequest):
        return DecodeTimedRequest(reader, decodeResponse);
    default:
        return CHIP_ERROR_NOT_IMPLEMENTED;
    }
}

CHIP_ERROR DecodeStatusResponse(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::StatusResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeReadRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::ReadRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeSubscribeRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::SubscribeRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeSubscribeResponse(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::SubscribeResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeReportData(TLV::TLVReader & reader, bool decode)
{
    ReturnErrorOnFailure(MaybeDecodeNestedReadResponse(reader.GetReadPoint(), reader.GetTotalLength()));

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::ReportDataMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeWriteRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::WriteRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeWriteResponse(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::WriteResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeInvokeCommandRequest(TLV::TLVReader & reader, bool decode)
{
    ReturnErrorOnFailure(MaybeDecodeNestedCommandRequest(reader.GetReadPoint(), reader.GetTotalLength()));

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::InvokeRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeInvokeCommandResponse(TLV::TLVReader & reader, bool decode)
{
    ReturnErrorOnFailure(MaybeDecodeNestedCommandResponse(reader.GetReadPoint(), reader.GetTotalLength()));

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::InvokeResponseMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

CHIP_ERROR DecodeTimedRequest(TLV::TLVReader & reader, bool decode)
{
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
    if (decode)
    {
        app::TimedRequestMessage::Parser parser;
        ReturnErrorOnFailure(parser.Init(reader));
        return parser.CheckSchemaValidity();
    }
#endif

    return CHIP_NO_ERROR;
}

} // namespace interaction_model
} // namespace trace
} // namespace chip
<|endoftext|>


<commit_before>#ifndef LIBTEN_HTTP_CLIENT_HH
#define LIBTEN_HTTP_CLIENT_HH

#include "ten/http/http_message.hh"
#include "ten/shared_pool.hh"
#include "ten/buffer.hh"
#include "ten/net.hh"
#include "ten/uri.hh"
#include <netinet/tcp.h>
#include <boost/algorithm/string/compare.hpp>

namespace ten {

class http_error : public errorx {
protected:
    http_error(const char *msg) : errorx(msg) {}
};

//! thrown on http network errors
struct http_dial_error : public http_error {
    http_dial_error() : http_error("dial") {}
};

//! thrown on http network errors
struct http_recv_error : public http_error {
    http_recv_error() : http_error("recv") {}
};

//! thrown on http network errors
struct http_send_error : public http_error {
    http_send_error() : http_error("send") {}
};

//! basic http client
class http_client {
private:
    netsock _sock;
    buffer _buf;
    std::string _host;
    uint16_t _port;

    void ensure_connection() {
        if (!_sock.valid()) {
            _sock = std::move(netsock(AF_INET, SOCK_STREAM));
            if (_sock.dial(_host.c_str(), _port) != 0) {
                throw http_dial_error{};
            }
            _sock.s.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
        }
    }

public:
    size_t max_content_length;

    http_client(const std::string &host_, uint16_t port_=80)
        : _buf(4*1024), _host(host_), _port(port_), max_content_length(~(size_t)0)
    {
        parse_host_port(_host, _port);
    }

    std::string host() const { return _host; }
    uint16_t port() const { return _port; }

    http_response perform(const std::string &method, const std::string &path,
                          const http_headers &hdrs = {}, const std::string &data = {},
                          optional_timeout timeout = {})
    {
        uri u;
        u.scheme = "http";
        u.host = _host;
        u.port = _port;
        u.path = path;
        u.normalize();

        http_request r(method, u.compose_path(), hdrs);
        // HTTP/1.1 requires host header
        if (r.find("Host") == r.headers.end())
            r.set("Host", u.host);
        r.body = data;

        return perform(r, timeout);
    }

    http_response perform(http_request &r, optional_timeout timeout = {}) {
        VLOG(4) << "-> " << r.method << " " << _host << ":" << _port << " " << r.uri;

        if (r.body.size()) {
            r.set("Content-Length", r.body.size());
        }

        try {
            ensure_connection();

            http_response resp(&r);

            std::string data = r.data();
            if (r.body.size() > 0) {
                data += r.body;
            }
            ssize_t nw = _sock.send(data.data(), data.size(), 0, timeout);
            if ((size_t)nw != data.size()) {
                throw http_send_error{};
            }

            http_parser parser;
            resp.parser_init(&parser);

            _buf.clear();

            while (!resp.complete) {
                _buf.reserve(4*1024);
                ssize_t nr = _sock.recv(_buf.back(), _buf.available(), 0, timeout);
                if (nr <= 0) { throw http_recv_error{}; }
                _buf.commit(nr);
                size_t len = _buf.size();
                resp.parse(&parser, _buf.front(), len);
                _buf.remove(len);
                if (resp.body.size() >= max_content_length) {
                    _sock.close(); // close this socket, we won't read anymore
                    return resp;
                }
            }
            // should not be any data left over in _buf
            CHECK(_buf.size() == 0);

            const auto conn = resp.get("Connection");
            if (
                    (conn && boost::iequals(*conn, "close")) ||
                    (resp.version <= http_1_0 && conn && !boost::iequals(*conn, "Keep-Alive")) ||
                    (resp.version <= http_1_0 && !conn)
               )
            {
                _sock.close();
            }

            VLOG(4) << "<- " << resp.status_code << " [" << resp.body.size() << "]";
            return resp;
        } catch (errorx &e) {
            _sock.close();
            throw;
        }
    }

    http_response get(const std::string &path, optional_timeout timeout = {}) {
        return perform("GET", path, {}, {}, timeout);
    }

    http_response post(const std::string &path, const std::string &data, optional_timeout timeout = {}) {
        return perform("POST", path, {}, data, timeout);
    }

};

class http_pool : public shared_pool<http_client> {
public:
    http_pool(const std::string &host_, uint16_t port_, optional<size_t> max_conn = {})
        : shared_pool<http_client>("http://" + host_ + ":" + std::to_string(port_),
            std::bind(&http_pool::new_resource, this),
            max_conn
        ),
        _host(host_), _port(port_) {}

protected:
    std::string _host;
    uint16_t _port;

    std::shared_ptr<http_client> new_resource() {
        VLOG(3) << "new http_client resource " << _host << ":" << _port;
        return std::make_shared<http_client>(_host, _port);
    }
};

} // end namespace ten

#endif // LIBTEN_HTTP_CLIENT_HH
<commit_msg>include strerror messages in http socket error logs<commit_after>#ifndef LIBTEN_HTTP_CLIENT_HH
#define LIBTEN_HTTP_CLIENT_HH

#include "ten/http/http_message.hh"
#include "ten/shared_pool.hh"
#include "ten/buffer.hh"
#include "ten/net.hh"
#include "ten/uri.hh"
#include <netinet/tcp.h>
#include <boost/algorithm/string/compare.hpp>

namespace ten {

class http_error : public errorx {
public:
    // TODO: variadic or inherited ctors
    http_error(const char *msg) : errorx(msg) {}
};

class http_errno_error : public errno_error {
protected:
    // TODO: variadic or inherited ctors
    http_errno_error(const char *msg) : errno_error(msg) {}
};

//! thrown on http network errors
struct http_makesock_error : public http_errno_error {
    http_makesock_error() : http_errno_error("makesock") {}
};

//! thrown on http network errors
struct http_dial_error : public http_errno_error {
    http_dial_error() : http_errno_error("dial") {}
};

//! thrown on http network errors
struct http_recv_error : public http_errno_error {
    http_recv_error() : http_errno_error("recv") {}
};

//! thrown on http network errors
struct http_send_error : public http_errno_error {
    http_send_error() : http_errno_error("send") {}
};

//! basic http client
class http_client {
private:
    netsock _sock;
    buffer _buf;
    std::string _host;
    uint16_t _port;

    void ensure_connection() {
        if (!_sock.valid()) {
            _sock = std::move(netsock(AF_INET, SOCK_STREAM));
            if (!_sock.valid()) {
                throw http_makesock_error{};
            }
            errno = 0; // "fred is not a tty"
            if (_sock.dial(_host.c_str(), _port) != 0) {
                throw http_dial_error{};
            }
            _sock.s.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
        }
    }

public:
    size_t max_content_length;

    http_client(const std::string &host_, uint16_t port_=80)
        : _buf(4*1024), _host(host_), _port(port_), max_content_length(~(size_t)0)
    {
        parse_host_port(_host, _port);
    }

    std::string host() const { return _host; }
    uint16_t port() const { return _port; }

    http_response perform(const std::string &method, const std::string &path,
                          const http_headers &hdrs = {}, const std::string &data = {},
                          optional_timeout timeout = {})
    {
        uri u;
        u.scheme = "http";
        u.host = _host;
        u.port = _port;
        u.path = path;
        u.normalize();

        http_request r(method, u.compose_path(), hdrs);
        // HTTP/1.1 requires host header
        if (r.find("Host") == r.headers.end())
            r.set("Host", u.host);
        r.body = data;

        return perform(r, timeout);
    }

    http_response perform(http_request &r, optional_timeout timeout = {}) {
        VLOG(4) << "-> " << r.method << " " << _host << ":" << _port << " " << r.uri;

        if (r.body.size()) {
            r.set("Content-Length", r.body.size());
        }

        try {
            ensure_connection();

            http_response resp(&r);

            std::string data = r.data();
            if (r.body.size() > 0) {
                data += r.body;
            }
            ssize_t nw = _sock.send(data.data(), data.size(), 0, timeout);
            if (nw < 0) {
                throw http_send_error{};
            }
            else if ((size_t)nw != data.size()) {
                std::ostringstream ss;
                ss << "short write: " << nw << " < " << data.size();
                throw http_error(ss.str().c_str());
            }

            http_parser parser;
            resp.parser_init(&parser);

            _buf.clear();

            while (!resp.complete) {
                _buf.reserve(4*1024);
                ssize_t nr = _sock.recv(_buf.back(), _buf.available(), 0, timeout);
                if (nr <= 0) { throw http_recv_error{}; }
                _buf.commit(nr);
                size_t len = _buf.size();
                resp.parse(&parser, _buf.front(), len);
                _buf.remove(len);
                if (resp.body.size() >= max_content_length) {
                    _sock.close(); // close this socket, we won't read anymore
                    return resp;
                }
            }
            // should not be any data left over in _buf
            CHECK(_buf.size() == 0);

            const auto conn = resp.get("Connection");
            if (
                    (conn && boost::iequals(*conn, "close")) ||
                    (resp.version <= http_1_0 && conn && !boost::iequals(*conn, "Keep-Alive")) ||
                    (resp.version <= http_1_0 && !conn)
               )
            {
                _sock.close();
            }

            VLOG(4) << "<- " << resp.status_code << " [" << resp.body.size() << "]";
            return resp;
        } catch (errorx &e) {
            _sock.close();
            throw;
        }
    }

    http_response get(const std::string &path, optional_timeout timeout = {}) {
        return perform("GET", path, {}, {}, timeout);
    }

    http_response post(const std::string &path, const std::string &data, optional_timeout timeout = {}) {
        return perform("POST", path, {}, data, timeout);
    }

};

class http_pool : public shared_pool<http_client> {
public:
    http_pool(const std::string &host_, uint16_t port_, optional<size_t> max_conn = {})
        : shared_pool<http_client>("http://" + host_ + ":" + std::to_string(port_),
            std::bind(&http_pool::new_resource, this),
            max_conn
        ),
        _host(host_), _port(port_) {}

protected:
    std::string _host;
    uint16_t _port;

    std::shared_ptr<http_client> new_resource() {
        VLOG(3) << "new http_client resource " << _host << ":" << _port;
        return std::make_shared<http_client>(_host, _port);
    }
};

} // end namespace ten

#endif // LIBTEN_HTTP_CLIENT_HH
<|endoftext|>


<commit_before>#ifndef LIBTEN_HTTP_SERVER_HH
#define LIBTEN_HTTP_SERVER_HH

#include <fnmatch.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/compare.hpp>
#include <chrono>
#include <functional>
#include <netinet/tcp.h>

#include "ten/buffer.hh"
#include "ten/logging.hh"
#include "ten/net.hh"
#include "ten/http/http_message.hh"
#include "ten/uri.hh"

namespace ten {

//! http request/response pair; keeps reference to request and socket
// (the term "exchange" appears in the standard)

struct http_exchange {
    http_request &req;
    netsock &sock;
    http_response resp {404};
    bool resp_sent {false};
    std::chrono::high_resolution_clock::time_point start;

    http_exchange(http_request &req_, netsock &sock_)
        : req(req_),
          sock(sock_),
          start(std::chrono::steady_clock::now())
        {}

    http_exchange(const http_exchange &) = delete;
    http_exchange &operator=(const http_exchange &) = delete;

    ~http_exchange() {
        if (!resp_sent) {
            // ensure a response is sent
            send_response();
        }
        if (resp.close_after()) {
            sock.close();
        }
    }

    //! compose a uri from the request uri
    uri get_uri(optional<std::string> host = nullopt) const {
        if (!host) {
            if (boost::starts_with(req.uri, "http://")) {
                // TODO: transform to preserve passed in host
                return req.uri;
            }
            host = req.get(hs::Host);
            if (!host)
                host.emplace("localhost");
        }
        uri tmp;
        tmp.scheme = "http";
        tmp.host = *host;
        tmp.path = req.uri;
        return tmp.compose();
    }

    //! send response to this request
    ssize_t send_response() {
        if (resp_sent) return 0;
        resp_sent = true;
        // TODO: Content-Length might be good to add to normal responses,
        //    but only if Transfer-Encoding isn't chunked?
        if (resp.status_code >= 400 && resp.status_code <= 599
            && !resp.get(hs::Content_Length)
            && req.method != hs::HEAD)
        {
            resp.set(hs::Content_Length, resp.body.size());
        }

        // HTTP/1.1 requires Date, so lets add it
        if (!resp.get(hs::Date)) {
            resp.set(hs::Date, http_base::rfc822_date());
        }

        // obey client's wishes on closing if we have none of our own,
        //  else prefer to keep http 1.1 open
        if (!resp.get(hs::Connection)) {
            const auto conn_hdr = req.get(hs::Connection);
            if (conn_hdr)
                resp.set(hs::Connection, *conn_hdr);
            else if (req.version == http_1_0)
                resp.set(hs::Connection, hs::close);
        }

        auto data = resp.data();
        if (!resp.body.empty() && req.method != hs::HEAD) {
            data += resp.body;
        }
        ssize_t nw = sock.send(data.data(), data.size());
        return nw;
    }

    //! the ip of the host making the request
    //! might use the X-Forwarded-For header
    optional<std::string> agent_ip(bool use_xff=false) const {
        if (use_xff) {
            auto xff_hdr = req.get("X-Forwarded-For");
            if (xff_hdr && !(*xff_hdr).empty()) {
                const char *xff = xff_hdr->c_str();
                // pick the first addr
                int i;
                for (i=0; *xff && i<256 && !isdigit((unsigned char)*xff); i++, xff++) {}
                if (*xff && i < 256) {
                    // now, find the end
                    const char *e = xff;
                    for (i = 0; *e && i<256 && (isdigit((unsigned char)*e) || *e == '.'); i++, e++) {}
                    if (i < 256 && e >= xff + 7 ) {
                        return std::string(xff, e - xff);
                    }
                }
            }
        }
        address addr;
        if (sock.getpeername(addr)) {
            char buf[INET6_ADDRSTRLEN];
            if (addr.ntop(buf, sizeof(buf))) {
                return optional<std::string>(emplace, buf);
            }
        }
        return nullopt;
    }
};

//! basic http server
class http_server : public netsock_server {
public:
    typedef std::function<void (http_exchange &)> callback_type;

    std::function<void ()> connect_watch;
    std::function<void ()> disconnect_watch;

protected:
    struct route {
        std::string pattern;
        callback_type callback;
        int fnmatch_flags;

        route(const std::string &pattern_,
                const callback_type &callback_,
                int fnmatch_flags_=0)
            : pattern(pattern_), callback(callback_), fnmatch_flags(fnmatch_flags_) {}
    };

    std::vector<route> _routes;
    callback_type _log_func;

public:
    http_server(size_t stacksize_=default_stacksize, optional_timeout recv_timeout_ms_={})
        : netsock_server("http", stacksize_, recv_timeout_ms_)
    {
    }

    //! add a callback for a uri with an fnmatch pattern
    void add_route(const std::string &pattern,
            const callback_type &callback,
            int fnmatch_flags = 0)
    {
        _routes.emplace_back(pattern, callback, fnmatch_flags);
    }

    //! set logging function, called after every exchange
    void set_log_callback(const callback_type &f) {
        _log_func = f;
    }

private:

    void setup_listen_socket(netsock &s) override {
        netsock_server::setup_listen_socket(s);
        s.setsockopt(IPPROTO_TCP, TCP_DEFER_ACCEPT, 30);
    }

    void on_connection(netsock &s) override {
        // TODO: tuneable buffer sizes
        buffer buf(4*1024);
        http_parser parser;

        if (connect_watch) {
            connect_watch();
        }

        bool nodelay_set = false;
        http_request req;
        while (s.valid()) {
            req.parser_init(&parser);
            bool got_headers = false;
            for (;;) {
                buf.reserve(4*1024);
                ssize_t nr = -1;
                if (buf.size() == 0) {
                    nr = s.recv(buf.back(), buf.available(), 0, _recv_timeout_ms);
                    if (nr <= 0) goto done;
                    buf.commit(nr);
                }
                size_t nparse = buf.size();
                req.parse(&parser, buf.front(), nparse);
                buf.remove(nparse);
                if (req.complete) {
                    DVLOG(4) << req.data();
                    // handle http exchange (request -> response)
                    http_exchange ex(req, s);
                    if (!nodelay_set && !req.close_after()) {
                        // this is likely a persistent connection, so low-latency sending is worth the overh
                        s.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
                        nodelay_set = true;
                    }
                    handle_exchange(ex);
                    break;
                }
                if (nr == 0) goto done;
                if (!got_headers && !req.method.empty()) {
                    got_headers = true;
                    auto exp_hdr = req.get("Expect");
                    if (exp_hdr && *exp_hdr == "100-continue") {
                        http_response cont_resp(100);
                        std::string data = cont_resp.data();
                        ssize_t nw = s.send(data.data(), data.size());
                        (void)nw;
                    }
                }
            }
        }
done:
        if (disconnect_watch) {
            disconnect_watch();
        }
    }

    void handle_exchange(http_exchange &ex) {
        const auto path = ex.req.path();
        DVLOG(5) << "path: " << path;
        // not super efficient, but good enough
        // note: empty string pattern matches everything
        for (const auto &i : _routes) {
            DVLOG(5) << "matching pattern: " << i.pattern;
            if (i.pattern.empty() || fnmatch(i.pattern.c_str(), path.c_str(), i.fnmatch_flags) == 0) {
                try {
                    i.callback(std::ref(ex));
                } catch (std::exception &e) {
                    DVLOG(2) << "unhandled exception in route [" << i.pattern << "]: " << e.what();
                    ex.resp = http_response(500, http_headers{hs::Connection, hs::close});
                    std::string msg = e.what();
                    if (!msg.empty() && *msg.rbegin() != '\n')
                        msg += '\n';
                    ex.resp.set_body(msg, hs::text_plain);
                    ex.send_response();
                }
                break;
            }
        }
        if (_log_func) {
            _log_func(ex);
        }
    }
};

} // end namespace ten

#endif // LIBTEN_HTTP_SERVER_HH
<commit_msg>per-http_exchange log function customization<commit_after>#ifndef LIBTEN_HTTP_SERVER_HH
#define LIBTEN_HTTP_SERVER_HH

#include <fnmatch.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/compare.hpp>
#include <chrono>
#include <functional>
#include <netinet/tcp.h>

#include "ten/buffer.hh"
#include "ten/logging.hh"
#include "ten/net.hh"
#include "ten/http/http_message.hh"
#include "ten/uri.hh"

namespace ten {

//! http request/response pair; keeps reference to request and socket
// (the term "exchange" appears in the standard)

struct http_exchange {
    typedef std::function<void (http_exchange &)> log_func_t;

    http_request &req;
    netsock &sock;
    http_response resp {404};
    bool resp_sent {false};
    std::chrono::high_resolution_clock::time_point start;
    log_func_t log_func;

    http_exchange(http_request &req_, netsock &sock_, const log_func_t &log_func_)
        : req(req_),
          sock(sock_),
          start(std::chrono::steady_clock::now()),
          log_func(log_func_)
        {}

    http_exchange(const http_exchange &) = delete;
    http_exchange &operator=(const http_exchange &) = delete;

    ~http_exchange() {
        if (log_func) {
            log_func(*this);
        }
        if (!resp_sent) {
            // ensure a response is sent
            send_response();
        }
        if (resp.close_after()) {
            sock.close();
        }
    }

    //! compose a uri from the request uri
    uri get_uri(optional<std::string> host = nullopt) const {
        if (!host) {
            if (boost::starts_with(req.uri, "http://")) {
                // TODO: transform to preserve passed in host
                return req.uri;
            }
            host = req.get(hs::Host);
            if (!host)
                host.emplace("localhost");
        }
        uri tmp;
        tmp.scheme = "http";
        tmp.host = *host;
        tmp.path = req.uri;
        return tmp.compose();
    }

    //! send response to this request
    ssize_t send_response() {
        if (resp_sent) return 0;
        resp_sent = true;
        // TODO: Content-Length might be good to add to normal responses,
        //    but only if Transfer-Encoding isn't chunked?
        if (resp.status_code >= 400 && resp.status_code <= 599
            && !resp.get(hs::Content_Length)
            && req.method != hs::HEAD)
        {
            resp.set(hs::Content_Length, resp.body.size());
        }

        // HTTP/1.1 requires Date, so lets add it
        if (!resp.get(hs::Date)) {
            resp.set(hs::Date, http_base::rfc822_date());
        }

        // obey client's wishes on closing if we have none of our own,
        //  else prefer to keep http 1.1 open
        if (!resp.get(hs::Connection)) {
            const auto conn_hdr = req.get(hs::Connection);
            if (conn_hdr)
                resp.set(hs::Connection, *conn_hdr);
            else if (req.version == http_1_0)
                resp.set(hs::Connection, hs::close);
        }

        auto data = resp.data();
        if (!resp.body.empty() && req.method != hs::HEAD) {
            data += resp.body;
        }
        ssize_t nw = sock.send(data.data(), data.size());
        return nw;
    }

    //! the ip of the host making the request
    //! might use the X-Forwarded-For header
    optional<std::string> agent_ip(bool use_xff=false) const {
        if (use_xff) {
            auto xff_hdr = req.get("X-Forwarded-For");
            if (xff_hdr && !(*xff_hdr).empty()) {
                const char *xff = xff_hdr->c_str();
                // pick the first addr
                int i;
                for (i=0; *xff && i<256 && !isdigit((unsigned char)*xff); i++, xff++) {}
                if (*xff && i < 256) {
                    // now, find the end
                    const char *e = xff;
                    for (i = 0; *e && i<256 && (isdigit((unsigned char)*e) || *e == '.'); i++, e++) {}
                    if (i < 256 && e >= xff + 7 ) {
                        return std::string(xff, e - xff);
                    }
                }
            }
        }
        address addr;
        if (sock.getpeername(addr)) {
            char buf[INET6_ADDRSTRLEN];
            if (addr.ntop(buf, sizeof(buf))) {
                return optional<std::string>(emplace, buf);
            }
        }
        return nullopt;
    }
};

//! basic http server
class http_server : public netsock_server {
public:
    using log_func_t = http_exchange::log_func_t;
    typedef std::function<void (http_exchange &)> callback_type;

    std::function<void ()> connect_watch;
    std::function<void ()> disconnect_watch;

private:
    struct route {
        std::string pattern;
        callback_type callback;
        int fnmatch_flags;

        route(const std::string &pattern_,
                const callback_type &callback_,
                int fnmatch_flags_=0)
            : pattern(pattern_), callback(callback_), fnmatch_flags(fnmatch_flags_) {}
    };

    std::vector<route> _routes;
    log_func_t _log_func;

public:
    http_server(size_t stacksize_=default_stacksize, optional_timeout recv_timeout_ms_={})
        : netsock_server("http", stacksize_, recv_timeout_ms_)
    {
    }

    //! add a callback for a uri with an fnmatch pattern
    void add_route(const std::string &pattern,
            const callback_type &callback,
            int fnmatch_flags = 0)
    {
        _routes.emplace_back(pattern, callback, fnmatch_flags);
    }

    //! set logging function, called after every exchange
    void set_log_callback(const log_func_t &f) {
        _log_func = f;
    }

private:

    void setup_listen_socket(netsock &s) override {
        netsock_server::setup_listen_socket(s);
        s.setsockopt(IPPROTO_TCP, TCP_DEFER_ACCEPT, 30);
    }

    void on_connection(netsock &s) override {
        // TODO: tuneable buffer sizes
        buffer buf(4*1024);
        http_parser parser;

        if (connect_watch) {
            connect_watch();
        }

        bool nodelay_set = false;
        http_request req;
        while (s.valid()) {
            req.parser_init(&parser);
            bool got_headers = false;
            for (;;) {
                buf.reserve(4*1024);
                ssize_t nr = -1;
                if (buf.size() == 0) {
                    nr = s.recv(buf.back(), buf.available(), 0, _recv_timeout_ms);
                    if (nr <= 0) goto done;
                    buf.commit(nr);
                }
                size_t nparse = buf.size();
                req.parse(&parser, buf.front(), nparse);
                buf.remove(nparse);
                if (req.complete) {
                    DVLOG(4) << req.data();
                    // handle http exchange (request -> response)
                    http_exchange ex(req, s, _log_func);
                    if (!nodelay_set && !req.close_after()) {
                        // this is likely a persistent connection, so low-latency sending is worth the overh
                        s.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
                        nodelay_set = true;
                    }
                    handle_exchange(ex);
                    break;
                }
                if (nr == 0) goto done;
                if (!got_headers && !req.method.empty()) {
                    got_headers = true;
                    auto exp_hdr = req.get("Expect");
                    if (exp_hdr && *exp_hdr == "100-continue") {
                        http_response cont_resp(100);
                        std::string data = cont_resp.data();
                        ssize_t nw = s.send(data.data(), data.size());
                        (void)nw;
                    }
                }
            }
        }
done:
        if (disconnect_watch) {
            disconnect_watch();
        }
    }

    void handle_exchange(http_exchange &ex) {
        const auto path = ex.req.path();
        DVLOG(5) << "path: " << path;
        // not super efficient, but good enough
        // note: empty string pattern matches everything
        for (const auto &i : _routes) {
            DVLOG(5) << "matching pattern: " << i.pattern;
            if (i.pattern.empty() || fnmatch(i.pattern.c_str(), path.c_str(), i.fnmatch_flags) == 0) {
                try {
                    i.callback(std::ref(ex));
                } catch (std::exception &e) {
                    DVLOG(2) << "unhandled exception in route [" << i.pattern << "]: " << e.what();
                    ex.resp = http_response(500, http_headers{hs::Connection, hs::close});
                    std::string msg = e.what();
                    if (!msg.empty() && *msg.rbegin() != '\n')
                        msg += '\n';
                    ex.resp.set_body(msg, hs::text_plain);
                    ex.send_response();
                }
                break;
            }
        }
    }
};

} // end namespace ten

#endif // LIBTEN_HTTP_SERVER_HH
<|endoftext|>


<commit_before>#pragma once
#include <thero/assert.hpp>
#include <memory>

namespace th
{
template<typename Type>
class Optional
{
    public:
        Optional()
        {
        }

        template<typename ...Args>
        Optional(Args&& ...arguments)
        {
            mValue = std::make_shared<Type>(arguments...);
        }

        Optional(Type&& value)
        {
            mValue = std::make_shared<Type>(std::move(value));
        }

        Optional(const Optional& other)
        {
            if(other.mValue)
                mValue = std::make_shared<Type>(*other.mValue);
        }

        Optional(Optional& other):
            Optional(static_cast<const Optional&>(other))
        {
        }

        Optional(Optional&& other)
        {
            mValue = other.mValue;
            other.mValue = nullptr;
        }

        Optional& operator=(const Optional& other)
        {
            if(other.mValue)
                mValue = std::make_shared<Type>(*other.mValue);

            return *this;
        }

        Optional& operator=(Optional& other)
        {
            return *this = static_cast<const Optional&>(other);
        }

        Optional& operator=(Optional&& other)
        {
            mValue = other.mValue;
            other.mValue = nullptr;

            return *this;
        }

        explicit operator bool() const
        {
            return static_cast<bool>(mValue);
        }

        const Type& operator*() const
        {
            TH_ASSERT(mValue != nullptr, "Derferencing empty Optional");
            return *mValue;
        }

        Type& operator*()
        {
            TH_ASSERT(mValue != nullptr, "Derferencing empty Optional");
            return *mValue;
        }

        const Type* operator->() const
        {
            TH_ASSERT(mValue != nullptr, "Accessing empty Optional");
            return mValue.get();
        }

        Type* operator->()
        {
            TH_ASSERT(mValue != nullptr, "Accessing empty Optional");
            return mValue.get();
        }

        bool isNull()
        {
            return !*this;
        }

        template <typename InValue>
        Type valueOr(InValue&& in) const
        {
            if(*this)
                return **this;
            else
                return in;
        }

        template<typename ...Args>
        void reset(Args&& ...arguments)
        {
            mValue = std::make_shared<Type>(std::move(arguments...));
        }

        void reset(Type&& value)
        {
            mValue = std::make_shared<Type>(std::move(value));
        }

        void reset()
        {
            mValue.reset();
        }

    private:
        std::shared_ptr<Type> mValue;
};
}
<commit_msg>fixed bug in optional<commit_after>#pragma once
#include <thero/assert.hpp>
#include <memory>

namespace th
{
template<typename Type>
class Optional
{
    public:
        Optional()
        {
        }

        template<typename ...Args>
        Optional(Args&& ...arguments)
        {
            mValue = std::make_shared<Type>(arguments...);
        }

        Optional(Type&& value)
        {
            mValue = std::make_shared<Type>(std::move(value));
        }

        Optional(const Optional& other)
        {
            if(other.mValue)
                mValue = std::make_shared<Type>(*other.mValue);
            else
                mValue.reset();
        }

        Optional(Optional& other):
            Optional(static_cast<const Optional&>(other))
        {
        }

        Optional(Optional&& other)
        {
            mValue = other.mValue;
            other.mValue = nullptr;
        }

        Optional& operator=(const Optional& other)
        {
            if(other.mValue)
                mValue = std::make_shared<Type>(*other.mValue);
            else
                mValue.reset();

            return *this;
        }

        Optional& operator=(Optional& other)
        {
            return *this = static_cast<const Optional&>(other);
        }

        Optional& operator=(Optional&& other)
        {
            mValue = other.mValue;
            other.mValue = nullptr;

            return *this;
        }

        explicit operator bool() const
        {
            return static_cast<bool>(mValue);
        }

        const Type& operator*() const
        {
            TH_ASSERT(mValue != nullptr, "Derferencing empty Optional");
            return *mValue;
        }

        Type& operator*()
        {
            TH_ASSERT(mValue != nullptr, "Derferencing empty Optional");
            return *mValue;
        }

        const Type* operator->() const
        {
            TH_ASSERT(mValue != nullptr, "Accessing empty Optional");
            return mValue.get();
        }

        Type* operator->()
        {
            TH_ASSERT(mValue != nullptr, "Accessing empty Optional");
            return mValue.get();
        }

        bool isNull()
        {
            return !*this;
        }

        template <typename InValue>
        Type valueOr(InValue&& in) const
        {
            if(*this)
                return **this;
            else
                return in;
        }

        template<typename ...Args>
        void reset(Args&& ...arguments)
        {
            mValue = std::make_shared<Type>(std::move(arguments...));
        }

        void reset(Type&& value)
        {
            mValue = std::make_shared<Type>(std::move(value));
        }

        void reset()
        {
            mValue.reset();
        }

    private:
        std::shared_ptr<Type> mValue;
};
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2008-2014 The Communi Project
 *
 * This example is free, and not covered by the LGPL license. There is no
 * restriction applied to their modification, redistribution, using and so on.
 * You can study them, modify them, use them in your own program - either
 * completely or partially.
 */

#include "chatpage.h"
#include "treeitem.h"
#include "treewidget.h"
#include "themeloader.h"
#include "textdocument.h"
#include "pluginloader.h"
#include "bufferview.h"
#include "textinput.h"
#include "splitview.h"
#include "titlebar.h"
#include "finder.h"
#include "mainwindow.h"
#include "messageformatter.h"
#include <QCoreApplication>
#include <IrcCommandParser>
#include <IrcBufferModel>
#include <IrcConnection>
#include <QStringList>
#include <IrcChannel>
#include <IrcBuffer>
#include <QSettings>
#include <Irc>

ChatPage::ChatPage(QWidget* parent) : QSplitter(parent)
{
    d.showEvents = true;
    d.showDetails = true;
    d.timestampFormat = "[hh:mm:ss]";

    d.splitView = new SplitView(this);
    d.treeWidget = new TreeWidget(this);
    addWidget(d.treeWidget);
    addWidget(d.splitView);

    connect(d.treeWidget, SIGNAL(bufferClosed(IrcBuffer*)), this, SLOT(closeBuffer(IrcBuffer*)));

    connect(d.treeWidget, SIGNAL(currentBufferChanged(IrcBuffer*)), this, SIGNAL(currentBufferChanged(IrcBuffer*)));
    connect(d.treeWidget, SIGNAL(currentBufferChanged(IrcBuffer*)), d.splitView, SLOT(setCurrentBuffer(IrcBuffer*)));
    connect(d.splitView, SIGNAL(currentBufferChanged(IrcBuffer*)), d.treeWidget, SLOT(setCurrentBuffer(IrcBuffer*)));

    connect(d.splitView, SIGNAL(viewAdded(BufferView*)), this, SLOT(addView(BufferView*)));
    connect(d.splitView, SIGNAL(viewRemoved(BufferView*)), this, SLOT(removeView(BufferView*)));
    connect(d.splitView, SIGNAL(currentViewChanged(BufferView*)), this, SIGNAL(currentViewChanged(BufferView*)));

    setStretchFactor(1, 1);
}

ChatPage::~ChatPage()
{
}

void ChatPage::init()
{
    addView(d.splitView->currentView());
    new Finder(this);
}

void ChatPage::cleanup()
{
}

TreeWidget* ChatPage::treeWidget() const
{
    return d.treeWidget;
}

SplitView* ChatPage::splitView() const
{
    return d.splitView;
}

BufferView* ChatPage::currentView() const
{
    return d.splitView->currentView();
}

IrcBuffer* ChatPage::currentBuffer() const
{
    return d.treeWidget->currentBuffer();
}

QString ChatPage::theme() const
{
    return d.theme.name();
}

void ChatPage::setTheme(const QString& theme)
{
    if (!d.theme.isValid() || d.theme.name() != theme) {
        d.theme = ThemeLoader::instance()->theme(theme);

        QString css = d.theme.style();
        window()->setStyleSheet(css);

        QTreeWidgetItemIterator it(d.treeWidget);
        while (*it) {
            TreeItem* item = static_cast<TreeItem*>(*it);
            QList<TextDocument*> documents = item->buffer()->findChildren<TextDocument*>();
            foreach (TextDocument* doc, documents)
                doc->setStyleSheet(css);
            it++;
        }
    }
}

QFont ChatPage::messageFont() const
{
    return d.messageFont;
}

void ChatPage::setMessageFont(const QFont& font)
{
    d.messageFont = font;
}

QString ChatPage::timeStampFormat() const
{
    return d.timestampFormat;
}

void ChatPage::setTimeStampFormat(const QString& format)
{
    d.timestampFormat = format;
}

bool ChatPage::showEvents() const
{
    return d.showEvents;
}

void ChatPage::setShowEvents(bool show)
{
    d.showEvents = show;
}

bool ChatPage::showDetails() const
{
    return d.showDetails;
}

void ChatPage::setShowDetails(bool show)
{
    d.showDetails = show;
}

QByteArray ChatPage::saveSettings() const
{
    QVariantMap settings;
    settings.insert("theme", d.theme.name());
    settings.insert("showEvents", d.showEvents);
    settings.insert("showDetails", d.showDetails);
    settings.insert("fontFamily", d.messageFont.family());
    settings.insert("fontSize", d.messageFont.pixelSize());

    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out << settings;
    return data;
}

void ChatPage::restoreSettings(const QByteArray& data)
{
    QVariantMap settings;
    QDataStream in(data);
    in >> settings;

    setTheme(settings.value("theme", "Cute").toString());
    setShowEvents(settings.value("showEvents", true).toBool());
    setShowDetails(settings.value("showDetails", true).toBool());

    QFont font;
    font.setFamily(settings.value("fontFamily", font.family()).toString());
    font.setPixelSize(settings.value("fontSize", font.pixelSize()).toInt());
    setMessageFont(font);
}

QByteArray ChatPage::saveState() const
{
    QVariantMap state;
    state.insert("splitter", QSplitter::saveState());
    state.insert("views", d.splitView->saveState());
    state.insert("tree", d.treeWidget->saveState());

    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out << state;
    return data;
}

void ChatPage::restoreState(const QByteArray& data)
{
    QVariantMap state;
    QDataStream in(data);
    in >> state;

    if (state.contains("tree"))
        d.treeWidget->restoreState(state.value("tree").toByteArray());
    if (state.contains("splitter"))
        QSplitter::restoreState(state.value("splitter").toByteArray());
    if (state.contains("views"))
        d.splitView->restoreState(state.value("views").toByteArray());
}

bool ChatPage::commandFilter(IrcCommand* command)
{
    if (command->type() == IrcCommand::Join) {
        if (command->property("TextInput").toBool())
            d.chans += command->toString().split(" ", QString::SkipEmptyParts).value(1);
    } else if (command->type() == IrcCommand::Custom) {
        const QString cmd = command->parameters().value(0);
        const QStringList params = command->parameters().mid(1);
        if (cmd == "CLEAR") {
            d.splitView->currentView()->textDocument()->clear();
            return true;
        } else if (cmd == "CLOSE") {
            IrcBuffer* buffer = currentBuffer();
            IrcChannel* channel = buffer->toChannel();
            if (channel)
                channel->part(qApp->property("description").toString());
            buffer->deleteLater();
            return true;
        } else if (cmd == "MSG") {
            const QString target = params.value(0);
            const QString message = QStringList(params.mid(1)).join(" ");
            if (!message.isEmpty()) {
                IrcBuffer* buffer = currentBuffer()->model()->add(target);
                IrcCommand* command = IrcCommand::createMessage(target, message);
                if (buffer->sendCommand(command)) {
                    IrcConnection* connection = buffer->connection();
                    buffer->receiveMessage(command->toMessage(connection->nickName(), connection));
                }
                d.splitView->setCurrentBuffer(buffer);
                return true;
            }
        } else if (cmd == "QUERY") {
            const QString target = params.value(0);
            const QString message = QStringList(params.mid(1)).join(" ");
            IrcBuffer* buffer = currentBuffer()->model()->add(target);
            if (!message.isEmpty()) {
                IrcCommand* command = IrcCommand::createMessage(target, message);
                if (buffer->sendCommand(command)) {
                    IrcConnection* connection = buffer->connection();
                    buffer->receiveMessage(command->toMessage(connection->nickName(), connection));
                }
            }
            d.splitView->setCurrentBuffer(buffer);
            return true;
        }
    }
    return false;
}

void ChatPage::addConnection(IrcConnection* connection)
{
    IrcBufferModel* bufferModel = new IrcBufferModel(connection);
    bufferModel->setSortMethod(Irc::SortByTitle);

    IrcBuffer* serverBuffer = bufferModel->add(connection->displayName());
    serverBuffer->setSticky(true);
    connect(connection, SIGNAL(displayNameChanged(QString)), serverBuffer, SLOT(setName(QString)));
    // TODO: more fine-grained delivery (WHOIS replies etc. to the current buffer)
    connect(bufferModel, SIGNAL(messageIgnored(IrcMessage*)), serverBuffer, SLOT(receiveMessage(IrcMessage*)));

    connect(bufferModel, SIGNAL(added(IrcBuffer*)), this, SLOT(addBuffer(IrcBuffer*)));
    connect(connection, SIGNAL(socketError(QAbstractSocket::SocketError)), this, SLOT(onSocketError()));

    addBuffer(serverBuffer);
    if (!d.treeWidget->currentBuffer())
        d.treeWidget->setCurrentBuffer(serverBuffer);

    connection->installCommandFilter(this);
    if (!connection->isActive() && connection->isEnabled())
        connection->open();

    PluginLoader::instance()->connectionAdded(connection);
}

void ChatPage::removeConnection(IrcConnection* connection)
{
    IrcBufferModel* bufferModel = connection->findChild<IrcBufferModel*>();
    disconnect(bufferModel, SIGNAL(added(IrcBuffer*)), this, SLOT(addBuffer(IrcBuffer*)));

    if (connection->isActive()) {
        connection->quit(qApp->property("description").toString());
        connection->close();
    }
    connection->deleteLater();

    PluginLoader::instance()->connectionRemoved(connection);
}

void ChatPage::closeBuffer(IrcBuffer* buffer)
{
    if (!buffer)
        buffer = d.treeWidget->currentBuffer();

    IrcChannel* channel = buffer->toChannel();
    if (channel && channel->isActive())
        channel->part(qApp->property("description").toString());
    buffer->deleteLater();
}

void ChatPage::addBuffer(IrcBuffer* buffer)
{
    TextDocument* doc = new TextDocument(buffer);
    buffer->setPersistent(true);

    d.treeWidget->addBuffer(buffer);
    d.splitView->addBuffer(buffer);

    PluginLoader::instance()->bufferAdded(buffer);

    setupDocument(doc);
    PluginLoader::instance()->documentAdded(doc);

    connect(buffer, SIGNAL(destroyed(IrcBuffer*)), this, SLOT(removeBuffer(IrcBuffer*)));

    if (buffer->isChannel() && d.chans.contains(buffer->title())) {
        d.chans.removeAll(buffer->title());
        d.splitView->setCurrentBuffer(buffer);
    }
}

void ChatPage::removeBuffer(IrcBuffer* buffer)
{
    QList<TextDocument*> documents = buffer->findChildren<TextDocument*>();
    foreach (TextDocument* doc, documents)
        PluginLoader::instance()->documentRemoved(doc);

    d.treeWidget->removeBuffer(buffer);
    d.splitView->removeBuffer(buffer);

    if (buffer->isSticky())
        buffer->connection()->deleteLater();

    PluginLoader::instance()->bufferRemoved(buffer);
}

void ChatPage::setupDocument(TextDocument* document)
{
    document->setStyleSheet(d.theme.style());
    document->setDefaultFont(d.messageFont);
    document->formatter()->setDetailed(d.showDetails);
    document->formatter()->setStripNicks(!d.showDetails);
    document->formatter()->setTimeStampFormat(d.timestampFormat);
}

void ChatPage::addView(BufferView* view)
{
    TitleBar* bar = view->titleBar();
    QTextDocument* doc = bar->findChild<QTextDocument*>();
    if (doc)
        doc->setDefaultStyleSheet(d.theme.style());

    view->textInput()->setParser(createParser(view));
    connect(view, SIGNAL(bufferClosed(IrcBuffer*)), this, SLOT(closeBuffer(IrcBuffer*)));
    connect(view, SIGNAL(cloned(TextDocument*)), this, SLOT(setupDocument(TextDocument*)));
    connect(view, SIGNAL(cloned(TextDocument*)), PluginLoader::instance(), SLOT(documentAdded(TextDocument*)));

    PluginLoader::instance()->viewAdded(view);
}

void ChatPage::removeView(BufferView* view)
{
    PluginLoader::instance()->viewRemoved(view);
}

void ChatPage::onSocketError()
{
    IrcConnection* connection = qobject_cast<IrcConnection*>(sender());
    if (connection) {
        IrcBufferModel* model = connection->findChild<IrcBufferModel*>(); // TODO
        if (model) {
            IrcBuffer* buffer = model->get(0);
            if (buffer) {
                QStringList params = QStringList() << connection->nickName() << connection->socket()->errorString();
                IrcMessage* message = IrcMessage::fromParameters(buffer->title(), QString::number(Irc::ERR_UNKNOWNERROR), params, connection);
                foreach (TextDocument* doc, buffer->findChildren<TextDocument*>())
                    doc->receiveMessage(message);
                delete message;

                TreeItem* item = d.treeWidget->connectionItem(connection);
                if (item && d.treeWidget->currentItem() != item)
                    d.treeWidget->highlightItem(item);
            }
        }
    }
}

IrcCommandParser* ChatPage::createParser(QObject *parent)
{
    IrcCommandParser* parser = new IrcCommandParser(parent);
    parser->setTriggers(QStringList("/"));
    parser->setTolerant(true);

    // TODO: IrcCommandParser: static default commands?
    parser->addCommand(IrcCommand::CtcpAction, "ACTION <target> <message...>");
    parser->addCommand(IrcCommand::Admin, "ADMIN (<server>)");
    parser->addCommand(IrcCommand::Away, "AWAY (<reason...>)");
    parser->addCommand(IrcCommand::Info, "INFO (<server>)");
    parser->addCommand(IrcCommand::Invite, "INVITE <user> (<#channel>)");
    parser->addCommand(IrcCommand::Join, "JOIN <#channel> (<key>)");
    parser->addCommand(IrcCommand::Kick, "KICK (<#channel>) <user> (<reason...>)");
    parser->addCommand(IrcCommand::Knock, "KNOCK <#channel> (<message...>)");
    parser->addCommand(IrcCommand::List, "LIST (<channels>) (<server>)");
    parser->addCommand(IrcCommand::CtcpAction, "ME [target] <message...>");
    parser->addCommand(IrcCommand::Mode, "MODE (<channel/user>) (<mode>) (<arg>)");
    parser->addCommand(IrcCommand::Motd, "MOTD (<server>)");
    parser->addCommand(IrcCommand::Names, "NAMES (<#channel>)");
    parser->addCommand(IrcCommand::Nick, "NICK <nick>");
    parser->addCommand(IrcCommand::Notice, "NOTICE <#channel/user> <message...>");
    parser->addCommand(IrcCommand::Part, "PART (<#channel>) (<message...>)");
    parser->addCommand(IrcCommand::Ping, "PING (<user>)");
    parser->addCommand(IrcCommand::Quit, "QUIT (<message...>)");
    parser->addCommand(IrcCommand::Quote, "QUOTE <command> (<parameters...>)");
    parser->addCommand(IrcCommand::Stats, "STATS <query> (<server>)");
    parser->addCommand(IrcCommand::Time, "TIME (<user>)");
    parser->addCommand(IrcCommand::Topic, "TOPIC (<#channel>) (<topic...>)");
    parser->addCommand(IrcCommand::Trace, "TRACE (<target>)");
    parser->addCommand(IrcCommand::Users, "USERS (<server>)");
    parser->addCommand(IrcCommand::Version, "VERSION (<user>)");
    parser->addCommand(IrcCommand::Who, "WHO <user>");
    parser->addCommand(IrcCommand::Whois, "WHOIS <user>");
    parser->addCommand(IrcCommand::Whowas, "WHOWAS <user>");

    parser->addCommand(IrcCommand::Custom, "CLEAR");
    parser->addCommand(IrcCommand::Custom, "CLOSE");
    parser->addCommand(IrcCommand::Custom, "MSG <user/channel> <message...>");
    parser->addCommand(IrcCommand::Custom, "QUERY <user> (<message...>)");

    return parser;
}
<commit_msg>Cleanup ChatPage::setTheme()<commit_after>/*
 * Copyright (C) 2008-2014 The Communi Project
 *
 * This example is free, and not covered by the LGPL license. There is no
 * restriction applied to their modification, redistribution, using and so on.
 * You can study them, modify them, use them in your own program - either
 * completely or partially.
 */

#include "chatpage.h"
#include "treeitem.h"
#include "treewidget.h"
#include "themeloader.h"
#include "textdocument.h"
#include "pluginloader.h"
#include "bufferview.h"
#include "textinput.h"
#include "splitview.h"
#include "titlebar.h"
#include "finder.h"
#include "mainwindow.h"
#include "messageformatter.h"
#include <QCoreApplication>
#include <IrcCommandParser>
#include <IrcBufferModel>
#include <IrcConnection>
#include <QStringList>
#include <IrcChannel>
#include <IrcBuffer>
#include <QSettings>
#include <Irc>

ChatPage::ChatPage(QWidget* parent) : QSplitter(parent)
{
    d.showEvents = true;
    d.showDetails = true;
    d.timestampFormat = "[hh:mm:ss]";

    d.splitView = new SplitView(this);
    d.treeWidget = new TreeWidget(this);
    addWidget(d.treeWidget);
    addWidget(d.splitView);

    connect(d.treeWidget, SIGNAL(bufferClosed(IrcBuffer*)), this, SLOT(closeBuffer(IrcBuffer*)));

    connect(d.treeWidget, SIGNAL(currentBufferChanged(IrcBuffer*)), this, SIGNAL(currentBufferChanged(IrcBuffer*)));
    connect(d.treeWidget, SIGNAL(currentBufferChanged(IrcBuffer*)), d.splitView, SLOT(setCurrentBuffer(IrcBuffer*)));
    connect(d.splitView, SIGNAL(currentBufferChanged(IrcBuffer*)), d.treeWidget, SLOT(setCurrentBuffer(IrcBuffer*)));

    connect(d.splitView, SIGNAL(viewAdded(BufferView*)), this, SLOT(addView(BufferView*)));
    connect(d.splitView, SIGNAL(viewRemoved(BufferView*)), this, SLOT(removeView(BufferView*)));
    connect(d.splitView, SIGNAL(currentViewChanged(BufferView*)), this, SIGNAL(currentViewChanged(BufferView*)));

    setStretchFactor(1, 1);
}

ChatPage::~ChatPage()
{
}

void ChatPage::init()
{
    addView(d.splitView->currentView());
    new Finder(this);
}

void ChatPage::cleanup()
{
}

TreeWidget* ChatPage::treeWidget() const
{
    return d.treeWidget;
}

SplitView* ChatPage::splitView() const
{
    return d.splitView;
}

BufferView* ChatPage::currentView() const
{
    return d.splitView->currentView();
}

IrcBuffer* ChatPage::currentBuffer() const
{
    return d.treeWidget->currentBuffer();
}

QString ChatPage::theme() const
{
    return d.theme.name();
}

void ChatPage::setTheme(const QString& theme)
{
    if (!d.theme.isValid() || d.theme.name() != theme) {
        d.theme = ThemeLoader::instance()->theme(theme);

        QString css = d.theme.style();
        window()->setStyleSheet(css);
    }
}

QFont ChatPage::messageFont() const
{
    return d.messageFont;
}

void ChatPage::setMessageFont(const QFont& font)
{
    d.messageFont = font;
}

QString ChatPage::timeStampFormat() const
{
    return d.timestampFormat;
}

void ChatPage::setTimeStampFormat(const QString& format)
{
    d.timestampFormat = format;
}

bool ChatPage::showEvents() const
{
    return d.showEvents;
}

void ChatPage::setShowEvents(bool show)
{
    d.showEvents = show;
}

bool ChatPage::showDetails() const
{
    return d.showDetails;
}

void ChatPage::setShowDetails(bool show)
{
    d.showDetails = show;
}

QByteArray ChatPage::saveSettings() const
{
    QVariantMap settings;
    settings.insert("theme", d.theme.name());
    settings.insert("showEvents", d.showEvents);
    settings.insert("showDetails", d.showDetails);
    settings.insert("fontFamily", d.messageFont.family());
    settings.insert("fontSize", d.messageFont.pixelSize());

    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out << settings;
    return data;
}

void ChatPage::restoreSettings(const QByteArray& data)
{
    QVariantMap settings;
    QDataStream in(data);
    in >> settings;

    setTheme(settings.value("theme", "Cute").toString());
    setShowEvents(settings.value("showEvents", true).toBool());
    setShowDetails(settings.value("showDetails", true).toBool());

    QFont font;
    font.setFamily(settings.value("fontFamily", font.family()).toString());
    font.setPixelSize(settings.value("fontSize", font.pixelSize()).toInt());
    setMessageFont(font);
}

QByteArray ChatPage::saveState() const
{
    QVariantMap state;
    state.insert("splitter", QSplitter::saveState());
    state.insert("views", d.splitView->saveState());
    state.insert("tree", d.treeWidget->saveState());

    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out << state;
    return data;
}

void ChatPage::restoreState(const QByteArray& data)
{
    QVariantMap state;
    QDataStream in(data);
    in >> state;

    if (state.contains("tree"))
        d.treeWidget->restoreState(state.value("tree").toByteArray());
    if (state.contains("splitter"))
        QSplitter::restoreState(state.value("splitter").toByteArray());
    if (state.contains("views"))
        d.splitView->restoreState(state.value("views").toByteArray());
}

bool ChatPage::commandFilter(IrcCommand* command)
{
    if (command->type() == IrcCommand::Join) {
        if (command->property("TextInput").toBool())
            d.chans += command->toString().split(" ", QString::SkipEmptyParts).value(1);
    } else if (command->type() == IrcCommand::Custom) {
        const QString cmd = command->parameters().value(0);
        const QStringList params = command->parameters().mid(1);
        if (cmd == "CLEAR") {
            d.splitView->currentView()->textDocument()->clear();
            return true;
        } else if (cmd == "CLOSE") {
            IrcBuffer* buffer = currentBuffer();
            IrcChannel* channel = buffer->toChannel();
            if (channel)
                channel->part(qApp->property("description").toString());
            buffer->deleteLater();
            return true;
        } else if (cmd == "MSG") {
            const QString target = params.value(0);
            const QString message = QStringList(params.mid(1)).join(" ");
            if (!message.isEmpty()) {
                IrcBuffer* buffer = currentBuffer()->model()->add(target);
                IrcCommand* command = IrcCommand::createMessage(target, message);
                if (buffer->sendCommand(command)) {
                    IrcConnection* connection = buffer->connection();
                    buffer->receiveMessage(command->toMessage(connection->nickName(), connection));
                }
                d.splitView->setCurrentBuffer(buffer);
                return true;
            }
        } else if (cmd == "QUERY") {
            const QString target = params.value(0);
            const QString message = QStringList(params.mid(1)).join(" ");
            IrcBuffer* buffer = currentBuffer()->model()->add(target);
            if (!message.isEmpty()) {
                IrcCommand* command = IrcCommand::createMessage(target, message);
                if (buffer->sendCommand(command)) {
                    IrcConnection* connection = buffer->connection();
                    buffer->receiveMessage(command->toMessage(connection->nickName(), connection));
                }
            }
            d.splitView->setCurrentBuffer(buffer);
            return true;
        }
    }
    return false;
}

void ChatPage::addConnection(IrcConnection* connection)
{
    IrcBufferModel* bufferModel = new IrcBufferModel(connection);
    bufferModel->setSortMethod(Irc::SortByTitle);

    IrcBuffer* serverBuffer = bufferModel->add(connection->displayName());
    serverBuffer->setSticky(true);
    connect(connection, SIGNAL(displayNameChanged(QString)), serverBuffer, SLOT(setName(QString)));
    // TODO: more fine-grained delivery (WHOIS replies etc. to the current buffer)
    connect(bufferModel, SIGNAL(messageIgnored(IrcMessage*)), serverBuffer, SLOT(receiveMessage(IrcMessage*)));

    connect(bufferModel, SIGNAL(added(IrcBuffer*)), this, SLOT(addBuffer(IrcBuffer*)));
    connect(connection, SIGNAL(socketError(QAbstractSocket::SocketError)), this, SLOT(onSocketError()));

    addBuffer(serverBuffer);
    if (!d.treeWidget->currentBuffer())
        d.treeWidget->setCurrentBuffer(serverBuffer);

    connection->installCommandFilter(this);
    if (!connection->isActive() && connection->isEnabled())
        connection->open();

    PluginLoader::instance()->connectionAdded(connection);
}

void ChatPage::removeConnection(IrcConnection* connection)
{
    IrcBufferModel* bufferModel = connection->findChild<IrcBufferModel*>();
    disconnect(bufferModel, SIGNAL(added(IrcBuffer*)), this, SLOT(addBuffer(IrcBuffer*)));

    if (connection->isActive()) {
        connection->quit(qApp->property("description").toString());
        connection->close();
    }
    connection->deleteLater();

    PluginLoader::instance()->connectionRemoved(connection);
}

void ChatPage::closeBuffer(IrcBuffer* buffer)
{
    if (!buffer)
        buffer = d.treeWidget->currentBuffer();

    IrcChannel* channel = buffer->toChannel();
    if (channel && channel->isActive())
        channel->part(qApp->property("description").toString());
    buffer->deleteLater();
}

void ChatPage::addBuffer(IrcBuffer* buffer)
{
    TextDocument* doc = new TextDocument(buffer);
    buffer->setPersistent(true);

    d.treeWidget->addBuffer(buffer);
    d.splitView->addBuffer(buffer);

    PluginLoader::instance()->bufferAdded(buffer);

    setupDocument(doc);
    PluginLoader::instance()->documentAdded(doc);

    connect(buffer, SIGNAL(destroyed(IrcBuffer*)), this, SLOT(removeBuffer(IrcBuffer*)));

    if (buffer->isChannel() && d.chans.contains(buffer->title())) {
        d.chans.removeAll(buffer->title());
        d.splitView->setCurrentBuffer(buffer);
    }
}

void ChatPage::removeBuffer(IrcBuffer* buffer)
{
    QList<TextDocument*> documents = buffer->findChildren<TextDocument*>();
    foreach (TextDocument* doc, documents)
        PluginLoader::instance()->documentRemoved(doc);

    d.treeWidget->removeBuffer(buffer);
    d.splitView->removeBuffer(buffer);

    if (buffer->isSticky())
        buffer->connection()->deleteLater();

    PluginLoader::instance()->bufferRemoved(buffer);
}

void ChatPage::setupDocument(TextDocument* document)
{
    document->setStyleSheet(d.theme.style());
    document->setDefaultFont(d.messageFont);
    document->formatter()->setDetailed(d.showDetails);
    document->formatter()->setStripNicks(!d.showDetails);
    document->formatter()->setTimeStampFormat(d.timestampFormat);
}

void ChatPage::addView(BufferView* view)
{
    TitleBar* bar = view->titleBar();
    QTextDocument* doc = bar->findChild<QTextDocument*>();
    if (doc)
        doc->setDefaultStyleSheet(d.theme.style());

    view->textInput()->setParser(createParser(view));
    connect(view, SIGNAL(bufferClosed(IrcBuffer*)), this, SLOT(closeBuffer(IrcBuffer*)));
    connect(view, SIGNAL(cloned(TextDocument*)), this, SLOT(setupDocument(TextDocument*)));
    connect(view, SIGNAL(cloned(TextDocument*)), PluginLoader::instance(), SLOT(documentAdded(TextDocument*)));

    PluginLoader::instance()->viewAdded(view);
}

void ChatPage::removeView(BufferView* view)
{
    PluginLoader::instance()->viewRemoved(view);
}

void ChatPage::onSocketError()
{
    IrcConnection* connection = qobject_cast<IrcConnection*>(sender());
    if (connection) {
        IrcBufferModel* model = connection->findChild<IrcBufferModel*>(); // TODO
        if (model) {
            IrcBuffer* buffer = model->get(0);
            if (buffer) {
                QStringList params = QStringList() << connection->nickName() << connection->socket()->errorString();
                IrcMessage* message = IrcMessage::fromParameters(buffer->title(), QString::number(Irc::ERR_UNKNOWNERROR), params, connection);
                foreach (TextDocument* doc, buffer->findChildren<TextDocument*>())
                    doc->receiveMessage(message);
                delete message;

                TreeItem* item = d.treeWidget->connectionItem(connection);
                if (item && d.treeWidget->currentItem() != item)
                    d.treeWidget->highlightItem(item);
            }
        }
    }
}

IrcCommandParser* ChatPage::createParser(QObject *parent)
{
    IrcCommandParser* parser = new IrcCommandParser(parent);
    parser->setTriggers(QStringList("/"));
    parser->setTolerant(true);

    // TODO: IrcCommandParser: static default commands?
    parser->addCommand(IrcCommand::CtcpAction, "ACTION <target> <message...>");
    parser->addCommand(IrcCommand::Admin, "ADMIN (<server>)");
    parser->addCommand(IrcCommand::Away, "AWAY (<reason...>)");
    parser->addCommand(IrcCommand::Info, "INFO (<server>)");
    parser->addCommand(IrcCommand::Invite, "INVITE <user> (<#channel>)");
    parser->addCommand(IrcCommand::Join, "JOIN <#channel> (<key>)");
    parser->addCommand(IrcCommand::Kick, "KICK (<#channel>) <user> (<reason...>)");
    parser->addCommand(IrcCommand::Knock, "KNOCK <#channel> (<message...>)");
    parser->addCommand(IrcCommand::List, "LIST (<channels>) (<server>)");
    parser->addCommand(IrcCommand::CtcpAction, "ME [target] <message...>");
    parser->addCommand(IrcCommand::Mode, "MODE (<channel/user>) (<mode>) (<arg>)");
    parser->addCommand(IrcCommand::Motd, "MOTD (<server>)");
    parser->addCommand(IrcCommand::Names, "NAMES (<#channel>)");
    parser->addCommand(IrcCommand::Nick, "NICK <nick>");
    parser->addCommand(IrcCommand::Notice, "NOTICE <#channel/user> <message...>");
    parser->addCommand(IrcCommand::Part, "PART (<#channel>) (<message...>)");
    parser->addCommand(IrcCommand::Ping, "PING (<user>)");
    parser->addCommand(IrcCommand::Quit, "QUIT (<message...>)");
    parser->addCommand(IrcCommand::Quote, "QUOTE <command> (<parameters...>)");
    parser->addCommand(IrcCommand::Stats, "STATS <query> (<server>)");
    parser->addCommand(IrcCommand::Time, "TIME (<user>)");
    parser->addCommand(IrcCommand::Topic, "TOPIC (<#channel>) (<topic...>)");
    parser->addCommand(IrcCommand::Trace, "TRACE (<target>)");
    parser->addCommand(IrcCommand::Users, "USERS (<server>)");
    parser->addCommand(IrcCommand::Version, "VERSION (<user>)");
    parser->addCommand(IrcCommand::Who, "WHO <user>");
    parser->addCommand(IrcCommand::Whois, "WHOIS <user>");
    parser->addCommand(IrcCommand::Whowas, "WHOWAS <user>");

    parser->addCommand(IrcCommand::Custom, "CLEAR");
    parser->addCommand(IrcCommand::Custom, "CLOSE");
    parser->addCommand(IrcCommand::Custom, "MSG <user/channel> <message...>");
    parser->addCommand(IrcCommand::Custom, "QUERY <user> (<message...>)");

    return parser;
}
<|endoftext|>


<commit_before>/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * 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 REALM_KEYS_HPP
#define REALM_KEYS_HPP

#include <realm/util/to_string.hpp>
#include <realm/column_type.hpp>
#include <ostream>
#include <vector>

namespace realm {

class ConstObj;
class Obj;

struct TableKey {
    static constexpr uint32_t null_value = uint32_t(-1) >> 1; // free top bit
    constexpr TableKey() noexcept
        : value(null_value)
    {
    }
    explicit TableKey(uint32_t val) noexcept
        : value(val)
    {
    }
    TableKey& operator=(uint32_t val) noexcept
    {
        value = val;
        return *this;
    }
    bool operator==(const TableKey& rhs) const noexcept
    {
        return value == rhs.value;
    }
    bool operator!=(const TableKey& rhs) const noexcept
    {
        return value != rhs.value;
    }
    bool operator<(const TableKey& rhs) const noexcept
    {
        return value < rhs.value;
    }
    bool operator>(const TableKey& rhs) const noexcept
    {
        return value > rhs.value;
    }

    explicit operator bool() const noexcept
    {
        return value != null_value;
    }
    uint32_t value;
};

inline std::ostream& operator<<(std::ostream& os, TableKey tk)
{
    os << "TableKey(" << tk.value << ")";
    return os;
}

namespace util {

inline std::string to_string(TableKey tk)
{
    return to_string(tk.value);
}
}

class TableVersions : public std::vector<std::pair<TableKey, uint64_t>> {
public:
    TableVersions()
    {
    }
    TableVersions(TableKey key, uint64_t version)
    {
        emplace_back(key, version);
    }
    bool operator==(const TableVersions& other) const;
};

struct ColKey {
    struct Idx {
        unsigned val;
    };

    constexpr ColKey() noexcept
        : value(uint64_t(-1) >> 1) // free top bit
    {
    }
    constexpr explicit ColKey(int64_t val) noexcept
        : value(val)
    {
    }
    constexpr ColKey(Idx index, ColumnType type, ColumnAttrMask attrs, unsigned tag) noexcept
        : ColKey((index.val & 0xFFFFUL) | ((type & 0x3FUL) << 16) | ((attrs.m_value & 0xFFUL) << 22) |
                 ((tag & 0xFFFFFFFFUL) << 30))
    {
    }
    bool is_nullable() const
    {
        return get_attrs().test(col_attr_Nullable);
    }
    bool is_list() const
    {
        return get_attrs().test(col_attr_List);
    }
    bool is_set() const
    {
        return get_attrs().test(col_attr_Set);
    }
    bool is_dictionary()
    {
        return get_attrs().test(col_attr_Dictionary);
    }
    bool is_collection()
    {
        return get_attrs().test(col_attr_Collection);
    }
    ColKey& operator=(int64_t val) noexcept
    {
        value = val;
        return *this;
    }
    bool operator==(const ColKey& rhs) const noexcept
    {
        return value == rhs.value;
    }
    bool operator!=(const ColKey& rhs) const noexcept
    {
        return value != rhs.value;
    }
    bool operator<(const ColKey& rhs) const noexcept
    {
        return value < rhs.value;
    }
    bool operator>(const ColKey& rhs) const noexcept
    {
        return value > rhs.value;
    }
    explicit operator bool() const noexcept
    {
        return value != ColKey().value;
    }
    Idx get_index() const noexcept
    {
        return Idx{static_cast<unsigned>(value) & 0xFFFFU};
    }
    ColumnType get_type() const noexcept
    {
        return ColumnType((static_cast<unsigned>(value) >> 16) & 0x3F);
    }
    ColumnAttrMask get_attrs() const noexcept
    {
        return ColumnAttrMask((static_cast<unsigned>(value) >> 22) & 0xFF);
    }
    unsigned get_tag() const noexcept
    {
        return (value >> 30) & 0xFFFFFFFFUL;
    }
    int64_t value;
};

inline std::ostream& operator<<(std::ostream& os, ColKey ck)
{
    os << "ColKey(" << ck.value << ")";
    return os;
}

struct ObjKey {
    constexpr ObjKey() noexcept
        : value(-1)
    {
    }
    explicit constexpr ObjKey(int64_t val) noexcept
        : value(val)
    {
    }
    bool is_unresolved() const
    {
        return value <= -2;
    }
    ObjKey get_unresolved() const
    {
        return ObjKey(-2 - value);
    }
    ObjKey& operator=(int64_t val) noexcept
    {
        value = val;
        return *this;
    }
    bool operator==(const ObjKey& rhs) const noexcept
    {
        return value == rhs.value;
    }
    bool operator!=(const ObjKey& rhs) const noexcept
    {
        return value != rhs.value;
    }
    bool operator<(const ObjKey& rhs) const noexcept
    {
        return value < rhs.value;
    }
    bool operator<=(const ObjKey& rhs) const noexcept
    {
        return value <= rhs.value;
    }
    bool operator>(const ObjKey& rhs) const noexcept
    {
        return value > rhs.value;
    }
    bool operator>=(const ObjKey& rhs) const noexcept
    {
        return value >= rhs.value;
    }
    explicit operator bool() const noexcept
    {
        return value != -1;
    }
    int64_t value;

private:
    // operator bool will enable casting to integer. Prevent this.
    operator int64_t() const = delete;
};

inline std::ostream& operator<<(std::ostream& ostr, ObjKey key)
{
    ostr << "ObjKey(" << key.value << ")";
    return ostr;
}

class ObjKeys : public std::vector<ObjKey> {
public:
    ObjKeys(const std::vector<int64_t>& init)
    {
        reserve(init.size());
        for (auto i : init) {
            emplace_back(i);
        }
    }
    ObjKeys()
    {
    }
};

struct ObjLink {
public:
    ObjLink() {}
    ObjLink(TableKey table_key, ObjKey obj_key)
        : m_obj_key(obj_key)
        , m_table_key(table_key)
    {
    }
    explicit operator bool() const
    {
        return m_table_key.operator bool();
    }
    bool is_null() const
    {
        return !m_table_key.operator bool();
    }
    bool operator==(const ObjLink& other) const
    {
        return m_obj_key == other.m_obj_key && m_table_key == other.m_table_key;
    }
    bool operator!=(const ObjLink& other) const
    {
        return m_obj_key != other.m_obj_key || m_table_key != other.m_table_key;
    }
    bool operator<(const ObjLink& rhs) const
    {
        return m_table_key < rhs.m_table_key || m_obj_key < rhs.m_obj_key;
    }
    bool operator>(const ObjLink& rhs) const
    {
        return m_table_key > rhs.m_table_key || m_obj_key > rhs.m_obj_key;
    }
    TableKey get_table_key() const
    {
        return m_table_key;
    }
    ObjKey get_obj_key() const
    {
        return m_obj_key;
    }

private:
    // Having ObjKey first ensures that there will be no uninitialized space
    // in the first 12 bytes. This is important when generating a hash
    ObjKey m_obj_key;
    TableKey m_table_key;
};

inline std::ostream& operator<<(std::ostream& os, ObjLink link)
{
    os << '{' << link.get_table_key() << ',' << link.get_obj_key() << '}';
    return os;
}
constexpr ObjKey null_key;

namespace util {

inline std::string to_string(ColKey ck)
{
    return to_string(ck.value);
}

} // namespace util

} // namespace realm

namespace std {

template <>
struct hash<realm::ObjKey> {
    size_t operator()(realm::ObjKey key) const
    {
        return std::hash<uint64_t>{}(key.value);
    }
};

} // namespace std

#endif
<commit_msg>Constify ColKey::is_dictionary()<commit_after>/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * 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 REALM_KEYS_HPP
#define REALM_KEYS_HPP

#include <realm/util/to_string.hpp>
#include <realm/column_type.hpp>
#include <ostream>
#include <vector>

namespace realm {

class ConstObj;
class Obj;

struct TableKey {
    static constexpr uint32_t null_value = uint32_t(-1) >> 1; // free top bit
    constexpr TableKey() noexcept
        : value(null_value)
    {
    }
    explicit TableKey(uint32_t val) noexcept
        : value(val)
    {
    }
    TableKey& operator=(uint32_t val) noexcept
    {
        value = val;
        return *this;
    }
    bool operator==(const TableKey& rhs) const noexcept
    {
        return value == rhs.value;
    }
    bool operator!=(const TableKey& rhs) const noexcept
    {
        return value != rhs.value;
    }
    bool operator<(const TableKey& rhs) const noexcept
    {
        return value < rhs.value;
    }
    bool operator>(const TableKey& rhs) const noexcept
    {
        return value > rhs.value;
    }

    explicit operator bool() const noexcept
    {
        return value != null_value;
    }
    uint32_t value;
};

inline std::ostream& operator<<(std::ostream& os, TableKey tk)
{
    os << "TableKey(" << tk.value << ")";
    return os;
}

namespace util {

inline std::string to_string(TableKey tk)
{
    return to_string(tk.value);
}
}

class TableVersions : public std::vector<std::pair<TableKey, uint64_t>> {
public:
    TableVersions()
    {
    }
    TableVersions(TableKey key, uint64_t version)
    {
        emplace_back(key, version);
    }
    bool operator==(const TableVersions& other) const;
};

struct ColKey {
    struct Idx {
        unsigned val;
    };

    constexpr ColKey() noexcept
        : value(uint64_t(-1) >> 1) // free top bit
    {
    }
    constexpr explicit ColKey(int64_t val) noexcept
        : value(val)
    {
    }
    constexpr ColKey(Idx index, ColumnType type, ColumnAttrMask attrs, unsigned tag) noexcept
        : ColKey((index.val & 0xFFFFUL) | ((type & 0x3FUL) << 16) | ((attrs.m_value & 0xFFUL) << 22) |
                 ((tag & 0xFFFFFFFFUL) << 30))
    {
    }
    bool is_nullable() const
    {
        return get_attrs().test(col_attr_Nullable);
    }
    bool is_list() const
    {
        return get_attrs().test(col_attr_List);
    }
    bool is_set() const
    {
        return get_attrs().test(col_attr_Set);
    }
    bool is_dictionary() const
    {
        return get_attrs().test(col_attr_Dictionary);
    }
    bool is_collection()
    {
        return get_attrs().test(col_attr_Collection);
    }
    ColKey& operator=(int64_t val) noexcept
    {
        value = val;
        return *this;
    }
    bool operator==(const ColKey& rhs) const noexcept
    {
        return value == rhs.value;
    }
    bool operator!=(const ColKey& rhs) const noexcept
    {
        return value != rhs.value;
    }
    bool operator<(const ColKey& rhs) const noexcept
    {
        return value < rhs.value;
    }
    bool operator>(const ColKey& rhs) const noexcept
    {
        return value > rhs.value;
    }
    explicit operator bool() const noexcept
    {
        return value != ColKey().value;
    }
    Idx get_index() const noexcept
    {
        return Idx{static_cast<unsigned>(value) & 0xFFFFU};
    }
    ColumnType get_type() const noexcept
    {
        return ColumnType((static_cast<unsigned>(value) >> 16) & 0x3F);
    }
    ColumnAttrMask get_attrs() const noexcept
    {
        return ColumnAttrMask((static_cast<unsigned>(value) >> 22) & 0xFF);
    }
    unsigned get_tag() const noexcept
    {
        return (value >> 30) & 0xFFFFFFFFUL;
    }
    int64_t value;
};

inline std::ostream& operator<<(std::ostream& os, ColKey ck)
{
    os << "ColKey(" << ck.value << ")";
    return os;
}

struct ObjKey {
    constexpr ObjKey() noexcept
        : value(-1)
    {
    }
    explicit constexpr ObjKey(int64_t val) noexcept
        : value(val)
    {
    }
    bool is_unresolved() const
    {
        return value <= -2;
    }
    ObjKey get_unresolved() const
    {
        return ObjKey(-2 - value);
    }
    ObjKey& operator=(int64_t val) noexcept
    {
        value = val;
        return *this;
    }
    bool operator==(const ObjKey& rhs) const noexcept
    {
        return value == rhs.value;
    }
    bool operator!=(const ObjKey& rhs) const noexcept
    {
        return value != rhs.value;
    }
    bool operator<(const ObjKey& rhs) const noexcept
    {
        return value < rhs.value;
    }
    bool operator<=(const ObjKey& rhs) const noexcept
    {
        return value <= rhs.value;
    }
    bool operator>(const ObjKey& rhs) const noexcept
    {
        return value > rhs.value;
    }
    bool operator>=(const ObjKey& rhs) const noexcept
    {
        return value >= rhs.value;
    }
    explicit operator bool() const noexcept
    {
        return value != -1;
    }
    int64_t value;

private:
    // operator bool will enable casting to integer. Prevent this.
    operator int64_t() const = delete;
};

inline std::ostream& operator<<(std::ostream& ostr, ObjKey key)
{
    ostr << "ObjKey(" << key.value << ")";
    return ostr;
}

class ObjKeys : public std::vector<ObjKey> {
public:
    ObjKeys(const std::vector<int64_t>& init)
    {
        reserve(init.size());
        for (auto i : init) {
            emplace_back(i);
        }
    }
    ObjKeys()
    {
    }
};

struct ObjLink {
public:
    ObjLink() {}
    ObjLink(TableKey table_key, ObjKey obj_key)
        : m_obj_key(obj_key)
        , m_table_key(table_key)
    {
    }
    explicit operator bool() const
    {
        return m_table_key.operator bool();
    }
    bool is_null() const
    {
        return !m_table_key.operator bool();
    }
    bool operator==(const ObjLink& other) const
    {
        return m_obj_key == other.m_obj_key && m_table_key == other.m_table_key;
    }
    bool operator!=(const ObjLink& other) const
    {
        return m_obj_key != other.m_obj_key || m_table_key != other.m_table_key;
    }
    bool operator<(const ObjLink& rhs) const
    {
        return m_table_key < rhs.m_table_key || m_obj_key < rhs.m_obj_key;
    }
    bool operator>(const ObjLink& rhs) const
    {
        return m_table_key > rhs.m_table_key || m_obj_key > rhs.m_obj_key;
    }
    TableKey get_table_key() const
    {
        return m_table_key;
    }
    ObjKey get_obj_key() const
    {
        return m_obj_key;
    }

private:
    // Having ObjKey first ensures that there will be no uninitialized space
    // in the first 12 bytes. This is important when generating a hash
    ObjKey m_obj_key;
    TableKey m_table_key;
};

inline std::ostream& operator<<(std::ostream& os, ObjLink link)
{
    os << '{' << link.get_table_key() << ',' << link.get_obj_key() << '}';
    return os;
}
constexpr ObjKey null_key;

namespace util {

inline std::string to_string(ColKey ck)
{
    return to_string(ck.value);
}

} // namespace util

} // namespace realm

namespace std {

template <>
struct hash<realm::ObjKey> {
    size_t operator()(realm::ObjKey key) const
    {
        return std::hash<uint64_t>{}(key.value);
    }
};

} // namespace std

#endif
<|endoftext|>