Skip to content

Commit 3a0f352

Browse files
BaseUI: Autosave Messages (#9269)
* Autosave Messages * fix * Add logging, code cleanup, and add save on delete. * We already save as part of delete messages, no need to do it again * fix spelling errors * Updating comment --------- Co-authored-by: Jason P <[email protected]>
1 parent daad424 commit 3a0f352

File tree

4 files changed

+103
-30
lines changed

4 files changed

+103
-30
lines changed

src/MessageStore.cpp

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
1414
#endif
1515

16+
// Default autosave interval 2 hours, override per device later with -DMESSAGE_AUTOSAVE_INTERVAL_SEC=300 (etc)
17+
#ifndef MESSAGE_AUTOSAVE_INTERVAL_SEC
18+
#define MESSAGE_AUTOSAVE_INTERVAL_SEC (2 * 60 * 60)
19+
#endif
20+
1621
// Global message text pool and state
1722
static char *g_messagePool = nullptr;
1823
static size_t g_poolWritePos = 0;
@@ -102,6 +107,60 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
102107
pushWithLimit(liveMessages, msg);
103108
}
104109

110+
#if ENABLE_MESSAGE_PERSISTENCE
111+
static bool g_messageStoreHasUnsavedChanges = false;
112+
static uint32_t g_lastAutoSaveMs = 0; // last time we actually saved
113+
114+
static inline uint32_t autosaveIntervalMs()
115+
{
116+
uint32_t sec = (uint32_t)MESSAGE_AUTOSAVE_INTERVAL_SEC;
117+
if (sec < 60)
118+
sec = 60;
119+
return sec * 1000UL;
120+
}
121+
122+
static inline bool reachedMs(uint32_t now, uint32_t target)
123+
{
124+
return (int32_t)(now - target) >= 0;
125+
}
126+
127+
// Mark new messages in RAM that need to be saved later
128+
static inline void markMessageStoreUnsaved()
129+
{
130+
g_messageStoreHasUnsavedChanges = true;
131+
132+
if (g_lastAutoSaveMs == 0) {
133+
g_lastAutoSaveMs = millis();
134+
}
135+
}
136+
137+
// Called periodically from the main loop in main.cpp
138+
static inline void autosaveTick(MessageStore *store)
139+
{
140+
if (!store)
141+
return;
142+
143+
uint32_t now = millis();
144+
145+
if (g_lastAutoSaveMs == 0) {
146+
g_lastAutoSaveMs = now;
147+
return;
148+
}
149+
150+
if (!reachedMs(now, g_lastAutoSaveMs + autosaveIntervalMs()))
151+
return;
152+
153+
// Autosave interval reached, only save if there are unsaved messages.
154+
if (g_messageStoreHasUnsavedChanges) {
155+
LOG_INFO("Autosaving MessageStore to flash");
156+
store->saveToFlash();
157+
} else {
158+
LOG_INFO("Autosave skipped, no changes to save");
159+
g_lastAutoSaveMs = now;
160+
}
161+
}
162+
#endif
163+
105164
// Add from incoming/outgoing packet
106165
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
107166
{
@@ -131,6 +190,11 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
131190
}
132191

133192
addLiveMessage(sm);
193+
194+
#if ENABLE_MESSAGE_PERSISTENCE
195+
markMessageStoreUnsaved();
196+
#endif
197+
134198
return liveMessages.back();
135199
}
136200

@@ -155,6 +219,10 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
155219
sm.ackStatus = AckStatus::NONE;
156220

157221
addLiveMessage(sm);
222+
223+
#if ENABLE_MESSAGE_PERSISTENCE
224+
markMessageStoreUnsaved();
225+
#endif
158226
}
159227

160228
#if ENABLE_MESSAGE_PERSISTENCE
@@ -239,6 +307,10 @@ void MessageStore::saveToFlash()
239307

240308
f.close();
241309
#endif
310+
311+
// Reset autosave state after any save
312+
g_messageStoreHasUnsavedChanges = false;
313+
g_lastAutoSaveMs = millis();
242314
}
243315

244316
void MessageStore::loadFromFlash()
@@ -270,6 +342,9 @@ void MessageStore::loadFromFlash()
270342

271343
f.close();
272344
#endif
345+
// Loading messages does not trigger an autosave
346+
g_messageStoreHasUnsavedChanges = false;
347+
g_lastAutoSaveMs = millis();
273348
}
274349

275350
#else
@@ -290,6 +365,11 @@ void MessageStore::clearAllMessages()
290365
f.write(&count, 1); // write "0 messages"
291366
f.close();
292367
#endif
368+
369+
#if ENABLE_MESSAGE_PERSISTENCE
370+
g_messageStoreHasUnsavedChanges = false;
371+
g_lastAutoSaveMs = millis();
372+
#endif
293373
}
294374

295375
// Internal helper: erase first or last message matching a predicate
@@ -421,6 +501,14 @@ uint16_t MessageStore::storeText(const char *src, size_t len)
421501
return storeTextInPool(src, len);
422502
}
423503

504+
#if ENABLE_MESSAGE_PERSISTENCE
505+
void messageStoreAutosaveTick()
506+
{
507+
// Called from the main loop to check autosave timing
508+
autosaveTick(&messageStore);
509+
}
510+
#endif
511+
424512
// Global definition
425513
MessageStore messageStore("default");
426-
#endif
514+
#endif

src/MessageStore.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ class MessageStore
125125
std::string filename; // Flash filename for persistence
126126
};
127127

128+
#if ENABLE_MESSAGE_PERSISTENCE
129+
// Called periodically from main loop to trigger time based autosave
130+
void messageStoreAutosaveTick();
131+
#endif
132+
128133
// Global instance (defined in MessageStore.cpp)
129134
extern MessageStore messageStore;
130135

src/graphics/draw/MenuHandler.cpp

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ void menuHandler::clockMenu()
449449
}
450450
void menuHandler::messageResponseMenu()
451451
{
452-
enum optionsNumbers { Back = 0, ViewMode, DeleteAll, DeleteOldest, ReplyMenu, MuteChannel, Aloud, enumEnd };
452+
enum optionsNumbers { Back = 0, ViewMode, DeleteMenu, ReplyMenu, MuteChannel, Aloud, enumEnd };
453453

454454
static const char *optionsArray[enumEnd];
455455
static int optionsEnumArray[enumEnd];
@@ -479,7 +479,7 @@ void menuHandler::messageResponseMenu()
479479

480480
// Delete submenu
481481
optionsArray[options] = "Delete";
482-
optionsEnumArray[options++] = 900;
482+
optionsEnumArray[options++] = DeleteMenu;
483483

484484
#ifdef HAS_I2S
485485
optionsArray[options] = "Read Aloud";
@@ -520,34 +520,10 @@ void menuHandler::messageResponseMenu()
520520
nodeDB->saveToDisk();
521521
}
522522

523-
// Delete submenu
524-
} else if (selected == 900) {
523+
} else if (selected == DeleteMenu) {
525524
menuHandler::menuQueue = menuHandler::delete_messages_menu;
526525
screen->runNow();
527526

528-
// Delete oldest FIRST (only change)
529-
} else if (selected == DeleteOldest) {
530-
auto mode = graphics::MessageRenderer::getThreadMode();
531-
int ch = graphics::MessageRenderer::getThreadChannel();
532-
uint32_t peer = graphics::MessageRenderer::getThreadPeer();
533-
534-
if (mode == graphics::MessageRenderer::ThreadMode::ALL) {
535-
// Global oldest
536-
messageStore.deleteOldestMessage();
537-
} else if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) {
538-
// Oldest in current channel
539-
messageStore.deleteOldestMessageInChannel(ch);
540-
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
541-
// Oldest in current DM
542-
messageStore.deleteOldestMessageWithPeer(peer);
543-
}
544-
545-
// Delete all messages
546-
} else if (selected == DeleteAll) {
547-
messageStore.clearAllMessages();
548-
graphics::MessageRenderer::clearThreadRegistries();
549-
graphics::MessageRenderer::clearMessageCache();
550-
551527
#ifdef HAS_I2S
552528
} else if (selected == Aloud) {
553529
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
@@ -716,7 +692,6 @@ void menuHandler::deleteMessagesMenu()
716692
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
717693
messageStore.deleteOldestMessageWithPeer(peer);
718694
}
719-
720695
return;
721696
}
722697

@@ -729,7 +704,6 @@ void menuHandler::deleteMessagesMenu()
729704
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
730705
messageStore.deleteAllMessagesWithPeer(peer);
731706
}
732-
733707
return;
734708
}
735709
};

src/main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
#include "target_specific.h"
3939
#include <memory>
4040
#include <utility>
41+
#if HAS_SCREEN
42+
#include "MessageStore.h"
43+
#endif
4144

4245
#ifdef ELECROW_ThinkNode_M5
4346
PCA9557 io(0x18, &Wire);
@@ -1652,6 +1655,9 @@ void loop()
16521655
if (dispdev)
16531656
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
16541657
}
1658+
#endif
1659+
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
1660+
messageStoreAutosaveTick();
16551661
#endif
16561662
long delayMsec = mainController.runOrDelay();
16571663

0 commit comments

Comments
 (0)