diff --git a/CMakeLists.txt b/CMakeLists.txt index c0c7610..2b8fecc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) +set(PROJECT_VER "1.0.1") + include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. diff --git a/main/apps/app_manager/app_manager.cpp b/main/apps/app_manager/app_manager.cpp index 52cc41c..3a7aaf5 100644 --- a/main/apps/app_manager/app_manager.cpp +++ b/main/apps/app_manager/app_manager.cpp @@ -689,7 +689,7 @@ static void app_task(void* param) // ---- Startup ---- esp_err_t app_manager_start() { - ESP_LOGI(g_tag, "Software version: V%u", APP_SW_VERSION); + ESP_LOGI(g_tag, "Software version: V%s", APP_SW_VERSION); // ── First boot guide image ── nvs_handle_t h; diff --git a/main/apps/app_manager/app_manager.h b/main/apps/app_manager/app_manager.h index ed44ce2..158f18c 100644 --- a/main/apps/app_manager/app_manager.h +++ b/main/apps/app_manager/app_manager.h @@ -21,7 +21,7 @@ #define WIFI_AP_AUTO_OFF_TIMEOUT_MIN (10) /** @brief Current application software version. */ -#define APP_SW_VERSION (0x01) +#define APP_SW_VERSION "1.0.1" /** * @brief Displays the boot guide image. diff --git a/main/apps/app_server/app_server.cpp b/main/apps/app_server/app_server.cpp index 5d14349..cf662c5 100644 --- a/main/apps/app_server/app_server.cpp +++ b/main/apps/app_server/app_server.cpp @@ -54,7 +54,7 @@ extern const char _binary_index_html_end[] asm("_binary_index_html_end"); #define SCAN_TIMEOUT_MS (8000) #define CONNECT_TIMEOUT_MS (15000) -#define UPLOAD_MAX_SIZE (512 * 1024) +#define UPLOAD_MAX_SIZE (2 * 1024 * 1024) #define PHOTOS_PER_PAGE (16) #define MAX_PHOTOS (500) @@ -138,7 +138,8 @@ static bool url_encode_component(const char *src, char *dst, size_t dst_sz) for (size_t i = 0; src[i]; i++) { unsigned char c = (unsigned char)src[i]; - bool safe = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || + + bool safe = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~'; if (safe) { diff --git a/main/apps/app_server/index.html b/main/apps/app_server/index.html index 8518961..3001e2e 100644 --- a/main/apps/app_server/index.html +++ b/main/apps/app_server/index.html @@ -1592,6 +1592,10 @@ let modeList = []; let confirmCallback = null; let photoPage = 1; let photoTotalPages = 1; +let photoPageCache = {}; // { [page]: { total_pages, names: [...] } } +let photoCache = {}; // { [name]: { name, url } } +let photoTotal = 0; +let photoPageLoading = false; // ==================== API Helper ==================== async function apiCall(method, url, body, isFormData) { @@ -1600,15 +1604,23 @@ async function apiCall(method, url, body, isFormData) { opts.body = isFormData ? body : JSON.stringify(body); if (!isFormData) opts.headers = { "Content-Type": "application/json" }; } + let res; try { - const res = await fetch(url, opts); - const data = await res.json(); - if (!res.ok) throw new Error(data.message || `HTTP ${res.status}`); - return data; + res = await fetch(url, opts); } catch (e) { - if (e.message && !e.message.startsWith("HTTP")) throw e; + throw e; + } + + let data = null; + try { + data = await res.json(); + } catch (e) { + if (!res.ok) throw new Error(`HTTP ${res.status}`); throw new Error("Network request failed"); } + + if (!res.ok) throw new Error(data.message || `HTTP ${res.status}`); + return data; } // ==================== Toast ==================== @@ -2654,11 +2666,6 @@ function applyModeConfig() { requestAnimationFrame(() => { rerenderCompositionLayout(false); }); - - if (currentMode === "mode_1") { - photoPage = 1; - loadPhotoList(photoPage); - } } // ==================== Unified Settings Modal ==================== @@ -3271,10 +3278,17 @@ async function uploadOnly() { formData.append("action", "upload_only"); formData.append("algorithm", displayMode); showToast("Uploading...", ""); - await apiCall("POST", "/api/photos/upload", formData, true); + const resp = await apiCall("POST", "/api/photos/upload", formData, true); showToast("Uploaded successfully", "success"); - photoPage = 9999; - loadPhotoList(photoPage); + if (displayMode === "nearest" && resp && resp.name) { + // Nearest photo inserts at front — all page boundaries shift, clear page cache + photoPageCache = {}; + const m = resp.name.match(/^imageN0*(\d+)/i); + if (m) photoPage = Math.ceil(Number(m[1]) / getPhotoPerPage()); + } else { + photoPage = 9999; + } + await loadPhotoList(photoPage, /* forceRefresh */ true); loadStorage(); } catch(e) { showToast(e.message, "error"); } } @@ -3300,10 +3314,18 @@ async function uploadWithDisplay() { formData.append("algorithm", displayMode); showToast("Uploading...", ""); - await apiCall("POST", "/api/photos/upload", formData, true); + const resp = await apiCall("POST", "/api/photos/upload", formData, true); showToast("Uploaded & display updated", "success"); - photoPage = 9999; - loadPhotoList(photoPage); + // Compute which page the new photo landed on from its name + if (displayMode === "nearest" && resp && resp.name) { + // Nearest photo inserts at front — all pages shift, clear page cache + photoPageCache = {}; + const m = resp.name.match(/^imageN0*(\d+)/i); + if (m) photoPage = Math.ceil(Number(m[1]) / getPhotoPerPage()); + } else { + photoPage = 9999; // Dither appends at end, only last page changes + } + await loadPhotoList(photoPage, /* forceRefresh */ true); loadStorage(); } catch(e) { showToast(e.message, "error"); } } @@ -3315,20 +3337,43 @@ function getPhotoPerPage() { return 12; } -async function loadPhotoList(page) { +async function loadPhotoList(page, forceRefresh = false) { page = page || photoPage; const perPage = getPhotoPerPage(); + + // Serve from cache if this page was loaded before + if (!forceRefresh && photoPageCache[page]) { + photoPage = page; + renderPhotoGridFromNames(photoPageCache[page]); + renderPagination(); + return; + } + + photoPageLoading = true; try { const data = await apiCall("GET", `/api/photos/list?page=${page}&per_page=${perPage}`); - photoPage = data.page; - photoTotalPages = data.total_pages; - renderPhotoGrid(data.photos, data.total); - renderPagination(); + if (data.page === page || page === photoPage) { + // Store per-photo data globally + const names = []; + data.photos.forEach(p => { + photoCache[p.name] = { name: p.name, url: p.url }; + names.push(p.name); + }); + photoPageCache[data.page] = names; + photoPage = data.page; + photoTotalPages = data.total_pages; + photoTotal = data.total; + renderPhotoGridFromNames(names); + renderPagination(); + } } catch(e) { /* silent */ } + photoPageLoading = false; } function goPhotoPage(page) { if (page < 1 || page > photoTotalPages) return; + photoPage = page; + renderPagination(); loadPhotoList(page); } @@ -3348,25 +3393,26 @@ function renderPagination() { next.disabled = photoPage >= photoTotalPages; } -function renderPhotoGrid(photos, total) { +function renderPhotoGridFromNames(names) { const grid = document.getElementById("photo-grid"); const count = document.getElementById("photo-count"); - count.textContent = total > 0 ? `(${total})` : ""; + count.textContent = photoTotal > 0 ? `(${photoTotal})` : ""; - if (photos.length === 0) { + if (names.length === 0) { grid.innerHTML = `
No photos
`; return; } - grid.innerHTML = photos.map(p => - `
+ grid.innerHTML = names.map(name => { + const p = photoCache[name] || { name, url: "" }; + return `
${escapeHtml(p.name)}
${escapeHtml(p.name)}
-
` - ).join(""); +
`; + }).join(""); } function escapeAttr(s) { return s.replace(/'/g, "\\'").replace(/"/g, """); } @@ -3385,13 +3431,23 @@ function deletePhoto(name) { await apiCall("DELETE", "/api/photos/delete?name=" + encodeURIComponent(name)); showToast("Deleted", "success"); loadStorage(); - // 如果当前页删空则回退一页 - if (photoPage > 1) { + // Remove only this photo from caches + delete photoCache[name]; + photoTotal = Math.max(0, photoTotal - 1); + for (const key of Object.keys(photoPageCache)) { + photoPageCache[key] = photoPageCache[key].filter(n => n !== name); + } + // If the current page is now empty, step back + const names = photoPageCache[photoPage]; + if (names && names.length === 0 && photoPage > 1) { + photoPage--; + } else if (!names && photoPage > 1) { + // Page not cached — check server whether it's now empty const perPage = getPhotoPerPage(); const data = await apiCall("GET", `/api/photos/list?page=${photoPage}&per_page=${perPage}`); - if (data.photos.length === 0 && photoPage > 1) { photoPage--; } + if (data.photos.length === 0) { photoPage--; } } - loadPhotoList(photoPage); + loadPhotoList(photoPage, /* forceRefresh */ true); } catch(e) { showToast(e.message, "error"); } }); } diff --git a/main/apps/local_photo_slideshow/local_photo_slideshow.cpp b/main/apps/local_photo_slideshow/local_photo_slideshow.cpp index 74249a4..adf98d6 100644 --- a/main/apps/local_photo_slideshow/local_photo_slideshow.cpp +++ b/main/apps/local_photo_slideshow/local_photo_slideshow.cpp @@ -22,9 +22,77 @@ static const char *TAG = "Slideshow"; -// Define an invalid index to represent “no photo is currently displayed” +static constexpr uint8_t RX8130_RAM_INDEX_CURRENT_STORAGE = 0; + +// Define an invalid index to represent "no photo is currently displayed" #define NO_PHOTO 0xFFFF +namespace { + +void resetPhotoIndexState(uint16_t &pending_index) +{ + pending_index = NO_PHOTO; + hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT_STORAGE, 0); + hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT_STORAGE + 1, 0); +} + +esp_err_t selectStorageMedia(bool sd_inserted, bool &sd_fallback_locked) +{ + sd_fallback_locked = false; + + if (sd_inserted) { + esp_err_t ret = hal_storage_init(APP_STORAGE_MEDIA_SDMMC); + if (ret == ESP_OK) { + return ESP_OK; + } + sd_fallback_locked = true; + ESP_LOGW(TAG, "SD init failed, fallback to SPI flash: %s", esp_err_to_name(ret)); + } + + return hal_storage_init(APP_STORAGE_MEDIA_SPIFLASH); +} + +void ensureStorageMedia(bool sd_inserted, bool &last_sd_inserted, bool &sd_fallback_locked, uint16_t &pending_index) +{ + if (!sd_inserted) { + last_sd_inserted = false; + sd_fallback_locked = false; + } else if (!last_sd_inserted) { + last_sd_inserted = true; + sd_fallback_locked = false; + } + + hal_storage_media_t target_media = + (sd_inserted && !sd_fallback_locked) ? APP_STORAGE_MEDIA_SDMMC : APP_STORAGE_MEDIA_SPIFLASH; + if (hal_storage_get_media() == target_media) { + return; + } + + esp_err_t ret = hal_storage_switch(target_media); + if (ret == ESP_OK) { + resetPhotoIndexState(pending_index); + return; + } + + if (!sd_inserted) { + ESP_LOGW(TAG, "SPI flash switch failed: %s", esp_err_to_name(ret)); + return; + } + + sd_fallback_locked = true; + ESP_LOGW(TAG, "SD switch failed, fallback to SPI flash: %s", esp_err_to_name(ret)); + if (hal_storage_get_media() != APP_STORAGE_MEDIA_SPIFLASH) { + esp_err_t fallback_ret = hal_storage_switch(APP_STORAGE_MEDIA_SPIFLASH); + if (fallback_ret != ESP_OK) { + ESP_LOGE(TAG, "SPI flash fallback switch failed: %s", esp_err_to_name(fallback_ret)); + return; + } + } + resetPhotoIndexState(pending_index); +} + +} // namespace + /* ---------- millis() ---------- */ static inline uint32_t millis_() { @@ -43,19 +111,15 @@ bool PhotoSlideshow::init(const char *dir_path, uint8_t interval_min) _needs_refresh = false; _last_btn_c = false; _last_btn_b = false; + _last_sd_inserted = hal.isSDCardInserted(); + _sd_fallback_locked = false; _photo_list.clear(); _scr_w = hal.Canvas->width(); _scr_h = hal.Canvas->height(); M5.Speaker.setVolume(120); - if (hal.isSDCardInserted()) { - // Use the SD card at startup - hal_storage_init(APP_STORAGE_MEDIA_SDMMC); - } else { - // Use APP storage at startup - hal_storage_init(APP_STORAGE_MEDIA_SPIFLASH); - } + selectStorageMedia(_last_sd_inserted, _sd_fallback_locked); scanPhotos(); hal.statusEventSend(OPERATION_EVENT_STARTUP_SUCCESS); @@ -238,23 +302,7 @@ void PhotoSlideshow::update() syncSettings(); - // If an SD card is inserted and FLASH storage is currently in use, switch to the SD card - // If no SD card is inserted and SD storage is currently in use, switch to FLASH storage - if (hal.isSDCardInserted()) { - if (hal_storage_get_media() != APP_STORAGE_MEDIA_SDMMC) { - hal_storage_switch(APP_STORAGE_MEDIA_SDMMC); - _pending_index = NO_PHOTO; - hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT, 0); - hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT + 1, 0); - } - } else { - if (hal_storage_get_media() != APP_STORAGE_MEDIA_SPIFLASH) { - hal_storage_switch(APP_STORAGE_MEDIA_SPIFLASH); - _pending_index = NO_PHOTO; - hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT, 0); - hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT + 1, 0); - } - } + ensureStorageMedia(hal.isSDCardInserted(), _last_sd_inserted, _sd_fallback_locked, _pending_index); // Check buttons handleButtons(); diff --git a/main/apps/local_photo_slideshow/local_photo_slideshow.h b/main/apps/local_photo_slideshow/local_photo_slideshow.h index 23d11b1..a248ab4 100644 --- a/main/apps/local_photo_slideshow/local_photo_slideshow.h +++ b/main/apps/local_photo_slideshow/local_photo_slideshow.h @@ -158,6 +158,8 @@ private: bool _last_btn_c = false; bool _last_btn_b = false; bool _last_btn_a = false; + bool _last_sd_inserted = false; + bool _sd_fallback_locked = false; /* ---- Internal helpers ---- */ void syncSettings(); diff --git a/main/hal/hal.cpp b/main/hal/hal.cpp index 5ae48c8..f8afd70 100644 --- a/main/hal/hal.cpp +++ b/main/hal/hal.cpp @@ -178,6 +178,8 @@ void Hal::init() s_spi_bus_inited = true; pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K); pm1.setI2cConfig(0); + pm1.pinMode(SD_DET_EN, OUTPUT); + pm1.digitalWrite(SD_DET_EN, HIGH); pm1.pinMode(SD_DEC, INPUT_PULLUP); pm1.pinMode(EPD_EN, OUTPUT); pm1.digitalWrite(EPD_EN, HIGH); diff --git a/main/hal/hal.h b/main/hal/hal.h index 18fc44b..5fdcd53 100644 --- a/main/hal/hal.h +++ b/main/hal/hal.h @@ -134,6 +134,7 @@ private: static void LedStatusIndicateTask(void* task_parameters); static constexpr m5pm1_gpio_num_t SD_DEC = M5PM1_GPIO_NUM_1; static constexpr m5pm1_gpio_num_t EPD_EN = M5PM1_GPIO_NUM_0; + static constexpr m5pm1_gpio_num_t SD_DET_EN = M5PM1_GPIO_NUM_4; static constexpr gpio_num_t SYS_SCL_PIN = GPIO_NUM_2; static constexpr gpio_num_t SYS_SDA_PIN = GPIO_NUM_3; TaskHandle_t _led_status_indicate_task_handle = nullptr; diff --git a/main/hal/storage/hal_storage.cpp b/main/hal/storage/hal_storage.cpp index 1b38c1b..cc22311 100644 --- a/main/hal/storage/hal_storage.cpp +++ b/main/hal/storage/hal_storage.cpp @@ -158,9 +158,46 @@ static void usb_event_cb(tinyusb_event_t *event, void *arg) } } -static void _mount(void) +static esp_err_t install_tinyusb_device_driver(void) { - ESP_ERROR_CHECK(tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP)); + if (s_ctx.driver_installed) { + return ESP_OK; + } + + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + tusb_cfg.descriptor.device = &descriptor_config; + tusb_cfg.descriptor.full_speed_config = msc_fs_configuration_desc; + tusb_cfg.descriptor.string = string_desc_arr; + tusb_cfg.descriptor.string_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]); + tusb_cfg.event_cb = usb_event_cb; + + esp_err_t ret = tinyusb_driver_install(&tusb_cfg); + if (ret == ESP_OK) { + s_ctx.driver_installed = true; + } + return ret; +} + +static esp_err_t uninstall_tinyusb_device_driver(void) +{ + if (!s_ctx.driver_installed) { + return ESP_OK; + } + + esp_err_t ret = tinyusb_driver_uninstall(); + if (ret == ESP_OK) { + s_ctx.driver_installed = false; + } + return ret; +} + +static esp_err_t _mount(void) +{ + esp_err_t ret = tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to switch storage mount point to APP: %s", esp_err_to_name(ret)); + return ret; + } ESP_LOGI(TAG, "ls %s:", BASE_PATH); DIR *directory_handle = opendir(BASE_PATH); @@ -168,16 +205,20 @@ static void _mount(void) if (!directory_handle) { if (errno == ENOENT) { ESP_LOGE(TAG, "Directory doesn't exist %s", BASE_PATH); + ret = ESP_ERR_NOT_FOUND; } else { ESP_LOGE(TAG, "Unable to read directory %s", BASE_PATH); + ret = ESP_FAIL; } - return; + tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB); + return ret; } struct dirent *entry; while ((entry = readdir(directory_handle)) != NULL) { printf(" %s\n", entry->d_name); } closedir(directory_handle); + return ESP_OK; } static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle) @@ -352,26 +393,37 @@ static esp_err_t _bringup(hal_storage_media_t media) ESP_LOGE(TAG, "set_storage_callback fail: %s", esp_err_to_name(ret)); goto err_after_storage; } - _mount(); - { - tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); - tusb_cfg.descriptor.device = &descriptor_config; - tusb_cfg.descriptor.full_speed_config = msc_fs_configuration_desc; - tusb_cfg.descriptor.string = string_desc_arr; - tusb_cfg.descriptor.string_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]); - tusb_cfg.event_cb = usb_event_cb; - ret = tinyusb_driver_install(&tusb_cfg); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "tinyusb_driver_install fail: %s", esp_err_to_name(ret)); - goto err_after_storage; - } - s_ctx.driver_installed = true; + ret = _mount(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Storage mount to APP failed: %s", esp_err_to_name(ret)); + goto err_after_storage; } + + ret = install_tinyusb_device_driver(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "tinyusb_driver_install fail: %s", esp_err_to_name(ret)); + goto err_after_storage; + } + return ESP_OK; err_after_storage: + uninstall_tinyusb_device_driver(); if (s_ctx.storage_hdl) { - tinyusb_msc_delete_storage(s_ctx.storage_hdl); + tinyusb_msc_mount_point_t cur; + if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK && + cur == TINYUSB_MSC_STORAGE_MOUNT_APP) { + esp_err_t unmount_ret = + tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB); + if (unmount_ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to switch storage mount point to USB during cleanup: %s", + esp_err_to_name(unmount_ret)); + } + } + esp_err_t delete_ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl); + if (delete_ret != ESP_OK) { + ESP_LOGW(TAG, "delete_storage during cleanup failed: %s", esp_err_to_name(delete_ret)); + } s_ctx.storage_hdl = NULL; } s_ctx.storage_created = false; @@ -409,13 +461,24 @@ esp_err_t hal_storage_switch(hal_storage_media_t media) esp_err_t ret = ESP_OK; + ret = uninstall_tinyusb_device_driver(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "tinyusb_driver_uninstall fail: %s", esp_err_to_name(ret)); + hal_storage_unlock(); + return ret; + } + if (s_ctx.storage_created && s_ctx.storage_hdl) { tinyusb_msc_mount_point_t cur; - if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK) { - if (cur != TINYUSB_MSC_STORAGE_MOUNT_APP) { - tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP); - vTaskDelay(pdMS_TO_TICKS(100)); + if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK && + cur == TINYUSB_MSC_STORAGE_MOUNT_APP) { + ret = tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "switch mount point to USB fail: %s", esp_err_to_name(ret)); + hal_storage_unlock(); + return ret; } + vTaskDelay(pdMS_TO_TICKS(100)); } ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl); @@ -484,7 +547,55 @@ esp_err_t hal_storage_switch(hal_storage_media_t media) ESP_LOGW(TAG, "set_storage_callback fail: %s", esp_err_to_name(ret)); } - _mount(); + ret = _mount(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Storage mount to APP failed after switch: %s", esp_err_to_name(ret)); + if (s_ctx.storage_hdl) { + esp_err_t delete_ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl); + if (delete_ret != ESP_OK) { + ESP_LOGW(TAG, "delete_storage after mount failure failed: %s", esp_err_to_name(delete_ret)); + } + s_ctx.storage_hdl = NULL; + } + s_ctx.storage_created = false; + if (media == APP_STORAGE_MEDIA_SPIFLASH) { + storage_deinit_spiflash(); + } else { + storage_deinit_sdmmc(); + } + hal_storage_unlock(); + return ret; + } + + ret = install_tinyusb_device_driver(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "tinyusb_driver_install fail after switch: %s", esp_err_to_name(ret)); + if (s_ctx.storage_hdl) { + tinyusb_msc_mount_point_t cur; + if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK && + cur == TINYUSB_MSC_STORAGE_MOUNT_APP) { + esp_err_t unmount_ret = + tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB); + if (unmount_ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to switch storage mount point to USB during install cleanup: %s", + esp_err_to_name(unmount_ret)); + } + } + esp_err_t delete_ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl); + if (delete_ret != ESP_OK) { + ESP_LOGW(TAG, "delete_storage after install failure failed: %s", esp_err_to_name(delete_ret)); + } + s_ctx.storage_hdl = NULL; + } + s_ctx.storage_created = false; + if (media == APP_STORAGE_MEDIA_SPIFLASH) { + storage_deinit_spiflash(); + } else { + storage_deinit_sdmmc(); + } + hal_storage_unlock(); + return ret; + } hal_storage_unlock(); return ESP_OK; } diff --git a/main/hal/utils/image/image_utils.cpp b/main/hal/utils/image/image_utils.cpp index 19bff55..b3b5884 100644 --- a/main/hal/utils/image/image_utils.cpp +++ b/main/hal/utils/image/image_utils.cpp @@ -7,6 +7,7 @@ #include #include #include +#include static bool get_bmp_size_mem(const uint8_t *data, size_t len, int *width, int *height) { @@ -82,18 +83,28 @@ bool get_image_size_from_file(const char *path, int *width, int *height) FILE *f = fopen(path, "rb"); if (!f) return false; - uint8_t buf[2048]; - size_t n = fread(buf, 1, sizeof(buf), f); + constexpr size_t BUF_SIZE = 65536; + uint8_t *buf = (uint8_t *)malloc(BUF_SIZE); + if (!buf) { + fclose(f); + return false; + } + size_t n = fread(buf, 1, BUF_SIZE, f); fclose(f); - if (n == 0) return false; - - if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0) { - return get_jpg_size_mem(buf, n, width, height); - } else if (strcasecmp(dot, ".bmp") == 0) { - return get_bmp_size_mem(buf, n, width, height); - } else if (strcasecmp(dot, ".png") == 0) { - return get_png_size_mem(buf, n, width, height); + if (n == 0) { + free(buf); + return false; } - return false; + + bool ok = false; + if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0) { + ok = get_jpg_size_mem(buf, n, width, height); + } else if (strcasecmp(dot, ".bmp") == 0) { + ok = get_bmp_size_mem(buf, n, width, height); + } else if (strcasecmp(dot, ".png") == 0) { + ok = get_png_size_mem(buf, n, width, height); + } + free(buf); + return ok; }