mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
Settings > Contacts gains "Expire" (Off/7d/30d/90d) and a "Prune now" action
that counts first and asks before removing anything. A contact with no
advert/update within the threshold is eligible; favourites are always kept.
Thresholds and their labels live in one NodePrefs table so the age shown and
the age enforced can't drift. SCHEMA_SENTINEL -> 0xC0DE002C (sizeof unchanged
at 2824, confirmed on native and a real WioTrackerL1 build).
Also fixes four bugs in the scope list from 89c02eea:
- removeScope() saved /scopes1 but never savePrefs(), so the ch_scope_idx[]
and repeat_extra_scope_mask fix-ups it makes were lost on reboot, leaving
shifted entries against unshifted indices.
- CMD_SET_DEFAULT_FLOOD_SCOPE wrote the legacy fields directly instead of
going through setPrimaryScope(), so the app's default-scope setting had
nothing reading it once sends resolved through the list.
- The reverse direction was stale too: an on-device "Set as default" never
refreshed default_scope_name/key, so CMD_GET_DEFAULT_FLOOD_SCOPE reported
a scope the device had stopped using. New syncLegacyDefaultScope().
- Upgrading from the old single Scope field set the default (so DMs kept it)
but left every channel unscoped, since "*" means unscoped, not "inherit".
loadScopeList() now reports when it migrated and begin() seeds the channels
that already exist, leaving empty slots alone.
"Set default" -> "Set as default", and setting it now says what it governs
("Default: DMs + relay") rather than leaving the [default] marker to imply
more than it does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
3.9 KiB
C++
86 lines
3.9 KiB
C++
#pragma once
|
|
|
|
#include <helpers/IdentityStore.h>
|
|
#include <helpers/ContactInfo.h>
|
|
#include <helpers/ChannelDetails.h>
|
|
#include "NodePrefs.h"
|
|
#include "ScopeList.h"
|
|
|
|
class DataStoreHost {
|
|
public:
|
|
virtual bool onContactLoaded(const ContactInfo& contact) =0;
|
|
virtual bool getContactForSave(uint32_t idx, ContactInfo& contact) =0;
|
|
virtual bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) =0;
|
|
virtual bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) =0;
|
|
};
|
|
|
|
class DataStore {
|
|
FILESYSTEM* _fs;
|
|
FILESYSTEM* _fsExtra;
|
|
mesh::RTCClock* _clock;
|
|
IdentityStore identity_store;
|
|
|
|
void loadPrefsInt(const char *filename, NodePrefs& prefs, double& node_lat, double& node_lon);
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
void checkAdvBlobFile();
|
|
#endif
|
|
|
|
public:
|
|
DataStore(FILESYSTEM& fs, mesh::RTCClock& clock);
|
|
DataStore(FILESYSTEM& fs, FILESYSTEM& fsExtra, mesh::RTCClock& clock);
|
|
void begin();
|
|
bool formatFileSystem();
|
|
FILESYSTEM* getPrimaryFS() const { return _fs; }
|
|
FILESYSTEM* getSecondaryFS() const { return _fsExtra; }
|
|
bool loadMainIdentity(mesh::LocalIdentity &identity);
|
|
bool saveMainIdentity(const mesh::LocalIdentity &identity);
|
|
void loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon);
|
|
void savePrefs(const NodePrefs& prefs, double node_lat, double node_lon);
|
|
void loadContacts(DataStoreHost* host);
|
|
void saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c) = NULL);
|
|
// Returns true if a channels file (current or legacy) existed and was
|
|
// loaded -- false only on a genuinely fresh device with neither file, so
|
|
// the caller knows whether it's safe to seed default channels (see
|
|
// MyMesh::begin()'s addChannel("Public", ...) -- seeding unconditionally
|
|
// would resurrect a channel the user had deliberately deleted, since a
|
|
// deleted slot is simply absent from the file, not written as empty).
|
|
bool loadChannels(DataStoreHost* host);
|
|
void saveChannels(DataStoreHost* host);
|
|
// /scopes1: the shared named-scope list (see ScopeList.h). `prefs` is only
|
|
// read, for a one-time migration of a pre-existing single
|
|
// default_scope_name/key into list entry 1 -- the file is authoritative
|
|
// once it exists.
|
|
//
|
|
// Returns true ONLY when that legacy migration just ran, i.e. this boot is
|
|
// the first on a device that had a Scope configured the old way. The caller
|
|
// uses that to seed the channels that already exist with the migrated entry
|
|
// (see MyMesh::begin) -- channels carry their own scope now, so without the
|
|
// seed an upgrader's channel traffic would silently drop to unscoped even
|
|
// though their DMs kept the old scope. Returns false when /scopes1 was
|
|
// already there, and on a genuinely fresh device with nothing to migrate
|
|
// (list left empty, default_idx 0 == "*").
|
|
bool loadScopeList(ScopeList& list, const NodePrefs& prefs);
|
|
void saveScopeList(const ScopeList& list);
|
|
void migrateToSecondaryFS();
|
|
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
|
|
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len);
|
|
bool deleteBlobByKey(const uint8_t key[], int key_len);
|
|
File openRead(const char* filename);
|
|
File openRead(FILESYSTEM* fs, const char* filename);
|
|
File openWrite(const char* filename);
|
|
// Atomically replace final_path with a fully-written temp file (see
|
|
// openWrite()). Use for small custom records that want the same crash-safety
|
|
// as contacts/channels: write everything to a .tmp, then commit. Returns
|
|
// false if the swap fails (the previous good file is left untouched).
|
|
bool commitFile(const char* tmp_path, const char* final_path);
|
|
bool removeFile(const char* filename);
|
|
bool removeFile(FILESYSTEM* fs, const char* filename);
|
|
uint32_t getStorageUsedKb() const;
|
|
uint32_t getStorageTotalKb() const;
|
|
void saveRTCTime();
|
|
void restoreRTCTime();
|
|
|
|
private:
|
|
FILESYSTEM* _getContactsChannelsFS() const { if (_fsExtra) return _fsExtra; return _fs;};
|
|
};
|