<commit_before>#ifndef __STORE_HPP__
#define __STORE_HPP__

#include "utils.hpp"
#include "arch/arch.hpp"
#include "data_provider.hpp"
#include "concurrency/cond_var.hpp"
#include "containers/iterators.hpp"
#include <boost/shared_ptr.hpp>

typedef uint32_t mcflags_t;
typedef uint32_t exptime_t;
typedef uint64_t cas_t;

// Note: Changing this struct changes the format of the data stored on disk.
// If you change this struct, previous stored data will be misinterpreted.
struct store_key_t {
    uint8_t size;
    char contents[0];
    uint16_t full_size() const {
        return size + offsetof(store_key_t, contents);
    }
    void print() const {
        printf("%*.*s", size, size, contents);
    }
};

inline bool str_to_key(const char *str, store_key_t *buf) {
    int len = strlen(str);
    if (len <= MAX_KEY_SIZE) {
        memcpy(buf->contents, str, len);
        buf->size = (uint8_t) len;
        return true;
    } else {
        return false;
    }
}

inline std::string key_to_str(const store_key_t* key) {
    return std::string(key->contents, key->size);
}

struct key_with_data_provider_t {
    std::string key;
    mcflags_t mcflags;
    boost::shared_ptr<data_provider_t> value_provider;

    key_with_data_provider_t(const std::string &key, mcflags_t mcflags, boost::shared_ptr<data_provider_t> value_provider) :
        key(key), mcflags(mcflags), value_provider(value_provider) { }

    struct less {
        bool operator()(const key_with_data_provider_t &pair1, const key_with_data_provider_t &pair2) {
            return pair1.key < pair2.key;
        }
    };
};

union store_key_and_buffer_t {
    store_key_t key;
    char buffer[sizeof(store_key_t) + MAX_KEY_SIZE];
};

// A castime_t contains proposed cas information (if it's needed) and
// timestamp information.  By deciding these now and passing them in
// at the top, the precise same information gets sent to the replicas.
struct castime_t {
    cas_t proposed_cas;
    repli_timestamp timestamp;

    castime_t(cas_t proposed_cas_, repli_timestamp timestamp_)
        : proposed_cas(proposed_cas_), timestamp(timestamp_) { }

    static castime_t dummy() {
        return castime_t(0, repli_timestamp::invalid);
    }
    bool is_dummy() {
        return proposed_cas == 0 && timestamp.time == repli_timestamp::invalid.time;
    }
};

class store_t {
public:
    /* To get a value from the store, call get() or get_cas(), providing the key you want to get.
    The store will return a get_result_t with either the value or NULL. If it returns a value, you
    must call the provided done_callback_t when you are done to release the buffers holding the
    value. If you call get_cas(), the cas will be in the 'cas' member of the get_result_t; if not,
    the value of 'cas' is undefined and should be ignored. */

    struct get_result_t {
        get_result_t(boost::shared_ptr<data_provider_t> v, mcflags_t f, cas_t c, threadsafe_cond_t *s) :
            value(v), flags(f), cas(c), to_signal_when_done(s) { }
        get_result_t() :
            value(), flags(0), cas(0), to_signal_when_done(NULL) { }

        // NULL means not found. If non-NULL you are responsible for calling get_data_as_buffer(),
        // or get_data_into_buffer() on value. Parts of the store may wait for the data_provider_t's
        // destructor, so don't hold on to it forever.
        boost::shared_ptr<data_provider_t> value;
        mcflags_t flags;
        cas_t cas;
        threadsafe_cond_t *to_signal_when_done;
    };
    virtual get_result_t get(store_key_t *key) = 0;
    virtual get_result_t get_cas(store_key_t *key, castime_t castime) = 0;

    typedef one_way_iterator_t<key_with_data_provider_t>* rget_result_t;
    virtual rget_result_t rget(store_key_t *start, store_key_t *end, bool left_open, bool right_open) = 0;

    /* To set a value in the database, call set(), add(), or replace(). Provide a key* for the key
    to be set and a data_provider_t* for the data. Note that the data_provider_t may be called on
    any core, so you must implement core-switching yourself if necessary. The data_provider_t will
    always be called exactly once. */

    enum set_result_t {
        /* Returned on success */
        sr_stored,
        /* Returned if you add() and it already exists or you replace() and it doesn't */
        sr_not_stored,
        /* Returned if you cas() and the key does not exist. */
        sr_not_found,
        /* Returned if you cas() and the key was modified since get_cas(). */
        sr_exists,
        /* Returned if the value to be stored is too big */
        sr_too_large,
        /* Returned if the data_provider_t that you gave returned have_failed(). */
        sr_data_provider_failed,
        /* Returned if the store doesn't want you to do what you're doing. */
        sr_not_allowed,
    };

    enum add_policy_t {
        add_policy_yes,
        add_policy_no
    };

    enum replace_policy_t {
        replace_policy_yes,
        replace_policy_if_cas_matches,
        replace_policy_no
    };

    static const cas_t NO_CAS_SUPPLIED = 0;

    virtual set_result_t sarc(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime, add_policy_t add_policy, replace_policy_t replace_policy, cas_t old_cas) = 0;

    set_result_t set(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_yes, replace_policy_yes, NO_CAS_SUPPLIED);
    }
    set_result_t add(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_yes, replace_policy_no, NO_CAS_SUPPLIED);
    }
    set_result_t replace(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_no, replace_policy_yes, NO_CAS_SUPPLIED);
    }
    set_result_t cas(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, cas_t unique, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_no, replace_policy_yes, unique);
    }

    /* To increment or decrement a value, use incr() or decr(). They're pretty straight-forward. */

    struct incr_decr_result_t {
        enum result_t {
            idr_success,
            idr_not_found,
            idr_not_numeric,
            idr_not_allowed,
        } res;
        uint64_t new_value;   // Valid only if idr_success
        incr_decr_result_t() { }
        incr_decr_result_t(result_t r, uint64_t n = 0) : res(r), new_value(n) { }
    };

    enum incr_decr_kind_t {
        incr_decr_INCR,
        incr_decr_DECR
    };

    virtual incr_decr_result_t incr_decr(incr_decr_kind_t kind, store_key_t *key, uint64_t amount, castime_t castime) = 0;
    incr_decr_result_t incr(store_key_t *key, uint64_t amount, castime_t castime) {
        return incr_decr(incr_decr_INCR, key, amount, castime);
    }
    incr_decr_result_t decr(store_key_t *key, uint64_t amount, castime_t castime) {
        return incr_decr(incr_decr_DECR, key, amount, castime);
    }

    /* To append or prepend a value, use append() or prepend(). */

    enum append_prepend_result_t {
        apr_success,
        apr_too_large,
        apr_not_found,
        apr_data_provider_failed,
        apr_not_allowed,
    };

    enum append_prepend_kind_t { append_prepend_APPEND, append_prepend_PREPEND };

    virtual append_prepend_result_t append_prepend(append_prepend_kind_t kind, store_key_t *key, data_provider_t *data, castime_t castime) = 0;

    append_prepend_result_t append(store_key_t *key, data_provider_t *data, castime_t castime) {
        return append_prepend(append_prepend_APPEND, key, data, castime);
    }
    append_prepend_result_t prepend(store_key_t *key, data_provider_t *data, castime_t castime) {
        return append_prepend(append_prepend_PREPEND, key, data, castime);
    }

    /* To delete a key-value pair, use delete(). */

    enum delete_result_t {
        dr_deleted,
        dr_not_found,
        dr_not_allowed
    };

    virtual delete_result_t delete_key(store_key_t *key, repli_timestamp timestamp) = 0;

    virtual ~store_t() {}
};

#endif /* __STORE_HPP__ */
<commit_msg>Fixed store_t::cas so that it actually works, using the right replacement policy.<commit_after>#ifndef __STORE_HPP__
#define __STORE_HPP__

#include "utils.hpp"
#include "arch/arch.hpp"
#include "data_provider.hpp"
#include "concurrency/cond_var.hpp"
#include "containers/iterators.hpp"
#include <boost/shared_ptr.hpp>

typedef uint32_t mcflags_t;
typedef uint32_t exptime_t;
typedef uint64_t cas_t;

// Note: Changing this struct changes the format of the data stored on disk.
// If you change this struct, previous stored data will be misinterpreted.
struct store_key_t {
    uint8_t size;
    char contents[0];
    uint16_t full_size() const {
        return size + offsetof(store_key_t, contents);
    }
    void print() const {
        printf("%*.*s", size, size, contents);
    }
};

inline bool str_to_key(const char *str, store_key_t *buf) {
    int len = strlen(str);
    if (len <= MAX_KEY_SIZE) {
        memcpy(buf->contents, str, len);
        buf->size = (uint8_t) len;
        return true;
    } else {
        return false;
    }
}

inline std::string key_to_str(const store_key_t* key) {
    return std::string(key->contents, key->size);
}

struct key_with_data_provider_t {
    std::string key;
    mcflags_t mcflags;
    boost::shared_ptr<data_provider_t> value_provider;

    key_with_data_provider_t(const std::string &key, mcflags_t mcflags, boost::shared_ptr<data_provider_t> value_provider) :
        key(key), mcflags(mcflags), value_provider(value_provider) { }

    struct less {
        bool operator()(const key_with_data_provider_t &pair1, const key_with_data_provider_t &pair2) {
            return pair1.key < pair2.key;
        }
    };
};

union store_key_and_buffer_t {
    store_key_t key;
    char buffer[sizeof(store_key_t) + MAX_KEY_SIZE];
};

// A castime_t contains proposed cas information (if it's needed) and
// timestamp information.  By deciding these now and passing them in
// at the top, the precise same information gets sent to the replicas.
struct castime_t {
    cas_t proposed_cas;
    repli_timestamp timestamp;

    castime_t(cas_t proposed_cas_, repli_timestamp timestamp_)
        : proposed_cas(proposed_cas_), timestamp(timestamp_) { }

    static castime_t dummy() {
        return castime_t(0, repli_timestamp::invalid);
    }
    bool is_dummy() {
        return proposed_cas == 0 && timestamp.time == repli_timestamp::invalid.time;
    }
};

class store_t {
public:
    /* To get a value from the store, call get() or get_cas(), providing the key you want to get.
    The store will return a get_result_t with either the value or NULL. If it returns a value, you
    must call the provided done_callback_t when you are done to release the buffers holding the
    value. If you call get_cas(), the cas will be in the 'cas' member of the get_result_t; if not,
    the value of 'cas' is undefined and should be ignored. */

    struct get_result_t {
        get_result_t(boost::shared_ptr<data_provider_t> v, mcflags_t f, cas_t c, threadsafe_cond_t *s) :
            value(v), flags(f), cas(c), to_signal_when_done(s) { }
        get_result_t() :
            value(), flags(0), cas(0), to_signal_when_done(NULL) { }

        // NULL means not found. If non-NULL you are responsible for calling get_data_as_buffer(),
        // or get_data_into_buffer() on value. Parts of the store may wait for the data_provider_t's
        // destructor, so don't hold on to it forever.
        boost::shared_ptr<data_provider_t> value;
        mcflags_t flags;
        cas_t cas;
        threadsafe_cond_t *to_signal_when_done;
    };
    virtual get_result_t get(store_key_t *key) = 0;
    virtual get_result_t get_cas(store_key_t *key, castime_t castime) = 0;

    typedef one_way_iterator_t<key_with_data_provider_t>* rget_result_t;
    virtual rget_result_t rget(store_key_t *start, store_key_t *end, bool left_open, bool right_open) = 0;

    /* To set a value in the database, call set(), add(), or replace(). Provide a key* for the key
    to be set and a data_provider_t* for the data. Note that the data_provider_t may be called on
    any core, so you must implement core-switching yourself if necessary. The data_provider_t will
    always be called exactly once. */

    enum set_result_t {
        /* Returned on success */
        sr_stored,
        /* Returned if you add() and it already exists or you replace() and it doesn't */
        sr_not_stored,
        /* Returned if you cas() and the key does not exist. */
        sr_not_found,
        /* Returned if you cas() and the key was modified since get_cas(). */
        sr_exists,
        /* Returned if the value to be stored is too big */
        sr_too_large,
        /* Returned if the data_provider_t that you gave returned have_failed(). */
        sr_data_provider_failed,
        /* Returned if the store doesn't want you to do what you're doing. */
        sr_not_allowed,
    };

    enum add_policy_t {
        add_policy_yes,
        add_policy_no
    };

    enum replace_policy_t {
        replace_policy_yes,
        replace_policy_if_cas_matches,
        replace_policy_no
    };

    static const cas_t NO_CAS_SUPPLIED = 0;

    virtual set_result_t sarc(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime, add_policy_t add_policy, replace_policy_t replace_policy, cas_t old_cas) = 0;

    set_result_t set(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_yes, replace_policy_yes, NO_CAS_SUPPLIED);
    }
    set_result_t add(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_yes, replace_policy_no, NO_CAS_SUPPLIED);
    }
    set_result_t replace(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_no, replace_policy_yes, NO_CAS_SUPPLIED);
    }
    set_result_t cas(store_key_t *key, data_provider_t *data, mcflags_t flags, exptime_t exptime, cas_t unique, castime_t castime) {
        return sarc(key, data, flags, exptime, castime, add_policy_no, replace_policy_if_cas_matches, unique);
    }

    /* To increment or decrement a value, use incr() or decr(). They're pretty straight-forward. */

    struct incr_decr_result_t {
        enum result_t {
            idr_success,
            idr_not_found,
            idr_not_numeric,
            idr_not_allowed,
        } res;
        uint64_t new_value;   // Valid only if idr_success
        incr_decr_result_t() { }
        incr_decr_result_t(result_t r, uint64_t n = 0) : res(r), new_value(n) { }
    };

    enum incr_decr_kind_t {
        incr_decr_INCR,
        incr_decr_DECR
    };

    virtual incr_decr_result_t incr_decr(incr_decr_kind_t kind, store_key_t *key, uint64_t amount, castime_t castime) = 0;
    incr_decr_result_t incr(store_key_t *key, uint64_t amount, castime_t castime) {
        return incr_decr(incr_decr_INCR, key, amount, castime);
    }
    incr_decr_result_t decr(store_key_t *key, uint64_t amount, castime_t castime) {
        return incr_decr(incr_decr_DECR, key, amount, castime);
    }

    /* To append or prepend a value, use append() or prepend(). */

    enum append_prepend_result_t {
        apr_success,
        apr_too_large,
        apr_not_found,
        apr_data_provider_failed,
        apr_not_allowed,
    };

    enum append_prepend_kind_t { append_prepend_APPEND, append_prepend_PREPEND };

    virtual append_prepend_result_t append_prepend(append_prepend_kind_t kind, store_key_t *key, data_provider_t *data, castime_t castime) = 0;

    append_prepend_result_t append(store_key_t *key, data_provider_t *data, castime_t castime) {
        return append_prepend(append_prepend_APPEND, key, data, castime);
    }
    append_prepend_result_t prepend(store_key_t *key, data_provider_t *data, castime_t castime) {
        return append_prepend(append_prepend_PREPEND, key, data, castime);
    }

    /* To delete a key-value pair, use delete(). */

    enum delete_result_t {
        dr_deleted,
        dr_not_found,
        dr_not_allowed
    };

    virtual delete_result_t delete_key(store_key_t *key, repli_timestamp timestamp) = 0;

    virtual ~store_t() {}
};

#endif /* __STORE_HPP__ */
<|endoftext|>


<commit_before>#include "IrcMessageHandler.hpp"

#include "Application.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/IrcHelpers.hpp"

#include <IrcMessage>

#include <unordered_set>

namespace chatterino {

IrcMessageHandler &IrcMessageHandler::getInstance()
{
    static IrcMessageHandler instance;
    return instance;
}

void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
                                          TwitchServer &server)
{
    this->addMessage(message, message->target(), message->content(), server,
                     false, message->isAction());
}

void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
                                   const QString &target,
                                   const QString &content, TwitchServer &server,
                                   bool isSub, bool isAction)
{
    QString channelName;
    if (!trimChannelName(target, channelName)) {
        return;
    }

    auto chan = server.getChannelOrEmpty(channelName);

    if (chan->isEmpty()) {
        return;
    }

    MessageParseArgs args;
    if (isSub) {
        args.trimSubscriberUsername = true;
    }

    if (chan->isBroadcaster()) {
        args.isStaffOrBroadcaster = true;
    }

    TwitchMessageBuilder builder(chan.get(), _message, args, content, isAction);

    if (isSub || !builder.isIgnored()) {
        if (isSub) {
            builder->flags.set(MessageFlag::Subscription);
            builder->flags.unset(MessageFlag::Highlighted);
        }

        auto msg = builder.build();
        auto highlighted = msg->flags.has(MessageFlag::Highlighted);

        if (!isSub) {
            if (highlighted) {
                server.mentionsChannel->addMessage(msg);
                getApp()->highlights->addHighlight(msg);
            }
        }

        chan->addMessage(msg);
    }
}

void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
{
    const auto &tags = message->tags();
    auto app = getApp();

    // get twitch channel
    QString chanName;
    if (!trimChannelName(message->parameter(0), chanName)) {
        return;
    }
    auto chan = app->twitch.server->getChannelOrEmpty(chanName);

    if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(chan.get())) {
        // room-id
        decltype(tags.find("xD")) it;

        if ((it = tags.find("room-id")) != tags.end()) {
            auto roomId = it.value().toString();

            twitchChannel->setRoomId(roomId);
        }

        // Room modes
        {
            auto roomModes = *twitchChannel->accessRoomModes();

            if ((it = tags.find("emote-only")) != tags.end()) {
                roomModes.emoteOnly = it.value() == "1";
            }
            if ((it = tags.find("subs-only")) != tags.end()) {
                roomModes.submode = it.value() == "1";
            }
            if ((it = tags.find("slow")) != tags.end()) {
                roomModes.slowMode = it.value().toInt();
            }
            if ((it = tags.find("r9k")) != tags.end()) {
                roomModes.r9k = it.value() == "1";
            }
            if ((it = tags.find("broadcaster-lang")) != tags.end()) {
                roomModes.broadcasterLang = it.value().toString();
            }
            twitchChannel->setRoomModes(roomModes);
        }

        twitchChannel->roomModesChanged.invoke();
    }
}

void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{
    // check parameter count
    if (message->parameters().length() < 1) {
        return;
    }

    QString chanName;
    if (!trimChannelName(message->parameter(0), chanName)) {
        return;
    }

    auto app = getApp();

    // get channel
    auto chan = app->twitch.server->getChannelOrEmpty(chanName);

    if (chan->isEmpty()) {
        log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
            "found",
            chanName);
        return;
    }

    // check if the chat has been cleared by a moderator
    if (message->parameters().length() == 1) {
        chan->disableAllMessages();
        chan->addMessage(
            makeSystemMessage("Chat has been cleared by a moderator."));

        return;
    }

    // get username, duration and message of the timed out user
    QString username = message->parameter(1);
    QString durationInSeconds, reason;
    QVariant v = message->tag("ban-duration");
    if (v.isValid()) {
        durationInSeconds = v.toString();
    }

    v = message->tag("ban-reason");
    if (v.isValid()) {
        reason = v.toString();
    }

    auto timeoutMsg = MessageBuilder(timeoutMessage, username,
                                     durationInSeconds, reason, false)
                          .release();
    chan->addOrReplaceTimeout(timeoutMsg);

    // refresh all
    app->windows->repaintVisibleChatWidgets(chan.get());
}

void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
{
    QVariant _mod = message->tag("mod");

    if (_mod.isValid()) {
        auto app = getApp();

        QString channelName;
        if (!trimChannelName(message->parameter(0), channelName)) {
            return;
        }

        auto c = app->twitch.server->getChannelOrEmpty(channelName);
        if (c->isEmpty()) {
            return;
        }

        TwitchChannel *tc = dynamic_cast<TwitchChannel *>(c.get());
        if (tc != nullptr) {
            tc->setMod(_mod == "1");
        }
    }
}

void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{
    auto app = getApp();
    log("Received whisper!");
    MessageParseArgs args;

    args.isReceivedWhisper = true;

    auto c = app->twitch.server->whispersChannel.get();

    TwitchMessageBuilder builder(c, message, args, message->parameter(1),
                                 false);

    if (!builder.isIgnored()) {
        MessagePtr _message = builder.build();

        app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);

        if (_message->flags.has(MessageFlag::Highlighted)) {
            app->twitch.server->mentionsChannel->addMessage(_message);
        }

        c->addMessage(_message);

        if (getSettings()->inlineWhispers) {
            app->twitch.server->forEachChannel([_message](ChannelPtr channel) {
                channel->addMessage(_message);  //
            });
        }
    }
}

void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
                                                TwitchServer &server)
{
    auto data = message->toData();

    auto tags = message->tags();
    auto parameters = message->parameters();

    auto target = parameters[0];
    QString msgType = tags.value("msg-id", "").toString();
    QString content;
    if (parameters.size() >= 2) {
        content = parameters[1];
    }

    if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
        // Sub-specific message. I think it's only allowed for "resub" messages
        // atm
        if (!content.isEmpty()) {
            this->addMessage(message, target, content, server, true, false);
        }
    }

    auto it = tags.find("system-msg");

    if (it != tags.end()) {
        auto b = MessageBuilder(systemMessage,
                                parseTagString(it.value().toString()));

        b->flags.set(MessageFlag::Subscription);
        auto newMessage = b.release();

        QString channelName;

        if (message->parameters().size() < 1) {
            return;
        }

        if (!trimChannelName(message->parameter(0), channelName)) {
            return;
        }

        auto chan = server.getChannelOrEmpty(channelName);

        if (!chan->isEmpty()) {
            chan->addMessage(newMessage);
        }
    }
}

void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
{
    auto app = getApp();

    auto channel = app->twitch.server->getChannelOrEmpty(
        message->parameter(0).remove(0, 1));

    if (channel->isEmpty()) {
        return;
    }

    if (message->parameter(1) == "+o") {
        channel->modList.append(message->parameter(2));
    } else if (message->parameter(1) == "-o") {
        channel->modList.append(message->parameter(2));
    }
}

void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
{
    auto app = getApp();
    MessagePtr msg = makeSystemMessage(message->content());

    QString channelName;
    if (!trimChannelName(message->target(), channelName)) {
        // Notice wasn't targeted at a single channel, send to all twitch
        // channels
        app->twitch.server->forEachChannelAndSpecialChannels(
            [msg](const auto &c) {
                c->addMessage(msg);  //
            });

        return;
    }

    auto channel = app->twitch.server->getChannelOrEmpty(channelName);

    if (channel->isEmpty()) {
        log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
            "manager ",
            channelName);
        return;
    }

    channel->addMessage(msg);
}

void IrcMessageHandler::handleWriteConnectionNoticeMessage(
    Communi::IrcNoticeMessage *message)
{
    static std::unordered_set<std::string> readConnectionOnlyIDs{
        "host_on",
        "host_off",
        "host_target_went_offline",
        "emote_only_on",
        "emote_only_off",
        "slow_on",
        "slow_off",
        "subs_on",
        "subs_off",
        "r9k_on",
        "r9k_off",

        // Display for user who times someone out. This implies you're a
        // moderator, at which point you will be connected to PubSub and receive
        // a better message from there
        "timeout_success",
        "ban_success",
    };

    QVariant v = message->tag("msg-id");
    if (v.isValid()) {
        std::string msgID = v.toString().toStdString();

        if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end()) {
            return;
        }

        log("Showing notice message from write connection with message id '{}'",
            msgID);
    }

    this->handleNoticeMessage(message);
}

void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
{
    auto app = getApp();
    auto channel = app->twitch.server->getChannelOrEmpty(
        message->parameter(0).remove(0, 1));

    if (TwitchChannel *twitchChannel =
            dynamic_cast<TwitchChannel *>(channel.get())) {
        twitchChannel->addJoinedUser(message->nick());
    }
}

void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
{
    auto app = getApp();
    auto channel = app->twitch.server->getChannelOrEmpty(
        message->parameter(0).remove(0, 1));

    if (TwitchChannel *twitchChannel =
            dynamic_cast<TwitchChannel *>(channel.get())) {
        twitchChannel->addPartedUser(message->nick());
    }
}

}  // namespace chatterino
<commit_msg>Don't change split header for whisper<commit_after>#include "IrcMessageHandler.hpp"

#include "Application.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/IrcHelpers.hpp"

#include <IrcMessage>

#include <unordered_set>

namespace chatterino {

IrcMessageHandler &IrcMessageHandler::getInstance()
{
    static IrcMessageHandler instance;
    return instance;
}

void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
                                          TwitchServer &server)
{
    this->addMessage(message, message->target(), message->content(), server,
                     false, message->isAction());
}

void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
                                   const QString &target,
                                   const QString &content, TwitchServer &server,
                                   bool isSub, bool isAction)
{
    QString channelName;
    if (!trimChannelName(target, channelName)) {
        return;
    }

    auto chan = server.getChannelOrEmpty(channelName);

    if (chan->isEmpty()) {
        return;
    }

    MessageParseArgs args;
    if (isSub) {
        args.trimSubscriberUsername = true;
    }

    if (chan->isBroadcaster()) {
        args.isStaffOrBroadcaster = true;
    }

    TwitchMessageBuilder builder(chan.get(), _message, args, content, isAction);

    if (isSub || !builder.isIgnored()) {
        if (isSub) {
            builder->flags.set(MessageFlag::Subscription);
            builder->flags.unset(MessageFlag::Highlighted);
        }

        auto msg = builder.build();
        auto highlighted = msg->flags.has(MessageFlag::Highlighted);

        if (!isSub) {
            if (highlighted) {
                server.mentionsChannel->addMessage(msg);
                getApp()->highlights->addHighlight(msg);
            }
        }

        chan->addMessage(msg);
    }
}

void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
{
    const auto &tags = message->tags();
    auto app = getApp();

    // get twitch channel
    QString chanName;
    if (!trimChannelName(message->parameter(0), chanName)) {
        return;
    }
    auto chan = app->twitch.server->getChannelOrEmpty(chanName);

    if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(chan.get())) {
        // room-id
        decltype(tags.find("xD")) it;

        if ((it = tags.find("room-id")) != tags.end()) {
            auto roomId = it.value().toString();

            twitchChannel->setRoomId(roomId);
        }

        // Room modes
        {
            auto roomModes = *twitchChannel->accessRoomModes();

            if ((it = tags.find("emote-only")) != tags.end()) {
                roomModes.emoteOnly = it.value() == "1";
            }
            if ((it = tags.find("subs-only")) != tags.end()) {
                roomModes.submode = it.value() == "1";
            }
            if ((it = tags.find("slow")) != tags.end()) {
                roomModes.slowMode = it.value().toInt();
            }
            if ((it = tags.find("r9k")) != tags.end()) {
                roomModes.r9k = it.value() == "1";
            }
            if ((it = tags.find("broadcaster-lang")) != tags.end()) {
                roomModes.broadcasterLang = it.value().toString();
            }
            twitchChannel->setRoomModes(roomModes);
        }

        twitchChannel->roomModesChanged.invoke();
    }
}

void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{
    // check parameter count
    if (message->parameters().length() < 1) {
        return;
    }

    QString chanName;
    if (!trimChannelName(message->parameter(0), chanName)) {
        return;
    }

    auto app = getApp();

    // get channel
    auto chan = app->twitch.server->getChannelOrEmpty(chanName);

    if (chan->isEmpty()) {
        log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
            "found",
            chanName);
        return;
    }

    // check if the chat has been cleared by a moderator
    if (message->parameters().length() == 1) {
        chan->disableAllMessages();
        chan->addMessage(
            makeSystemMessage("Chat has been cleared by a moderator."));

        return;
    }

    // get username, duration and message of the timed out user
    QString username = message->parameter(1);
    QString durationInSeconds, reason;
    QVariant v = message->tag("ban-duration");
    if (v.isValid()) {
        durationInSeconds = v.toString();
    }

    v = message->tag("ban-reason");
    if (v.isValid()) {
        reason = v.toString();
    }

    auto timeoutMsg = MessageBuilder(timeoutMessage, username,
                                     durationInSeconds, reason, false)
                          .release();
    chan->addOrReplaceTimeout(timeoutMsg);

    // refresh all
    app->windows->repaintVisibleChatWidgets(chan.get());
}

void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
{
    QVariant _mod = message->tag("mod");

    if (_mod.isValid()) {
        auto app = getApp();

        QString channelName;
        if (!trimChannelName(message->parameter(0), channelName)) {
            return;
        }

        auto c = app->twitch.server->getChannelOrEmpty(channelName);
        if (c->isEmpty()) {
            return;
        }

        TwitchChannel *tc = dynamic_cast<TwitchChannel *>(c.get());
        if (tc != nullptr) {
            tc->setMod(_mod == "1");
        }
    }
}

void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{
    auto app = getApp();
    log("Received whisper!");
    MessageParseArgs args;

    args.isReceivedWhisper = true;

    auto c = app->twitch.server->whispersChannel.get();

    TwitchMessageBuilder builder(c, message, args, message->parameter(1),
                                 false);

    if (!builder.isIgnored()) {
        MessagePtr _message = builder.build();

        app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);

        if (_message->flags.has(MessageFlag::Highlighted)) {
            app->twitch.server->mentionsChannel->addMessage(_message);
        }

        _message->flags.set(MessageFlag::DoNotTriggerNotification);
        c->addMessage(_message);

        if (getSettings()->inlineWhispers) {
            app->twitch.server->forEachChannel([_message](ChannelPtr channel) {
                channel->addMessage(_message);  //
            });
        }
    }
}

void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
                                                TwitchServer &server)
{
    auto data = message->toData();

    auto tags = message->tags();
    auto parameters = message->parameters();

    auto target = parameters[0];
    QString msgType = tags.value("msg-id", "").toString();
    QString content;
    if (parameters.size() >= 2) {
        content = parameters[1];
    }

    if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
        // Sub-specific message. I think it's only allowed for "resub" messages
        // atm
        if (!content.isEmpty()) {
            this->addMessage(message, target, content, server, true, false);
        }
    }

    auto it = tags.find("system-msg");

    if (it != tags.end()) {
        auto b = MessageBuilder(systemMessage,
                                parseTagString(it.value().toString()));

        b->flags.set(MessageFlag::Subscription);
        auto newMessage = b.release();

        QString channelName;

        if (message->parameters().size() < 1) {
            return;
        }

        if (!trimChannelName(message->parameter(0), channelName)) {
            return;
        }

        auto chan = server.getChannelOrEmpty(channelName);

        if (!chan->isEmpty()) {
            chan->addMessage(newMessage);
        }
    }
}

void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
{
    auto app = getApp();

    auto channel = app->twitch.server->getChannelOrEmpty(
        message->parameter(0).remove(0, 1));

    if (channel->isEmpty()) {
        return;
    }

    if (message->parameter(1) == "+o") {
        channel->modList.append(message->parameter(2));
    } else if (message->parameter(1) == "-o") {
        channel->modList.append(message->parameter(2));
    }
}

void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
{
    auto app = getApp();
    MessagePtr msg = makeSystemMessage(message->content());

    QString channelName;
    if (!trimChannelName(message->target(), channelName)) {
        // Notice wasn't targeted at a single channel, send to all twitch
        // channels
        app->twitch.server->forEachChannelAndSpecialChannels(
            [msg](const auto &c) {
                c->addMessage(msg);  //
            });

        return;
    }

    auto channel = app->twitch.server->getChannelOrEmpty(channelName);

    if (channel->isEmpty()) {
        log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
            "manager ",
            channelName);
        return;
    }

    channel->addMessage(msg);
}

void IrcMessageHandler::handleWriteConnectionNoticeMessage(
    Communi::IrcNoticeMessage *message)
{
    static std::unordered_set<std::string> readConnectionOnlyIDs{
        "host_on",
        "host_off",
        "host_target_went_offline",
        "emote_only_on",
        "emote_only_off",
        "slow_on",
        "slow_off",
        "subs_on",
        "subs_off",
        "r9k_on",
        "r9k_off",

        // Display for user who times someone out. This implies you're a
        // moderator, at which point you will be connected to PubSub and receive
        // a better message from there
        "timeout_success",
        "ban_success",
    };

    QVariant v = message->tag("msg-id");
    if (v.isValid()) {
        std::string msgID = v.toString().toStdString();

        if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end()) {
            return;
        }

        log("Showing notice message from write connection with message id '{}'",
            msgID);
    }

    this->handleNoticeMessage(message);
}

void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
{
    auto app = getApp();
    auto channel = app->twitch.server->getChannelOrEmpty(
        message->parameter(0).remove(0, 1));

    if (TwitchChannel *twitchChannel =
            dynamic_cast<TwitchChannel *>(channel.get())) {
        twitchChannel->addJoinedUser(message->nick());
    }
}

void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
{
    auto app = getApp();
    auto channel = app->twitch.server->getChannelOrEmpty(
        message->parameter(0).remove(0, 1));

    if (TwitchChannel *twitchChannel =
            dynamic_cast<TwitchChannel *>(channel.get())) {
        twitchChannel->addPartedUser(message->nick());
    }
}

}  // namespace chatterino
<|endoftext|>


<commit_before>/*
 * Copyright (©) 2015 Nate Rosenblum
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "wte/stream.h"

#include <errno.h>
#if !defined(_WIN32)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#else
#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#include <cassert>
#include <limits>

#include <event2/util.h>

#include "wte/buffer.h"
#include "wte/event_base.h"
#include "wte/event_handler.h"
#include "wte/porting.h"
#include "xplat-io.h"

namespace wte {

class StreamImpl final : public Stream {
public:
    // TODO: temporary fd-based constructor for testing
    StreamImpl(EventBase *base, int fd) : handler_(this, fd),
        base_(base), requests_({nullptr, nullptr}), readCallback_(nullptr),
        connectCallback_(nullptr) { }

    explicit StreamImpl(EventBase *base) : handler_(this, -1),
        base_(base), requests_({nullptr, nullptr}), readCallback_(nullptr),
        connectCallback_(nullptr) { }

    ~StreamImpl();

    void write(const char *buf, size_t size, WriteCallback *cb) override;
    void write(Buffer *buf, WriteCallback *cb) override;
    void startRead(ReadCallback *cb) override;
    void stopRead() override;
    void close() override;
    void connect(std::string const& ip_addr, int16_t port, ConnectCallback *cb)
        override;
private:
    void writeHelper();
    void readHelper();
    void connectHelper();

    class SockHandler final : public EventHandler {
    public:
        SockHandler(StreamImpl *stream, int fd)
            : EventHandler(fd), stream_(stream) { }
        void ready(What what) NOEXCEPT override;
    private:
        StreamImpl *stream_;
    };

    // State about a write request (buffer, callback)
    class WriteRequest {
    public:
        WriteRequest(const char *buffer, size_t size, WriteCallback *cb);
        WriteRequest(Buffer *buf, WriteCallback *cb);
        ~WriteRequest();
        Buffer buffer_;
        WriteCallback *callback_;
        WriteRequest *next_;
    };

    SockHandler handler_;
    EventBase *base_;
    struct Requests {
        WriteRequest *head;
        WriteRequest *tail;

        void append(WriteRequest *req) {
            if (!head) {
                assert(!tail);
                head = req;
                tail = req;
            } else {
                tail->next_ = req;
            }
        }

        WriteRequest* consumeFront() {
            if (!head) {
                return nullptr;
            }
            WriteRequest *tmp = head;
            head = head->next_;
            if (!head) {
                tail = nullptr;
            }
            delete tmp;
            return head;
        }
    } requests_;
    ReadCallback *readCallback_;
    ConnectCallback *connectCallback_;
    Buffer readBuffer_;
};

void StreamImpl::SockHandler::ready(What event) NOEXCEPT {
    if (isWrite(event)) {
        if (stream_->connectCallback_) {
            stream_->connectHelper();
        }
        stream_->writeHelper();
    }

   if (isRead(event)) {
       stream_->readHelper();
   }
}

StreamImpl::WriteRequest::WriteRequest(const char *buffer, size_t size,
        WriteCallback *cb) : callback_(cb), next_(nullptr) {
    buffer_.append(buffer, size);
}

StreamImpl::WriteRequest::WriteRequest(Buffer *buffer, WriteCallback *cb)
        : callback_(cb), next_(nullptr) {
    buffer_.append(buffer);
}

StreamImpl::WriteRequest::~WriteRequest() { }

void StreamImpl::startRead(Stream::ReadCallback *cb) {
    if (readCallback_ == cb) {
        return;
    }
    readCallback_ = cb;
    base_->registerHandler(&handler_, ensureRead(handler_.watched()));
}

void StreamImpl::stopRead() {
    if (!readCallback_) {
        return;
    }

    readCallback_ = nullptr;

    What events = handler_.watched();
    if (!isRead(handler_.watched())) {
        return;
    } else if (isWrite(handler_.watched())) {
        assert(events == What::READ_WRITE);
        base_->registerHandler(&handler_, What::WRITE);
    }
}

void StreamImpl::write(const char *buf, size_t size, WriteCallback *cb) {
    // TODO: try writing immediately, saving a loop iteration if the socket
    // can handle the entire write w/o blocking
    WriteRequest *req = new WriteRequest(buf, size, cb);
    requests_.append(req);
    base_->registerHandler(&handler_, ensureWrite(handler_.watched()));
}

void StreamImpl::write(Buffer *buf, WriteCallback *cb) {
    WriteRequest *req = new WriteRequest(buf, cb);
    requests_.append(req);
    base_->registerHandler(&handler_, ensureWrite(handler_.watched()));
}

void StreamImpl::close() {
    if (readCallback_) {
        readCallback_->eof();
    }
    if (connectCallback_) {
        connectCallback_->error(std::runtime_error("Closed before connect"));
    }
    if (handler_.registered()) {
        handler_.unregister();
    }

    if (handler_.fd() != -1) {
        xclose(handler_.fd());
    }
}

void StreamImpl::connect(std::string const& ip, int16_t port,
        ConnectCallback *cb) {
    assert(handler_.fd() == -1);
    assert(connectCallback_ == nullptr);

    const char *error = nullptr;
    int fd = -1;

    for (;;) {
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (-1 == fd) {
            error = "Failed to allocate socket";
            break;
        }

        int rc = evutil_make_socket_nonblocking(fd);
        if (-1 == rc) {
            error = "Failed to set socket non-blocking";
            break;
        }

        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        rc = inet_pton(AF_INET, ip.c_str(), &saddr.sin_addr);
        if (1 != rc) {
            error = "Failed to convert address";
            break;
        }

        saddr.sin_port = htons(port);
        socklen_t len = sizeof(saddr);
        rc = ::connect(fd, reinterpret_cast<struct sockaddr*>(&saddr), len);
        if (rc == -1) {
            if (errno == EINPROGRESS) {
                // Expected; queue up the callback
                handler_.setFd(fd);
                connectCallback_ = cb;
                base_->registerHandler(&handler_, ensureWrite(
                    handler_.watched()));
                return;
            } else {
                error = "Connect failed";
                break;
            }
        }

        // Connect succeeded immediately
        handler_.setFd(fd);
        cb->complete();

        return;
    }

    if (fd != -1) {
        xclose(fd);
    }

    cb->error(std::runtime_error(error));
}

StreamImpl::~StreamImpl() {
    handler_.unregister();
    WriteRequest *r;
    // Delete all outstanding write requests
    while ((r = requests_.consumeFront()) != nullptr) { }
}

void StreamImpl::readHelper() {
    char buf[4096];

    // TODO: consider not reading indefinitely
    for (;;) {
        int nread = xread(handler_.fd(), buf, sizeof(buf));
        if (nread < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                break;
            }
            // TODO: better errors
            if (readCallback_) {
                readCallback_->error(std::runtime_error("Read failed"));
            }
            break;
        } else if (nread == 0) {
            if (readCallback_) {
                readCallback_->eof();
            }
            break;
        }

        // TODO: a reserve + get readable iovec + readv will be more efficient
        // than a read + append.
        readBuffer_.append(buf, nread);

        if (readCallback_) {
            readCallback_->available(&readBuffer_);
        }
        if (nread < sizeof(buf)) {
            break;
        }
    }
}

void StreamImpl::connectHelper() {
    assert(connectCallback_);

    const char *error = nullptr;

    for (;;) {
        // Check connection status
        int err = 0;
        socklen_t len = sizeof(error);
#if defined(_WIN32)
        int rc = getsockopt(handler_.fd(), SOL_SOCKET, SO_ERROR,
            (char *) &err, &len);
#else
        int rc = getsockopt(handler_.fd(), SOL_SOCKET, SO_ERROR, &err, &len);
#endif
        if (-1 == rc) {
            error = "Failed to query connection status";
            break;
        }

        if (error != 0) {
            error = "Connection failed";
            break;
        }

        // Good to go
        auto *cb = connectCallback_;
        connectCallback_ = nullptr;
        cb->complete();
        return;
    }

    auto *cb = connectCallback_;
    connectCallback_ = nullptr;
    cb->error(std::runtime_error(error));
}

void StreamImpl::writeHelper() {
    WriteRequest *req = requests_.head;
    std::vector<Extent> extents;
    bool blocked = false;
    bool failed = false;
    while (req) {
        extents.clear();
        // TODO: better limit
        req->buffer_.peek(std::numeric_limits<size_t>::max(), &extents);
        assert(!extents.empty());

        // TODO: writev
        size_t total_written = 0;
        for (auto& extent : extents) {
            int written = xwrite(handler_.fd(), extent.data, extent.size);
            if (written >= 0) {
                total_written += written;
                if (written < extent.size) {
                    blocked = true;
                }
            } else {
                failed = true;
                break;
            }
        }

        req->buffer_.drain(total_written);

        if (blocked || failed) {
            break;
        }

        WriteCallback *cb = req->callback_;
        // Must not touch req after this call
        WriteRequest *next = requests_.consumeFront();
        if (!next) {
            // Uninstall the write handler before invoking the callback
            // for this final request; callbacks that know that they are
            // last invocation may legitimately do destructive things like
            // freeing this stream.
            base_->registerHandler(&handler_, removeWrite(handler_.watched()));
        }

        if (cb) {
            cb->complete(this);
        }

        req = next;
    }

    if (failed && req->callback_) {
        // TODO: better errors
        req->callback_->error(std::runtime_error("Write failed"));
    }
}

void Stream::Deleter::operator()(Stream *stream) {
    delete stream;
}

std::unique_ptr<Stream, Stream::Deleter> wrapFd(EventBase *base, int fd) {
    return std::unique_ptr<Stream, Stream::Deleter>(
         new StreamImpl(base, fd), Stream::Deleter());
}

std::unique_ptr<Stream, Stream::Deleter> Stream::create(EventBase *base) {
    return std::unique_ptr<Stream, Stream::Deleter>(
         new StreamImpl(base), Stream::Deleter());
}

} // wte namespace
<commit_msg>Use xplat errno accessor<commit_after>/*
 * Copyright (©) 2015 Nate Rosenblum
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "wte/stream.h"

#if !defined(_WIN32)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#else
#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#include <cassert>
#include <limits>

#include <event2/util.h>

#include "wte/buffer.h"
#include "wte/event_base.h"
#include "wte/event_handler.h"
#include "wte/porting.h"
#include "xplat-io.h"

namespace wte {

inline bool isConnectRetryable(int e) {
#if !defined(_WIN32)
    return e == EINPROGRESS;
#else
    return e == WSAEWOULDBLOCK || e == WSAEINPROGRESS || e == WSAEINTR;
#endif
}

inline bool isReadRetryable(int e) {
#if !defined(_WIN32)
    return e == EAGAIN || e == EWOULDBLOCK;
#else
    return e == WSAEWOULDBLOCK || e == WSAEINTR;
#endif
}

class StreamImpl final : public Stream {
public:
    // TODO: temporary fd-based constructor for testing
    StreamImpl(EventBase *base, int fd) : handler_(this, fd),
        base_(base), requests_({nullptr, nullptr}), readCallback_(nullptr),
        connectCallback_(nullptr) { }

    explicit StreamImpl(EventBase *base) : handler_(this, -1),
        base_(base), requests_({nullptr, nullptr}), readCallback_(nullptr),
        connectCallback_(nullptr) { }

    ~StreamImpl();

    void write(const char *buf, size_t size, WriteCallback *cb) override;
    void write(Buffer *buf, WriteCallback *cb) override;
    void startRead(ReadCallback *cb) override;
    void stopRead() override;
    void close() override;
    void connect(std::string const& ip_addr, int16_t port, ConnectCallback *cb)
        override;
private:
    void writeHelper();
    void readHelper();
    void connectHelper();

    class SockHandler final : public EventHandler {
    public:
        SockHandler(StreamImpl *stream, int fd)
            : EventHandler(fd), stream_(stream) { }
        void ready(What what) NOEXCEPT override;
    private:
        StreamImpl *stream_;
    };

    // State about a write request (buffer, callback)
    class WriteRequest {
    public:
        WriteRequest(const char *buffer, size_t size, WriteCallback *cb);
        WriteRequest(Buffer *buf, WriteCallback *cb);
        ~WriteRequest();
        Buffer buffer_;
        WriteCallback *callback_;
        WriteRequest *next_;
    };

    SockHandler handler_;
    EventBase *base_;
    struct Requests {
        WriteRequest *head;
        WriteRequest *tail;

        void append(WriteRequest *req) {
            if (!head) {
                assert(!tail);
                head = req;
                tail = req;
            } else {
                tail->next_ = req;
            }
        }

        WriteRequest* consumeFront() {
            if (!head) {
                return nullptr;
            }
            WriteRequest *tmp = head;
            head = head->next_;
            if (!head) {
                tail = nullptr;
            }
            delete tmp;
            return head;
        }
    } requests_;
    ReadCallback *readCallback_;
    ConnectCallback *connectCallback_;
    Buffer readBuffer_;
};

void StreamImpl::SockHandler::ready(What event) NOEXCEPT {
    if (isWrite(event)) {
        if (stream_->connectCallback_) {
            stream_->connectHelper();
        }
        stream_->writeHelper();
    }

   if (isRead(event)) {
       stream_->readHelper();
   }
}

StreamImpl::WriteRequest::WriteRequest(const char *buffer, size_t size,
        WriteCallback *cb) : callback_(cb), next_(nullptr) {
    buffer_.append(buffer, size);
}

StreamImpl::WriteRequest::WriteRequest(Buffer *buffer, WriteCallback *cb)
        : callback_(cb), next_(nullptr) {
    buffer_.append(buffer);
}

StreamImpl::WriteRequest::~WriteRequest() { }

void StreamImpl::startRead(Stream::ReadCallback *cb) {
    if (readCallback_ == cb) {
        return;
    }
    readCallback_ = cb;
    base_->registerHandler(&handler_, ensureRead(handler_.watched()));
}

void StreamImpl::stopRead() {
    if (!readCallback_) {
        return;
    }

    readCallback_ = nullptr;

    What events = handler_.watched();
    if (!isRead(handler_.watched())) {
        return;
    } else if (isWrite(handler_.watched())) {
        assert(events == What::READ_WRITE);
        base_->registerHandler(&handler_, What::WRITE);
    }
}

void StreamImpl::write(const char *buf, size_t size, WriteCallback *cb) {
    // TODO: try writing immediately, saving a loop iteration if the socket
    // can handle the entire write w/o blocking
    WriteRequest *req = new WriteRequest(buf, size, cb);
    requests_.append(req);
    base_->registerHandler(&handler_, ensureWrite(handler_.watched()));
}

void StreamImpl::write(Buffer *buf, WriteCallback *cb) {
    WriteRequest *req = new WriteRequest(buf, cb);
    requests_.append(req);
    base_->registerHandler(&handler_, ensureWrite(handler_.watched()));
}

void StreamImpl::close() {
    if (readCallback_) {
        readCallback_->eof();
    }
    if (connectCallback_) {
        connectCallback_->error(std::runtime_error("Closed before connect"));
    }
    if (handler_.registered()) {
        handler_.unregister();
    }

    if (handler_.fd() != -1) {
        xclose(handler_.fd());
    }
}

void StreamImpl::connect(std::string const& ip, int16_t port,
        ConnectCallback *cb) {
    assert(handler_.fd() == -1);
    assert(connectCallback_ == nullptr);

    const char *error = nullptr;
    int fd = -1;

    for (;;) {
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (-1 == fd) {
            error = "Failed to allocate socket";
            break;
        }

        int rc = evutil_make_socket_nonblocking(fd);
        if (-1 == rc) {
            error = "Failed to set socket non-blocking";
            break;
        }

        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        rc = inet_pton(AF_INET, ip.c_str(), &saddr.sin_addr);
        if (1 != rc) {
            error = "Failed to convert address";
            break;
        }

        saddr.sin_port = htons(port);
        socklen_t len = sizeof(saddr);
        rc = ::connect(fd, reinterpret_cast<struct sockaddr*>(&saddr), len);
        if (rc == -1) {
            if (isConnectRetryable(evutil_socket_geterror(fd))) {
                // Expected; queue up the callback
                handler_.setFd(fd);
                connectCallback_ = cb;
                base_->registerHandler(&handler_, ensureWrite(
                    handler_.watched()));
                return;
            } else {
                error = "Connect failed";
                break;
            }
        }

        // Connect succeeded immediately
        handler_.setFd(fd);
        cb->complete();

        return;
    }

    if (fd != -1) {
        xclose(fd);
    }

    cb->error(std::runtime_error(error));
}

StreamImpl::~StreamImpl() {
    handler_.unregister();
    WriteRequest *r;
    // Delete all outstanding write requests
    while ((r = requests_.consumeFront()) != nullptr) { }
}

void StreamImpl::readHelper() {
    char buf[4096];

    // TODO: consider not reading indefinitely
    for (;;) {
        int nread = xread(handler_.fd(), buf, sizeof(buf));
        if (nread < 0) {
            if (isReadRetryable(evutil_socket_geterror(handler_.fd()))) {
                break;
            }
            // TODO: better errors
            if (readCallback_) {
                readCallback_->error(std::runtime_error("Read failed"));
            }
            break;
        } else if (nread == 0) {
            if (readCallback_) {
                readCallback_->eof();
            }
            break;
        }

        // TODO: a reserve + get readable iovec + readv will be more efficient
        // than a read + append.
        readBuffer_.append(buf, nread);

        if (readCallback_) {
            readCallback_->available(&readBuffer_);
        }
        if (nread < sizeof(buf)) {
            break;
        }
    }
}

void StreamImpl::connectHelper() {
    assert(connectCallback_);

    const char *error = nullptr;

    for (;;) {
        // Check connection status
        int err = 0;
        socklen_t len = sizeof(error);
#if defined(_WIN32)
        int rc = getsockopt(handler_.fd(), SOL_SOCKET, SO_ERROR,
            (char *) &err, &len);
#else
        int rc = getsockopt(handler_.fd(), SOL_SOCKET, SO_ERROR, &err, &len);
#endif
        if (-1 == rc) {
            error = "Failed to query connection status";
            break;
        }

        if (error != 0) {
            error = "Connection failed";
            break;
        }

        // Good to go
        auto *cb = connectCallback_;
        connectCallback_ = nullptr;
        cb->complete();
        return;
    }

    auto *cb = connectCallback_;
    connectCallback_ = nullptr;
    cb->error(std::runtime_error(error));
}

void StreamImpl::writeHelper() {
    WriteRequest *req = requests_.head;
    std::vector<Extent> extents;
    bool blocked = false;
    bool failed = false;
    while (req) {
        extents.clear();
        // TODO: better limit
        req->buffer_.peek(std::numeric_limits<size_t>::max(), &extents);
        assert(!extents.empty());

        // TODO: writev
        size_t total_written = 0;
        for (auto& extent : extents) {
            int written = xwrite(handler_.fd(), extent.data, extent.size);
            if (written >= 0) {
                total_written += written;
                if (written < extent.size) {
                    blocked = true;
                }
            } else {
                failed = true;
                break;
            }
        }

        req->buffer_.drain(total_written);

        if (blocked || failed) {
            break;
        }

        WriteCallback *cb = req->callback_;
        // Must not touch req after this call
        WriteRequest *next = requests_.consumeFront();
        if (!next) {
            // Uninstall the write handler before invoking the callback
            // for this final request; callbacks that know that they are
            // last invocation may legitimately do destructive things like
            // freeing this stream.
            base_->registerHandler(&handler_, removeWrite(handler_.watched()));
        }

        if (cb) {
            cb->complete(this);
        }

        req = next;
    }

    if (failed && req->callback_) {
        // TODO: better errors
        req->callback_->error(std::runtime_error("Write failed"));
    }
}

void Stream::Deleter::operator()(Stream *stream) {
    delete stream;
}

std::unique_ptr<Stream, Stream::Deleter> wrapFd(EventBase *base, int fd) {
    return std::unique_ptr<Stream, Stream::Deleter>(
         new StreamImpl(base, fd), Stream::Deleter());
}

std::unique_ptr<Stream, Stream::Deleter> Stream::create(EventBase *base) {
    return std::unique_ptr<Stream, Stream::Deleter>(
         new StreamImpl(base), Stream::Deleter());
}

} // wte namespace
<|endoftext|>


<commit_before>// This code is in the public domain -- icastano@gmail.com

#include "Fitting.h"

#include <nvcore/Algorithms.h> // max
#include <nvcore/Containers.h> // swap
#include <float.h> // FLT_MAX

using namespace nv;

Vector3 nv::ComputeCentroid(int n, const Vector3 * points, const float * weights, Vector3::Arg metric)
{
	Vector3 centroid(zero);
	float total = 0.0f;

	for (int i = 0; i < n; i++)
	{
		total += weights[i];
		centroid += weights[i]*points[i];
	}
	centroid /= total;

	return centroid;
}

void nv::ComputeCovariance(int n, const Vector3 * points, const float * weights, Vector3::Arg metric, float * covariance)
{
	// compute the centroid
	Vector3 centroid = ComputeCentroid(n, points, weights, metric);

	// compute covariance matrix
	for (int i = 0; i < 6; i++)
	{
		covariance[i] = 0.0f;
	}

	for (int i = 0; i < n; i++)
	{
		Vector3 a = (points[i] - centroid) * metric;
		Vector3 b = weights[i]*a;

		covariance[0] += a.x()*b.x();
		covariance[1] += a.x()*b.y();
		covariance[2] += a.x()*b.z();
		covariance[3] += a.y()*b.y();
		covariance[4] += a.y()*b.z();
		covariance[5] += a.z()*b.z();
	}
}

Vector3 nv::ComputePrincipalComponent(int n, const Vector3 * points, const float * weights, Vector3::Arg metric)
{
	float matrix[6];
	ComputeCovariance(n, points, weights, metric, matrix);

	if (matrix[0] == 0 || matrix[3] == 0 || matrix[5] == 0)
	{
		return Vector3(zero);
	}

	const int NUM = 8;

	Vector3 v(1, 1, 1);
	for (int i = 0; i < NUM; i++)
	{
		float x = v.x() * matrix[0] + v.y() * matrix[1] + v.z() * matrix[2];
		float y = v.x() * matrix[1] + v.y() * matrix[3] + v.z() * matrix[4];
		float z = v.x() * matrix[2] + v.y() * matrix[4] + v.z() * matrix[5];

		float norm = max(max(x, y), z);

		v = Vector3(x, y, z) / norm;
	}

	return v;
}

int nv::Compute4Means(int n, const Vector3 * points, const float * weights, Vector3::Arg metric, Vector3 * cluster)
{
	Vector3 centroid = ComputeCentroid(n, points, weights, metric);

	// Compute principal component.
	Vector3 principal = ComputePrincipalComponent(n, points, weights, metric);

	// Pick initial solution.
	int mini, maxi;
	mini = maxi = 0;

	float mindps, maxdps;
	mindps = maxdps = dot(points[0] - centroid, principal);

	for (int i = 1; i < n; ++i)
	{
		float dps = dot(points[i] - centroid, principal);

		if (dps < mindps) {
			mindps = dps;
			mini = i;
		}
		else {
			maxdps = dps;
			maxi = i;
		}
	}

	//cluster[0] = points[mini];
	//cluster[1] = points[maxi];
	cluster[0] = centroid + mindps * principal;
	cluster[1] = centroid + maxdps * principal;
	cluster[2] = (2 * cluster[0] + cluster[1]) / 3;
	cluster[3] = (2 * cluster[1] + cluster[0]) / 3;

	// Now we have to iteratively refine the clusters.
	while (true)
	{
		Vector3 newCluster[4] = { Vector3(zero), Vector3(zero), Vector3(zero), Vector3(zero) };
		float total[4] = {0, 0, 0, 0};

		for (int i = 0; i < n; ++i)
		{
			// Find nearest cluster.
			int nearest = 0;
			float mindist = FLT_MAX;
			for (int j = 0; j < 4; j++)
			{
				float dist = length_squared(cluster[j] - points[i]);
				if (dist < mindist)
				{
					mindist = dist;
					nearest = j;
				}
			}

			newCluster[nearest] += weights[i] * points[i];
			total[nearest] += weights[i];
		}

		for (int j = 0; j < 4; j++)
		{
			newCluster[j] /= total[j];
		}

		if ((equal(cluster[0], newCluster[0]) || total[0] == 0) &&
			(equal(cluster[1], newCluster[1]) || total[1] == 0) &&
			(equal(cluster[2], newCluster[2]) || total[2] == 0) &&
			(equal(cluster[3], newCluster[3]) || total[3] == 0))
		{
			return (total[0] != 0) + (total[1] != 0) + (total[2] != 0) + (total[3] != 0);
		}

		cluster[0] = newCluster[0];
		cluster[1] = newCluster[1];
		cluster[2] = newCluster[2];
		cluster[3] = newCluster[3];

		// Sort clusters by weight.
		for (int i = 0; i < 4; i++)
		{
			for (int j = i; j > 0 && total[j] > total[j - 1]; j--)
			{
				swap( total[j], total[j - 1] );
				swap( cluster[j], cluster[j - 1] );
			}
		}
	}
}

/*
int nv::Compute2Means(int n, const Vector3 * points, const float * weights, Vector3::Arg metric, Vector3 * cluster)
{
	Vector3 centroid = ComputeCentroid(n, points, weights, metric);

	// Compute principal component.
	Vector3 principal = ComputePrincipalComponent(n, points, weights, metric);

	// Pick initial solution.
	int mini, maxi;
	mini = maxi = 0;

	float mindps, maxdps;
	mindps = maxdps = dot(points[0] - centroid, principal);

	for (int i = 1; i < n; ++i)
	{
		float dps = dot(points[i] - centroid, principal);

		if (dps < mindps) {
			mindps = dps;
			mini = i;
		}
		else {
			maxdps = dps;
			maxi = i;
		}
	}

	cluster[0] = points[mini];
	cluster[3] = points[maxi];
	//cluster[0] = centroid + mindps * principal;
	//cluster[1] = centroid + maxdps * principal;
	cluster[2] = (2 * cluster[0] + cluster[1]) / 3;
	cluster[3] = (2 * cluster[1] + cluster[0]) / 3;

	// Now we have to iteratively refine the clusters.
	while (true)
	{
		Vector3 newCluster[4] = { Vector3(zero), Vector3(zero), Vector3(zero), Vector3(zero) };
		float total[4] = {0, 0, 0, 0};

		for (int i = 0; i < n; ++i)
		{
			// Find nearest cluster.
			int nearest = 0;
			float mindist = FLT_MAX;
			for (int j = 0; j < 4; j++)
			{
				float dist = length_squared(cluster[j] - points[i]);
				if (dist < mindist)
				{
					mindist = dist;
					nearest = j;
				}
			}

			newCluster[nearest] += weights[i] * points[i];
			total[nearest] += weights[i];
		}

		for (int j = 0; j < 4; j++)
		{
			newCluster[j] /= total[j];
		}

		if ((equal(cluster[0], newCluster[0]) || total[0] == 0) &&
			(equal(cluster[1], newCluster[1]) || total[1] == 0) &&
			(equal(cluster[2], newCluster[2]) || total[2] == 0) &&
			(equal(cluster[3], newCluster[3]) || total[3] == 0))
		{
			return (total[0] != 0) + (total[1] != 0) + (total[2] != 0) + (total[3] != 0);
		}

		cluster[0] = newCluster[0];
		cluster[1] = newCluster[1];
		cluster[2] = newCluster[2];
		cluster[3] = newCluster[3];

		// Sort clusters by weight.
		for (int i = 0; i < 4; i++)
		{
			for (int j = i; j > 0 && total[j] > total[j - 1]; j--)
			{
				swap( total[j], total[j - 1] );
				swap( cluster[j], cluster[j - 1] );
			}
		}
	}
}
*/<commit_msg>Use metric to measure distance to clusters.<commit_after>// This code is in the public domain -- icastano@gmail.com

#include "Fitting.h"

#include <nvcore/Algorithms.h> // max
#include <nvcore/Containers.h> // swap
#include <float.h> // FLT_MAX

using namespace nv;

Vector3 nv::ComputeCentroid(int n, const Vector3 * points, const float * weights, Vector3::Arg metric)
{
	Vector3 centroid(zero);
	float total = 0.0f;

	for (int i = 0; i < n; i++)
	{
		total += weights[i];
		centroid += weights[i]*points[i];
	}
	centroid /= total;

	return centroid;
}

void nv::ComputeCovariance(int n, const Vector3 * points, const float * weights, Vector3::Arg metric, float * covariance)
{
	// compute the centroid
	Vector3 centroid = ComputeCentroid(n, points, weights, metric);

	// compute covariance matrix
	for (int i = 0; i < 6; i++)
	{
		covariance[i] = 0.0f;
	}

	for (int i = 0; i < n; i++)
	{
		Vector3 a = (points[i] - centroid) * metric;
		Vector3 b = weights[i]*a;

		covariance[0] += a.x()*b.x();
		covariance[1] += a.x()*b.y();
		covariance[2] += a.x()*b.z();
		covariance[3] += a.y()*b.y();
		covariance[4] += a.y()*b.z();
		covariance[5] += a.z()*b.z();
	}
}

Vector3 nv::ComputePrincipalComponent(int n, const Vector3 * points, const float * weights, Vector3::Arg metric)
{
	float matrix[6];
	ComputeCovariance(n, points, weights, metric, matrix);

	if (matrix[0] == 0 || matrix[3] == 0 || matrix[5] == 0)
	{
		return Vector3(zero);
	}

	const int NUM = 8;

	Vector3 v(1, 1, 1);
	for (int i = 0; i < NUM; i++)
	{
		float x = v.x() * matrix[0] + v.y() * matrix[1] + v.z() * matrix[2];
		float y = v.x() * matrix[1] + v.y() * matrix[3] + v.z() * matrix[4];
		float z = v.x() * matrix[2] + v.y() * matrix[4] + v.z() * matrix[5];

		float norm = max(max(x, y), z);

		v = Vector3(x, y, z) / norm;
	}

	return v;
}

int nv::Compute4Means(int n, const Vector3 * points, const float * weights, Vector3::Arg metric, Vector3 * cluster)
{
	Vector3 centroid = ComputeCentroid(n, points, weights, metric);

	// Compute principal component.
	Vector3 principal = ComputePrincipalComponent(n, points, weights, metric);

	// Pick initial solution.
	int mini, maxi;
	mini = maxi = 0;

	float mindps, maxdps;
	mindps = maxdps = dot(points[0] - centroid, principal);

	for (int i = 1; i < n; ++i)
	{
		float dps = dot(points[i] - centroid, principal);

		if (dps < mindps) {
			mindps = dps;
			mini = i;
		}
		else {
			maxdps = dps;
			maxi = i;
		}
	}

	//cluster[0] = points[mini];
	//cluster[1] = points[maxi];
	cluster[0] = centroid + mindps * principal;
	cluster[1] = centroid + maxdps * principal;
	cluster[2] = (2 * cluster[0] + cluster[1]) / 3;
	cluster[3] = (2 * cluster[1] + cluster[0]) / 3;

	// Now we have to iteratively refine the clusters.
	while (true)
	{
		Vector3 newCluster[4] = { Vector3(zero), Vector3(zero), Vector3(zero), Vector3(zero) };
		float total[4] = {0, 0, 0, 0};

		for (int i = 0; i < n; ++i)
		{
			// Find nearest cluster.
			int nearest = 0;
			float mindist = FLT_MAX;
			for (int j = 0; j < 4; j++)
			{
				float dist = length_squared((cluster[j] - points[i]) * metric);
				if (dist < mindist)
				{
					mindist = dist;
					nearest = j;
				}
			}

			newCluster[nearest] += weights[i] * points[i];
			total[nearest] += weights[i];
		}

		for (int j = 0; j < 4; j++)
		{
			newCluster[j] /= total[j];
		}

		if ((equal(cluster[0], newCluster[0]) || total[0] == 0) &&
			(equal(cluster[1], newCluster[1]) || total[1] == 0) &&
			(equal(cluster[2], newCluster[2]) || total[2] == 0) &&
			(equal(cluster[3], newCluster[3]) || total[3] == 0))
		{
			return (total[0] != 0) + (total[1] != 0) + (total[2] != 0) + (total[3] != 0);
		}

		cluster[0] = newCluster[0];
		cluster[1] = newCluster[1];
		cluster[2] = newCluster[2];
		cluster[3] = newCluster[3];

		// Sort clusters by weight.
		for (int i = 0; i < 4; i++)
		{
			for (int j = i; j > 0 && total[j] > total[j - 1]; j--)
			{
				swap( total[j], total[j - 1] );
				swap( cluster[j], cluster[j - 1] );
			}
		}
	}
}

<|endoftext|>


<commit_before>/***************************************************************************
 *   Copyright (C) 2007 by carm   *
 *   carm@localhost   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <QtGui>
#include "QProjectM_MainWindow.hpp"
#include "QProjectMFileDialog.hpp"

#include <QTextStream>
#include <QCloseEvent>
#include <QFileDialog>

#include "QPlaylistModel.hpp"

QProjectM_MainWindow::QProjectM_MainWindow(const std::string & config_file)
:m_QProjectMFileDialog(new QProjectMFileDialog(this)), oldPresetIndex(-1)
{

      ui.setupUi(this);

      m_QProjectMWidget = new QProjectMWidget(config_file, this);

      m_timer = new QTimer(this);
      connect(m_timer, SIGNAL(timeout()), m_QProjectMWidget, SLOT(updateGL()));

      connect(ui.lockPresetCheckBox, SIGNAL(stateChanged(int)),
	m_QProjectMWidget, SLOT(setPresetLock(int)));

      connect(ui.clearPresetList_PushButton, SIGNAL(pressed()),
	this, SLOT(clearPlaylist()));

      connect(m_QProjectMWidget, SIGNAL(projectM_Initialized()), this, SLOT(postProjectM_Initialize()));

      m_QProjectMWidget->makeCurrent();
      m_QProjectMWidget->setFocus();

      m_timer->start(0);

      setCentralWidget(m_QProjectMWidget);

      createActions();
      createMenus();
      createToolBars();
      createStatusBar();
      readSettings();
      ui.presetPlayListDockWidget->hide();

}

void QProjectM_MainWindow::clearPlaylist() {

	playlistModel->clear();
	for (QHash<QString, PlaylistItemVector*>::iterator pos = historyHash.begin(); pos != historyHash.end(); ++pos) {
		delete(pos.value());
	}
	historyHash.clear();
	historyHash.insert(QString(), new PlaylistItemVector);
	previousFilter = QString();
	ui.presetSearchBarLineEdit->clear();
}

QProjectM * QProjectM_MainWindow::getQProjectM() {
	return m_QProjectMWidget->getQProjectM();
}

void QProjectM_MainWindow::updatePlaylistSelection(bool hardCut, int index) {

	if (hardCut)
		statusBar()->showMessage(tr("*** Hard cut ***") , 2000);
	else
		statusBar()->showMessage(tr("*** Soft cut ***") , 2000);

	if (oldPresetIndex > 0)
		playlistModel->setData(playlistModel->index(oldPresetIndex, 0), Qt::white, Qt::BackgroundRole);

	playlistModel->setData(playlistModel->index(index, 0), Qt::green, Qt::BackgroundRole);
	oldPresetIndex = index;

}

void QProjectM_MainWindow::selectPlaylistItem(const QModelIndex & index) {
	getQProjectM()->selectPreset(index.row());
}

void QProjectM_MainWindow::postProjectM_Initialize() {

	playlistModel = new QPlaylistModel(*m_QProjectMWidget->getQProjectM(),this);
	refreshPlaylist();

	ui.tableView->setModel(playlistModel);
        connect(m_QProjectMWidget->getQProjectM(), SIGNAL(presetSwitchedSignal(bool,unsigned int)), this, SLOT(updatePlaylistSelection(bool,unsigned int)));
	connect(ui.presetSearchBarLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(updateFilteredPlaylist(const QString&)));

        connect(ui.tableView, SIGNAL(activated(const QModelIndex &)),
		this, SLOT(selectPlaylistItem(const QModelIndex &)));

	connect(ui.tableView, SIGNAL(clicked(const QModelIndex &)),
		this, SLOT(changeRating(const QModelIndex &)));

}

void QProjectM_MainWindow::changeRating(const QModelIndex & index) {
	if (index.column() == 0)
		return;

	playlistModel->setData
		(index, (playlistModel->data(index, QPlaylistModel::RatingRole).toInt()+1) % 6, QPlaylistModel::RatingRole);
}

void QProjectM_MainWindow::keyReleaseEvent ( QKeyEvent * e )  {

	QModelIndex modelIndex;
	switch (e->key()) {
	case Qt::Key_L:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;

		if (ui.lockPresetCheckBox->checkState() == Qt::Checked)
			ui.lockPresetCheckBox->setCheckState(Qt::Unchecked);
		else
			ui.lockPresetCheckBox->setCheckState(Qt::Checked);

		// the projectM widget handles the actual lock
		//e->ignore();
		//m_QProjectMWidget->keyReleaseEvent(e);
		return;

	case Qt::Key_F1:
		 //emit(keyPressed m_QProjectMWidget,
	case Qt::Key_F:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;
		this->setWindowState(this->windowState() ^ Qt::WindowFullScreen);
		return;

	case Qt::Key_M:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;

		if (ui.presetPlayListDockWidget->isVisible()) {
			ui.presetPlayListDockWidget->hide();
		} else
			ui.presetPlayListDockWidget->show();

		if (menuBar()->isVisible()) {
			menuBar()->hide();
			m_QProjectMWidget->setFocus();
		}
		else {
		   menuBar()->show();
		}

		if (statusBar()->isVisible()) {
			statusBar()->hide();
		}
		else {
		   statusBar()->show();
		}
		return;

	case Qt::Key_R:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;

		//modelIndex.selectRandom()
		//modelIndex = QModelIndex(0,0,0);
		//selectPlaylistItem(modelIndex);
		//updatePlaylistSelection(true, modelIndex.row());
		return;
	default:
		//m_QProjectMWidget->keyReleaseEvent(e);
		break;//e->ignore();
	}

}

void QProjectM_MainWindow::closeEvent(QCloseEvent *event)
{
}

void QProjectM_MainWindow::open()
{

            if ( m_QProjectMFileDialog->exec()) {
		const QStringList & files = m_QProjectMFileDialog->selectedFiles();

		for (QStringList::const_iterator pos = files.begin();
			pos != files.end(); ++pos) {
			if (*pos != "")
				loadFile(*pos);
		}

		PlaylistItemVector * playlistItems = historyHash.value(QString());

		for (QHash<QString, PlaylistItemVector*>::iterator pos = historyHash.begin(); pos != historyHash.end(); ++pos) {
			if (pos.key() != QString())
				delete(pos.value());
		}

		historyHash.clear();
		historyHash.insert(QString(), playlistItems);

		updateFilteredPlaylist(previousFilter);

	    }

	//playlistModel->setHeaderData(0, Qt::Horizontal, tr("Preset"));//, Qt::DisplayRole);
}

//void QAbstractItemView::clicked ( const QModelIndex & index )   [signal]
void QProjectM_MainWindow::refreshPlaylist() {

    PlaylistItemVector * playlistItems = new PlaylistItemVector();
    for (int i = 0; i < playlistModel->rowCount();i++) {
	QModelIndex index = playlistModel->index(i, 0);
	const QString & name = playlistModel->data(index, Qt::DisplayRole).toString();
	const QString & url = playlistModel->data(index, QPlaylistModel::URLInfoRole).toString();

	playlistItems->push_back(PlaylistItemMetaData(url, name, 3));
    }
    historyHash.insert(QString(), playlistItems);

    QHeaderView * hHeader = new QHeaderView(Qt::Horizontal, this);
    QHeaderView * vHeader = new QHeaderView(Qt::Vertical, this);

    hHeader->setClickable(false);
    hHeader->setSortIndicatorShown(false);
    //hHeader->setSortIndicator(1, Qt::AscendingOrder);
    hHeader->setStretchLastSection(false);

	ui.tableView->setVerticalHeader(vHeader);
	ui.tableView->setHorizontalHeader(hHeader);

	hHeader->setResizeMode(QHeaderView::Fixed);
	hHeader->resizeSection(0, 500);
	hHeader->resizeSection(1, 50);

	/*
	hHeader->resizeSection(0, 200);
	hHeader->setResizeMode(0, QHeaderView::Stretch);
	hHeader->setResizeMode(1, QHeaderView::Fixed);
	hHeader->resizeSection(1, 25);
*/

//	playlistModel->setHeaderData(0, Qt::Horizontal, tr("Preset"));//, Qt::DisplayRole);

	vHeader->hide();
//	playlistModel->setHeaderData(0, Qt::Horizontal, 200, Qt::SizeHintRole);
	//playlistModel->setHeaderData(1, Qt::Horizontal, tr("Rating"));//, Qt::DisplayRole);
	//playlistModel->setHeaderData(2, Qt::Horizontal, tr("Preset"));//, Qt::DisplayRole);

}

void QProjectM_MainWindow::about()
{
      QMessageBox::about(this, tr("About QProjectM and ProjectM"),
            tr("QProjectM provides useful gui extensions to the projectM core library. For problems please email Carmelo Piccione (carmelo.piccione@gmail.com). \nprojectM is an advanced music visualizer based on Geiss's Milkdrop. For more info visit us at projectm.sf.net."));
}

void QProjectM_MainWindow::createActions()
{

      connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));

      connect(ui.actionAddPresets, SIGNAL(triggered()), this, SLOT(open()));

      connect(ui.actionAbout_qprojectM, SIGNAL(triggered()), this, SLOT(about()));

      //connect(ui.actionAbout_Qt, SIGNAL(triggered()), this, SLOT(aboutQt()));

}

void QProjectM_MainWindow::createMenus()
{
	ui.menuBar->hide();

}

void QProjectM_MainWindow::createToolBars()
{
}

void QProjectM_MainWindow::createStatusBar()
{
      statusBar()->hide();
      statusBar()->showMessage(tr("Welcome to qprojectM!"));
}

void QProjectM_MainWindow::readSettings()
{
      QSettings settings("Trolltech", "QProjectM");
      QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
      QSize size = settings.value("size", QSize(1000, 600)).toSize();
      resize(size);
      move(pos);
}

void QProjectM_MainWindow::writeSettings()
{
      QSettings settings("Trolltech", "QProjectM");
      settings.setValue("pos", pos());
      settings.setValue("size", size());
}

void QProjectM_MainWindow::loadFile(const QString &fileName, int rating)
{

	const QString & name = strippedName(fileName);

		PlaylistItemVector * playlistItems = historyHash.value(QString(), 0);
		assert (playlistItems != 0);

		playlistItems->push_back(PlaylistItemMetaData(fileName, name, rating));

}

QString QProjectM_MainWindow::strippedName(const QString &fullFileName)
{
      return QFileInfo(fullFileName).fileName();
}

void QProjectM_MainWindow::updateFilteredPlaylist(const QString & text) {

	const QString filter = text.toLower();

	playlistModel->clear();

	if (historyHash.contains(filter)) {
		const PlaylistItemVector & playlistItems = *historyHash.value(filter);
		for (PlaylistItemVector::const_iterator pos = playlistItems.begin(); pos != playlistItems.end();++pos) {
			playlistModel->appendRow(pos->url, pos->name, pos->rating);
		}
	} else {
		const PlaylistItemVector & playlistItems = *historyHash.value(QString());

		PlaylistItemVector * playlistItems2 = new PlaylistItemVector();
		for (PlaylistItemVector::const_iterator pos = playlistItems.begin(); pos != playlistItems.end();++pos) {
			if ((pos->name).contains(filter, Qt::CaseInsensitive)) {
				playlistModel->appendRow(pos->url, pos->name, pos->rating);
				playlistItems2->push_back(*pos);
			}
		}
		historyHash.insert(filter, playlistItems2);
	}
	previousFilter = filter;

	#if 0
	if (!(filter.substring(0, filter.length()-1) == previousFilter)) {

		PlaylistItemVector & playlistItems = *exclusionHash.value(previousFilter);
		while (!playlistItems.empty()) {
			const StringPair & pair = playlistItems.last();
			/// @bug need to do **ordered** insert, or something faster (index reference book keeping?)
			playlistModel->appendRow(pair.first, pair.second);
			playlistItems.pop_back();

		}
		exclusionHash.remove(previousFilter);
		delete(&playlistItems);

	} else {
		assert(filter.length() != previousFilter.length());
		PlaylistItemVector * playlistItems = new PlaylistItemVector();
		exclusionHash.insert(filter, playlistItems);

		for (int i = 0; i < playlistModel->rowCount(); i++) {
			QModelIndex index = playlistModel->index(i, 0);
			const QString & name = playlistModel->data(index, Qt::DisplayRole).toString();
			const QString & url = playlistModel->data(index, QPlaylistModel::URLInfoRole).toString();
			if (!name.contains(filter, Qt::CaseInsensitive)) {

				playlistItems->push_back(StringPair(url, name));
				assert (i < playlistModel->rowCount());
				assert (i >= 0);
				playlistModel->removeRow(i);
				i--;
			}

		}

	}
#endif
}

<commit_msg>changed fixed to interactive for headerviews. slightly better<commit_after>/***************************************************************************
 *   Copyright (C) 2007 by carm   *
 *   carm@localhost   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <QtGui>
#include "QProjectM_MainWindow.hpp"
#include "QProjectMFileDialog.hpp"

#include <QTextStream>
#include <QCloseEvent>
#include <QFileDialog>

#include "QPlaylistModel.hpp"

QProjectM_MainWindow::QProjectM_MainWindow(const std::string & config_file)
:m_QProjectMFileDialog(new QProjectMFileDialog(this)), oldPresetIndex(-1)
{

      ui.setupUi(this);

      m_QProjectMWidget = new QProjectMWidget(config_file, this);

      m_timer = new QTimer(this);
      connect(m_timer, SIGNAL(timeout()), m_QProjectMWidget, SLOT(updateGL()));

      connect(ui.lockPresetCheckBox, SIGNAL(stateChanged(int)),
	m_QProjectMWidget, SLOT(setPresetLock(int)));

      connect(ui.clearPresetList_PushButton, SIGNAL(pressed()),
	this, SLOT(clearPlaylist()));

      connect(m_QProjectMWidget, SIGNAL(projectM_Initialized()), this, SLOT(postProjectM_Initialize()));

      m_QProjectMWidget->makeCurrent();
      m_QProjectMWidget->setFocus();

      m_timer->start(0);

      setCentralWidget(m_QProjectMWidget);

      createActions();
      createMenus();
      createToolBars();
      createStatusBar();
      readSettings();
      ui.presetPlayListDockWidget->hide();

}

void QProjectM_MainWindow::clearPlaylist() {

	playlistModel->clear();
	for (QHash<QString, PlaylistItemVector*>::iterator pos = historyHash.begin(); pos != historyHash.end(); ++pos) {
		delete(pos.value());
	}
	historyHash.clear();
	historyHash.insert(QString(), new PlaylistItemVector);
	previousFilter = QString();
	ui.presetSearchBarLineEdit->clear();
}

QProjectM * QProjectM_MainWindow::getQProjectM() {
	return m_QProjectMWidget->getQProjectM();
}

void QProjectM_MainWindow::updatePlaylistSelection(bool hardCut, int index) {

	if (hardCut)
		statusBar()->showMessage(tr("*** Hard cut ***") , 2000);
	else
		statusBar()->showMessage(tr("*** Soft cut ***") , 2000);

	if (oldPresetIndex > 0)
		playlistModel->setData(playlistModel->index(oldPresetIndex, 0), Qt::white, Qt::BackgroundRole);

	playlistModel->setData(playlistModel->index(index, 0), Qt::green, Qt::BackgroundRole);
	oldPresetIndex = index;

}

void QProjectM_MainWindow::selectPlaylistItem(const QModelIndex & index) {
	getQProjectM()->selectPreset(index.row());
}

void QProjectM_MainWindow::postProjectM_Initialize() {

	playlistModel = new QPlaylistModel(*m_QProjectMWidget->getQProjectM(),this);
	refreshPlaylist();

	ui.tableView->setModel(playlistModel);
        connect(m_QProjectMWidget->getQProjectM(), SIGNAL(presetSwitchedSignal(bool,unsigned int)), this, SLOT(updatePlaylistSelection(bool,unsigned int)));
	connect(ui.presetSearchBarLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(updateFilteredPlaylist(const QString&)));

        connect(ui.tableView, SIGNAL(activated(const QModelIndex &)),
		this, SLOT(selectPlaylistItem(const QModelIndex &)));

	connect(ui.tableView, SIGNAL(clicked(const QModelIndex &)),
		this, SLOT(changeRating(const QModelIndex &)));

}

void QProjectM_MainWindow::changeRating(const QModelIndex & index) {
	if (index.column() == 0)
		return;

	playlistModel->setData
		(index, (playlistModel->data(index, QPlaylistModel::RatingRole).toInt()+1) % 6, QPlaylistModel::RatingRole);
}

void QProjectM_MainWindow::keyReleaseEvent ( QKeyEvent * e )  {

	QModelIndex modelIndex;
	switch (e->key()) {
	case Qt::Key_L:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;

		if (ui.lockPresetCheckBox->checkState() == Qt::Checked)
			ui.lockPresetCheckBox->setCheckState(Qt::Unchecked);
		else
			ui.lockPresetCheckBox->setCheckState(Qt::Checked);

		// the projectM widget handles the actual lock
		//e->ignore();
		//m_QProjectMWidget->keyReleaseEvent(e);
		return;

	case Qt::Key_F1:
		 //emit(keyPressed m_QProjectMWidget,
	case Qt::Key_F:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;
		this->setWindowState(this->windowState() ^ Qt::WindowFullScreen);
		return;

	case Qt::Key_M:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;

		if (ui.presetPlayListDockWidget->isVisible()) {
			ui.presetPlayListDockWidget->hide();
		} else
			ui.presetPlayListDockWidget->show();

		if (menuBar()->isVisible()) {
			menuBar()->hide();
			m_QProjectMWidget->setFocus();
		}
		else {
		   menuBar()->show();
		}

		if (statusBar()->isVisible()) {
			statusBar()->hide();
		}
		else {
		   statusBar()->show();
		}
		return;

	case Qt::Key_R:
		if (ui.presetSearchBarLineEdit->hasFocus())
			return;

		//modelIndex.selectRandom()
		//modelIndex = QModelIndex(0,0,0);
		//selectPlaylistItem(modelIndex);
		//updatePlaylistSelection(true, modelIndex.row());
		return;
	default:
		//m_QProjectMWidget->keyReleaseEvent(e);
		break;//e->ignore();
	}

}

void QProjectM_MainWindow::closeEvent(QCloseEvent *event)
{
}

void QProjectM_MainWindow::open()
{

            if ( m_QProjectMFileDialog->exec()) {
		const QStringList & files = m_QProjectMFileDialog->selectedFiles();

		for (QStringList::const_iterator pos = files.begin();
			pos != files.end(); ++pos) {
			if (*pos != "")
				loadFile(*pos);
		}

		PlaylistItemVector * playlistItems = historyHash.value(QString());

		for (QHash<QString, PlaylistItemVector*>::iterator pos = historyHash.begin(); pos != historyHash.end(); ++pos) {
			if (pos.key() != QString())
				delete(pos.value());
		}

		historyHash.clear();
		historyHash.insert(QString(), playlistItems);

		updateFilteredPlaylist(previousFilter);

	    }

	//playlistModel->setHeaderData(0, Qt::Horizontal, tr("Preset"));//, Qt::DisplayRole);
}

//void QAbstractItemView::clicked ( const QModelIndex & index )   [signal]
void QProjectM_MainWindow::refreshPlaylist() {

    PlaylistItemVector * playlistItems = new PlaylistItemVector();
    for (int i = 0; i < playlistModel->rowCount();i++) {
	QModelIndex index = playlistModel->index(i, 0);
	const QString & name = playlistModel->data(index, Qt::DisplayRole).toString();
	const QString & url = playlistModel->data(index, QPlaylistModel::URLInfoRole).toString();

	playlistItems->push_back(PlaylistItemMetaData(url, name, 3));
    }
    historyHash.insert(QString(), playlistItems);

    QHeaderView * hHeader = new QHeaderView(Qt::Horizontal, this);
    QHeaderView * vHeader = new QHeaderView(Qt::Vertical, this);

    hHeader->setClickable(false);
    hHeader->setSortIndicatorShown(false);
    //hHeader->setSortIndicator(1, Qt::AscendingOrder);
    hHeader->setStretchLastSection(false);

	ui.tableView->setVerticalHeader(vHeader);
	ui.tableView->setHorizontalHeader(hHeader);

	hHeader->setResizeMode(QHeaderView::Interactive);
	hHeader->resizeSection(0, 500);
	hHeader->resizeSection(1, 50);

	/*
	hHeader->resizeSection(0, 200);
	hHeader->setResizeMode(0, QHeaderView::Stretch);
	hHeader->setResizeMode(1, QHeaderView::Fixed);
	hHeader->resizeSection(1, 25);
*/

//	playlistModel->setHeaderData(0, Qt::Horizontal, tr("Preset"));//, Qt::DisplayRole);

	vHeader->hide();
//	playlistModel->setHeaderData(0, Qt::Horizontal, 200, Qt::SizeHintRole);
	//playlistModel->setHeaderData(1, Qt::Horizontal, tr("Rating"));//, Qt::DisplayRole);
	//playlistModel->setHeaderData(2, Qt::Horizontal, tr("Preset"));//, Qt::DisplayRole);

}

void QProjectM_MainWindow::about()
{
      QMessageBox::about(this, tr("About QProjectM and ProjectM"),
            tr("QProjectM provides useful gui extensions to the projectM core library. For problems please email Carmelo Piccione (carmelo.piccione@gmail.com). \nprojectM is an advanced music visualizer based on Geiss's Milkdrop. For more info visit us at projectm.sf.net."));
}

void QProjectM_MainWindow::createActions()
{

      connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));

      connect(ui.actionAddPresets, SIGNAL(triggered()), this, SLOT(open()));

      connect(ui.actionAbout_qprojectM, SIGNAL(triggered()), this, SLOT(about()));

      //connect(ui.actionAbout_Qt, SIGNAL(triggered()), this, SLOT(aboutQt()));

}

void QProjectM_MainWindow::createMenus()
{
	ui.menuBar->hide();

}

void QProjectM_MainWindow::createToolBars()
{
}

void QProjectM_MainWindow::createStatusBar()
{
      statusBar()->hide();
      statusBar()->showMessage(tr("Welcome to qprojectM!"));
}

void QProjectM_MainWindow::readSettings()
{
      QSettings settings("Trolltech", "QProjectM");
      QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
      QSize size = settings.value("size", QSize(1000, 600)).toSize();
      resize(size);
      move(pos);
}

void QProjectM_MainWindow::writeSettings()
{
      QSettings settings("Trolltech", "QProjectM");
      settings.setValue("pos", pos());
      settings.setValue("size", size());
}

void QProjectM_MainWindow::loadFile(const QString &fileName, int rating)
{

	const QString & name = strippedName(fileName);

		PlaylistItemVector * playlistItems = historyHash.value(QString(), 0);
		assert (playlistItems != 0);

		playlistItems->push_back(PlaylistItemMetaData(fileName, name, rating));

}

QString QProjectM_MainWindow::strippedName(const QString &fullFileName)
{
      return QFileInfo(fullFileName).fileName();
}

void QProjectM_MainWindow::updateFilteredPlaylist(const QString & text) {

	const QString filter = text.toLower();

	playlistModel->clear();

	if (historyHash.contains(filter)) {
		const PlaylistItemVector & playlistItems = *historyHash.value(filter);
		for (PlaylistItemVector::const_iterator pos = playlistItems.begin(); pos != playlistItems.end();++pos) {
			playlistModel->appendRow(pos->url, pos->name, pos->rating);
		}
	} else {
		const PlaylistItemVector & playlistItems = *historyHash.value(QString());

		PlaylistItemVector * playlistItems2 = new PlaylistItemVector();
		for (PlaylistItemVector::const_iterator pos = playlistItems.begin(); pos != playlistItems.end();++pos) {
			if ((pos->name).contains(filter, Qt::CaseInsensitive)) {
				playlistModel->appendRow(pos->url, pos->name, pos->rating);
				playlistItems2->push_back(*pos);
			}
		}
		historyHash.insert(filter, playlistItems2);
	}
	previousFilter = filter;

	#if 0
	if (!(filter.substring(0, filter.length()-1) == previousFilter)) {

		PlaylistItemVector & playlistItems = *exclusionHash.value(previousFilter);
		while (!playlistItems.empty()) {
			const StringPair & pair = playlistItems.last();
			/// @bug need to do **ordered** insert, or something faster (index reference book keeping?)
			playlistModel->appendRow(pair.first, pair.second);
			playlistItems.pop_back();

		}
		exclusionHash.remove(previousFilter);
		delete(&playlistItems);

	} else {
		assert(filter.length() != previousFilter.length());
		PlaylistItemVector * playlistItems = new PlaylistItemVector();
		exclusionHash.insert(filter, playlistItems);

		for (int i = 0; i < playlistModel->rowCount(); i++) {
			QModelIndex index = playlistModel->index(i, 0);
			const QString & name = playlistModel->data(index, Qt::DisplayRole).toString();
			const QString & url = playlistModel->data(index, QPlaylistModel::URLInfoRole).toString();
			if (!name.contains(filter, Qt::CaseInsensitive)) {

				playlistItems->push_back(StringPair(url, name));
				assert (i < playlistModel->rowCount());
				assert (i >= 0);
				playlistModel->removeRow(i);
				i--;
			}

		}

	}
#endif
}

<|endoftext|>