touch: tell the map when SD storage comes back

After a reinsert the chat store re-landed but the map stayed blank: the
tile layer was never told the storage changed. It keeps decoded tiles in
s_map_tiles across renders and only renders on pan / zoom / tab-open, so
a card that came back produced no reload; a boot with no card never
adopted a later-inserted one as the tile cache at all.

mapNoteStorageChanged() now runs from every mount-state transition
(wedge-recovery remount, 30 s reinsert watch, file-manager insert poll,
post-format, and both card-lost paths), right next to the existing
flushHistorySoon(). It adopts the card as the tile backend when this
boot resolved none, drops the decoded tiles (a swapped card holds
different content), clears the fetch dedup ring so downloads that failed
during the outage are retried instead of being remembered as in-flight
forever, and re-renders immediately when the map is the visible tab.

Builds verified: all five SD-relevant touch envs.

Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
This commit is contained in:
Dan Vybiral committed 2026-07-27 15:28:25 +02:00
1 parent 5627c90bb3
commit f2c1b73636
1 file changed
+36
+36
View File
@@ -4419,6 +4419,7 @@ static void refreshMapInfoLabel();
static void renderMapTiles();
static void renderMapMarkers();
static void freeMapTiles();
static void mapNoteStorageChanged(); // tile storage appeared/vanished: adopt, invalidate, re-render
// Single entry point used by tabChangedCb. Recenters on self GPS and
// rebuilds the tile grid. Defined alongside the map state below.
static void onMapTabActivated();
@@ -25036,6 +25037,35 @@ static void freeMapTiles() {
for (int _ti = 0; _ti < k_map_visible_tiles_max; ++_ti) freeMapTileSlot(s_map_tiles[_ti]);
}
// The tile storage changed underneath us: a card was remounted (wedge recovery,
// reinsert watch, file-manager insert poll, post-format) or lost. The map keeps
// decoded tiles in s_map_tiles across renders and only renders on pan / zoom /
// tab-open, so without this a card that came back showed a permanently blank
// map — the chat store re-landed (flushHistorySoon) while the map was never
// told anything had happened.
//
// * adopts the card as the tile cache when this boot had no backend at all
// (booted card-less, or the "tiles" partition is absent — Launcher installs)
// * drops the decoded tiles, since a swapped card holds different content
// * clears the fetch dedup ring so tiles whose download failed during the
// outage are retried instead of being remembered as in-flight forever
// * re-renders immediately when the map is the visible tab
static void mapNoteStorageChanged() {
#if CAP_SD
if (s_sd_mounted && !s_tiles_fs_ready) {
// No tile backend was resolved at boot; the card can serve as one now
// (same layout the boot fallback uses: SD ROOT /tiles/<z>/<x>/<y>).
s_tile_fs = &SD;
s_tile_root[0] = '\0';
s_tiles_fs_ready = true;
}
#endif
for (int i = 0; i < k_tile_fetch_dedup_size; ++i) s_tile_fetch_dedup[i] = 0;
s_tile_fetch_dedup_head = 0;
freeMapTiles();
if (g_lv.tabview && lv_tabview_get_tab_act(g_lv.tabview) == MAP_TAB_INDEX) renderMapTiles();
}
#if defined(ESP32)
// Is *some* tile source available to probe at all? (SD pack or LittleFS /tiles.)
static bool mapTileSourceReady() {
@@ -45506,6 +45536,7 @@ static void sdHealthTick() {
uiDataEnsureDirs(); // segment dir too — a fresh replacement card has neither
if (g_lv.task) g_lv.task->flushHistorySoon();
#endif
mapNoteStorageChanged(); // the map layer has to be told too, or it stays blank
} else {
SD.end(); // leave cardType() honestly CARD_NONE
}
@@ -45548,11 +45579,13 @@ static void sdHealthTick() {
uiDataEnsureDirs(); // segment dir too
if (g_lv.task) g_lv.task->flushHistorySoon(); // land the RAM ring promptly (off-thread — no UI stall)
#endif
mapNoteStorageChanged();
} else {
// Ladder failed — card needs a real power cycle (reinsert). s_sd_mounted is
// now false and SD.cardType() reads CARD_NONE, so features degrade honestly
// instead of silently failing, and the usual remount paths keep retrying.
if (g_lv.task) g_lv.task->showAlert(TR("SD card lost - reinsert to recover"), 2600);
mapNoteStorageChanged(); // drop tiles read off the card that just went away
}
}
#endif
@@ -46007,6 +46040,7 @@ void UITask::loop() {
uiDataEnsureDirs(); // segment dir too
flushHistorySoon(); // land the RAM ring promptly (off-thread — no UI stall)
#endif
mapNoteStorageChanged();
if (!s_fm_fs) fmShowRoots(); // refresh roots so it appears
}
} else if (!s_hist_flush_busy && !s_hist_flush_req && !sdProbeAlive()) {
@@ -46018,6 +46052,7 @@ void UITask::loop() {
// later write on it fails FR_INVALID_OBJECT -> EBADF ("bad file
// number") — recovery must not sabotage the very write it protects.
fmSdUnmount();
mapNoteStorageChanged();
showAlert(TR("SD card removed"), 1500);
if (s_fm_fs == &SD || !s_fm_fs) fmShowRoots();
}
@@ -46518,6 +46553,7 @@ void UITask::loop() {
// next append targets a segment file that is gone. Rebuild + re-land.
uiDataEnsureDirs();
flushHistorySoon();
mapNoteStorageChanged();
char done[56], cs[16];
fmFmtSize64(s_sd_size, cs, sizeof cs);
snprintf(done, sizeof done, TR("SD formatted - %s (MESHCOMOD)"), cs);