feat(DM+SdDDL): sequential full-season download queue, fix close crash, return true
- DM: add dl_missing_* fields to AppCtx for sequential queue - DM player: count_missing_in_season, find_next_missing, download_episode, download_missing_season_start/next - DM main onResult: auto-continue queue when dl_missing_active, updates status DL X/Y, finishes with done ok/fail - SdDownloader: file exists returns true (Already on SD) with total=file size, not false - SdDownloader: set_result_and_close minimal bundle (only success bool) to avoid internal heap OOM crash on close - SdDownloader: Cancel button becomes Close after done, calls tt_app_stop() directly (manual close per request, auto-close unreliable) - Fixes crash on close observed in demo mode (file exists) and after download done Tested on 192.168.68.133: SdDownloader shows Already on SD + Close, DM shows S10 5/6 after sequential, no crash on open/close cycles
This commit is contained in:
@@ -90,6 +90,13 @@ typedef struct {
|
|||||||
|
|
||||||
// External downloader integration (SdDownloader)
|
// External downloader integration (SdDownloader)
|
||||||
int pending_dl_idx;
|
int pending_dl_idx;
|
||||||
|
|
||||||
|
// Full season download queue (sequential single-file to avoid batch crash)
|
||||||
|
bool dl_missing_active;
|
||||||
|
int dl_missing_season;
|
||||||
|
int dl_missing_total;
|
||||||
|
int dl_missing_done;
|
||||||
|
int dl_missing_failed;
|
||||||
} AppCtx;
|
} AppCtx;
|
||||||
|
|
||||||
extern AppCtx G;
|
extern AppCtx G;
|
||||||
|
|||||||
@@ -297,6 +297,55 @@ static void onResult(AppHandle app, void* data, AppLaunchId launchId, AppResult
|
|||||||
save_state();
|
save_state();
|
||||||
ui_rebuild_episode_list();
|
ui_rebuild_episode_list();
|
||||||
|
|
||||||
|
// Handle sequential full-season download queue (safe single-file mode)
|
||||||
|
if(G.dl_missing_active){
|
||||||
|
if(success) G.dl_missing_done++;
|
||||||
|
else G.dl_missing_failed++;
|
||||||
|
|
||||||
|
int next = find_next_missing_in_season(G.dl_missing_season);
|
||||||
|
if(next!=-1){
|
||||||
|
ESP_LOGI(TAG, "Batch queue continuing: %d/%d done, next idx %d", G.dl_missing_done, G.dl_missing_total, next);
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if(G.lbl_status){
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg,sizeof(msg),"DL %d/%d S%02d...", G.dl_missing_done+1, G.dl_missing_total, G.dl_missing_season);
|
||||||
|
lv_label_set_text(G.lbl_status, msg);
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
// Small delay to let UI update before launching next downloader
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(300));
|
||||||
|
if(download_episode(next)){
|
||||||
|
// started next
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// failed to start next, abort queue
|
||||||
|
G.dl_missing_active=false;
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if(G.lbl_status){
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg,sizeof(msg),"DL stopped %d ok %d fail", G.dl_missing_done, G.dl_missing_failed);
|
||||||
|
lv_label_set_text(G.lbl_status, msg);
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No more missing – queue finished
|
||||||
|
ESP_LOGI(TAG, "Batch queue finished: %d ok %d fail", G.dl_missing_done, G.dl_missing_failed);
|
||||||
|
G.dl_missing_active=false;
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if(G.lbl_status){
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg,sizeof(msg),"DL S%02d done %d ok %d fail", G.dl_missing_season, G.dl_missing_done, G.dl_missing_failed);
|
||||||
|
lv_label_set_text(G.lbl_status, msg);
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
if(success && idx>=0) ESP_LOGI(TAG, "Single DL success idx %d", idx);
|
||||||
|
else ESP_LOGW(TAG, "External DL failed for idx %d", idx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
if (G.lbl_status) {
|
if (G.lbl_status) {
|
||||||
if (success) lv_label_set_text(G.lbl_status, "DL done – tap to play");
|
if (success) lv_label_set_text(G.lbl_status, "DL done – tap to play");
|
||||||
@@ -316,6 +365,7 @@ static void onHide(AppHandle app, void* data){
|
|||||||
save_current_ep_pos();
|
save_current_ep_pos();
|
||||||
G.fetch_cancel_req=true;
|
G.fetch_cancel_req=true;
|
||||||
G.dl_cancel_req=true;
|
G.dl_cancel_req=true;
|
||||||
|
G.dl_missing_active=false;
|
||||||
|
|
||||||
if(G.fetch_handle){
|
if(G.fetch_handle){
|
||||||
ESP_LOGI(TAG,"onHide: waiting fetch to exit");
|
ESP_LOGI(TAG,"onHide: waiting fetch to exit");
|
||||||
|
|||||||
@@ -62,24 +62,81 @@ static bool try_external_downloader(int idx) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool try_external_downloader_batch(int season) {
|
int count_missing_in_season(int season){
|
||||||
// Reverted to single-file mode – batch array caused crashes in SdDownloader
|
int cnt=0;
|
||||||
// Now just download first missing episode in season
|
|
||||||
int first_missing = -1;
|
|
||||||
for(int i=0;i<G.ep_cnt;i++){
|
for(int i=0;i<G.ep_cnt;i++){
|
||||||
if(G.eps[i].season == season && !G.eps[i].seen && strlen(G.eps[i].audio_url)>=8){
|
if(G.eps[i].season==season && !G.eps[i].seen && strlen(G.eps[i].audio_url)>=8) cnt++;
|
||||||
first_missing = i;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
return cnt;
|
||||||
}
|
}
|
||||||
if(first_missing==-1){
|
int find_next_missing_in_season(int season){
|
||||||
|
for(int i=0;i<G.ep_cnt;i++){
|
||||||
|
if(G.eps[i].season==season && !G.eps[i].seen && strlen(G.eps[i].audio_url)>=8) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
bool download_episode(int idx){
|
||||||
|
return try_external_downloader(idx);
|
||||||
|
}
|
||||||
|
bool download_missing_next(void){
|
||||||
|
if(!G.dl_missing_active) return false;
|
||||||
|
int next = find_next_missing_in_season(G.dl_missing_season);
|
||||||
|
if(next==-1){
|
||||||
|
// No more missing – done
|
||||||
|
G.dl_missing_active=false;
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if(G.lbl_status){
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg,sizeof(msg),"DL S%02d done %d ok %d fail", G.dl_missing_season, G.dl_missing_done, G.dl_missing_failed);
|
||||||
|
lv_label_set_text(G.lbl_status, msg);
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Update UI for next file
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if(G.lbl_status){
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg,sizeof(msg),"DL %d/%d S%02d...", G.dl_missing_done+1, G.dl_missing_total, G.dl_missing_season);
|
||||||
|
lv_label_set_text(G.lbl_status, msg);
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
return try_external_downloader(next);
|
||||||
|
}
|
||||||
|
bool download_missing_season_start(int season){
|
||||||
|
int missing = count_missing_in_season(season);
|
||||||
|
if(missing==0){
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
if(G.lbl_status) lv_label_set_text(G.lbl_status,"Season already cached");
|
if(G.lbl_status) lv_label_set_text(G.lbl_status,"Season already cached");
|
||||||
tt_lvgl_unlock();
|
tt_lvgl_unlock();
|
||||||
|
G.dl_missing_active=false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "DL Missing (single-file revert) – downloading first missing S%02d idx %d", season, first_missing);
|
ESP_LOGI(TAG, "Starting sequential DL for S%02d, %d missing", season, missing);
|
||||||
return try_external_downloader(first_missing);
|
G.dl_missing_active=true;
|
||||||
|
G.dl_missing_season=season;
|
||||||
|
G.dl_missing_total=missing;
|
||||||
|
G.dl_missing_done=0;
|
||||||
|
G.dl_missing_failed=0;
|
||||||
|
int first = find_next_missing_in_season(season);
|
||||||
|
if(first==-1){
|
||||||
|
G.dl_missing_active=false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Update status
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if(G.lbl_status){
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg,sizeof(msg),"DL 1/%d S%02d...", missing, season);
|
||||||
|
lv_label_set_text(G.lbl_status, msg);
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
return try_external_downloader(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool try_external_downloader_batch(int season) {
|
||||||
|
// Sequential queue – safe single-file mode
|
||||||
|
return download_missing_season_start(season);
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_idx_internal(int idx){
|
void start_idx_internal(int idx){
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ void go_next(void);
|
|||||||
void go_prev(void);
|
void go_prev(void);
|
||||||
bool download_season(int season);
|
bool download_season(int season);
|
||||||
|
|
||||||
|
// New single-file download API
|
||||||
|
bool download_episode(int idx);
|
||||||
|
bool download_missing_season_start(int season);
|
||||||
|
bool download_missing_next(void); // continue queue, returns false if done
|
||||||
|
int count_missing_in_season(int season);
|
||||||
|
int find_next_missing_in_season(int season);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -90,24 +90,29 @@ static void update_ui_status(const char *txt) {
|
|||||||
|
|
||||||
static void set_result_and_close(bool success) {
|
static void set_result_and_close(bool success) {
|
||||||
G.result_success = success;
|
G.result_success = success;
|
||||||
BundleHandle result_bundle = tt_bundle_alloc();
|
size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
BundleHandle result_bundle = NULL;
|
||||||
|
if (free_internal > 8000) {
|
||||||
|
result_bundle = tt_bundle_alloc();
|
||||||
|
}
|
||||||
if (result_bundle) {
|
if (result_bundle) {
|
||||||
tt_bundle_put_bool(result_bundle, "success", success);
|
tt_bundle_put_bool(result_bundle, "success", success);
|
||||||
tt_bundle_put_string(result_bundle, "path", G.path);
|
// Keep bundle minimal to avoid low heap crash (previous put_string path 512B caused OOM)
|
||||||
tt_bundle_put_int32(result_bundle, "bytes", G.total);
|
|
||||||
if (!success) {
|
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
|
||||||
const char *status = G.lbl_status ? lv_label_get_text(G.lbl_status) : "failed";
|
|
||||||
char status_copy[128];
|
|
||||||
strncpy(status_copy, status, sizeof(status_copy)-1);
|
|
||||||
status_copy[sizeof(status_copy)-1]=0;
|
|
||||||
tt_lvgl_unlock();
|
|
||||||
tt_bundle_put_string(result_bundle, "error", status_copy);
|
|
||||||
}
|
|
||||||
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, result_bundle);
|
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, result_bundle);
|
||||||
} else {
|
} else {
|
||||||
|
// Low heap – return OK without bundle (DM will still refresh via has_slug)
|
||||||
|
if (free_internal <= 8000) {
|
||||||
|
ESP_LOGW(TAG, "Low heap %d, returning result without bundle", free_internal);
|
||||||
|
}
|
||||||
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, NULL);
|
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, NULL);
|
||||||
}
|
}
|
||||||
|
// Change Cancel button to Close once done (manual close, auto-close unreliable)
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if (G.btn_cancel) {
|
||||||
|
lv_obj_t* lbl = lv_obj_get_child(G.btn_cancel, 0);
|
||||||
|
if (lbl) lv_label_set_text(lbl, "Close");
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool download_one_file(const char *url, const char *path) {
|
static bool download_one_file(const char *url, const char *path) {
|
||||||
@@ -116,9 +121,10 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(path, &st)==0) {
|
if (stat(path, &st)==0) {
|
||||||
if (!G.override_existing) {
|
if (!G.override_existing) {
|
||||||
ESP_LOGW(TAG, "File exists and override=false: %s", path);
|
ESP_LOGW(TAG, "File exists and override=false: %s (returning true as cached)", path);
|
||||||
update_ui_status("File exists");
|
G.total = st.st_size;
|
||||||
return false;
|
update_ui_status("Already on SD");
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "File exists but override=true, overwriting %s", path);
|
ESP_LOGI(TAG, "File exists but override=true, overwriting %s", path);
|
||||||
unlink(path);
|
unlink(path);
|
||||||
@@ -294,7 +300,10 @@ static void download_task(void *arg) {
|
|||||||
|
|
||||||
static void btn_cancel_cb(lv_event_t *e) {
|
static void btn_cancel_cb(lv_event_t *e) {
|
||||||
(void)e;
|
(void)e;
|
||||||
if (!G.downloading) { tt_app_stop(); return; }
|
if (!G.downloading) {
|
||||||
|
tt_app_stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
G.cancel_req = true;
|
G.cancel_req = true;
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
if (G.lbl_status) lv_label_set_text(G.lbl_status, "Canceling...");
|
if (G.lbl_status) lv_label_set_text(G.lbl_status, "Canceling...");
|
||||||
|
|||||||
Reference in New Issue
Block a user