Files
nomines/src/leaderboard.cpp
T
bernardo 62a09e4261 Leaderboard: clear button and hygiene for migrated legacy data
A scratch test earlier in development wrote a fake 42s Beginner record into
the legacy ~/.config/minesweeper/leaderboard.ini, which the migration imported;
as a stored best it can never be beaten by slower real games, so the popover
showed a lower time than actually played.

- Add a 'Clear scores' button to the leaderboard popover
- Delete the legacy file after a successful migration so stale data can't
  be re-imported
2026-08-01 17:09:08 +01:00

82 lines
2.3 KiB
C++

#include "leaderboard.hpp"
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <glib.h>
#include <glib/gstdio.h>
namespace {
std::string file_path() {
return Glib::build_filename(
Glib::get_user_config_dir(), "nomines", "leaderboard.ini");
}
} // namespace
void Leaderboard::load() {
if (loaded_) return;
loaded_ = true;
// Migrate scores from pre-1.0 builds that stored them under minesweeper/
auto path = file_path();
auto legacy = Glib::build_filename(
Glib::get_user_config_dir(), "minesweeper", "leaderboard.ini");
bool migrated = false;
if (!Glib::file_test(path, Glib::FileTest::EXISTS) &&
Glib::file_test(legacy, Glib::FileTest::EXISTS)) {
path = legacy;
migrated = true;
}
try {
auto kf = Glib::KeyFile::create();
kf->load_from_file(path);
for (const auto& group : kf->get_groups()) {
if (kf->has_key(group, "best")) {
times_[group.raw()] = kf->get_integer(group, "best");
}
}
if (migrated) {
// Stale legacy files can hold outdated/experimental data; drop it
// so it can never be re-imported after the current file is gone.
g_remove(legacy.c_str());
}
} catch (const Glib::Error&) {
// No file yet or unreadable — start with an empty leaderboard
}
}
bool Leaderboard::record(const std::string& difficulty, int seconds) {
load();
auto it = times_.find(difficulty);
if (it != times_.end() && it->second <= seconds) return false;
times_[difficulty] = seconds;
save();
return true;
}
std::optional<int> Leaderboard::best_time(const std::string& difficulty) const {
auto it = times_.find(difficulty);
if (it == times_.end()) return std::nullopt;
return it->second;
}
void Leaderboard::clear() {
load();
times_.clear();
save();
}
void Leaderboard::save() {
auto kf = Glib::KeyFile::create();
for (const auto& [difficulty, seconds] : times_) {
kf->set_integer(difficulty, "best", seconds);
}
try {
auto dir = Glib::path_get_dirname(file_path());
g_mkdir_with_parents(dir.c_str(), 0700);
kf->save_to_file(file_path());
} catch (const Glib::Error&) {
// Best-effort persistence; a failed save is not fatal
}
}