Fix Cardputer delivery, live settings and power management
Keep selected chats bound to full contact keys and route keyboard channel sends through the active transport. Deliver accepted plain, room and Meshtext messages to local history and notifications once; disarm rejected sends and retries. Use shared live radio settings, GPS acquisition and power-saving policy, screen timers and red night palette. Report save failures, preserve verified custom preferences, and guard display controls and shutdown. Add executable sanitizer regressions for routing, retries, selection, storage faults, UI lifecycle and GPS. Both Companion builds and the shared T-Deck regression pass; no physical Cardputer testing performed.
This commit is contained in:
1 parent
e5d5981e5e
commit
9a2211e85a
27 files changed
+1950
-346
No files matched your search
@@ -1,5 +1,9 @@
|
||||
#include "CardputerDataStore.h"
|
||||
#include <SD.h> // Keep optional framework-SD discovery local to this variant.
|
||||
#include <helpers/ContactFileTransaction.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
@@ -24,30 +28,97 @@ void CardputerDataStore::beginCustom(FILESYSTEM *history_fs) {
|
||||
_history_fs->mkdir(HISTORY_DIR_DIRECT);
|
||||
}
|
||||
|
||||
void CardputerDataStore::loadCustomPrefs(CustomNodePrefs &prefs) {
|
||||
if (!_fs->exists(CUSTOM_PREFS_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
File file = _fs->open(CUSTOM_PREFS_FILE, FILE_READ);
|
||||
|
||||
// Read file only if it exactly matches struct size, otherwise keep default values
|
||||
if (file.size() == sizeof(prefs)) {
|
||||
file.read((uint8_t *)&prefs, sizeof(prefs));
|
||||
}
|
||||
|
||||
file.close();
|
||||
static bool validCustomPrefs(const CustomNodePrefs &prefs) {
|
||||
return isfinite(prefs.battery_correction) && prefs.battery_correction >= 0.5f
|
||||
&& prefs.battery_correction <= 2.0f && prefs.text_level < 3;
|
||||
}
|
||||
|
||||
void CardputerDataStore::saveCustomPrefs(const CustomNodePrefs &prefs) {
|
||||
File file = _fs->open(CUSTOM_PREFS_FILE, FILE_WRITE);
|
||||
static bool customPrefsPresence(FILESYSTEM *fs, const char *path, bool &present) {
|
||||
(void)fs; // Custom preferences always use the default internal SPIFFS mount.
|
||||
char vfs_path[96];
|
||||
const int length = snprintf(vfs_path, sizeof(vfs_path), "/spiffs%s", path);
|
||||
if (length < 0 || static_cast<size_t>(length) >= sizeof(vfs_path)) return false;
|
||||
struct stat info;
|
||||
const int result = ::stat(vfs_path, &info);
|
||||
if (result != 0 && errno != ENOENT) return false;
|
||||
present = result == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file.write((uint8_t *)&prefs, sizeof(prefs));
|
||||
static bool readCustomPrefs(FILESYSTEM *fs, const char *path, CustomNodePrefs &prefs) {
|
||||
File file = fs->open(path, FILE_READ);
|
||||
if (!file) return false;
|
||||
CustomNodePrefs candidate;
|
||||
const bool valid = !file.isDirectory() && file.size() == sizeof(candidate)
|
||||
&& file.read(reinterpret_cast<uint8_t *>(&candidate), sizeof(candidate)) == sizeof(candidate)
|
||||
&& validCustomPrefs(candidate);
|
||||
file.close();
|
||||
if (valid) prefs = candidate;
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool CardputerDataStore::loadCustomPrefs(CustomNodePrefs &prefs) {
|
||||
_custom_prefs_write_blocked = false;
|
||||
bool present = false, backup_present = false;
|
||||
if (!customPrefsPresence(_fs, CUSTOM_PREFS_FILE, present)
|
||||
|| !customPrefsPresence(_fs, CUSTOM_PREFS_FILE ".bak", backup_present)) {
|
||||
_custom_prefs_write_blocked = true;
|
||||
return false;
|
||||
}
|
||||
if (present && readCustomPrefs(_fs, CUSTOM_PREFS_FILE, prefs)) return true;
|
||||
CustomNodePrefs backup;
|
||||
if (backup_present && readCustomPrefs(_fs, CUSTOM_PREFS_FILE ".bak", backup)) {
|
||||
prefs = backup;
|
||||
if (present && !_fs->remove(CUSTOM_PREFS_FILE)) {
|
||||
_custom_prefs_write_blocked = true;
|
||||
return true;
|
||||
}
|
||||
_fs->rename(CUSTOM_PREFS_FILE ".bak", CUSTOM_PREFS_FILE);
|
||||
CustomNodePrefs verified;
|
||||
_custom_prefs_write_blocked = !readCustomPrefs(_fs, CUSTOM_PREFS_FILE, verified)
|
||||
|| memcmp(&verified, &backup, sizeof(backup)) != 0;
|
||||
return true;
|
||||
}
|
||||
// An unreadable existing image is not permission to replace its only copy.
|
||||
_custom_prefs_write_blocked = present || backup_present;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CardputerDataStore::saveCustomPrefs(const CustomNodePrefs &prefs) {
|
||||
if (_custom_prefs_write_blocked || !validCustomPrefs(prefs)) return false;
|
||||
mesh::ContactFileTransaction transaction(_fs, CUSTOM_PREFS_FILE, customPrefsPresence);
|
||||
bool valid = transaction && transaction.write(reinterpret_cast<const uint8_t *>(&prefs), sizeof(prefs)) == sizeof(prefs);
|
||||
mesh::ContactFileTransaction::CommitProgress progress;
|
||||
do {
|
||||
progress = transaction.serviceCommit(valid, 1, true);
|
||||
} while (progress == mesh::ContactFileTransaction::CommitProgress::Pending);
|
||||
if (progress != mesh::ContactFileTransaction::CommitProgress::Succeeded) {
|
||||
_custom_prefs_write_blocked = transaction.outcome() == mesh::ContactFileTransaction::Outcome::Indeterminate;
|
||||
return false;
|
||||
}
|
||||
CustomNodePrefs verified;
|
||||
if (readCustomPrefs(_fs, CUSTOM_PREFS_FILE, verified) && memcmp(&verified, &prefs, sizeof(prefs)) == 0) {
|
||||
_fs->remove(CUSTOM_PREFS_FILE ".bak");
|
||||
return true;
|
||||
}
|
||||
// Keep the old verified image until the public candidate has been read back.
|
||||
CustomNodePrefs backup;
|
||||
bool backup_present = false;
|
||||
if (!customPrefsPresence(_fs, CUSTOM_PREFS_FILE ".bak", backup_present)
|
||||
|| (backup_present && !readCustomPrefs(_fs, CUSTOM_PREFS_FILE ".bak", backup))
|
||||
|| !_fs->remove(CUSTOM_PREFS_FILE)) {
|
||||
_custom_prefs_write_blocked = true;
|
||||
return false;
|
||||
}
|
||||
if (backup_present) {
|
||||
_fs->rename(CUSTOM_PREFS_FILE ".bak", CUSTOM_PREFS_FILE);
|
||||
_custom_prefs_write_blocked = !readCustomPrefs(_fs, CUSTOM_PREFS_FILE, verified)
|
||||
|| memcmp(&verified, &backup, sizeof(backup)) != 0;
|
||||
} else {
|
||||
bool present = false;
|
||||
_custom_prefs_write_blocked = !customPrefsPresence(_fs, CUSTOM_PREFS_FILE, present) || present;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardputerDataStore::storeMessage(const uint8_t pkey[PUB_KEY_SIZE], const char *text, bool is_sent,
|
||||
|
||||
Reference in new issue
Block a user