<commit_before>#include "CommandController.hpp"

#include "Application.hpp"
#include "common/SignalVector.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/Command.hpp"
#include "controllers/commands/CommandModel.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
#include "util/CombinePath.hpp"
#include "widgets/dialogs/LogsPopup.hpp"

#include <QApplication>
#include <QFile>
#include <QRegularExpression>

#define TWITCH_DEFAULT_COMMANDS                                         \
    {                                                                   \
        "/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", \
            "/unban", "/timeout", "/untimeout", "/slow", "/slowoff",    \
            "/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff",   \
            "/clear", "/subscribers", "/subscribersoff", "/followers",  \
            "/followersoff"                                             \
    }

namespace chatterino {

CommandController::CommandController()
{
    auto addFirstMatchToMap = [this](auto args) {
        this->commandsMap_.remove(args.item.name);

        for (const Command &cmd : this->items.getVector())
        {
            if (cmd.name == args.item.name)
            {
                this->commandsMap_[cmd.name] = cmd;
                break;
            }
        }
    };

    this->items.itemInserted.connect(addFirstMatchToMap);
    this->items.itemRemoved.connect(addFirstMatchToMap);
}

void CommandController::initialize(Settings &, Paths &paths)
{
    this->load(paths);
}

void CommandController::load(Paths &paths)
{
    this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt");

    QFile textFile(this->filePath_);
    if (!textFile.open(QIODevice::ReadOnly))
    {
        // No commands file created yet
        return;
    }

    QList<QByteArray> test = textFile.readAll().split('\n');

    for (const auto &command : test)
    {
        if (command.isEmpty())
        {
            continue;
        }

        this->items.appendItem(Command(command));
    }

    textFile.close();
}

void CommandController::save()
{
    QFile textFile(this->filePath_);
    if (!textFile.open(QIODevice::WriteOnly))
    {
        log("[CommandController::saveCommands] Unable to open {} for writing",
            this->filePath_);
        return;
    }

    for (const Command &cmd : this->items.getVector())
    {
        textFile.write((cmd.toString() + "\n").toUtf8());
    }

    textFile.close();
}

CommandModel *CommandController::createModel(QObject *parent)
{
    CommandModel *model = new CommandModel(parent);
    model->init(&this->items);

    return model;
}

QString CommandController::execCommand(const QString &text, ChannelPtr channel,
                                       bool dryRun)
{
    QStringList words = text.split(' ', QString::SkipEmptyParts);
    Command command;

    {
        std::lock_guard<std::mutex> lock(this->mutex_);

        if (words.length() == 0)
        {
            return text;
        }

        QString commandName = words[0];

        // works in a valid twitch channel and /whispers, etc...
        if (!dryRun && channel->isTwitchChannel())
        {
            if (commandName == "/w")
            {
                if (words.length() <= 2)
                {
                    return "";
                }

                auto app = getApp();

                MessageBuilder b;

                b.emplace<TimestampElement>();
                b.emplace<TextElement>(
                    app->accounts->twitch.getCurrent()->getUserName(),
                    MessageElementFlag::Text, MessageColor::Text,
                    FontStyle::ChatMediumBold);
                b.emplace<TextElement>("->", MessageElementFlag::Text);
                b.emplace<TextElement>(words[1] + ":", MessageElementFlag::Text,
                                       MessageColor::Text,
                                       FontStyle::ChatMediumBold);

                const auto &acc = app->accounts->twitch.getCurrent();
                const auto &accemotes = *acc->accessEmotes();
                const auto &bttvemotes = app->twitch.server->getBttvEmotes();
                const auto &ffzemotes = app->twitch.server->getFfzEmotes();
                auto flags = MessageElementFlags();
                auto emote = boost::optional<EmotePtr>{};
                for (int i = 2; i < words.length(); i++) {
                    {  // twitch emote
                        auto it = accemotes.emotes.find({words[i]});
                        if (it != accemotes.emotes.end()) {
                            b.emplace<EmoteElement>(
                                it->second, MessageElementFlag::TwitchEmote);
                            continue;
                        }
                    }  // twitch emote

                    {  // bttv/ffz emote
                        if ((emote = bttvemotes.emote({words[i]}))) {
                            flags = MessageElementFlag::BttvEmote;
                        } else if ((emote = ffzemotes.emote({words[i]}))) {
                            flags = MessageElementFlag::FfzEmote;
                        }
                        if (emote) {
                            b.emplace<EmoteElement>(emote.get(), flags);
                            continue;
                        }
                    }  // bttv/ffz emote
                    {  // emoji/text
                        for (auto &variant : app->emotes->emojis.parse(
                                 app->emotes->emojis.replaceShortCodes(
                                     words[i]))) {
                            constexpr const static struct {
                                void operator()(EmotePtr emote,
                                                MessageBuilder &b) const
                                {
                                    b.emplace<EmoteElement>(
                                        emote, MessageElementFlag::EmojiAll);
                                }
                                void operator()(const QString &string,
                                                MessageBuilder &b) const
                                {
                                    b.emplace<TextElement>(
                                        string, MessageElementFlag::Text);
                                }
                            } visitor;
                            boost::apply_visitor(
                                [&b](auto &&arg) { visitor(arg, b); }, variant);
                        }  // emoji/text
                    }
                }

                b->flags.set(MessageFlag::DoNotTriggerNotification);
                auto messagexD = b.release();

                app->twitch.server->whispersChannel->addMessage(messagexD);

                app->twitch.server->sendMessage("jtv", text);

                if (getSettings()->inlineWhispers) {
                    app->twitch.server->forEachChannel(
                        [&messagexD](ChannelPtr _channel) {
                            _channel->addMessage(messagexD);
                        });
                }

                return "";
            }
        }

        // check if default command exists
        auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());

        // works only in a valid twitch channel
        if (!dryRun && twitchChannel != nullptr)
        {
            if (commandName == "/debug-args")
            {
                QString msg = QApplication::instance()->arguments().join(' ');

                channel->addMessage(makeSystemMessage(msg));

                return "";
            }
            else if (commandName == "/uptime")
            {
                const auto &streamStatus = twitchChannel->accessStreamStatus();

                QString messageText = streamStatus->live
                                          ? streamStatus->uptime
                                          : "Channel is not live.";

                channel->addMessage(makeSystemMessage(messageText));

                return "";
            }
            else if (commandName == "/ignore")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /ignore [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to ignore someone"));
                    return "";
                }

                user->ignore(
                    target, [channel](auto resultCode, const QString &message) {
                        channel->addMessage(makeSystemMessage(message));
                    });

                return "";
            }
            else if (commandName == "/unignore")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /unignore [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to ignore someone"));
                    return "";
                }

                user->unignore(
                    target, [channel](auto resultCode, const QString &message) {
                        channel->addMessage(makeSystemMessage(message));
                    });

                return "";
            }
            else if (commandName == "/follow")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /follow [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to follow someone"));
                    return "";
                }

                TwitchApi::findUserId(
                    target, [user, channel, target](QString userId) {
                        if (userId.isEmpty())
                        {
                            channel->addMessage(makeSystemMessage(
                                "User " + target + " could not be followed!"));
                            return;
                        }
                        user->followUser(userId, [channel, target]() {
                            channel->addMessage(makeSystemMessage(
                                "You successfully followed " + target));
                        });
                    });

                return "";
            }
            else if (commandName == "/unfollow")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /unfollow [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to follow someone"));
                    return "";
                }

                TwitchApi::findUserId(
                    target, [user, channel, target](QString userId) {
                        if (userId.isEmpty())
                        {
                            channel->addMessage(makeSystemMessage(
                                "User " + target + " could not be followed!"));
                            return;
                        }
                        user->unfollowUser(userId, [channel, target]() {
                            channel->addMessage(makeSystemMessage(
                                "You successfully unfollowed " + target));
                        });
                    });

                return "";
            }
            else if (commandName == "/logs")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /logs [user] (channel)"));
                    return "";
                }
                auto app = getApp();

                auto logs = new LogsPopup();
                QString target;

                if (words.at(1).at(0) == "@")
                {
                    target = words.at(1).mid(1);
                }
                else
                {
                    target = words.at(1);
                }

                if (words.size() == 3)
                {
                    QString channelName = words.at(2);
                    if (words.at(2).at(0) == "#")
                    {
                        channelName = words.at(2).mid(1);
                    }
                    auto logsChannel =
                        app->twitch.server->getChannelOrEmpty(channelName);
                    if (logsChannel == nullptr)
                    {
                    }
                    else
                    {
                        logs->setInfo(logsChannel, target);
                    }
                }
                else
                {
                    logs->setInfo(channel, target);
                }
                logs->setAttribute(Qt::WA_DeleteOnClose);
                logs->show();
                return "";
            }
        }

        // check if custom command exists
        auto it = this->commandsMap_.find(commandName);
        if (it == this->commandsMap_.end())
        {
            return text;
        }

        command = it.value();
    }

    return this->execCustomCommand(words, command);
}

QString CommandController::execCustomCommand(const QStringList &words,
                                             const Command &command)
{
    QString result;

    static QRegularExpression parseCommand("(^|[^{])({{)*{(\\d+\\+?)}");

    int lastCaptureEnd = 0;

    auto globalMatch = parseCommand.globalMatch(command.func);
    int matchOffset = 0;

    while (true)
    {
        QRegularExpressionMatch match =
            parseCommand.match(command.func, matchOffset);

        if (!match.hasMatch())
        {
            break;
        }

        result += command.func.mid(lastCaptureEnd,
                                   match.capturedStart() - lastCaptureEnd + 1);

        lastCaptureEnd = match.capturedEnd();
        matchOffset = lastCaptureEnd - 1;

        QString wordIndexMatch = match.captured(3);

        bool plus = wordIndexMatch.at(wordIndexMatch.size() - 1) == '+';
        wordIndexMatch = wordIndexMatch.replace("+", "");

        bool ok;
        int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok);
        if (!ok || wordIndex == 0)
        {
            result += "{" + match.captured(3) + "}";
            continue;
        }

        if (words.length() <= wordIndex)
        {
            continue;
        }

        if (plus)
        {
            bool first = true;
            for (int i = wordIndex; i < words.length(); i++)
            {
                if (!first)
                {
                    result += " ";
                }
                result += words[i];
                first = false;
            }
        }
        else
        {
            result += words[wordIndex];
        }
    }

    result += command.func.mid(lastCaptureEnd);

    if (result.size() > 0 && result.at(0) == '{')
    {
        result = result.mid(1);
    }

    return result.replace("{{", "{");
}

QStringList CommandController::getDefaultTwitchCommandList()
{
    QStringList l = TWITCH_DEFAULT_COMMANDS;
    l += "/uptime";

    return l;
}

}  // namespace chatterino
<commit_msg>render emoji short codes<commit_after>#include "CommandController.hpp"

#include "Application.hpp"
#include "common/SignalVector.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/Command.hpp"
#include "controllers/commands/CommandModel.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
#include "util/CombinePath.hpp"
#include "widgets/dialogs/LogsPopup.hpp"

#include <QApplication>
#include <QFile>
#include <QRegularExpression>

#define TWITCH_DEFAULT_COMMANDS                                         \
    {                                                                   \
        "/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", \
            "/unban", "/timeout", "/untimeout", "/slow", "/slowoff",    \
            "/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff",   \
            "/clear", "/subscribers", "/subscribersoff", "/followers",  \
            "/followersoff"                                             \
    }

namespace chatterino {

CommandController::CommandController()
{
    auto addFirstMatchToMap = [this](auto args) {
        this->commandsMap_.remove(args.item.name);

        for (const Command &cmd : this->items.getVector())
        {
            if (cmd.name == args.item.name)
            {
                this->commandsMap_[cmd.name] = cmd;
                break;
            }
        }
    };

    this->items.itemInserted.connect(addFirstMatchToMap);
    this->items.itemRemoved.connect(addFirstMatchToMap);
}

void CommandController::initialize(Settings &, Paths &paths)
{
    this->load(paths);
}

void CommandController::load(Paths &paths)
{
    this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt");

    QFile textFile(this->filePath_);
    if (!textFile.open(QIODevice::ReadOnly))
    {
        // No commands file created yet
        return;
    }

    QList<QByteArray> test = textFile.readAll().split('\n');

    for (const auto &command : test)
    {
        if (command.isEmpty())
        {
            continue;
        }

        this->items.appendItem(Command(command));
    }

    textFile.close();
}

void CommandController::save()
{
    QFile textFile(this->filePath_);
    if (!textFile.open(QIODevice::WriteOnly))
    {
        log("[CommandController::saveCommands] Unable to open {} for writing",
            this->filePath_);
        return;
    }

    for (const Command &cmd : this->items.getVector())
    {
        textFile.write((cmd.toString() + "\n").toUtf8());
    }

    textFile.close();
}

CommandModel *CommandController::createModel(QObject *parent)
{
    CommandModel *model = new CommandModel(parent);
    model->init(&this->items);

    return model;
}

QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr channel,
                                       bool dryRun)
{
    QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji);
    QStringList words = text.split(' ', QString::SkipEmptyParts);
    Command command;

    {
        std::lock_guard<std::mutex> lock(this->mutex_);

        if (words.length() == 0)
        {
            return text;
        }

        QString commandName = words[0];

        // works in a valid twitch channel and /whispers, etc...
        if (!dryRun && channel->isTwitchChannel())
        {
            if (commandName == "/w")
            {
                if (words.length() <= 2)
                {
                    return "";
                }

                auto app = getApp();

                MessageBuilder b;

                b.emplace<TimestampElement>();
                b.emplace<TextElement>(
                    app->accounts->twitch.getCurrent()->getUserName(),
                    MessageElementFlag::Text, MessageColor::Text,
                    FontStyle::ChatMediumBold);
                b.emplace<TextElement>("->", MessageElementFlag::Text);
                b.emplace<TextElement>(words[1] + ":", MessageElementFlag::Text,
                                       MessageColor::Text,
                                       FontStyle::ChatMediumBold);

                const auto &acc = app->accounts->twitch.getCurrent();
                const auto &accemotes = *acc->accessEmotes();
                const auto &bttvemotes = app->twitch.server->getBttvEmotes();
                const auto &ffzemotes = app->twitch.server->getFfzEmotes();
                auto flags = MessageElementFlags();
                auto emote = boost::optional<EmotePtr>{};
                for (int i = 2; i < words.length(); i++) {
                    {  // twitch emote
                        auto it = accemotes.emotes.find({words[i]});
                        if (it != accemotes.emotes.end()) {
                            b.emplace<EmoteElement>(
                                it->second, MessageElementFlag::TwitchEmote);
                            continue;
                        }
                    }  // twitch emote

                    {  // bttv/ffz emote
                        if ((emote = bttvemotes.emote({words[i]}))) {
                            flags = MessageElementFlag::BttvEmote;
                        } else if ((emote = ffzemotes.emote({words[i]}))) {
                            flags = MessageElementFlag::FfzEmote;
                        }
                        if (emote) {
                            b.emplace<EmoteElement>(emote.get(), flags);
                            continue;
                        }
                    }  // bttv/ffz emote
                    {  // emoji/text
                        for (auto &variant :
                             app->emotes->emojis.parse(words[i])) {
                            constexpr const static struct {
                                void operator()(EmotePtr emote,
                                                MessageBuilder &b) const
                                {
                                    b.emplace<EmoteElement>(
                                        emote, MessageElementFlag::EmojiAll);
                                }
                                void operator()(const QString &string,
                                                MessageBuilder &b) const
                                {
                                    b.emplace<TextElement>(
                                        string, MessageElementFlag::Text);
                                }
                            } visitor;
                            boost::apply_visitor(
                                [&b](auto &&arg) { visitor(arg, b); }, variant);
                        }  // emoji/text
                    }
                }

                b->flags.set(MessageFlag::DoNotTriggerNotification);
                auto messagexD = b.release();

                app->twitch.server->whispersChannel->addMessage(messagexD);

                app->twitch.server->sendMessage("jtv", text);

                if (getSettings()->inlineWhispers) {
                    app->twitch.server->forEachChannel(
                        [&messagexD](ChannelPtr _channel) {
                            _channel->addMessage(messagexD);
                        });
                }

                return "";
            }
        }

        // check if default command exists
        auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());

        // works only in a valid twitch channel
        if (!dryRun && twitchChannel != nullptr)
        {
            if (commandName == "/debug-args")
            {
                QString msg = QApplication::instance()->arguments().join(' ');

                channel->addMessage(makeSystemMessage(msg));

                return "";
            }
            else if (commandName == "/uptime")
            {
                const auto &streamStatus = twitchChannel->accessStreamStatus();

                QString messageText = streamStatus->live
                                          ? streamStatus->uptime
                                          : "Channel is not live.";

                channel->addMessage(makeSystemMessage(messageText));

                return "";
            }
            else if (commandName == "/ignore")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /ignore [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to ignore someone"));
                    return "";
                }

                user->ignore(
                    target, [channel](auto resultCode, const QString &message) {
                        channel->addMessage(makeSystemMessage(message));
                    });

                return "";
            }
            else if (commandName == "/unignore")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /unignore [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to ignore someone"));
                    return "";
                }

                user->unignore(
                    target, [channel](auto resultCode, const QString &message) {
                        channel->addMessage(makeSystemMessage(message));
                    });

                return "";
            }
            else if (commandName == "/follow")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /follow [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to follow someone"));
                    return "";
                }

                TwitchApi::findUserId(
                    target, [user, channel, target](QString userId) {
                        if (userId.isEmpty())
                        {
                            channel->addMessage(makeSystemMessage(
                                "User " + target + " could not be followed!"));
                            return;
                        }
                        user->followUser(userId, [channel, target]() {
                            channel->addMessage(makeSystemMessage(
                                "You successfully followed " + target));
                        });
                    });

                return "";
            }
            else if (commandName == "/unfollow")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /unfollow [user]"));
                    return "";
                }
                auto app = getApp();

                auto user = app->accounts->twitch.getCurrent();
                auto target = words.at(1);

                if (user->isAnon())
                {
                    channel->addMessage(makeSystemMessage(
                        "You must be logged in to follow someone"));
                    return "";
                }

                TwitchApi::findUserId(
                    target, [user, channel, target](QString userId) {
                        if (userId.isEmpty())
                        {
                            channel->addMessage(makeSystemMessage(
                                "User " + target + " could not be followed!"));
                            return;
                        }
                        user->unfollowUser(userId, [channel, target]() {
                            channel->addMessage(makeSystemMessage(
                                "You successfully unfollowed " + target));
                        });
                    });

                return "";
            }
            else if (commandName == "/logs")
            {
                if (words.size() < 2)
                {
                    channel->addMessage(
                        makeSystemMessage("Usage: /logs [user] (channel)"));
                    return "";
                }
                auto app = getApp();

                auto logs = new LogsPopup();
                QString target;

                if (words.at(1).at(0) == "@")
                {
                    target = words.at(1).mid(1);
                }
                else
                {
                    target = words.at(1);
                }

                if (words.size() == 3)
                {
                    QString channelName = words.at(2);
                    if (words.at(2).at(0) == "#")
                    {
                        channelName = words.at(2).mid(1);
                    }
                    auto logsChannel =
                        app->twitch.server->getChannelOrEmpty(channelName);
                    if (logsChannel == nullptr)
                    {
                    }
                    else
                    {
                        logs->setInfo(logsChannel, target);
                    }
                }
                else
                {
                    logs->setInfo(channel, target);
                }
                logs->setAttribute(Qt::WA_DeleteOnClose);
                logs->show();
                return "";
            }
        }

        // check if custom command exists
        auto it = this->commandsMap_.find(commandName);
        if (it == this->commandsMap_.end())
        {
            return text;
        }

        command = it.value();
    }

    return this->execCustomCommand(words, command);
}

QString CommandController::execCustomCommand(const QStringList &words,
                                             const Command &command)
{
    QString result;

    static QRegularExpression parseCommand("(^|[^{])({{)*{(\\d+\\+?)}");

    int lastCaptureEnd = 0;

    auto globalMatch = parseCommand.globalMatch(command.func);
    int matchOffset = 0;

    while (true)
    {
        QRegularExpressionMatch match =
            parseCommand.match(command.func, matchOffset);

        if (!match.hasMatch())
        {
            break;
        }

        result += command.func.mid(lastCaptureEnd,
                                   match.capturedStart() - lastCaptureEnd + 1);

        lastCaptureEnd = match.capturedEnd();
        matchOffset = lastCaptureEnd - 1;

        QString wordIndexMatch = match.captured(3);

        bool plus = wordIndexMatch.at(wordIndexMatch.size() - 1) == '+';
        wordIndexMatch = wordIndexMatch.replace("+", "");

        bool ok;
        int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok);
        if (!ok || wordIndex == 0)
        {
            result += "{" + match.captured(3) + "}";
            continue;
        }

        if (words.length() <= wordIndex)
        {
            continue;
        }

        if (plus)
        {
            bool first = true;
            for (int i = wordIndex; i < words.length(); i++)
            {
                if (!first)
                {
                    result += " ";
                }
                result += words[i];
                first = false;
            }
        }
        else
        {
            result += words[wordIndex];
        }
    }

    result += command.func.mid(lastCaptureEnd);

    if (result.size() > 0 && result.at(0) == '{')
    {
        result = result.mid(1);
    }

    return result.replace("{{", "{");
}

QStringList CommandController::getDefaultTwitchCommandList()
{
    QStringList l = TWITCH_DEFAULT_COMMANDS;
    l += "/uptime";

    return l;
}

}  // namespace chatterino
<|endoftext|>


<commit_before>#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <linux/unistd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "library.h"
#include "maps.h"
#include "sandbox_impl.h"

namespace playground {

Maps::Maps(const std::string& maps_file) :
    maps_file_(maps_file),
    begin_iter_(this, true, false),
    end_iter_(this, false, true),
    pid_(-1),
    vsyscall_(0) {
  memset(fds_, -1, sizeof(fds_));
  int fd = open(maps_file.c_str(), O_RDONLY);
  Sandbox::SysCalls sys;
  if (fd >= 0) {
    char buf[256] = { 0 };
    int len = 0, rc = 1;
    bool long_line = false;
    do {
      if (rc > 0) {
        rc = Sandbox::read(sys, fd, buf + len, sizeof(buf) - len - 1);
        if (rc > 0) {
          len += rc;
        }
      }
      char *ptr = buf;
      if (!long_line) {
        long_line = true;
        unsigned long start = strtoul(ptr, &ptr, 16);
        unsigned long stop = strtoul(ptr + 1, &ptr, 16);
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        char *perm_ptr = ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t') ++ptr;
        std::string perm(perm_ptr, ptr - perm_ptr);
        unsigned long offset = strtoul(ptr, &ptr, 16);
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        char *id_ptr = ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t') ++ptr;
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t') ++ptr;
        std::string id(id_ptr, ptr - id_ptr);
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        char *library_ptr = ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\n') ++ptr;
        std::string library(library_ptr, ptr - library_ptr);
        bool isVDSO = false;
        if (library == "[vdso]") {
          // /proc/self/maps has a misleading file offset in the [vdso] entry.
          // Override it with a sane value.
          offset = 0;
          isVDSO = true;
        } else if (library == "[vsyscall]") {
          vsyscall_ = reinterpret_cast<char *>(start);
        } else if (library.empty() || library[0] == '[') {
          goto skip_entry;
        }
        int prot = 0;
        if (perm.find('r') != std::string::npos) {
          prot |= PROT_READ;
        }
        if (perm.find('w') != std::string::npos) {
          prot |= PROT_WRITE;
        }
        if (perm.find('x') != std::string::npos) {
          prot |= PROT_EXEC;
        }
        if ((prot & (PROT_EXEC | PROT_READ)) == 0) {
          goto skip_entry;
        }
        libs_[id + ' ' + library].addMemoryRange(
            reinterpret_cast<void *>(start),
            reinterpret_cast<void *>(stop),
            Elf_Addr(offset),
            prot, isVDSO);
      }
   skip_entry:
      for (;;) {
        if (!*ptr || *ptr++ == '\n') {
          long_line = false;
          memmove(buf, ptr, len - (ptr - buf));
          memset(buf + len - (ptr - buf), 0, ptr - buf);
          len -= (ptr - buf);
          break;
        }
      }
    } while (len || long_line);
    NOINTR_SYS(close(fd));

    // The runtime loader clobbers some of the data that we want to read,
    // when it relocates objects. As we cannot trust the filename that we
    // obtained from /proc/self/maps, we instead fork() a child process and
    // use mremap() to uncover the obscured data.
    int tmp_fds[4];
    pipe(tmp_fds);
    pipe(tmp_fds + 2);
    pid_ = fork();
    if (pid_ >= 0) {
      // Set up read and write file descriptors for exchanging data
      // between parent and child.
      fds_[ !pid_] = tmp_fds[     !pid_];
      fds_[!!pid_] = tmp_fds[2 + !!pid_];
      NOINTR_SYS(close(  tmp_fds[    !!pid_]));
      NOINTR_SYS(close(  tmp_fds[2 +  !pid_]));

      for (LibraryMap::iterator iter = libs_.begin(); iter != libs_.end(); ){
        Library* lib = &iter->second;
        if (pid_) {
          lib->recoverOriginalDataParent(this);
        } else {
          lib->recoverOriginalDataChild(strrchr(iter->first.c_str(), ' ') + 1);
        }
        if (pid_ && !lib->parseElf()) {
          libs_.erase(iter++);
        } else {
          ++iter;
        }
      }

      // Handle requests sent from the parent to the child
      if (!pid_) {
        Request req;
        for (;;) {
          if (Sandbox::read(sys, fds_[0], &req, sizeof(Request)) !=
              sizeof(Request)) {
            _exit(0);
          }
          switch (req.type) {
            case Request::REQ_GET:
              {
                char *buf = new char[req.length];
                if (!req.library->get(req.offset, buf, req.length)) {
                  req.length = -1;
                  Sandbox::write(sys, fds_[1], &req.length,sizeof(req.length));
                } else {
                  Sandbox::write(sys, fds_[1], &req.length,sizeof(req.length));
                  Sandbox::write(sys, fds_[1], buf, req.length);
                }
                delete[] buf;
              }
              break;
            case Request::REQ_GET_STR:
              {
                std::string s = req.library->get(req.offset);
                req.length = s.length();
                Sandbox::write(sys, fds_[1], &req.length, sizeof(req.length));
                Sandbox::write(sys, fds_[1], s.c_str(), req.length);
              }
              break;
          }
        }
      }
    } else {
      for (int i = 0; i < 4; i++) {
        NOINTR_SYS(close(tmp_fds[i]));
      }
    }
  }
}

Maps::~Maps() {
  Sandbox::SysCalls sys;
  sys.kill(pid_, SIGKILL);
  sys.waitpid(pid_, NULL, 0);
}

char *Maps::forwardGetRequest(Library *library, Elf_Addr offset,
                              char *buf, size_t length) const {
  Request req(Request::REQ_GET, library, offset, length);
  Sandbox::SysCalls sys;
  if (Sandbox::write(sys, fds_[1], &req, sizeof(Request)) != sizeof(Request) ||
      Sandbox::read(sys, fds_[0], &req.length, sizeof(req.length)) !=
      sizeof(req.length) ||
      req.length == -1 ||
      Sandbox::read(sys, fds_[0], buf, length) != (ssize_t)length) {
    memset(buf, 0, length);
    return NULL;
  }
  return buf;
}

std::string Maps::forwardGetRequest(Library *library,
                                    Elf_Addr offset) const {
  Request req(Request::REQ_GET_STR, library, offset, -1);
  Sandbox::SysCalls sys;
  if (Sandbox::write(sys, fds_[1], &req, sizeof(Request)) != sizeof(Request) ||
      Sandbox::read(sys, fds_[0], &req.length, sizeof(req.length)) !=
      sizeof(req.length)) {
    return "";
  }
  char *buf = new char[req.length];
  if (Sandbox::read(sys, fds_[0], buf, req.length) != (ssize_t)req.length) {
    delete[] buf;
    return "";
  }
  std::string s(buf, req.length);
  delete[] buf;
  return s;
}

Maps::Iterator::Iterator(Maps* maps, bool at_beginning, bool at_end)
    : maps_(maps),
      at_beginning_(at_beginning),
      at_end_(at_end) {
}

Maps::LibraryMap::iterator& Maps::Iterator::getIterator() const {
  if (at_beginning_) {
    iter_ = maps_->libs_.begin();
  } else if (at_end_) {
    iter_ = maps_->libs_.end();
  }
  return iter_;
}

Maps::Iterator Maps::Iterator::begin() {
  return maps_->begin_iter_;
}

Maps::Iterator Maps::Iterator::end() {
  return maps_->end_iter_;
}

Maps::Iterator& Maps::Iterator::operator++() {
  getIterator().operator++();
  at_beginning_ = false;
  return *this;
}

Maps::Iterator Maps::Iterator::operator++(int i) {
  getIterator().operator++(i);
  at_beginning_ = false;
  return *this;
}

Library* Maps::Iterator::operator*() const {
  return &getIterator().operator*().second;
}

bool Maps::Iterator::operator==(const Maps::Iterator& iter) const {
  return getIterator().operator==(iter.getIterator());
}

bool Maps::Iterator::operator!=(const Maps::Iterator& iter) const {
  return !operator==(iter);
}

std::string Maps::Iterator::name() const {
  return getIterator()->first;
}

char* Maps::allocNearAddr(char* addr, size_t size, int prot) const {
  // We try to allocate memory within 1.5GB of a target address. This means,
  // we will be able to perform relative 32bit jumps from the target address.
  size = (size + 4095) & ~4095;
  Sandbox::SysCalls sys;
  int fd = sys.open(maps_file_.c_str(), O_RDONLY, 0);
  if (fd < 0) {
    return NULL;
  }

  char buf[256] = { 0 };
  int len = 0, rc = 1;
  bool long_line = false;
  unsigned long gap_start = 0x10000;
  char *new_addr;
  do {
    if (rc > 0) {
      do {
        rc = Sandbox::read(sys, fd, buf + len, sizeof(buf) - len - 1);
        if (rc > 0) {
          len += rc;
        }
      } while (rc > 0 && len < (int)sizeof(buf) - 1);
    }
    char *ptr = buf;
    if (!long_line) {
      long_line = true;
      unsigned long start = strtoul(ptr, &ptr, 16);
      unsigned long stop = strtoul(ptr + 1, &ptr, 16);
      if (start - gap_start >= size) {
        if (reinterpret_cast<long>(addr) - static_cast<long>(start) >= 0) {
          if (reinterpret_cast<long>(addr) - (start - size) < (1536 << 20)) {
            new_addr = reinterpret_cast<char *>(sys.MMAP
                           (reinterpret_cast<void *>(start - size), size, prot,
                            MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0));
            if (new_addr != MAP_FAILED) {
              goto done;
            }
          }
        } else if (gap_start + size - reinterpret_cast<long>(addr) <
                   (1536 << 20)) {
          new_addr = reinterpret_cast<char *>(sys.MMAP
                         (reinterpret_cast<void *>(gap_start), size, prot,
                          MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1 ,0));
          if (new_addr != MAP_FAILED) {
            goto done;
          }
        }
      }
      gap_start = stop;
    }
    for (;;) {
      if (!*ptr || *ptr++ == '\n') {
        long_line = false;
        memmove(buf, ptr, len - (ptr - buf));
        memset(buf + len - (ptr - buf), 0, ptr - buf);
        len -= (ptr - buf);
        break;
      }
    }
  } while (len || long_line);
  new_addr = NULL;
done:
  sys.close(fd);
  return new_addr;
}

} // namespace
<commit_msg>Linux: fix seccomp sandbox for GCC 4.4<commit_after>#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <linux/unistd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "library.h"
#include "maps.h"
#include "sandbox_impl.h"

namespace playground {

Maps::Maps(const std::string& maps_file) :
    maps_file_(maps_file),
    begin_iter_(this, true, false),
    end_iter_(this, false, true),
    pid_(-1),
    vsyscall_(0) {
  memset(fds_, -1, sizeof(fds_));
  int fd = open(maps_file.c_str(), O_RDONLY);
  Sandbox::SysCalls sys;
  if (fd >= 0) {
    char buf[256] = { 0 };
    int len = 0, rc = 1;
    bool long_line = false;
    do {
      if (rc > 0) {
        rc = Sandbox::read(sys, fd, buf + len, sizeof(buf) - len - 1);
        if (rc > 0) {
          len += rc;
        }
      }
      char *ptr = buf;
      if (!long_line) {
        long_line = true;
        unsigned long start = strtoul(ptr, &ptr, 16);
        unsigned long stop = strtoul(ptr + 1, &ptr, 16);
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        char *perm_ptr = ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t') ++ptr;
        std::string perm(perm_ptr, ptr - perm_ptr);
        unsigned long offset = strtoul(ptr, &ptr, 16);
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        char *id_ptr = ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t') ++ptr;
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t') ++ptr;
        std::string id(id_ptr, ptr - id_ptr);
        while (*ptr == ' ' || *ptr == '\t') ++ptr;
        char *library_ptr = ptr;
        while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\n') ++ptr;
        std::string library(library_ptr, ptr - library_ptr);
        bool isVDSO = false;
        if (library == "[vdso]") {
          // /proc/self/maps has a misleading file offset in the [vdso] entry.
          // Override it with a sane value.
          offset = 0;
          isVDSO = true;
        } else if (library == "[vsyscall]") {
          vsyscall_ = reinterpret_cast<char *>(start);
        } else if (library.empty() || library[0] == '[') {
          goto skip_entry;
        }
        int prot = 0;
        if (perm.find('r') != std::string::npos) {
          prot |= PROT_READ;
        }
        if (perm.find('w') != std::string::npos) {
          prot |= PROT_WRITE;
        }
        if (perm.find('x') != std::string::npos) {
          prot |= PROT_EXEC;
        }
        if ((prot & (PROT_EXEC | PROT_READ)) == 0) {
          goto skip_entry;
        }
        libs_[id + ' ' + library].addMemoryRange(
            reinterpret_cast<void *>(start),
            reinterpret_cast<void *>(stop),
            Elf_Addr(offset),
            prot, isVDSO);
      }
   skip_entry:
      for (;;) {
        if (!*ptr || *ptr++ == '\n') {
          long_line = false;
          memmove(buf, ptr, len - (ptr - buf));
          memset(buf + len - (ptr - buf), 0, ptr - buf);
          len -= (ptr - buf);
          break;
        }
      }
    } while (len || long_line);
    NOINTR_SYS(close(fd));

    // The runtime loader clobbers some of the data that we want to read,
    // when it relocates objects. As we cannot trust the filename that we
    // obtained from /proc/self/maps, we instead fork() a child process and
    // use mremap() to uncover the obscured data.
    int tmp_fds[4];
    if (pipe(tmp_fds))
      abort();
    if (pipe(tmp_fds + 2))
      abort();
    pid_ = fork();
    if (pid_ >= 0) {
      // Set up read and write file descriptors for exchanging data
      // between parent and child.
      fds_[ !pid_] = tmp_fds[     !pid_];
      fds_[!!pid_] = tmp_fds[2 + !!pid_];
      NOINTR_SYS(close(  tmp_fds[    !!pid_]));
      NOINTR_SYS(close(  tmp_fds[2 +  !pid_]));

      for (LibraryMap::iterator iter = libs_.begin(); iter != libs_.end(); ){
        Library* lib = &iter->second;
        if (pid_) {
          lib->recoverOriginalDataParent(this);
        } else {
          lib->recoverOriginalDataChild(strrchr(iter->first.c_str(), ' ') + 1);
        }
        if (pid_ && !lib->parseElf()) {
          libs_.erase(iter++);
        } else {
          ++iter;
        }
      }

      // Handle requests sent from the parent to the child
      if (!pid_) {
        Request req;
        for (;;) {
          if (Sandbox::read(sys, fds_[0], &req, sizeof(Request)) !=
              sizeof(Request)) {
            _exit(0);
          }
          switch (req.type) {
            case Request::REQ_GET:
              {
                char *buf = new char[req.length];
                if (!req.library->get(req.offset, buf, req.length)) {
                  req.length = -1;
                  Sandbox::write(sys, fds_[1], &req.length,sizeof(req.length));
                } else {
                  Sandbox::write(sys, fds_[1], &req.length,sizeof(req.length));
                  Sandbox::write(sys, fds_[1], buf, req.length);
                }
                delete[] buf;
              }
              break;
            case Request::REQ_GET_STR:
              {
                std::string s = req.library->get(req.offset);
                req.length = s.length();
                Sandbox::write(sys, fds_[1], &req.length, sizeof(req.length));
                Sandbox::write(sys, fds_[1], s.c_str(), req.length);
              }
              break;
          }
        }
      }
    } else {
      for (int i = 0; i < 4; i++) {
        NOINTR_SYS(close(tmp_fds[i]));
      }
    }
  }
}

Maps::~Maps() {
  Sandbox::SysCalls sys;
  sys.kill(pid_, SIGKILL);
  sys.waitpid(pid_, NULL, 0);
}

char *Maps::forwardGetRequest(Library *library, Elf_Addr offset,
                              char *buf, size_t length) const {
  Request req(Request::REQ_GET, library, offset, length);
  Sandbox::SysCalls sys;
  if (Sandbox::write(sys, fds_[1], &req, sizeof(Request)) != sizeof(Request) ||
      Sandbox::read(sys, fds_[0], &req.length, sizeof(req.length)) !=
      sizeof(req.length) ||
      req.length == -1 ||
      Sandbox::read(sys, fds_[0], buf, length) != (ssize_t)length) {
    memset(buf, 0, length);
    return NULL;
  }
  return buf;
}

std::string Maps::forwardGetRequest(Library *library,
                                    Elf_Addr offset) const {
  Request req(Request::REQ_GET_STR, library, offset, -1);
  Sandbox::SysCalls sys;
  if (Sandbox::write(sys, fds_[1], &req, sizeof(Request)) != sizeof(Request) ||
      Sandbox::read(sys, fds_[0], &req.length, sizeof(req.length)) !=
      sizeof(req.length)) {
    return "";
  }
  char *buf = new char[req.length];
  if (Sandbox::read(sys, fds_[0], buf, req.length) != (ssize_t)req.length) {
    delete[] buf;
    return "";
  }
  std::string s(buf, req.length);
  delete[] buf;
  return s;
}

Maps::Iterator::Iterator(Maps* maps, bool at_beginning, bool at_end)
    : maps_(maps),
      at_beginning_(at_beginning),
      at_end_(at_end) {
}

Maps::LibraryMap::iterator& Maps::Iterator::getIterator() const {
  if (at_beginning_) {
    iter_ = maps_->libs_.begin();
  } else if (at_end_) {
    iter_ = maps_->libs_.end();
  }
  return iter_;
}

Maps::Iterator Maps::Iterator::begin() {
  return maps_->begin_iter_;
}

Maps::Iterator Maps::Iterator::end() {
  return maps_->end_iter_;
}

Maps::Iterator& Maps::Iterator::operator++() {
  getIterator().operator++();
  at_beginning_ = false;
  return *this;
}

Maps::Iterator Maps::Iterator::operator++(int i) {
  getIterator().operator++(i);
  at_beginning_ = false;
  return *this;
}

Library* Maps::Iterator::operator*() const {
  return &getIterator().operator*().second;
}

bool Maps::Iterator::operator==(const Maps::Iterator& iter) const {
  return getIterator().operator==(iter.getIterator());
}

bool Maps::Iterator::operator!=(const Maps::Iterator& iter) const {
  return !operator==(iter);
}

std::string Maps::Iterator::name() const {
  return getIterator()->first;
}

char* Maps::allocNearAddr(char* addr, size_t size, int prot) const {
  // We try to allocate memory within 1.5GB of a target address. This means,
  // we will be able to perform relative 32bit jumps from the target address.
  size = (size + 4095) & ~4095;
  Sandbox::SysCalls sys;
  int fd = sys.open(maps_file_.c_str(), O_RDONLY, 0);
  if (fd < 0) {
    return NULL;
  }

  char buf[256] = { 0 };
  int len = 0, rc = 1;
  bool long_line = false;
  unsigned long gap_start = 0x10000;
  char *new_addr;
  do {
    if (rc > 0) {
      do {
        rc = Sandbox::read(sys, fd, buf + len, sizeof(buf) - len - 1);
        if (rc > 0) {
          len += rc;
        }
      } while (rc > 0 && len < (int)sizeof(buf) - 1);
    }
    char *ptr = buf;
    if (!long_line) {
      long_line = true;
      unsigned long start = strtoul(ptr, &ptr, 16);
      unsigned long stop = strtoul(ptr + 1, &ptr, 16);
      if (start - gap_start >= size) {
        if (reinterpret_cast<long>(addr) - static_cast<long>(start) >= 0) {
          if (reinterpret_cast<long>(addr) - (start - size) < (1536 << 20)) {
            new_addr = reinterpret_cast<char *>(sys.MMAP
                           (reinterpret_cast<void *>(start - size), size, prot,
                            MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0));
            if (new_addr != MAP_FAILED) {
              goto done;
            }
          }
        } else if (gap_start + size - reinterpret_cast<long>(addr) <
                   (1536 << 20)) {
          new_addr = reinterpret_cast<char *>(sys.MMAP
                         (reinterpret_cast<void *>(gap_start), size, prot,
                          MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1 ,0));
          if (new_addr != MAP_FAILED) {
            goto done;
          }
        }
      }
      gap_start = stop;
    }
    for (;;) {
      if (!*ptr || *ptr++ == '\n') {
        long_line = false;
        memmove(buf, ptr, len - (ptr - buf));
        memset(buf + len - (ptr - buf), 0, ptr - buf);
        len -= (ptr - buf);
        break;
      }
    }
  } while (len || long_line);
  new_addr = NULL;
done:
  sys.close(fd);
  return new_addr;
}

} // namespace
<|endoftext|>


<commit_before>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2014-2015 Inviwo Foundation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *********************************************************************************/

#include <inviwo/core/io/serialization/versionconverter.h>
#include <inviwo/core/util/stringconversion.h>
#include <inviwo/core/ports/inport.h>
#include <inviwo/core/ports/outport.h>

namespace inviwo {
VersionConverter::VersionConverter() {}

NodeVersionConverter::NodeVersionConverter(std::function<bool(TxElement*)> fun)
    : VersionConverter(), fun_(fun) {}

bool NodeVersionConverter::convert(TxElement* root) { return fun_(root); }

TraversingVersionConverter::TraversingVersionConverter(std::function<bool(TxElement*)> fun)
    : VersionConverter(), fun_(fun) {}

bool TraversingVersionConverter::convert(TxElement* root) { return traverseNodes(root); }

bool TraversingVersionConverter::traverseNodes(TxElement* node) {
    bool res = true;
    res = res && fun_(node);
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        res = res && traverseNodes(child.Get());
    }
    return res;
}

bool util::xmlCopyMatchingSubPropsIntoComposite(TxElement* node, const CompositeProperty& prop) {
    TxElement propitem("Property");
    propitem.SetAttribute("type", prop.getClassIdentifier());
    propitem.SetAttribute("identifier", prop.getIdentifier());
    propitem.SetAttribute("displayName", prop.getDisplayName());
    propitem.SetAttribute("key", prop.getIdentifier());
    TxElement list("Properties");

    std::vector<Property*> props = prop.getProperties();

    bool res = false;

    // temp list
    std::vector<TxElement*> toBeDeleted;

    for (auto& p : props) {
        bool match = false;

        ticpp::Iterator<ticpp::Element> child;
        for (child = child.begin(node); child != child.end(); child++) {
            std::string name;
            child->GetValue(&name);
            std::string type = child->GetAttributeOrDefault("type", "");
            std::string id = child->GetAttributeOrDefault("identifier", "");

            if (p->getIdentifier() == id &&
                (p->getClassIdentifier() == type ||
                 p->getClassIdentifier() == splitString(type, '.').back())) {
                LogInfoCustom("VersionConverter", "    Match for sub property: " +
                                                          joinString(p->getPath(), ".") +
                                                          " found in type: "
                                                      << type << " id: " << id);

                list.InsertEndChild(*(child->Clone()));
                toBeDeleted.push_back(child.Get());
                match = true;
            }
        }
        res = res && match;
    }

    for (auto& elem : toBeDeleted) {
        node->RemoveChild(elem);
    }

    propitem.InsertEndChild(list);
    node->InsertEndChild(propitem);

    return res;
}

bool util::xmlHasProp(TxElement* node, const Property& prop) {
    bool result = false;
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        if (prop.getClassIdentifier() == child->GetAttributeOrDefault("type", "")
            && prop.getIdentifier() == child->GetAttributeOrDefault("identifier", "")) {
            result = true;
        }
    }
    return result;
}

std::vector<TxElement*> util::xmlGetMatchingElements(TxElement* node, std::string key) {
    std::vector<TxElement*> res;
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        std::string childkey;
        child->GetValue(&childkey);

        if(childkey == key){
            res.push_back(child.Get());
        }
    }
    return res;
}

bool util::xmlFindMatchingSubPropertiesForComposites(
    TxElement* processornode, const std::vector<const CompositeProperty*>& props) {
    std::vector<TxElement*> pelm = util::xmlGetMatchingElements(processornode, "Properties");

    bool res = true;

    for (auto& prop : props) {
        if (!util::xmlHasProp(pelm[0], *prop)) {

            bool foundMatchingComposite = util::xmlCopyMatchingCompositeProperty(pelm[0], *prop);
            bool foundSubProp = false;
            if (!foundMatchingComposite) {
                foundSubProp = util::xmlCopyMatchingSubPropsIntoComposite(pelm[0], *prop);
            }
            res = res && (foundSubProp || foundMatchingComposite);
        }
    }
    return res;
}

TxElement* util::xmlGetElement(TxElement* node, std::string path) {
    std::vector<std::string> parts = splitString(path, '/');
    if (parts.size() > 0) {
        std::vector<std::string> components = splitString(parts[0], '&');
        std::string name = components[0];

        ticpp::Iterator<ticpp::Element> child;
        for (child = child.begin(node); child != child.end(); child++) {
            bool match = true;
            std::string childname;
            child->GetValue(&childname);
            if (childname == name) {
                for (size_t i = 1; i < components.size(); ++i) {
                    std::vector<std::string> pair = splitString(components[i], '=');
                    match = match && child->GetAttributeOrDefault(pair[0], "") == pair[1];
                }
            } else {
                match = false;
            }
            if (match) {
                if (parts.size() > 1) {
                    return xmlGetElement(child.Get(),
                                         joinString(parts.begin() + 1, parts.end(), "/"));
                } else {
                    return child.Get();
                }
                break;
            }
        }
    }
    return nullptr;
}

bool util::xmlCopyMatchingCompositeProperty(TxElement* node, const CompositeProperty& prop) {
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        std::string name;
        child->GetValue(&name);
        std::string type = child->GetAttributeOrDefault("type", "");
        std::string id = child->GetAttributeOrDefault("identifier", "");

        if ((type == "CompositeProperty" || type == "org.inviwo.CompositeProperty") && prop.getIdentifier() == id) {
            LogInfoCustom("VersionConverter", "    Found Composite with same identifier");

            TxElement* newChild = node->InsertEndChild(*(child.Get()))->ToElement();
            newChild->SetAttribute("type", prop.getClassIdentifier());
            return true;
        }
    }

    return false;
}

void util::renamePort(Deserializer& d, std::initializer_list<std::pair<const Port*, std::string>> rules) {
    NodeVersionConverter vc([&rules](TxElement* node) {
        for (auto rule : rules) {
            TxElement* elem = nullptr;
            if (auto p = dynamic_cast<const Outport*>(rule.first)) {
                elem = util::xmlGetElement(node, "OutPorts/OutPort&type=" + p->getClassIdentifier() +
                                           "&identifier=" + rule.second);
            } else if (auto p = dynamic_cast<const Inport*>(rule.first)) {
                elem = util::xmlGetElement(node, "InPorts/InPort&type=" + p->getClassIdentifier() +
                                           "&identifier=" + rule.second);
            }
            if (elem) elem->SetAttribute("identifier", rule.first->getIdentifier());
        }
        return true;
    });
    d.convertVersion(&vc);
}

void util::renameProperty(Deserializer& d, std::initializer_list<std::pair<const Property*, std::string>> rules) {
    NodeVersionConverter vc([&rules](TxElement* node) {
        for (auto rule : rules) {
            TxElement* p = util::xmlGetElement(node, "Properties/Property&type=" +
                                               rule.first->getClassIdentifier() +
                                               "&identifier=" + rule.second);
            if (p) p->SetAttribute("identifier", rule.first->getIdentifier());
        }
        return true;
    });
    d.convertVersion(&vc);
}

void util::changePropertyType(
    Deserializer& d, std::initializer_list<std::pair<const Property*, std::string>> rules) {
    NodeVersionConverter vc([&rules](TxElement* node) {
        for (auto rule : rules) {
            std::ofstream f1("D:/temp/before.xml");
            std::ofstream f2("D:/temp/after.xml");
            f1 << *(node);
            TxElement* p = util::xmlGetElement(
                node, "Properties/Property&type=" + rule.first->getClassIdentifier() +
                "&identifier=" + rule.first->getIdentifier());
            if (p) p->SetAttribute("type", rule.second);
            f2 << *(node);
        }
        return true;
    });
    d.convertVersion(&vc);
}

}  // namespace
<commit_msg>Core: VersionConverter: Removed debug ofstreams<commit_after>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2014-2015 Inviwo Foundation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *********************************************************************************/

#include <inviwo/core/io/serialization/versionconverter.h>
#include <inviwo/core/util/stringconversion.h>
#include <inviwo/core/ports/inport.h>
#include <inviwo/core/ports/outport.h>

namespace inviwo {
VersionConverter::VersionConverter() {}

NodeVersionConverter::NodeVersionConverter(std::function<bool(TxElement*)> fun)
    : VersionConverter(), fun_(fun) {}

bool NodeVersionConverter::convert(TxElement* root) { return fun_(root); }

TraversingVersionConverter::TraversingVersionConverter(std::function<bool(TxElement*)> fun)
    : VersionConverter(), fun_(fun) {}

bool TraversingVersionConverter::convert(TxElement* root) { return traverseNodes(root); }

bool TraversingVersionConverter::traverseNodes(TxElement* node) {
    bool res = true;
    res = res && fun_(node);
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        res = res && traverseNodes(child.Get());
    }
    return res;
}

bool util::xmlCopyMatchingSubPropsIntoComposite(TxElement* node, const CompositeProperty& prop) {
    TxElement propitem("Property");
    propitem.SetAttribute("type", prop.getClassIdentifier());
    propitem.SetAttribute("identifier", prop.getIdentifier());
    propitem.SetAttribute("displayName", prop.getDisplayName());
    propitem.SetAttribute("key", prop.getIdentifier());
    TxElement list("Properties");

    std::vector<Property*> props = prop.getProperties();

    bool res = false;

    // temp list
    std::vector<TxElement*> toBeDeleted;

    for (auto& p : props) {
        bool match = false;

        ticpp::Iterator<ticpp::Element> child;
        for (child = child.begin(node); child != child.end(); child++) {
            std::string name;
            child->GetValue(&name);
            std::string type = child->GetAttributeOrDefault("type", "");
            std::string id = child->GetAttributeOrDefault("identifier", "");

            if (p->getIdentifier() == id &&
                (p->getClassIdentifier() == type ||
                 p->getClassIdentifier() == splitString(type, '.').back())) {
                LogInfoCustom("VersionConverter", "    Match for sub property: " +
                                                          joinString(p->getPath(), ".") +
                                                          " found in type: "
                                                      << type << " id: " << id);

                list.InsertEndChild(*(child->Clone()));
                toBeDeleted.push_back(child.Get());
                match = true;
            }
        }
        res = res && match;
    }

    for (auto& elem : toBeDeleted) {
        node->RemoveChild(elem);
    }

    propitem.InsertEndChild(list);
    node->InsertEndChild(propitem);

    return res;
}

bool util::xmlHasProp(TxElement* node, const Property& prop) {
    bool result = false;
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        if (prop.getClassIdentifier() == child->GetAttributeOrDefault("type", "")
            && prop.getIdentifier() == child->GetAttributeOrDefault("identifier", "")) {
            result = true;
        }
    }
    return result;
}

std::vector<TxElement*> util::xmlGetMatchingElements(TxElement* node, std::string key) {
    std::vector<TxElement*> res;
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        std::string childkey;
        child->GetValue(&childkey);

        if(childkey == key){
            res.push_back(child.Get());
        }
    }
    return res;
}

bool util::xmlFindMatchingSubPropertiesForComposites(
    TxElement* processornode, const std::vector<const CompositeProperty*>& props) {
    std::vector<TxElement*> pelm = util::xmlGetMatchingElements(processornode, "Properties");

    bool res = true;

    for (auto& prop : props) {
        if (!util::xmlHasProp(pelm[0], *prop)) {

            bool foundMatchingComposite = util::xmlCopyMatchingCompositeProperty(pelm[0], *prop);
            bool foundSubProp = false;
            if (!foundMatchingComposite) {
                foundSubProp = util::xmlCopyMatchingSubPropsIntoComposite(pelm[0], *prop);
            }
            res = res && (foundSubProp || foundMatchingComposite);
        }
    }
    return res;
}

TxElement* util::xmlGetElement(TxElement* node, std::string path) {
    std::vector<std::string> parts = splitString(path, '/');
    if (parts.size() > 0) {
        std::vector<std::string> components = splitString(parts[0], '&');
        std::string name = components[0];

        ticpp::Iterator<ticpp::Element> child;
        for (child = child.begin(node); child != child.end(); child++) {
            bool match = true;
            std::string childname;
            child->GetValue(&childname);
            if (childname == name) {
                for (size_t i = 1; i < components.size(); ++i) {
                    std::vector<std::string> pair = splitString(components[i], '=');
                    match = match && child->GetAttributeOrDefault(pair[0], "") == pair[1];
                }
            } else {
                match = false;
            }
            if (match) {
                if (parts.size() > 1) {
                    return xmlGetElement(child.Get(),
                                         joinString(parts.begin() + 1, parts.end(), "/"));
                } else {
                    return child.Get();
                }
                break;
            }
        }
    }
    return nullptr;
}

bool util::xmlCopyMatchingCompositeProperty(TxElement* node, const CompositeProperty& prop) {
    ticpp::Iterator<ticpp::Element> child;
    for (child = child.begin(node); child != child.end(); child++) {
        std::string name;
        child->GetValue(&name);
        std::string type = child->GetAttributeOrDefault("type", "");
        std::string id = child->GetAttributeOrDefault("identifier", "");

        if ((type == "CompositeProperty" || type == "org.inviwo.CompositeProperty") && prop.getIdentifier() == id) {
            LogInfoCustom("VersionConverter", "    Found Composite with same identifier");

            TxElement* newChild = node->InsertEndChild(*(child.Get()))->ToElement();
            newChild->SetAttribute("type", prop.getClassIdentifier());
            return true;
        }
    }

    return false;
}

void util::renamePort(Deserializer& d, std::initializer_list<std::pair<const Port*, std::string>> rules) {
    NodeVersionConverter vc([&rules](TxElement* node) {
        for (auto rule : rules) {
            TxElement* elem = nullptr;
            if (auto p = dynamic_cast<const Outport*>(rule.first)) {
                elem = util::xmlGetElement(node, "OutPorts/OutPort&type=" + p->getClassIdentifier() +
                                           "&identifier=" + rule.second);
            } else if (auto p = dynamic_cast<const Inport*>(rule.first)) {
                elem = util::xmlGetElement(node, "InPorts/InPort&type=" + p->getClassIdentifier() +
                                           "&identifier=" + rule.second);
            }
            if (elem) elem->SetAttribute("identifier", rule.first->getIdentifier());
        }
        return true;
    });
    d.convertVersion(&vc);
}

void util::renameProperty(Deserializer& d, std::initializer_list<std::pair<const Property*, std::string>> rules) {
    NodeVersionConverter vc([&rules](TxElement* node) {
        for (auto rule : rules) {
            TxElement* p = util::xmlGetElement(node, "Properties/Property&type=" +
                                               rule.first->getClassIdentifier() +
                                               "&identifier=" + rule.second);
            if (p) p->SetAttribute("identifier", rule.first->getIdentifier());
        }
        return true;
    });
    d.convertVersion(&vc);
}

void util::changePropertyType(
    Deserializer& d, std::initializer_list<std::pair<const Property*, std::string>> rules) {
    NodeVersionConverter vc([&rules](TxElement* node) {
        for (auto rule : rules) {
            TxElement* p = util::xmlGetElement(
                node, "Properties/Property&type=" + rule.first->getClassIdentifier() +
                "&identifier=" + rule.first->getIdentifier());
            if (p) p->SetAttribute("type", rule.second);
        }
        return true;
    });
    d.convertVersion(&vc);
}

}  // namespace
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0.
// Author: Jin Qing (http://blog.csdn.net/jq0123)

#include "client_async_writer_impl2.h"

#include <cassert>  // for assert()

#include <grpc_cb/channel.h>  // for MakeSharedCall()
#include <grpc_cb/impl/client/client_async_writer_close_handler.h>  // for OnClose()
#include <grpc_cb/impl/client/client_init_md_cqtag.h>  // for ClientInitMdCqTag

#include "client_async_writer_close_cqtag.h"  // for ClientAsyncWriterCloseCqTag
#include "client_async_writer_helper.h"       // for ClientAsyncWriterHelper

namespace grpc_cb {

ClientAsyncWriterImpl2::ClientAsyncWriterImpl2(const ChannelSptr& channel,
                                             const std::string& method,
                                             const CompletionQueueSptr& cq_sptr)
    : cq_sptr_(cq_sptr),
      call_sptr_(channel->MakeSharedCall(method, *cq_sptr)) {
  assert(cq_sptr);
  assert(channel);
  ClientInitMdCqTag* tag = new ClientInitMdCqTag(call_sptr_);
  if (tag->Start()) return;
  delete tag;
  status_.SetInternalError("Failed to init client stream.");
}

ClientAsyncWriterImpl2::~ClientAsyncWriterImpl2() {
  // Have done CallCloseHandler().
}

bool ClientAsyncWriterImpl2::Write(const MessageSptr& request_sptr) {
  Guard g(mtx_);
  if (!status_.ok())
    return false;
  if (writer_sptr_)
    return writer_sptr_->Queue(request_sptr);

  if (writing_started_)
    return false;  // Writer ended
  writing_started_ = true;

  // Impl2 and WriterHelper shared each other untill OnEnd().
  auto sptr = shared_from_this();
  writer_sptr_.reset(new ClientAsyncWriterHelper(call_sptr_,
      [sptr]() { sptr->OnEndOfWriting(); }));
  return writer_sptr_->Queue(request_sptr);
}

void ClientAsyncWriterImpl2::Close(const CloseHandlerSptr& handler_sptr) {
  Guard g(mtx_);

  if (close_handler_sptr_) return;  // already done
  close_handler_sptr_ = handler_sptr;
  writing_started_ = true;  // Maybe without any Write().

  if (!status_.ok()) {
    CallCloseHandler();
    return;
  }

  if (writer_sptr_)
      writer_sptr_->QueueEnd();  // May trigger OnEndOfWriting().
  else
      SendCloseIfNot();
}

// Finally close...
void ClientAsyncWriterImpl2::SendCloseIfNot() {
  assert(!writer_sptr_);  // Must be ended.
  if (!status_.ok())
    return;

  if (has_sent_close_) return;
  has_sent_close_ = true;
  auto sptr = shared_from_this();
  using Tag = ClientAsyncWriterCloseCqTag;
  Tag* tag = new Tag(call_sptr_, [sptr, tag]() {
      sptr->OnClosed(*tag);
  });
  if (tag->Start())
      return;

  delete tag;
  status_.SetInternalError("Failed to close client stream.");
  CallCloseHandler();
}  // Close()

void ClientAsyncWriterImpl2::CallCloseHandler() {
  if (!close_handler_sptr_)
    return;
  // XXX Not in CallCloseHandler()
  //if (writer_sptr_->IsWritingClosed())
  //  return;
  //writer_sptr_->SetWritingClosed();

  // XXX OnClose() in OnEndOfWriting()?
  close_handler_sptr_->OnClose(status_);
}

// Callback of ClientAsyncWriterCloseCqTag
void ClientAsyncWriterImpl2::OnClosed(ClientAsyncWriterCloseCqTag& tag) {
  Guard g(mtx_);

  // Todo: Get trailing metadata.
  if (tag.IsStatusOk()) {
    if (close_handler_sptr_) {
      status_ = tag.GetResponse(close_handler_sptr_->GetMsg());
    } else {
      status_.InternalError("Response is ignored.");
    }
  } else {
    status_ = tag.GetStatus();
  }

  CallCloseHandler();
}

void ClientAsyncWriterImpl2::OnEndOfWriting() {
  Guard g(mtx_);

  if (!writer_sptr_) return;
  const Status& status = writer_sptr_->GetStatus();

  // XXX to close, call on_status() ...
  writer_sptr_.reset();  // Stop circular sharing.
  SendCloseIfNot();
}

}  // namespace grpc_cb
<commit_msg>Cleanup.<commit_after>// Licensed under the Apache License, Version 2.0.
// Author: Jin Qing (http://blog.csdn.net/jq0123)

#include "client_async_writer_impl2.h"

#include <cassert>  // for assert()

#include <grpc_cb/channel.h>  // for MakeSharedCall()
#include <grpc_cb/impl/client/client_async_writer_close_handler.h>  // for OnClose()
#include <grpc_cb/impl/client/client_init_md_cqtag.h>  // for ClientInitMdCqTag

#include "client_async_writer_close_cqtag.h"  // for ClientAsyncWriterCloseCqTag
#include "client_async_writer_helper.h"       // for ClientAsyncWriterHelper

namespace grpc_cb {

ClientAsyncWriterImpl2::ClientAsyncWriterImpl2(const ChannelSptr& channel,
                                             const std::string& method,
                                             const CompletionQueueSptr& cq_sptr)
    : cq_sptr_(cq_sptr),
      call_sptr_(channel->MakeSharedCall(method, *cq_sptr)) {
  assert(cq_sptr);
  assert(channel);
  ClientInitMdCqTag* tag = new ClientInitMdCqTag(call_sptr_);
  if (tag->Start()) return;
  delete tag;
  status_.SetInternalError("Failed to init client stream.");
}

ClientAsyncWriterImpl2::~ClientAsyncWriterImpl2() {
  // Have done CallCloseHandler().
}

bool ClientAsyncWriterImpl2::Write(const MessageSptr& request_sptr) {
  Guard g(mtx_);
  if (!status_.ok())
    return false;
  if (writer_sptr_)
    return writer_sptr_->Queue(request_sptr);

  if (writing_started_)
    return false;  // Writer ended
  writing_started_ = true;

  // Impl2 and WriterHelper shared each other untill OnEnd().
  auto sptr = shared_from_this();
  writer_sptr_.reset(new ClientAsyncWriterHelper(call_sptr_,
      [sptr]() { sptr->OnEndOfWriting(); }));
  return writer_sptr_->Queue(request_sptr);
}

void ClientAsyncWriterImpl2::Close(const CloseHandlerSptr& handler_sptr) {
  Guard g(mtx_);

  if (close_handler_sptr_) return;  // already done
  close_handler_sptr_ = handler_sptr;
  writing_started_ = true;  // Maybe without any Write().

  if (!status_.ok()) {
    CallCloseHandler();
    return;
  }

  if (writer_sptr_)
      writer_sptr_->QueueEnd();  // May trigger OnEndOfWriting().
  else
      SendCloseIfNot();
}

// Finally close...
void ClientAsyncWriterImpl2::SendCloseIfNot() {
  assert(!writer_sptr_);  // Must be ended.
  if (!status_.ok())
    return;

  if (has_sent_close_) return;
  has_sent_close_ = true;
  auto sptr = shared_from_this();
  using Tag = ClientAsyncWriterCloseCqTag;
  Tag* tag = new Tag(call_sptr_, [sptr, tag]() {
      sptr->OnClosed(*tag);
  });
  if (tag->Start())
      return;

  delete tag;
  status_.SetInternalError("Failed to close client stream.");
  CallCloseHandler();
}  // Close()

void ClientAsyncWriterImpl2::CallCloseHandler() {
  if (close_handler_sptr_)
      close_handler_sptr_->OnClose(status_);
}

// Callback of ClientAsyncWriterCloseCqTag
void ClientAsyncWriterImpl2::OnClosed(ClientAsyncWriterCloseCqTag& tag) {
  Guard g(mtx_);

  // Todo: Get trailing metadata.
  if (tag.IsStatusOk()) {
    if (close_handler_sptr_) {
      status_ = tag.GetResponse(close_handler_sptr_->GetMsg());
    } else {
      status_.InternalError("Response is ignored.");
    }
  } else {
    status_ = tag.GetStatus();
  }

  CallCloseHandler();
}

void ClientAsyncWriterImpl2::OnEndOfWriting() {
  Guard g(mtx_);

  if (!writer_sptr_) return;
  const Status& status = writer_sptr_->GetStatus();

  // XXX to close, call on_status() ...
  writer_sptr_.reset();  // Stop circular sharing.
  SendCloseIfNot();
}

}  // namespace grpc_cb
<|endoftext|>


<commit_before>// =============================================================================
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2014 projectchrono.org
// All right reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file at the top level of the distribution and at
// http://projectchrono.org/license-chrono.txt.
//
// =============================================================================
// Authors: Radu Serban
// =============================================================================
//
// Generic chase camera.  Such a camera tracks a specified point on a specified
// body. It supports three different modes:
// - Chase:  camera attached to a flexible cantilever behind the body;
// - Follow: camera attached to a flexible beam articulated on the body;
// - Track:  camera is fixed and tracks the body;
// - Inside: camera is fixed at a given point on the body.
//
// TODO:
// - relax current assumption that the body forward direction is in the positive
//   X direction.
//
// =============================================================================

#include "ChChaseCamera.h"

namespace chrono {
namespace utils {

// -----------------------------------------------------------------------------
// Static variables
// -----------------------------------------------------------------------------

const double ChChaseCamera::m_maxTrackDist2 = 100 * 100;
const std::string ChChaseCamera::m_stateNames[] = { "Chase", "Follow", "Track", "Inside" };

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
ChChaseCamera::ChChaseCamera(const ChSharedBodyPtr chassis)
: m_chassis(chassis),
  m_mult(1),
  m_maxMult(10),
  m_minMult(0.5f),
  m_horizGain(4.0f),
  m_vertGain(2.0f),
  m_state(State::Chase)
{
  Initialize(ChVector<>(0, 0, 0), ChCoordsys<>(), 5, 1);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Initialize(const ChVector<>&   ptOnChassis,
                               const ChCoordsys<>& driverCoordsys,
                               double              chaseDist,
                               double              chaseHeight)
{
  m_ptOnChassis = ptOnChassis;
  m_driverCsys = driverCoordsys;
  m_dist = chaseDist;
  m_height = chaseHeight;
  m_angle = 0;

  // if(Z_up)
  // ChVector<> localOffset(-chaseDist, 0, chaseHeight);
  // else (Y_up)
  ChVector<> localOffset(-chaseDist, chaseHeight, 0);
  m_loc = m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(ptOnChassis + localOffset);
  m_lastLoc = m_loc;
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::SetState(State s)
{
  m_lastLoc = m_loc;
  m_state = s;
  if (m_state == Chase)
    m_angle = 0;
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Zoom(int val)
{
  if (val == 0 || m_state == Track)
    return;

  if (m_state != Inside) {
    if (val < 0 && m_mult > m_minMult)
      m_mult /= 1.01;
    else if (val > 0 && m_mult < m_maxMult)
      m_mult *= 1.01;

    if (m_mult <= m_minMult)
      SetState(Inside);
  }
  else if (val > 0) {
    SetState(Chase);
  }
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Turn(int val)
{
  if (val == 0 || m_state != Chase)
    return;

  if (val < 0 && m_angle > -CH_C_PI)
    m_angle -= CH_C_PI / 100;
  else if (val > 0 && m_angle < CH_C_PI)
    m_angle += CH_C_PI / 100;
}

// -----------------------------------------------------------------------------
// Return the camera location and the camera target (look at) location,
// respectively.
// Note that in Inside mode, in order to accomodate a narrow field of view, we
// set the target location to be at the current driver location and move back
// the camera position.
// -----------------------------------------------------------------------------
ChVector<> ChChaseCamera::GetCameraPos() const
{
  if (m_state == Inside) {
    ChVector<> driverPos = m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(m_driverCsys.pos);
    ChVector<> driverViewDir = m_chassis->GetFrame_REF_to_abs().TransformDirectionLocalToParent(m_driverCsys.rot.GetXaxis());
    return driverPos - 1.1 * driverViewDir;
  }

  return (m_state == Track) ? m_lastLoc : m_loc;
}

void ChChaseCamera::SetCameraPos(const ChVector<>& pos)
{
  m_loc = pos;
  m_lastLoc = pos;
}

ChVector<> ChChaseCamera::GetTargetPos() const
{
  if (m_state == Inside) {
    return m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(m_driverCsys.pos);
  }

  return m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(m_ptOnChassis);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::SetMultLimits(double minMult, double maxMult)
{
  m_minMult = minMult;
  m_maxMult = maxMult;
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Update(double step)
{
  // Perform a simple Euler integration step (derivative controller).
  m_loc += step * calcDeriv(m_loc);

  if (m_state != Track)
    return;

  // If in 'Track' mode, force a state change if too far from tracked body.
  double dist2 = (m_chassis->GetFrame_REF_to_abs().GetPos() - m_lastLoc).Length2();
  if (dist2 > m_maxTrackDist2)
    SetState(Chase);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
ChVector<> ChChaseCamera::calcDeriv(const ChVector<>& loc)
{
  // Calculate the desired camera location, based on the current state of the
  // chassis.
  ChVector<> targetLoc = GetTargetPos();
  ChVector<> uC2T;
  ChVector<> desCamLoc;

  if (m_state == Follow)
    uC2T = targetLoc - m_loc;
  else {
    ChQuaternion<> rot = Q_from_AngAxis(m_angle, ChVector<>(0, 1, 0));
    uC2T = rot.Rotate(m_chassis->GetA().Get_A_Xaxis());
  }

  uC2T.y = 0;
  uC2T.Normalize();

  desCamLoc = targetLoc - m_mult * m_dist * uC2T;
  desCamLoc.y = targetLoc.y + m_mult * m_height;

  // Calculate the derivative vector (RHS of filter ODEs).
  ChVector<> deriv;

  deriv.x = m_horizGain * (desCamLoc.x - m_loc.x);
  deriv.y = m_horizGain * (desCamLoc.y - m_loc.y);
  deriv.z = m_vertGain  * (desCamLoc.z - m_loc.z);

  return deriv;
}

}  // end namespace utils
}  // end namespace chrono
<commit_msg>Fix for GCC (scoped enum)<commit_after>// =============================================================================
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2014 projectchrono.org
// All right reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file at the top level of the distribution and at
// http://projectchrono.org/license-chrono.txt.
//
// =============================================================================
// Authors: Radu Serban
// =============================================================================
//
// Generic chase camera.  Such a camera tracks a specified point on a specified
// body. It supports three different modes:
// - Chase:  camera attached to a flexible cantilever behind the body;
// - Follow: camera attached to a flexible beam articulated on the body;
// - Track:  camera is fixed and tracks the body;
// - Inside: camera is fixed at a given point on the body.
//
// TODO:
// - relax current assumption that the body forward direction is in the positive
//   X direction.
//
// =============================================================================

#include "ChChaseCamera.h"

namespace chrono {
namespace utils {

// -----------------------------------------------------------------------------
// Static variables
// -----------------------------------------------------------------------------

const double ChChaseCamera::m_maxTrackDist2 = 100 * 100;
const std::string ChChaseCamera::m_stateNames[] = { "Chase", "Follow", "Track", "Inside" };

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
ChChaseCamera::ChChaseCamera(const ChSharedBodyPtr chassis)
: m_chassis(chassis),
  m_mult(1),
  m_maxMult(10),
  m_minMult(0.5f),
  m_horizGain(4.0f),
  m_vertGain(2.0f),
  m_state(Chase)
{
  Initialize(ChVector<>(0, 0, 0), ChCoordsys<>(), 5, 1);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Initialize(const ChVector<>&   ptOnChassis,
                               const ChCoordsys<>& driverCoordsys,
                               double              chaseDist,
                               double              chaseHeight)
{
  m_ptOnChassis = ptOnChassis;
  m_driverCsys = driverCoordsys;
  m_dist = chaseDist;
  m_height = chaseHeight;
  m_angle = 0;

  // if(Z_up)
  // ChVector<> localOffset(-chaseDist, 0, chaseHeight);
  // else (Y_up)
  ChVector<> localOffset(-chaseDist, chaseHeight, 0);
  m_loc = m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(ptOnChassis + localOffset);
  m_lastLoc = m_loc;
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::SetState(State s)
{
  m_lastLoc = m_loc;
  m_state = s;
  if (m_state == Chase)
    m_angle = 0;
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Zoom(int val)
{
  if (val == 0 || m_state == Track)
    return;

  if (m_state != Inside) {
    if (val < 0 && m_mult > m_minMult)
      m_mult /= 1.01;
    else if (val > 0 && m_mult < m_maxMult)
      m_mult *= 1.01;

    if (m_mult <= m_minMult)
      SetState(Inside);
  }
  else if (val > 0) {
    SetState(Chase);
  }
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Turn(int val)
{
  if (val == 0 || m_state != Chase)
    return;

  if (val < 0 && m_angle > -CH_C_PI)
    m_angle -= CH_C_PI / 100;
  else if (val > 0 && m_angle < CH_C_PI)
    m_angle += CH_C_PI / 100;
}

// -----------------------------------------------------------------------------
// Return the camera location and the camera target (look at) location,
// respectively.
// Note that in Inside mode, in order to accomodate a narrow field of view, we
// set the target location to be at the current driver location and move back
// the camera position.
// -----------------------------------------------------------------------------
ChVector<> ChChaseCamera::GetCameraPos() const
{
  if (m_state == Inside) {
    ChVector<> driverPos = m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(m_driverCsys.pos);
    ChVector<> driverViewDir = m_chassis->GetFrame_REF_to_abs().TransformDirectionLocalToParent(m_driverCsys.rot.GetXaxis());
    return driverPos - 1.1 * driverViewDir;
  }

  return (m_state == Track) ? m_lastLoc : m_loc;
}

void ChChaseCamera::SetCameraPos(const ChVector<>& pos)
{
  m_loc = pos;
  m_lastLoc = pos;
}

ChVector<> ChChaseCamera::GetTargetPos() const
{
  if (m_state == Inside) {
    return m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(m_driverCsys.pos);
  }

  return m_chassis->GetFrame_REF_to_abs().TransformPointLocalToParent(m_ptOnChassis);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::SetMultLimits(double minMult, double maxMult)
{
  m_minMult = minMult;
  m_maxMult = maxMult;
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChChaseCamera::Update(double step)
{
  // Perform a simple Euler integration step (derivative controller).
  m_loc += step * calcDeriv(m_loc);

  if (m_state != Track)
    return;

  // If in 'Track' mode, force a state change if too far from tracked body.
  double dist2 = (m_chassis->GetFrame_REF_to_abs().GetPos() - m_lastLoc).Length2();
  if (dist2 > m_maxTrackDist2)
    SetState(Chase);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
ChVector<> ChChaseCamera::calcDeriv(const ChVector<>& loc)
{
  // Calculate the desired camera location, based on the current state of the
  // chassis.
  ChVector<> targetLoc = GetTargetPos();
  ChVector<> uC2T;
  ChVector<> desCamLoc;

  if (m_state == Follow)
    uC2T = targetLoc - m_loc;
  else {
    ChQuaternion<> rot = Q_from_AngAxis(m_angle, ChVector<>(0, 1, 0));
    uC2T = rot.Rotate(m_chassis->GetA().Get_A_Xaxis());
  }

  uC2T.y = 0;
  uC2T.Normalize();

  desCamLoc = targetLoc - m_mult * m_dist * uC2T;
  desCamLoc.y = targetLoc.y + m_mult * m_height;

  // Calculate the derivative vector (RHS of filter ODEs).
  ChVector<> deriv;

  deriv.x = m_horizGain * (desCamLoc.x - m_loc.x);
  deriv.y = m_horizGain * (desCamLoc.y - m_loc.y);
  deriv.z = m_vertGain  * (desCamLoc.z - m_loc.z);

  return deriv;
}

}  // end namespace utils
}  // end namespace chrono
<|endoftext|>