Files
pay/modules/backup-sync/BackupSyncModuleInfo.cpp
T

270 lines
9.8 KiB
C++
Raw Permalink Normal View History

2025-11-12 13:54:22 +01:00
/*
* This file is part of the Flowee project
* Copyright (C) 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 "BackupSyncModuleInfo.h"
#include "BackupNFCHandler.h"
#include "BackupAccountInfo.h"
#include "QMLData.h"
#include "WalletData.h"
#include <FloweePay.h>
#include <QQmlEngine> // for the qmlRegisterType
#include <QTimer>
#include <streaming/BufferPools.h>
#include <streaming/MessageBuilder.h>
#include <SeedsBackup.h>
namespace Module {
enum Tags {
WalletSegment,
PrivateKey,
LastSavedTime, // long, epoch based timestamp
NeedsExport, // bool
// LastSaveHash,
};
}
namespace {
static BackupSyncModuleInfo *g_instance = nullptr;
}
BackupSyncModuleInfo *BackupSyncModuleInfo::instance()
{
assert(g_instance);
return g_instance;
}
ModuleInfo * BackupSyncModuleInfo::build()
{
assert(g_instance == nullptr);
g_instance = new BackupSyncModuleInfo();
BackupSyncModuleInfo *info = g_instance;
info->setId("backupSyncModule");
info->setTitle(tr("Backup Sync"));
info->setDescription(tr("Transaction comments cloud backup."));
// Provide a way to configure backup on a certain wallet.
auto *section = new ModuleSection(ModuleSection::CustomSectionType, info);
section->setSectionId("walletOptions");
if (ModuleManager::frontEnd() == ModuleManager::Mobile)
section->setStartQMLFile("qrc:/backup-sync/WalletOptions.qml");
else
section->setStartQMLFile("qrc:/backup-sync/WalletOptions-desktop.qml");
info->addSection(section);
// Starting point to initiate storing a wallet on an NFC tag
auto *section2 = new ModuleSection(ModuleSection::CustomSectionType, info);
section2->setSectionId("storeOnNfc");
section2->setStartQMLFile("qrc:/backup-sync/WalletToNFC.qml");
info->addSection(section2);
qmlRegisterType<BackupNFCHandler>("Flowee.org.pay.backup", 1, 0, "NFC");
qmlRegisterType<BackupAccountInfo>("Flowee.org.pay.backup", 1, 0, "BackupAccountInfo");
return info;
}
BackupSyncModuleInfo::BackupSyncModuleInfo()
{
connect(FloweePay::instance(), &FloweePay::newNfcTagData,
this, &BackupSyncModuleInfo::onTagRead, Qt::QueuedConnection);
connect(FloweePay::instance(), &FloweePay::nfcStatusChanged,
this, &BackupSyncModuleInfo::nfcAvailableChanged);
#if 0
QTimer::singleShot(5000, this, [=]() {
auto data = QByteArray::fromHex("0");
std::shared_ptr<NFCTagData> tag = std::make_shared<NFCTagData>();
NFCTagData::Record record;
record.mimeType = "application/bch-seed";
auto pool = Streaming::pool(data.size());
pool->write(data.constData(), data.size());
record.payload = pool->commit();
tag->addRecord(record);
onTagRead(tag);
});
#endif
}
void BackupSyncModuleInfo::loadSettings(const Streaming::ConstBuffer &data)
{
// delay the basic setup a little to ensure that the
// FloweePay instance has been created.
QTimer::singleShot(0, this, [=]() {
// Delay more to ensure all wallets are created.
connect(FloweePay::instance(), &FloweePay::loadComplete, this, [=]() {
// now load wallet data from blob
Streaming::MessageParser parser(data);
const auto wallets = FloweePay::instance()->wallets();
WalletData *wd = nullptr;
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == Module::WalletSegment) {
wd = nullptr; // avoid corruption when a wallet was deleted.
int id = parser.intData();
for (const std::shared_ptr<Wallet> &wallet : wallets) {
if (wallet->segment()->segmentId() == id) {
wd = new WalletData(wallet, this);
m_walletData.append(wd);
break;
}
}
}
else if (parser.tag() == Module::PrivateKey) {
if (wd)
wd->setPrivKeyData(parser.bytesDataBuffer());
}
else if (parser.tag() == Module::LastSavedTime) {
if (wd)
wd->setLastSavedTime(parser.longData());
}
else if (parser.tag() == Module::NeedsExport) {
if (wd && parser.boolData())
wd->markDirty();
}
}
m_loaded = true;
setupWallets();
connect (FloweePay::instance(), &FloweePay::walletsChanged,
this, [=]() { setupWallets(); });
});
});
}
Streaming::ConstBuffer BackupSyncModuleInfo::saveSettings(std::shared_ptr<Streaming::BufferPool> &pool) const
{
2026-01-14 17:36:54 +01:00
pool->reserve(m_walletData.size() * 25);
2025-11-12 13:54:22 +01:00
Streaming::MessageBuilder builder(pool);
for (auto *wd : m_walletData) {
builder.add(Module::WalletSegment, wd->wallet()->segment()->segmentId());
if (wd->wallet()->encryption() != Wallet::NotEncrypted && wd->privKey().isValid()) {
const auto &key = wd->privKey();
builder.addByteArray(Module::PrivateKey, key.begin(), key.size());
}
builder.add(Module::LastSavedTime, static_cast<uint64_t>(wd->lastSavedTime()));
if (wd->needsSave())
builder.add(Module::NeedsExport, true);
}
return builder.buffer();
}
void BackupSyncModuleInfo::setupWallets()
{
bool addedOne = false;
for (const auto &wallet : FloweePay::instance()->wallets()) {
bool known = false;
for (const auto *wd : std::as_const(m_walletData)) {
if (wd->wallet() == wallet) {
known = true;
break;
}
}
if (!known) {
if (!wallet->isHDWallet())
continue;
// ignore archived wallets.
if (wallet->segment()->priority() >= PrivacySegment::OnlyManual)
continue;
auto path = HDMasterKey::deriveFromString(wallet->derivationPath().toStdString());
if (path.size() != 3) {
logInfo(9313) << "Notice non standard derived wallet" << wallet->name();
continue;
}
addedOne = true;
// wallets get this enabled by default.
AccountConfig accountConfig(wallet);
accountConfig.setCloudStoreDetails(true);
// create our wrapper.
auto *wd = new WalletData(wallet, this);
m_walletData.append(wd);
// as this is either an existing wallet because this feature is new,
// or this is a new wallet just imported or created, lets check for prior backups.
wd->restore(WalletData::JustRestore);
}
}
if (addedOne)
emit walletDataChanged();
}
ModuleManager *BackupSyncModuleInfo::mmanager() const
{
return qobject_cast<ModuleManager*>(parent());
}
QList<WalletData *> BackupSyncModuleInfo::walletData() const
{
return m_walletData;
}
bool BackupSyncModuleInfo::nfcAvailable() const
{
return FloweePay::instance()->nfcStatus() != WalletEnums::NFC_Unsupported;
}
void BackupSyncModuleInfo::onTagRead(const std::shared_ptr<NFCTagData> &data)
{
assert(data.get());
logInfo() << "Found an NFC tag with" << data.get()->records().size() << "record(s)";
auto records = data->records();
assert(!records.isEmpty());
if (records.first().mimeType == "application/bch-seed") {
const auto payload = records.first().payload;
logInfo() << "NFC: Going to try to restore the seed.";
auto *qmlData = new QMLData(this);
auto *backup = SeedsBackup::fromData(payload);
if (backup && !backup->paths().isEmpty()) {
qmlData->setSeeds(backup);
const auto seedPhrase = backup->generateSeedPhrase();
qmlData->setSeed(seedPhrase);
const auto paths = backup->paths();
// iterate through existing wallets and check if the seed matches one of the wallets
for (const auto &wallet : FloweePay::instance()->wallets()) {
if (wallet->isHDWallet()) {
if (wallet->hdWalletMnemonic() == seedPhrase) {
bool fullMatch = false;
for (const auto &item : paths) {
if (item.path == wallet->derivationPath()) {
fullMatch = true;
break;
}
}
qmlData->addWallet(wallet, fullMatch);
}
}
}
if (qmlData->seedKnown()) {
mmanager()->requestAddPage("qrc:/backup-sync/WalletExists.qml", qmlData);
return;
}
mmanager()->requestAddPage("qrc:/backup-sync/ImportSeed.qml", qmlData);
return;
}
else {
logWarning() << "NFC failed restoring, data didn't parse or too new version";
// TODO FloweePay::setNotification()
// would be nice to show an "NFC tag data not understood" or so
delete qmlData;
}
}
// TODO
// if we get here, some other mime type was found, likely initiating a payment.
}