// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include #include namespace dragonx { namespace util { class CoalescingWriter; } namespace data { /** * @brief Cached, file-keyed metadata for one wallet file. * * A wallet.dat on disk only reveals its size + mtime; balance and address count require the daemon * to have loaded it. So those are cached here after each load and shown in the wallet-files list * (P2) even when the wallet isn't the active one. walletIdentityHash bridges file -> per-wallet data * (address book, tx history) so the right caches can be selected before/after a switch. */ struct WalletIndexEntry { std::string fileName; // plain wallet filename in the datadir (e.g. "wallet.dat") std::string displayName; // user-facing name (defaults to fileName) std::string walletIdentityHash; // address-derived identity of the last load; "" = unknown std::string scopeId; // stable per-wallet id ("w:"+hex) for scoping contacts etc.; // generated once, never recomputed from the (mutable) address set double cachedBalance = -1.0; // last-known total balance; < 0 = unknown (never opened) long long cachedAddressCount = -1;// < 0 = unknown long long lastOpenedEpoch = 0; // unix seconds of last open; 0 = never opened long long sizeBytesAtLastOpen = 0; bool syncedHere = false; // loaded in this datadir before -> catch-up on switch (no full rescan) }; /** * @brief Persistent wallet-files metadata index (wallets.json, in the config dir). * * Keyed by wallet file name. Populated after each load (P1b) and read by the wallet-files list (P2). * Also remembers extra folders the user added to scan for wallet files. Not encrypted — it holds * only file names + coarse metadata (no keys/addresses). */ class WalletIndex { public: bool load(); bool save(); // queued on the writer when one is attached (returns true once queued) static std::string getDefaultPath(); /** @brief Route save() through a background writer (not owned; must outlive this). Null = synchronous. */ void setFileWriter(util::CoalescingWriter* writer) { writer_ = writer; } const std::vector& entries() const { return entries_; } const std::vector& extraFolders() const { return extra_folders_; } /** * @brief Insert or update the entry for e.fileName. * @return true if a new entry was added or any field changed (so the caller can skip save()). */ bool upsert(const WalletIndexEntry& e); /** @brief Find the entry for a wallet file, or nullptr. */ const WalletIndexEntry* find(const std::string& fileName) const; /** @brief Add/remove an extra scan folder. Returns true if the set changed. */ bool addExtraFolder(const std::string& dir); bool removeExtraFolder(const std::string& dir); /** * @brief UI-sweep ONLY: snapshot the entries and extra folders; until sweepEnd(), save() returns * true without writing or queueing. The sweep's demo upserts drop the real entry's scopeId, and * activeWalletScopeId() would then mint and save a new one, hiding the user's "w:"-scoped contacts. */ void sweepBegin() { if (sweep_active_) return; sweep_entries_ = entries_; sweep_extra_folders_ = extra_folders_; sweep_active_ = true; } /** @brief UI-sweep ONLY: restore the snapshot (no disk write). Idempotent. */ void sweepEnd() { if (!sweep_active_) return; entries_ = std::move(sweep_entries_); extra_folders_ = std::move(sweep_extra_folders_); sweep_entries_.clear(); sweep_extra_folders_.clear(); sweep_active_ = false; } bool sweepActive() const { return sweep_active_; } private: std::vector entries_; std::vector extra_folders_; std::string file_path_; util::CoalescingWriter* writer_ = nullptr; // null -> synchronous writes std::vector sweep_entries_; // the real index while a UI sweep runs std::vector sweep_extra_folders_; bool sweep_active_ = false; }; } // namespace data } // namespace dragonx