Files
pay/src/ModuleManager.cpp
T

390 lines
12 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2023-2025 Tom Zander <tom@flowee.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ModuleManager.h"
#include <QFile>
#include <QCoreApplication>
#include <QLocale>
#include <QStandardPaths>
#include <QTranslator>
#include <QGuiApplication>
#include <QTimer>
#include <ripemd160.h>
#include <Logger.h>
#include <streaming/BufferPool.h>
#include <streaming/BufferPools.h>
#include <streaming/MessageBuilder.h>
#include <streaming/MessageParser.h>
#include <fstream>
#include <boost/filesystem.hpp>
enum ModuleConfigSaveTags {
ModuleId = 1,
LegacyModuleSectionId, // Deprecated (since 2024-10)
LegacyModuleSectionEnabled, // Deprecated (since 2024-10)
ModuleEnabled, // bool
ModuleSaveData // byte-array for a module to save local config.
};
static ModuleManager::FrontEnd g_moduleManager_FrontEnd = ModuleManager::Desktop;
ModuleManager::ModuleManager(QObject *parent)
: QObject(parent)
{
m_configFile = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "modules.conf");
if (m_configFile.isEmpty()) {
// make sure the directory exists
auto path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
std::filesystem::create_directories(path.toStdString());
m_configFile = path + '/' + "modules.conf";
}
#ifdef TARGET_OS_Android
auto guiApp = qobject_cast<QGuiApplication*>(QCoreApplication::instance());
assert(guiApp);
connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) {
if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended)
save();
});
#endif
extern void load_all_modules(ModuleManager*);
// the load_all_modules method is generated by cmake in the modules builddir.
// it essentially makes each module call 'load' on this class.
load_all_modules(this);
// connect to signals of the sections so we tell the QML to get the new
// filtered list on the user enabling / disabling something.
for (const auto *m : std::as_const(m_modules)) {
for (auto *s : m->sections()) {
connect (s, &ModuleSection::enabledChanged, this, [=]() {
switch (s->type()) {
case ModuleSection::SendMethod:
emit sendMenuSectionsChanged();
case ModuleSection::MainMenuItem:
emit mainMenuSectionsChanged();
case ModuleSection::OtherSectionType:
emit exploreTabItemsChanged();
default:
break;
}
});
}
}
}
ModuleManager::~ModuleManager()
{
save();
}
void ModuleManager::setFrontEnd(FrontEnd fe)
{
g_moduleManager_FrontEnd = fe;
}
ModuleManager::FrontEnd ModuleManager::frontEnd()
{
return g_moduleManager_FrontEnd;
}
void ModuleManager::load(const char *translationUnit, const std::function<ModuleInfo *()> &function)
{
if (translationUnit) {
QString translations = QString::fromUtf8(translationUnit);
auto *translator = new QTranslator(this);
/*
* We try to load it from the default location as used in the translations subdir (:/i18n)
* and if that fails we try again at the root level of the QRC because individual modules
* can then just use one data.qrc and include translations, if they manage their own.
*/
if (translator->load(QLocale(), translations, QLatin1String("_"), QLatin1String(":/i18n")))
QCoreApplication::installTranslator(translator);
else if (translator->load(QLocale(), translations, QLatin1String("_"), QLatin1String(":")))
QCoreApplication::installTranslator(translator);
else
delete translator;
}
auto *info = function();
if (info == nullptr)
return;
if (info->enabled()) {
logCritical() << "ModuleInfo starts 'enabled', denying user choice. Cowerdly refusing to register it";
return;
}
info->setParent(this); // take ownership
int insertPoint = m_modules.size();
while (insertPoint > 0) {
if (m_modules.at(insertPoint - 1)->priority() >= info->priority())
break;
--insertPoint;
}
m_modules.insert(insertPoint, info);
connect (info, &ModuleInfo::requestSaveSettings, this, [=]() {
QTimer::singleShot(2000, this, &ModuleManager::save);
});
}
void ModuleManager::loadConfiguration()
{
std::ifstream in(m_configFile.toStdString());
if (!in.is_open())
return;
auto dataSize = boost::filesystem::file_size(m_configFile.toStdString());
Streaming::BufferPool pool(dataSize);
in.read(pool.data(), dataSize);
Streaming::MessageParser parser(pool.commit(dataSize));
ModuleInfo *info = nullptr;
int type = -1;
bool prevEnabledCalled = false; // make sure we call those regardless of what the save-file contains
bool prevSettingsLoaded = false;
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == ModuleId) {
if (info && !prevEnabledCalled)
info->setEnabled(false);
if (info && !prevSettingsLoaded)
info->loadSettings(Streaming::ConstBuffer());
info = nullptr;
prevEnabledCalled = false;
prevSettingsLoaded = false;
type = -1;
const QString wantedId = QString::fromUtf8(parser.stringData());
for (auto *module : std::as_const(m_modules)) {
if (module->id() == wantedId) {
info = module;
break;
}
}
}
// legacy support
else if (parser.tag() == LegacyModuleSectionId) {
type = parser.intData();
}
// legacy support
else if (parser.tag() == LegacyModuleSectionEnabled) {
if (info) {
info->setEnabled(info->enabled() || parser.boolData());
for (auto *s : info->sections()) {
if (s->type() == type) {
s->setEnabled(parser.boolData());
break;
}
}
}
}
else if (parser.tag() == ModuleEnabled) {
if (info)
info->setEnabled(parser.boolData());
prevEnabledCalled = true;
}
else if (parser.tag() == ModuleSaveData) {
if (info)
info->loadSettings(parser.bytesDataBuffer());
prevSettingsLoaded = true;
}
}
}
void ModuleManager::save() const
{
if (m_modules.isEmpty()) {
// be nice to the devs that use both mobile and non-mobile on their
// account, the non-mobile has no modules, as such we just don't
// (over)write the config file.
return;
}
int saveFileSize = 100;
auto pool = std::make_shared<Streaming::BufferPool>(100000);
QList<Streaming::ConstBuffer> saveData;
for (const auto *m : m_modules) {
saveFileSize += m->id().size() * 3 + 3;
saveData.append(m->saveSettings(pool));
saveFileSize += saveData.back().size() + 12;
}
pool->reserve(saveFileSize);
Streaming::MessageBuilder builder(pool);
for (int i = 0; i < m_modules.size(); ++i) {
const auto *m = m_modules.at(i);
builder.add(ModuleId, m->id().toStdString());
if (m->enabled())
builder.add(ModuleEnabled, true);
auto saveFile = saveData.at(i);
if (!saveFile.isEmpty())
builder.add(ModuleSaveData, saveFile);
}
auto data = builder.buffer();
// hash the new file and check if its different lest we can skip saving
QFile origFile(m_configFile);
if (origFile.open(QIODevice::ReadOnly)) {
CRIPEMD160 fileHasher;
auto origContent = origFile.readAll();
fileHasher.write(origContent.data(), origContent.size());
char fileHash[CRIPEMD160::OUTPUT_SIZE];
fileHasher.finalize(fileHash);
CRIPEMD160 memHasher;
memHasher.write(data.begin(), data.size());
char memHash[CRIPEMD160::OUTPUT_SIZE];
memHasher.finalize(memHash);
if (memcmp(fileHash, memHash, CRIPEMD160::OUTPUT_SIZE) == 0) {
// no changes, so don't write.
return;
}
}
try {
auto configFile = m_configFile.toStdString();
auto configFileNew = configFile + "~";
std::ofstream outFile(configFileNew);
outFile.write(data.begin(), data.size());
outFile.flush();
outFile.close();
std::filesystem::rename(configFileNew, configFile);
} catch (const std::exception &e) {
logFatal() << "Failed to save the wallet.dat. Reason:" << e;
}
}
QList<ModuleInfo *> ModuleManager::registeredModules() const
{
return m_modules;
}
bool ModuleManager::isModuleAvailable(const QString &id) const
{
for (const auto *m : m_modules) {
if (m->id() == id)
return true;
}
return false;
}
QList<ModuleSection *> ModuleManager::sendMenuSections() const
{
QList<ModuleSection *> answer;
for (const auto *m : m_modules) {
for (auto *s : m->sections()) {
if (s->enabled() && s->type() == ModuleSection::SendMethod)
answer.append(s);
}
}
return answer;
}
QList<ModuleSection *> ModuleManager::mainMenuSections() const
{
QList<ModuleSection *> answer;
for (const auto *m : m_modules) {
for (auto *s : m->sections()) {
if (s->enabled() && s->type() == ModuleSection::MainMenuItem)
answer.append(s);
}
}
return answer;
}
QList<ModuleSection*> ModuleManager::exploreTabItems() const
{
QList<ModuleSection *> answer;
for (const auto *m : m_modules) {
for (auto *s : m->sections()) {
if (s->type() == ModuleSection::OtherSectionTypeDefault
|| (s->enabled() && s->type() == ModuleSection::OtherSectionType))
answer.append(s);
}
}
return answer;
}
QList<ModuleSection*> ModuleManager::oftenUsedItems() const
{
QList<ModuleSection *> answer;
// TODO
return answer;
}
ModuleSection *ModuleManager::sectionOnPlugin(const QString &pluginId, const QString &sectionId) const
{
for (const auto *m : m_modules) {
if (m->id() == pluginId) {
for (auto *s : m->sections()) {
if (s->type() == ModuleSection::CustomSectionType
&& s->sectionId() == sectionId)
return s;
}
break;
}
}
return nullptr;
}
ModuleInfo *ModuleManager::moduleInfo(const QString &pluginId) const
{
for (auto *m : m_modules) {
if (m->id() == pluginId) {
return m;
}
}
return nullptr;
}
ModuleSection *ModuleManager::moduleSection(const QString &pluginId) const
{
for (const auto *m : m_modules) {
if (m->id() == pluginId) {
if (m->sections().empty())
return nullptr;
for (auto *s : m->sections()) {
if (s->type() == ModuleSection::OtherSectionType)
return s;
}
return m->sections().first();
}
}
return nullptr;
}
void ModuleManager::allSections(const std::function<void (const QString &, ModuleSection *)> &handler)
{
for (const auto *m : std::as_const(m_modules)) {
for (auto *s : m->sections()) {
handler(m->id(), s);
}
}
}
void ModuleManager::requestAddPage(const QString &url, QObject *dataObject)
{
emit addPage(url, dataObject);
}
void ModuleManager::markChild(QObject *parent, QObject *child)
{
if (parent && child)
child->setParent(parent);
}