Initial backend test for user-based data/customizations (#4144)

Right now only support for colors and no real UX, idea is to test it & allow the idea to grow while figuring out the UX
This commit is contained in:
pajlada
2022-11-13 18:21:21 +01:00
committed by GitHub
parent a9d3c00369
commit 1eabda8668
13 changed files with 372 additions and 1 deletions
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QColor>
#include <QString>
#include <boost/optional.hpp>
#include <pajlada/serialize.hpp>
namespace chatterino {
// UserData defines a set of data that is defined for a unique user
// It can contain things like optional replacement color for the user, a unique alias
// or a user note that should be displayed with the user
// Replacement fields should be optional, where none denotes that the field should not be updated for the user
struct UserData {
boost::optional<QColor> color{boost::none};
// TODO: User note?
};
} // namespace chatterino
namespace pajlada {
template <>
struct Serialize<chatterino::UserData> {
static rapidjson::Value get(const chatterino::UserData &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value obj;
obj.SetObject();
if (value.color)
{
const auto &color = *value.color;
chatterino::rj::set(obj, "color",
color.name().toUtf8().toStdString(), a);
}
return obj;
}
};
template <>
struct Deserialize<chatterino::UserData> {
static chatterino::UserData get(const rapidjson::Value &value,
bool *error = nullptr)
{
if (!value.IsObject())
{
PAJLADA_REPORT_ERROR(error)
return chatterino::UserData{};
}
chatterino::UserData user;
QString colorString;
if (chatterino::rj::getSafe(value, "color", colorString))
{
QColor color(colorString);
if (color.isValid())
{
user.color = color;
}
}
return user;
}
};
} // namespace pajlada
@@ -0,0 +1,100 @@
#include "controllers/userdata/UserDataController.hpp"
#include "singletons/Paths.hpp"
#include "util/CombinePath.hpp"
namespace {
using namespace chatterino;
std::shared_ptr<pajlada::Settings::SettingManager> initSettingsInstance()
{
auto sm = std::make_shared<pajlada::Settings::SettingManager>();
auto *paths = getPaths();
auto path = combinePath(paths->settingsDirectory, "user-data.json");
sm->setPath(path.toUtf8().toStdString());
sm->setBackupEnabled(true);
sm->setBackupSlots(9);
sm->saveMethod =
pajlada::Settings::SettingManager::SaveMethod::SaveAllTheTime;
return sm;
}
} // namespace
namespace chatterino {
UserDataController::UserDataController()
: sm(initSettingsInstance())
, setting("/users", this->sm)
{
this->sm->load();
this->users = this->setting.getValue();
}
void UserDataController::save()
{
this->sm->save();
}
boost::optional<UserData> UserDataController::getUser(
const QString &userID) const
{
std::shared_lock lock(this->usersMutex);
auto it = this->users.find(userID);
if (it == this->users.end())
{
return boost::none;
}
return it->second;
}
std::unordered_map<QString, UserData> UserDataController::getUsers() const
{
std::shared_lock lock(this->usersMutex);
return this->users;
}
void UserDataController::setUserColor(const QString &userID,
const QString &colorString)
{
auto c = this->getUsers();
auto it = c.find(userID);
boost::optional<QColor> finalColor =
boost::make_optional(!colorString.isEmpty(), QColor(colorString));
if (it == c.end())
{
if (!finalColor)
{
// Early out - user is not configured and will not get a new color
return;
}
UserData user;
user.color = finalColor;
c.insert({userID, user});
}
else
{
it->second.color = finalColor;
}
this->update(std::move(c));
}
void UserDataController::update(
std::unordered_map<QString, UserData> &&newUsers)
{
std::unique_lock lock(this->usersMutex);
this->users = std::move(newUsers);
this->setting.setValue(this->users);
}
} // namespace chatterino
@@ -0,0 +1,60 @@
#pragma once
#include "common/Singleton.hpp"
#include "controllers/userdata/UserData.hpp"
#include "util/QStringHash.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include "util/serialize/Container.hpp"
#include <QColor>
#include <QString>
#include <boost/optional.hpp>
#include <pajlada/settings.hpp>
#include <shared_mutex>
#include <unordered_map>
namespace chatterino {
class IUserDataController
{
public:
virtual ~IUserDataController() = default;
virtual boost::optional<UserData> getUser(const QString &userID) const = 0;
virtual void setUserColor(const QString &userID,
const QString &colorString) = 0;
};
class UserDataController : public IUserDataController, public Singleton
{
public:
UserDataController();
// Get extra data about a user
// If the user does not have any extra data, return none
boost::optional<UserData> getUser(const QString &userID) const override;
// Update or insert extra data for the user's color override
void setUserColor(const QString &userID,
const QString &colorString) override;
protected:
void save() override;
private:
void update(std::unordered_map<QString, UserData> &&newUsers);
std::unordered_map<QString, UserData> getUsers() const;
// Stores a real-time list of users & their customizations
std::unordered_map<QString, UserData> users;
mutable std::shared_mutex usersMutex;
std::shared_ptr<pajlada::Settings::SettingManager> sm;
pajlada::Settings::Setting<std::unordered_map<QString, UserData>> setting;
};
} // namespace chatterino