From 52c6d1ffd88730eec532cffa782d7fcab1db8fea Mon Sep 17 00:00:00 2001 From: liu weikai Date: Fri, 10 Jul 2026 23:16:30 +0800 Subject: [PATCH] Fix route preview saved image state --- .../include/platform/ui/route_storage.h | 3 + .../tracker/tracker_page_components.cpp | 84 +++++++++++++------ .../src/platform_ui_route_storage.cpp | 79 +++++++++++++++++ .../src/platform_ui_route_storage.cpp | 84 +++++++++++++++++++ .../common/src/platform/ui/route_storage.cpp | 74 ++++++++++++++++ 5 files changed, 300 insertions(+), 24 deletions(-) diff --git a/modules/core_sys/include/platform/ui/route_storage.h b/modules/core_sys/include/platform/ui/route_storage.h index 6d637d52..a4b43914 100644 --- a/modules/core_sys/include/platform/ui/route_storage.h +++ b/modules/core_sys/include/platform/ui/route_storage.h @@ -62,6 +62,9 @@ bool list_routes(std::vector& out_routes, std::size_t max_count = 6 bool remove_route(const std::string& path); const char* route_dir(); bool ensure_route_asset_dir(const std::string& asset_id, std::string& out_dir); +bool count_route_saved_images(const std::string& asset_id, + std::size_t max_count, + std::size_t& out_count); bool route_asset_file_exists(const std::string& path); RouteImageDownloadResult download_route_image(const std::string& url, const std::string& output_path, diff --git a/modules/ui_shared/src/ui/screens/tracker/tracker_page_components.cpp b/modules/ui_shared/src/ui/screens/tracker/tracker_page_components.cpp index 23a52cb8..3f5e3fa0 100644 --- a/modules/ui_shared/src/ui/screens/tracker/tracker_page_components.cpp +++ b/modules/ui_shared/src/ui/screens/tracker/tracker_page_components.cpp @@ -115,6 +115,7 @@ bool s_preview_image_strip_visible = false; bool s_preview_map_loader_paused = false; bool s_preview_image_saved_state_known = false; bool s_preview_image_cache_state_known = false; +std::size_t s_preview_saved_image_count_hint = 0; ::ui::widgets::map::Runtime s_preview_map_runtime; lv_timer_t* s_preview_download_poll_timer = nullptr; bool s_preview_download_refresh_map_on_finish = false; @@ -912,13 +913,40 @@ void assign_preview_image_paths() } } +void refresh_preview_saved_image_count_hint() +{ + assign_preview_image_paths(); + s_preview_saved_image_count_hint = 0; + s_preview_image_saved_state_known = false; + if (s_preview_asset_id.empty() || s_preview_images.empty()) + { + return; + } + + std::size_t saved_count = 0; + if (platform::ui::route_storage::count_route_saved_images( + s_preview_asset_id, + s_preview_images.size(), + saved_count)) + { + s_preview_saved_image_count_hint = + std::min(saved_count, s_preview_images.size()); + s_preview_image_saved_state_known = true; + } +} + void refresh_preview_image_storage_state(bool include_cache_state) { assign_preview_image_paths(); + std::size_t saved_count = 0; for (RoutePreviewImage& image : s_preview_images) { image.downloaded = platform::ui::route_storage::route_asset_file_exists(image.local_path); + if (image.downloaded) + { + ++saved_count; + } if (include_cache_state) { image.preview_ready = @@ -931,6 +959,7 @@ void refresh_preview_image_storage_state(bool include_cache_state) } image.view_ready = false; } + s_preview_saved_image_count_hint = saved_count; s_preview_image_saved_state_known = true; s_preview_image_cache_state_known = include_cache_state; } @@ -992,6 +1021,18 @@ bool apply_route_preview_download_status_to_images( } } } + if (changed || (status.busy && status.saved > 0)) + { + s_preview_saved_image_count_hint = 0; + for (const RoutePreviewImage& image : s_preview_images) + { + if (image.downloaded) + { + ++s_preview_saved_image_count_hint; + } + } + s_preview_image_saved_state_known = true; + } return changed; } @@ -1013,6 +1054,7 @@ void clear_preview_model() s_preview_image_strip_visible = false; s_preview_image_saved_state_known = false; s_preview_image_cache_state_known = false; + s_preview_saved_image_count_hint = 0; } bool load_route_preview_model(const std::string& route_name) @@ -1133,6 +1175,7 @@ bool load_route_preview_model(const std::string& route_name) } refresh_preview_metrics(); assign_preview_image_paths(); + refresh_preview_saved_image_count_hint(); if (!s_preview_images.empty()) { s_preview_selected_image = 0; @@ -1550,6 +1593,16 @@ std::size_t preview_saved_image_count() })); } +std::size_t route_preview_saved_image_count() +{ + if (s_preview_image_saved_state_known) + { + return std::min(s_preview_saved_image_count_hint, + s_preview_images.size()); + } + return preview_saved_image_count(); +} + bool route_preview_image_cache_ready(const RoutePreviewImage& image) { if (!image.downloaded) @@ -1652,7 +1705,7 @@ bool route_preview_all_images_saved() { return s_preview_image_saved_state_known && !s_preview_images.empty() && - preview_saved_image_count() >= s_preview_images.size(); + route_preview_saved_image_count() >= s_preview_images.size(); } bool route_preview_load_button_disabled() @@ -1692,7 +1745,7 @@ bool route_preview_download_button_disabled() { return true; } - return route_preview_all_image_caches_ready(); + return route_preview_all_images_saved(); } void show_route_preview_notice(const char* message, uint32_t duration_ms = 1800) @@ -1883,7 +1936,7 @@ void ensure_route_preview_image_cache_build(bool refresh_state) void update_route_preview_status() { auto& state = g_tracker_state; - const std::size_t saved_count = preview_saved_image_count(); + const std::size_t saved_count = route_preview_saved_image_count(); const std::size_t cached_count = preview_cached_image_count(); const auto download_status = platform::ui::route_storage::route_image_download_status(); @@ -1984,7 +2037,10 @@ void update_route_preview_status() else { const std::size_t numerator = - saved_count >= s_preview_images.size() ? cached_count : saved_count; + saved_count >= s_preview_images.size() && + s_preview_image_cache_state_known + ? cached_count + : saved_count; value = static_cast((numerator * 100U) / s_preview_images.size()); } lv_bar_set_value(state.route_preview_progress, value, LV_ANIM_ON); @@ -2519,16 +2575,6 @@ void on_route_preview_download_clicked(lv_event_t*) if (saved_count >= total) { - if (!route_preview_all_image_caches_ready()) - { - std::printf("[RoutePreview][down] cache_start asset=%s saved=%u/%u\n", - s_preview_asset_id.c_str(), - static_cast(saved_count), - static_cast(total)); - show_route_preview_notice("Preparing image cache"); - ensure_route_preview_image_cache_build(false); - return; - } s_preview_download_state = RoutePreviewDownloadState::Done; std::snprintf(detail, sizeof(detail), @@ -2602,16 +2648,6 @@ void on_route_preview_download_clicked(lv_event_t*) if (items.empty()) { - if (!route_preview_all_image_caches_ready()) - { - std::printf("[RoutePreview][down] no_missing_images_cache_start asset=%s saved=%u/%u\n", - s_preview_asset_id.c_str(), - static_cast(saved_count), - static_cast(total)); - show_route_preview_notice("Preparing image cache"); - ensure_route_preview_image_cache_build(false); - return; - } s_preview_download_state = RoutePreviewDownloadState::Done; s_preview_status_text = "Images already saved"; update_route_preview_status(); diff --git a/platform/esp/arduino_common/src/platform_ui_route_storage.cpp b/platform/esp/arduino_common/src/platform_ui_route_storage.cpp index b061f56c..d2e4b4a1 100644 --- a/platform/esp/arduino_common/src/platform_ui_route_storage.cpp +++ b/platform/esp/arduino_common/src/platform_ui_route_storage.cpp @@ -148,6 +148,43 @@ bool is_safe_asset_id(const std::string& asset_id) return true; } +bool parse_route_image_name(const char* name, + std::size_t max_count, + std::size_t& out_index) +{ + out_index = 0; + if (!name || max_count == 0) + { + return false; + } + if (std::strlen(name) != 12 || std::strncmp(name, "img-", 4) != 0) + { + return false; + } + std::uint32_t value = 0; + for (int offset = 4; offset < 8; ++offset) + { + const char ch = name[offset]; + if (ch < '0' || ch > '9') + { + return false; + } + value = (value * 10U) + static_cast(ch - '0'); + } + if (name[8] != '.' || + std::tolower(static_cast(name[9])) != 'j' || + std::tolower(static_cast(name[10])) != 'p' || + std::tolower(static_cast(name[11])) != 'g' || + name[12] != '\0' || + value == 0 || + value > max_count) + { + return false; + } + out_index = static_cast(value - 1U); + return true; +} + bool ensure_dir(const char* path) { if (!path || path[0] == '\0') @@ -1294,6 +1331,48 @@ bool ensure_route_asset_dir(const std::string& asset_id, std::string& out_dir) return true; } +bool count_route_saved_images(const std::string& asset_id, + std::size_t max_count, + std::size_t& out_count) +{ + out_count = 0; + if (!platform::ui::device::sd_ready() || + !is_safe_asset_id(asset_id) || + max_count == 0) + { + return false; + } + + const std::string image_dir = + std::string(kRouteAssetRoot) + "/" + asset_id + "/" + kRouteAssetImageSubdir; + ::platform::esp::arduino_common::storage::SdRuntimeDir dir; + if (!dir.open(image_dir.c_str())) + { + return false; + } + + std::vector seen(max_count, 0); + char name_buf[64]; + bool is_dir = false; + while (out_count < max_count && dir.read_next(name_buf, sizeof(name_buf), &is_dir)) + { + if (is_dir) + { + continue; + } + std::size_t index = 0; + if (parse_route_image_name(name_buf, max_count, index) && + index < seen.size() && + !seen[index]) + { + seen[index] = 1; + ++out_count; + } + } + dir.close(); + return true; +} + bool route_asset_file_exists(const std::string& path) { return platform::ui::device::sd_ready() && !path.empty() && diff --git a/platform/esp/idf_common/src/platform_ui_route_storage.cpp b/platform/esp/idf_common/src/platform_ui_route_storage.cpp index f3319ceb..ac40e4d1 100644 --- a/platform/esp/idf_common/src/platform_ui_route_storage.cpp +++ b/platform/esp/idf_common/src/platform_ui_route_storage.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace platform::ui::route_storage { @@ -17,6 +18,7 @@ namespace constexpr const char* kRouteDir = "/routes"; constexpr const char* kRouteAssetRoot = "/routes/.trailmate"; +constexpr const char* kRouteAssetImageSubdir = "images"; bool has_kml_extension(const std::string& name) { @@ -47,6 +49,43 @@ bool is_safe_asset_id(const std::string& asset_id) return true; } +bool parse_route_image_name(const char* name, + std::size_t max_count, + std::size_t& out_index) +{ + out_index = 0; + if (name == nullptr || max_count == 0) + { + return false; + } + if (std::strlen(name) != 12 || std::strncmp(name, "img-", 4) != 0) + { + return false; + } + std::uint32_t value = 0; + for (int offset = 4; offset < 8; ++offset) + { + const char ch = name[offset]; + if (ch < '0' || ch > '9') + { + return false; + } + value = (value * 10U) + static_cast(ch - '0'); + } + if (name[8] != '.' || + std::tolower(static_cast(name[9])) != 'j' || + std::tolower(static_cast(name[10])) != 'p' || + std::tolower(static_cast(name[11])) != 'g' || + name[12] != '\0' || + value == 0 || + value > max_count) + { + return false; + } + out_index = static_cast(value - 1U); + return true; +} + bool is_regular_file(const std::string& path) { struct stat st @@ -163,6 +202,51 @@ bool ensure_route_asset_dir(const std::string& asset_id, std::string& out_dir) return true; } +bool count_route_saved_images(const std::string& asset_id, + std::size_t max_count, + std::size_t& out_count) +{ + out_count = 0; + if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() || + !is_safe_asset_id(asset_id) || + max_count == 0) + { + return false; + } + + const std::string image_dir = + mount_path_for(std::string(kRouteAssetRoot) + "/" + asset_id + "/" + kRouteAssetImageSubdir); + DIR* dir = ::opendir(image_dir.c_str()); + if (dir == nullptr) + { + return false; + } + + std::vector seen(max_count, 0); + while (out_count < max_count) + { + dirent* entry = ::readdir(dir); + if (entry == nullptr) + { + break; + } + std::size_t index = 0; + if (parse_route_image_name(entry->d_name, max_count, index) && + index < seen.size() && + !seen[index]) + { + const std::string full_path = image_dir + "/" + entry->d_name; + if (is_regular_file(full_path)) + { + seen[index] = 1; + ++out_count; + } + } + } + ::closedir(dir); + return true; +} + bool route_asset_file_exists(const std::string& path) { if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() || path.empty()) diff --git a/platform/linux/common/src/platform/ui/route_storage.cpp b/platform/linux/common/src/platform/ui/route_storage.cpp index c53bdc66..4e2aaae4 100644 --- a/platform/linux/common/src/platform/ui/route_storage.cpp +++ b/platform/linux/common/src/platform/ui/route_storage.cpp @@ -59,6 +59,38 @@ bool is_safe_asset_id(const std::string& asset_id) return true; } +bool parse_route_image_name(const std::string& name, + std::size_t max_count, + std::size_t& out_index) +{ + out_index = 0; + if (max_count == 0 || name.size() != 12 || name.rfind("img-", 0) != 0) + { + return false; + } + std::uint32_t value = 0; + for (std::size_t offset = 4; offset < 8; ++offset) + { + const char ch = name[offset]; + if (ch < '0' || ch > '9') + { + return false; + } + value = (value * 10U) + static_cast(ch - '0'); + } + if (name[8] != '.' || + std::tolower(static_cast(name[9])) != 'j' || + std::tolower(static_cast(name[10])) != 'p' || + std::tolower(static_cast(name[11])) != 'g' || + value == 0 || + value > max_count) + { + return false; + } + out_index = static_cast(value - 1U); + return true; +} + std::filesystem::path resolve_route_path(const std::string& path_or_name) { std::filesystem::path candidate(path_or_name); @@ -270,6 +302,48 @@ bool ensure_route_asset_dir(const std::string& asset_id, std::string& out_dir) return true; } +bool count_route_saved_images(const std::string& asset_id, + std::size_t max_count, + std::size_t& out_count) +{ + out_count = 0; + if (!ensure_route_dir() || !is_safe_asset_id(asset_id) || max_count == 0) + { + return false; + } + + const std::filesystem::path image_dir = + route_dir_path() / ".trailmate" / asset_id / "images"; + std::error_code ec; + if (!std::filesystem::is_directory(image_dir, ec) || ec) + { + return false; + } + + std::vector seen(max_count, 0); + for (std::filesystem::directory_iterator it(image_dir, ec), end; + !ec && it != end && out_count < max_count; it.increment(ec)) + { + if (ec) + { + break; + } + if (!it->is_regular_file(ec) || ec) + { + continue; + } + std::size_t index = 0; + if (parse_route_image_name(it->path().filename().string(), max_count, index) && + index < seen.size() && + !seen[index]) + { + seen[index] = 1; + ++out_count; + } + } + return !ec; +} + bool route_asset_file_exists(const std::string& path) { std::error_code ec;