Files
triangles_v5/src/qt/introdialog.cpp
T
sami7777 2ba0ecf428 Cleanup: drop boost::filesystem/thread/chrono, retire dead code
Migration from boost to std-library equivalents and removal of unreachable
code paths. Touches infrastructure only — no consensus rule or wallet
serialization changes.

Dead code removed:
- IRC bootstrap (irc.cpp/h, 417 lines): orphan from pre-Tor era, no callers.
- Alert system (alert.cpp/h + sendalert RPC + Qt UI signal, ~500 lines):
  retired post-V5 fork; old peers' alert messages now hit the unknown-cmd
  default branch, logged + ignored.
- Legacy P2P handlers in main.cpp: "checkpoint" (already a no-op stub since
  V5 fork master-key removal), "checkorder"/"reply" (2010-era Receive-by-IP
  feature), plus their unused supporting structures (CRequestTracker,
  PushRequest overloads, mapRequests/cs_mapRequests, mapReuseKey).
- Unreachable RPCs clearwallettransactions and scanforalltxns (~175 lines):
  defined in rpcwallet.cpp but never registered in the dispatch table.
- Stale -alertnotify CLI help text (option was advertised but never wired).

boost::filesystem -> std::filesystem (C++17):
- 30 source files, 5 headers. namespace fs = boost::filesystem swapped to
  namespace fs = std::filesystem; boost::filesystem::ifstream/ofstream
  replaced with std::ifstream/ofstream (path-aware in C++17);
  fs::system_complete -> fs::absolute; boost::filesystem::filesystem_error
  -> std::filesystem::filesystem_error.
- Build system: dropped Boost::filesystem from link libs and Boost
  components; PCH includes updated.
- Added explicit <filesystem> includes where types were previously
  available only transitively (db.h, rpcblockchain.cpp).

boost::thread -> std::thread (12 files):
- sync.h CCriticalSection/CWaitableCriticalSection now alias
  std::recursive_mutex/std::mutex. boost::unique_lock and
  boost::condition_variable / boost::mutex::scoped_lock swapped to std
  equivalents; sync.cpp boost::thread_specific_ptr -> thread_local
  std::unique_ptr.
- init.cpp boost::thread_group rewritten as std::vector<std::thread> with
  manual join loop. boost::thread::hardware_concurrency ->
  std::thread::hardware_concurrency.
- main.cpp/wallet.cpp -blocknotify/-walletnotify shell-out threads now use
  std::thread(...).detach() — fixes a latent bug where modern boost::thread
  destructor would call std::terminate on the joinable thread.
- util.cpp NewThread now catches std::system_error.
- No interruption_point/interrupt usage anywhere — pure mechanical swap.

boost::chrono / boost::posix_time -> std::chrono (3 of 5 files):
- util.h: MilliSleep, GetTimeMillis, GetTimeMicros rewritten on std::chrono
  (system_clock for epoch math, sleep_for for delays).
- snapshotnet.cpp: sleep_for swapped.
- DoS_tests.cpp: timing harness uses steady_clock.
- Skipped: rpcdump.cpp (boost::posix_time::time_input_facet has no clean
  std::get_time equivalent) and qt/qtipcserver.cpp (locked to
  boost::posix_time by boost::interprocess::message_queue::timed_receive).

Other housekeeping:
- Dropped unnecessary "using namespace boost;" from txdb-leveldb.cpp,
  txdb-rocksdb.cpp, walletdb.cpp, db.cpp (verified no unqualified boost
  names in those TUs).
- Removed unused extern declaration for clearwallettransactions.

Build fixes for non-unity builds on MinGW64/GCC 15:
- net.cpp: dropped stale #include "irc.h".
- addrman.cpp + main.cpp: explicit <cmath> include for sqrt/pow (was
  arriving transitively via boost headers).
- rpcblockchain.cpp + init.cpp: defensive #undef STRICT/ADVISORY/PERMISSIVE
  since windows.h macros collide with the Checkpoints:: enum values when
  std headers reorder include flow.
- tor_embed_hooks.cpp: triangles_tor_check_interrupted now polls fShutdown
  instead of boost::this_thread::interruption_requested (we never used
  boost interruption — the hook was always effectively a no-op).
- snapshotnet.cpp: fs::remove error handle uses std::error_code.
- serialize.h: added <ios> for std::ios::badbit/failbit (was relying on
  transitive include via boost).

Note: unity builds currently fail on this branch due to std::byte (C++17)
colliding with COM 'byte' typedef from shlobj.h when 'using namespace std;'
from earlier files in the unity slice leaks into util.cpp's parse of
shlobj.h. Build with -DENABLE_UNITY_BUILD=OFF (the default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 15:27:14 -07:00

449 lines
16 KiB
C++

#include "introdialog.h"
#include "util.h"
#include "bootstrap.h"
#include <QSettings>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QFileDialog>
#include <QDir>
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QProgressDialog>
#include <QCheckBox>
#include <QApplication>
#include <filesystem>
#include <set>
IntroDialog::IntroDialog(QWidget *parent) :
QDialog(parent)
{
setWindowTitle("Triangles");
setMinimumWidth(520);
// Match existing Triangles dark theme
setStyleSheet(
"QDialog { background-color: #000; color: #f26522; }"
"QLabel { color: #f26522; }"
"QRadioButton { color: #f26522; }"
"QRadioButton::indicator { border: 1px solid #f26522; background-color: #000; width: 12px; height: 12px; border-radius: 7px; }"
"QRadioButton::indicator:checked { background-color: #f26522; }"
"QLineEdit { background-color: #1c1c1c; border: 1px solid #f26522; color: #f26522; padding: 4px; }"
"QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; padding: 4px 16px; min-height: 20px; }"
"QPushButton:hover { background-color: #61280E; }"
);
defaultDataDir = QString::fromStdString(GetDefaultDataDir().string());
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(20, 20, 20, 20);
mainLayout->setSpacing(12);
// Welcome header
QLabel *welcomeLabel = new QLabel(tr("Welcome to Triangles!"));
welcomeLabel->setStyleSheet("font-size: 16px; font-weight: bold; color: #f26522;");
mainLayout->addWidget(welcomeLabel);
// Description
QLabel *descLabel = new QLabel(tr(
"Triangles will store its blockchain data, wallet, and configuration in a data directory. "
"You can use the default directory or choose a custom location. "
"The data directory requires several hundred MB of free space."
));
descLabel->setWordWrap(true);
mainLayout->addWidget(descLabel);
mainLayout->addSpacing(8);
// Default directory radio
defaultRadio = new QRadioButton(tr("Use the default data directory"));
defaultRadio->setChecked(true);
mainLayout->addWidget(defaultRadio);
// Show default path
QLabel *defaultPathLabel = new QLabel(defaultDataDir);
defaultPathLabel->setStyleSheet("color: #999; margin-left: 24px; font-size: 11px;");
mainLayout->addWidget(defaultPathLabel);
mainLayout->addSpacing(4);
// Custom directory radio
customRadio = new QRadioButton(tr("Use a custom data directory:"));
mainLayout->addWidget(customRadio);
// Path input + browse button
QHBoxLayout *pathLayout = new QHBoxLayout();
pathLayout->setContentsMargins(24, 0, 0, 0);
pathEdit = new QLineEdit(defaultDataDir);
pathEdit->setEnabled(false);
pathLayout->addWidget(pathEdit);
browseButton = new QPushButton(tr("Browse..."));
browseButton->setEnabled(false);
pathLayout->addWidget(browseButton);
mainLayout->addLayout(pathLayout);
// Free space label
freeSpaceLabel = new QLabel();
freeSpaceLabel->setStyleSheet("color: #999; margin-left: 24px; font-size: 11px;");
mainLayout->addWidget(freeSpaceLabel);
mainLayout->addStretch(1);
// OK / Cancel buttons
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addStretch(1);
QPushButton *okButton = new QPushButton(tr("OK"));
QPushButton *cancelButton = new QPushButton(tr("Cancel"));
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
mainLayout->addLayout(buttonLayout);
// Connections
connect(defaultRadio, SIGNAL(toggled(bool)), this, SLOT(on_defaultRadio_toggled(bool)));
connect(browseButton, SIGNAL(clicked()), this, SLOT(on_browseButton_clicked()));
connect(pathEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFreeSpace()));
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
updateFreeSpace();
}
QString IntroDialog::getDataDirectory() const
{
if (defaultRadio->isChecked())
return defaultDataDir;
return pathEdit->text();
}
void IntroDialog::setDataDirectory(const QString &dir)
{
pathEdit->setText(dir);
if (dir == defaultDataDir) {
defaultRadio->setChecked(true);
} else {
customRadio->setChecked(true);
}
}
void IntroDialog::on_browseButton_clicked()
{
QString dir = QFileDialog::getExistingDirectory(this, tr("Choose data directory"), pathEdit->text());
if (!dir.isEmpty())
pathEdit->setText(dir);
}
void IntroDialog::on_defaultRadio_toggled(bool checked)
{
pathEdit->setEnabled(!checked);
browseButton->setEnabled(!checked);
if (checked)
pathEdit->setText(defaultDataDir);
updateFreeSpace();
}
void IntroDialog::updateFreeSpace()
{
QString path = getDataDirectory();
std::filesystem::path fsPath(path.toStdString());
// Walk up to find an existing parent
try {
while (!fsPath.empty() && !std::filesystem::exists(fsPath))
fsPath = fsPath.parent_path();
if (!fsPath.empty()) {
std::filesystem::space_info si = std::filesystem::space(fsPath);
double freeGB = (double)si.available / (1024.0 * 1024.0 * 1024.0);
freeSpaceLabel->setText(tr("Free space: %1 GB").arg(QString::number(freeGB, 'f', 2)));
} else {
freeSpaceLabel->setText(tr("Cannot determine free space"));
}
} catch (const std::filesystem::filesystem_error &) {
freeSpaceLabel->setText(tr("Cannot determine free space"));
}
}
bool IntroDialog::pickDataDirectory()
{
namespace fs = std::filesystem;
QSettings settings;
// If -datadir was passed on the command line, skip the dialog entirely
if (mapArgs.count("-datadir"))
return true;
QString dataDir = settings.value("strDataDir", "").toString();
if (dataDir.isEmpty()) {
// First run - show the dialog
IntroDialog dlg;
if (dlg.exec() != QDialog::Accepted)
return false;
dataDir = dlg.getDataDirectory();
settings.setValue("strDataDir", dataDir);
}
// Check for pending data directory migration
if (settings.value("fPendingDataDirMigration", false).toBool()) {
QString oldDir = settings.value("strDataDirPrevious", "").toString();
if (!oldDir.isEmpty() && oldDir != dataDir) {
if (!migrateDataDirectory(oldDir, dataDir)) {
// Migration failed - revert to old directory
QMessageBox::warning(0, "Triangles",
QString("Data directory migration failed.\nContinuing with the previous directory:\n%1")
.arg(oldDir));
dataDir = oldDir;
settings.setValue("strDataDir", oldDir);
}
}
// Clear migration state regardless
settings.remove("strDataDirPrevious");
settings.setValue("fPendingDataDirMigration", false);
}
// If the saved path is the default, don't set -datadir (let normal defaults work)
QString defaultDir = QString::fromStdString(GetDefaultDataDir().string());
if (dataDir != defaultDir) {
mapArgs["-datadir"] = dataDir.toStdString();
}
// Ensure the directory exists
try {
fs::create_directories(fs::path(dataDir.toStdString()));
} catch (const fs::filesystem_error &) {
QMessageBox::critical(0, "Triangles",
QString("Error: Could not create data directory \"%1\".").arg(dataDir));
return false;
}
// Auto-bootstrap: if no blockchain data exists, download automatically.
// If data exists, offer optional re-download (unless user checked "don't ask again").
fs::path dataDirPath(dataDir.toStdString());
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataDirPath);
bool userWantsBootstrap = false;
if (needsBootstrap)
{
// No blockchain data — bootstrap automatically, just inform the user
QMessageBox::information(0, "Triangles",
"No blockchain data found.\n\n"
"Downloading the latest blockchain snapshot automatically.\n"
"This will only take a few minutes.");
userWantsBootstrap = true;
}
else if (!settings.value("bootstrapDontAsk", false).toBool())
{
QMessageBox msgBox;
msgBox.setWindowTitle("Triangles");
msgBox.setText(
"Would you like to download the latest blockchain snapshot?\n\n"
"This will download the blockchain data from the Triangles network "
"and replace any existing chain data in your data directory.\n\n"
"Click Yes to download, or No to sync from the network.");
msgBox.setIcon(QMessageBox::Question);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
QCheckBox *dontAskBox = new QCheckBox("Don't show this again");
msgBox.setCheckBox(dontAskBox);
int ret = msgBox.exec();
if (dontAskBox->isChecked())
settings.setValue("bootstrapDontAsk", true);
userWantsBootstrap = (ret == QMessageBox::Yes);
}
if (userWantsBootstrap)
{
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
QProgressDialog progress("Downloading blockchain snapshot...", "Cancel",
0, 100, 0);
progress.setWindowTitle("Triangles - Bootstrap");
progress.setWindowModality(Qt::ApplicationModal);
progress.setMinimumDuration(0);
progress.setValue(0);
auto progressFn = [&progress](int64_t bytesDownloaded, int64_t totalBytes) {
if (totalBytes > 0) {
int pct = (int)((bytesDownloaded * 100) / totalBytes);
progress.setValue(pct);
progress.setLabelText(
QString("Downloading blockchain snapshot... %1 MB / %2 MB")
.arg(bytesDownloaded / (1024*1024))
.arg(totalBytes / (1024*1024)));
} else {
progress.setLabelText(
QString("Downloading blockchain snapshot... %1 MB")
.arg(bytesDownloaded / (1024*1024)));
}
QApplication::processEvents();
};
bool success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
if (!success) {
QMessageBox::warning(0, "Triangles",
QString("Could not download blockchain snapshot:\n%1\n\n"
"The wallet will sync from the network instead.")
.arg(QString::fromStdString(strError)));
} else {
progress.setValue(100);
}
}
return true;
}
static void copyDirectoryRecursive(const std::filesystem::path& src,
const std::filesystem::path& dst)
{
namespace fs = std::filesystem;
fs::create_directories(dst);
for (fs::directory_iterator it(src), end; it != end; ++it) {
fs::path dstChild = dst / it->path().filename();
if (fs::is_directory(it->path())) {
copyDirectoryRecursive(it->path(), dstChild);
} else {
fs::copy_file(it->path(), dstChild, fs::copy_options::overwrite_existing);
}
}
}
bool IntroDialog::migrateDataDirectory(const QString& oldPath, const QString& newPath)
{
namespace fs = std::filesystem;
fs::path srcDir(oldPath.toStdString());
fs::path dstDir(newPath.toStdString());
if (!fs::exists(srcDir) || !fs::is_directory(srcDir))
return false;
// Create destination directory
try {
fs::create_directories(dstDir);
} catch (const fs::filesystem_error& e) {
printf("Migration: Cannot create destination directory: %s\n", e.what());
return false;
}
// Check free space
try {
quint64 srcSize = 0;
for (fs::recursive_directory_iterator it(srcDir), end; it != end; ++it) {
if (fs::is_regular_file(*it))
srcSize += fs::file_size(*it);
}
fs::space_info si = fs::space(dstDir);
if (si.available < srcSize + (50 * 1024 * 1024)) { // 50MB headroom
printf("Migration: Insufficient disk space. Need %llu, have %llu\n",
(unsigned long long)srcSize, (unsigned long long)si.available);
return false;
}
} catch (const fs::filesystem_error& e) {
printf("Migration: Cannot check disk space: %s\n", e.what());
return false;
}
// Files/directories to skip during copy
static const std::set<std::string> skipFiles = {
".lock",
"debug.log",
"db.log",
};
// Show progress dialog
QProgressDialog progress("Moving data directory...", QString(), 0, 0, 0);
progress.setWindowTitle("Triangles - Data Migration");
progress.setWindowModality(Qt::ApplicationModal);
progress.setMinimumDuration(0);
progress.setCancelButton(0);
progress.show();
QApplication::processEvents();
// Phase 1: Copy wallet.dat FIRST (most critical file)
fs::path walletSrc = srcDir / "wallet.dat";
fs::path walletDst = dstDir / "wallet.dat";
if (fs::exists(walletSrc)) {
progress.setLabelText("Copying wallet.dat...");
QApplication::processEvents();
try {
// Copy to temp name first, then rename for atomicity
fs::path walletTmp = dstDir / "wallet.dat.migrating";
fs::copy_file(walletSrc, walletTmp, fs::copy_options::overwrite_existing);
// Verify copy by checking file size
if (fs::file_size(walletTmp) != fs::file_size(walletSrc)) {
fs::remove(walletTmp);
printf("Migration: wallet.dat copy size mismatch!\n");
return false;
}
// Rename into place
if (fs::exists(walletDst))
fs::remove(walletDst);
fs::rename(walletTmp, walletDst);
} catch (const fs::filesystem_error& e) {
printf("Migration: Failed to copy wallet.dat: %s\n", e.what());
return false; // Abort - wallet is critical
}
}
// Phase 2: Copy everything else
int filesCopied = 0;
try {
for (fs::directory_iterator it(srcDir), end; it != end; ++it) {
std::string filename = it->path().filename().string();
// Skip special files
if (skipFiles.count(filename))
continue;
// Skip wallet.dat (already copied)
if (filename == "wallet.dat")
continue;
fs::path dst = dstDir / filename;
progress.setLabelText(QString("Copying %1...").arg(QString::fromStdString(filename)));
QApplication::processEvents();
if (fs::is_directory(it->path())) {
copyDirectoryRecursive(it->path(), dst);
} else {
fs::copy_file(it->path(), dst, fs::copy_options::overwrite_existing);
}
filesCopied++;
}
} catch (const fs::filesystem_error& e) {
// Non-wallet copy failure: log but don't abort
// Chain data can be re-synced; wallet was already safely copied
printf("Migration: Warning: failed to copy some files: %s\n", e.what());
}
// Phase 3: Rename old wallet.dat as safety backup (don't delete old dir)
try {
if (fs::exists(walletSrc)) {
fs::rename(walletSrc, srcDir / "wallet.dat.bak-migrated");
}
} catch (...) {
// Not critical
}
progress.close();
printf("Migration: Successfully copied %d items from %s to %s\n",
filesCopied, srcDir.string().c_str(), dstDir.string().c_str());
return true;
}