Move settings into a separate JSON file.

This will unfortunately mean losing your commands, but they can be restored by
converting the old commands.txt format into the commands.json file

Fix #372
This commit is contained in:
Rasmus Karlsson 2018-11-03 13:37:09 +01:00
parent 221ec4f1e8
commit a4fd7b5366
5 changed files with 110 additions and 64 deletions

View file

@ -163,7 +163,7 @@ void CompletionModel::refresh(const QString &prefix)
} }
// Commands // Commands
for (auto &command : getApp()->commands->items.getVector()) for (auto &command : getApp()->commands->items_.getVector())
{ {
addString(command.name, TaggedString::Command); addString(command.name, TaggedString::Command);
} }

View file

@ -1,6 +1,9 @@
#pragma once #pragma once
#include "util/RapidjsonHelpers.hpp"
#include <QString> #include <QString>
#include <pajlada/serialize.hpp>
namespace chatterino { namespace chatterino {
@ -16,3 +19,49 @@ struct Command {
}; };
} // namespace chatterino } // namespace chatterino
namespace pajlada {
template <>
struct Serialize<chatterino::Command> {
static rapidjson::Value get(const chatterino::Command &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
chatterino::rj::set(ret, "name", value.name, a);
chatterino::rj::set(ret, "func", value.func, a);
return ret;
}
};
template <>
struct Deserialize<chatterino::Command> {
static chatterino::Command get(const rapidjson::Value &value,
bool *error = nullptr)
{
chatterino::Command command;
if (!value.IsObject())
{
PAJLADA_REPORT_ERROR(error);
return command;
}
if (!chatterino::rj::getSafe(value, "name", command.name))
{
PAJLADA_REPORT_ERROR(error);
return command;
}
if (!chatterino::rj::getSafe(value, "func", command.func))
{
PAJLADA_REPORT_ERROR(error);
return command;
}
return command;
}
};
} // namespace pajlada

View file

@ -33,12 +33,13 @@
namespace chatterino { namespace chatterino {
CommandController::CommandController() void CommandController::initialize(Settings &, Paths &paths)
{ {
// Update commands map when the vector of commands has been updated
auto addFirstMatchToMap = [this](auto args) { auto addFirstMatchToMap = [this](auto args) {
this->commandsMap_.remove(args.item.name); this->commandsMap_.remove(args.item.name);
for (const Command &cmd : this->items.getVector()) for (const Command &cmd : this->items_.getVector())
{ {
if (cmd.name == args.item.name) if (cmd.name == args.item.name)
{ {
@ -47,70 +48,51 @@ CommandController::CommandController()
} }
} }
}; };
this->items_.itemInserted.connect(addFirstMatchToMap);
this->items_.itemRemoved.connect(addFirstMatchToMap);
this->items.itemInserted.connect(addFirstMatchToMap); // Initialize setting manager for commands.json
this->items.itemRemoved.connect(addFirstMatchToMap); auto path = combinePath(paths.settingsDirectory, "commands.json");
} this->sm_ = std::make_shared<pajlada::Settings::SettingManager>();
this->sm_->setPath(path.toStdString());
void CommandController::initialize(Settings &, Paths &paths) // Delayed initialization of the setting storing all commands
{ this->commandsSetting_.reset(
this->load(paths); new pajlada::Settings::Setting<std::vector<Command>>("/commands",
} this->sm_));
void CommandController::load(Paths &paths) // Update the setting when the vector of commands has been updated (most
{ // likely from the settings dialog)
this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt"); this->items_.delayedItemsChanged.connect([this] { //
this->commandsSetting_->setValue(this->items_.getVector());
});
QFile textFile(this->filePath_); // Load commands from commands.json
if (!textFile.open(QIODevice::ReadOnly)) this->sm_->load();
// Add loaded commands to our vector of commands (which will update the map
// of commands)
for (const auto &command : this->commandsSetting_->getValue())
{ {
// No commands file created yet this->items_.appendItem(command);
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() void CommandController::save()
{ {
QFile textFile(this->filePath_); this->sm_->save();
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 *CommandController::createModel(QObject *parent)
{ {
CommandModel *model = new CommandModel(parent); CommandModel *model = new CommandModel(parent);
model->init(&this->items); model->init(&this->items_);
return model; return model;
} }
QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr channel, QString CommandController::execCommand(const QString &textNoEmoji,
bool dryRun) ChannelPtr channel, bool dryRun)
{ {
QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji); QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji);
QStringList words = text.split(' ', QString::SkipEmptyParts); QStringList words = text.split(' ', QString::SkipEmptyParts);
@ -156,10 +138,12 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr ch
const auto &ffzemotes = app->twitch.server->getFfzEmotes(); const auto &ffzemotes = app->twitch.server->getFfzEmotes();
auto flags = MessageElementFlags(); auto flags = MessageElementFlags();
auto emote = boost::optional<EmotePtr>{}; auto emote = boost::optional<EmotePtr>{};
for (int i = 2; i < words.length(); i++) { for (int i = 2; i < words.length(); i++)
{
{ // twitch emote { // twitch emote
auto it = accemotes.emotes.find({words[i]}); auto it = accemotes.emotes.find({words[i]});
if (it != accemotes.emotes.end()) { if (it != accemotes.emotes.end())
{
b.emplace<EmoteElement>( b.emplace<EmoteElement>(
it->second, MessageElementFlag::TwitchEmote); it->second, MessageElementFlag::TwitchEmote);
continue; continue;
@ -167,19 +151,24 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr ch
} // twitch emote } // twitch emote
{ // bttv/ffz emote { // bttv/ffz emote
if ((emote = bttvemotes.emote({words[i]}))) { if ((emote = bttvemotes.emote({words[i]})))
{
flags = MessageElementFlag::BttvEmote; flags = MessageElementFlag::BttvEmote;
} else if ((emote = ffzemotes.emote({words[i]}))) { }
else if ((emote = ffzemotes.emote({words[i]})))
{
flags = MessageElementFlag::FfzEmote; flags = MessageElementFlag::FfzEmote;
} }
if (emote) { if (emote)
{
b.emplace<EmoteElement>(emote.get(), flags); b.emplace<EmoteElement>(emote.get(), flags);
continue; continue;
} }
} // bttv/ffz emote } // bttv/ffz emote
{ // emoji/text { // emoji/text
for (auto &variant : for (auto &variant :
app->emotes->emojis.parse(words[i])) { app->emotes->emojis.parse(words[i]))
{
constexpr const static struct { constexpr const static struct {
void operator()(EmotePtr emote, void operator()(EmotePtr emote,
MessageBuilder &b) const MessageBuilder &b) const
@ -207,7 +196,8 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr ch
app->twitch.server->sendMessage("jtv", text); app->twitch.server->sendMessage("jtv", text);
if (getSettings()->inlineWhispers) { if (getSettings()->inlineWhispers)
{
app->twitch.server->forEachChannel( app->twitch.server->forEachChannel(
[&messagexD](ChannelPtr _channel) { [&messagexD](ChannelPtr _channel) {
_channel->addMessage(messagexD); _channel->addMessage(messagexD);

View file

@ -1,14 +1,16 @@
#pragma once #pragma once
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "controllers/commands/Command.hpp"
#include <QMap> #include <QMap>
#include <pajlada/settings.hpp>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include "common/SignalVector.hpp"
#include "controllers/commands/Command.hpp"
namespace chatterino { namespace chatterino {
class Settings; class Settings;
@ -20,26 +22,31 @@ class CommandModel;
class CommandController final : public Singleton class CommandController final : public Singleton
{ {
public: public:
CommandController(); UnsortedSignalVector<Command> items_;
QString execCommand(const QString &text, std::shared_ptr<Channel> channel, QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
bool dryRun); bool dryRun);
QStringList getDefaultTwitchCommandList(); QStringList getDefaultTwitchCommandList();
virtual void initialize(Settings &settings, Paths &paths) override; virtual void initialize(Settings &, Paths &paths) override;
virtual void save() override; virtual void save() override;
CommandModel *createModel(QObject *parent); CommandModel *createModel(QObject *parent);
UnsortedSignalVector<Command> items;
private: private:
void load(Paths &paths); void load(Paths &paths);
QMap<QString, Command> commandsMap_; QMap<QString, Command> commandsMap_;
std::mutex mutex_; std::mutex mutex_;
QString filePath_;
std::shared_ptr<pajlada::Settings::SettingManager> sm_;
// Because the setting manager is not initialized until the initialize
// function is called (and not in the constructor), we have to
// late-initialize the setting, which is why we're storing it as a
// unique_ptr
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
commandsSetting_;
QString execCustomCommand(const QStringList &words, const Command &command); QString execCustomCommand(const QStringList &words, const Command &command);
}; };

View file

@ -41,7 +41,7 @@ CommandPage::CommandPage()
view->setTitles({"Trigger", "Command"}); view->setTitles({"Trigger", "Command"});
view->getTableView()->horizontalHeader()->setStretchLastSection(true); view->getTableView()->horizontalHeader()->setStretchLastSection(true);
view->addButtonPressed.connect([] { view->addButtonPressed.connect([] {
getApp()->commands->items.appendItem( getApp()->commands->items_.appendItem(
Command{"/command", "I made a new command HeyGuys"}); Command{"/command", "I made a new command HeyGuys"});
}); });