Merge pull request #207 from jpirnay/chore-fontmanager
chore: Font Manager overhaul
This commit is contained in:
@@ -193,7 +193,8 @@ STR_REGISTER_FAILED: "Registratie mislukt"
|
|||||||
STR_USERNAME_TAKEN: "Gebruikersnaam al in gebruik"
|
STR_USERNAME_TAKEN: "Gebruikersnaam al in gebruik"
|
||||||
STR_DONE: "Klaar"
|
STR_DONE: "Klaar"
|
||||||
STR_INSTALLED: "Geïnstalleerd"
|
STR_INSTALLED: "Geïnstalleerd"
|
||||||
STR_DOWNLOAD_ALL: "Alles downloaden / bijwerken"
|
STR_DOWNLOAD_ALL: "Alles downloaden"
|
||||||
|
STR_UPDATE_ALL: "Alles bijwerken"
|
||||||
STR_UPDATE_AVAILABLE: "Update"
|
STR_UPDATE_AVAILABLE: "Update"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Dit wist alle gecachte boekgegevens."
|
STR_CLEAR_CACHE_WARNING_1: "Dit wist alle gecachte boekgegevens."
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Alle leesvoortgang gaat verloren!"
|
STR_CLEAR_CACHE_WARNING_2: "Alle leesvoortgang gaat verloren!"
|
||||||
|
|||||||
@@ -204,7 +204,8 @@ STR_REGISTER_FAILED: "Registration failed"
|
|||||||
STR_USERNAME_TAKEN: "Username already taken"
|
STR_USERNAME_TAKEN: "Username already taken"
|
||||||
STR_DONE: "Done"
|
STR_DONE: "Done"
|
||||||
STR_INSTALLED: "Installed"
|
STR_INSTALLED: "Installed"
|
||||||
STR_DOWNLOAD_ALL: "Download / Update All"
|
STR_DOWNLOAD_ALL: "Download All"
|
||||||
|
STR_UPDATE_ALL: "Update All"
|
||||||
STR_UPDATE_AVAILABLE: "Update"
|
STR_UPDATE_AVAILABLE: "Update"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "This will clear all cached book data."
|
STR_CLEAR_CACHE_WARNING_1: "This will clear all cached book data."
|
||||||
STR_CLEAR_CACHE_WARNING_2: "All reading progress will be lost!"
|
STR_CLEAR_CACHE_WARNING_2: "All reading progress will be lost!"
|
||||||
|
|||||||
@@ -582,7 +582,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Envoi automatique à la fermeture du livre"
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Distant en avance, passage"
|
STR_KO_AUTO_SYNC_SKIPPED: "Distant en avance, passage"
|
||||||
STR_KO_LONGPRESS_HINT: "Maintenez Confirmer pour ouvrir avec la synchronisation"
|
STR_KO_LONGPRESS_HINT: "Maintenez Confirmer pour ouvrir avec la synchronisation"
|
||||||
STR_INSTALLED: "Installé"
|
STR_INSTALLED: "Installé"
|
||||||
STR_DOWNLOAD_ALL: "Tout télécharger / mettre à jour"
|
STR_DOWNLOAD_ALL: "Tout télécharger"
|
||||||
|
STR_UPDATE_ALL: "Tout mettre à jour"
|
||||||
STR_UPDATE_AVAILABLE: "Mettre à jour"
|
STR_UPDATE_AVAILABLE: "Mettre à jour"
|
||||||
STR_TINY: "Minuscule"
|
STR_TINY: "Minuscule"
|
||||||
STR_NO_FONTS_AVAILABLE: "Aucune police disponible"
|
STR_NO_FONTS_AVAILABLE: "Aucune police disponible"
|
||||||
|
|||||||
@@ -582,7 +582,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Automatischer Push bei Buchschließen"
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Remote voraus, überspringe"
|
STR_KO_AUTO_SYNC_SKIPPED: "Remote voraus, überspringe"
|
||||||
STR_KO_LONGPRESS_HINT: "Halte Bestätigen gedrückt, um mit Sync zu öffnen"
|
STR_KO_LONGPRESS_HINT: "Halte Bestätigen gedrückt, um mit Sync zu öffnen"
|
||||||
STR_INSTALLED: "Installiert"
|
STR_INSTALLED: "Installiert"
|
||||||
STR_DOWNLOAD_ALL: "Alle herunterladen/aktualisieren"
|
STR_DOWNLOAD_ALL: "Alle herunterladen"
|
||||||
|
STR_UPDATE_ALL: "Alle aktualisieren"
|
||||||
STR_UPDATE_AVAILABLE: "Aktualisieren"
|
STR_UPDATE_AVAILABLE: "Aktualisieren"
|
||||||
STR_TINY: "Winzig"
|
STR_TINY: "Winzig"
|
||||||
STR_NO_FONTS_AVAILABLE: "Keine Schriften verfügbar"
|
STR_NO_FONTS_AVAILABLE: "Keine Schriften verfügbar"
|
||||||
|
|||||||
@@ -582,7 +582,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Invio automatico alla chiusura del libro"
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Remoto in anticipo, salto"
|
STR_KO_AUTO_SYNC_SKIPPED: "Remoto in anticipo, salto"
|
||||||
STR_KO_LONGPRESS_HINT: "Tieni premuto Conferma per aprire con la sincronizzazione"
|
STR_KO_LONGPRESS_HINT: "Tieni premuto Conferma per aprire con la sincronizzazione"
|
||||||
STR_INSTALLED: "Installato"
|
STR_INSTALLED: "Installato"
|
||||||
STR_DOWNLOAD_ALL: "Scarica / aggiorna tutto"
|
STR_DOWNLOAD_ALL: "Scarica tutto"
|
||||||
|
STR_UPDATE_ALL: "Aggiorna tutto"
|
||||||
STR_UPDATE_AVAILABLE: "Aggiorna"
|
STR_UPDATE_AVAILABLE: "Aggiorna"
|
||||||
STR_TINY: "Minuscolo"
|
STR_TINY: "Minuscolo"
|
||||||
STR_NO_FONTS_AVAILABLE: "Nessun font disponibile"
|
STR_NO_FONTS_AVAILABLE: "Nessun font disponibile"
|
||||||
|
|||||||
@@ -427,7 +427,8 @@ STR_SD_CARD_FULL: "Za mało pamięci na karcie SD"
|
|||||||
STR_FILES_LABEL: "Pliki: "
|
STR_FILES_LABEL: "Pliki: "
|
||||||
STR_SIZE_LABEL: "Rozmiar: "
|
STR_SIZE_LABEL: "Rozmiar: "
|
||||||
STR_REDOWNLOAD: "Pobierz ponownie"
|
STR_REDOWNLOAD: "Pobierz ponownie"
|
||||||
STR_DOWNLOAD_ALL: "Pobierz / Uaktualnij wszystkie"
|
STR_DOWNLOAD_ALL: "Pobierz wszystkie"
|
||||||
|
STR_UPDATE_ALL: "Aktualizuj wszystko"
|
||||||
STR_ALL_FONTS_INSTALLED: "Wszystkie czcionki zainstalowane!"
|
STR_ALL_FONTS_INSTALLED: "Wszystkie czcionki zainstalowane!"
|
||||||
STR_UPDATE_AVAILABLE: "Uaktualnij"
|
STR_UPDATE_AVAILABLE: "Uaktualnij"
|
||||||
STR_CRASH_TITLE: "Awaria systemu"
|
STR_CRASH_TITLE: "Awaria systemu"
|
||||||
|
|||||||
@@ -577,7 +577,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Envio automático ao fechar o livro"
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Remoto à frente, pulando"
|
STR_KO_AUTO_SYNC_SKIPPED: "Remoto à frente, pulando"
|
||||||
STR_KO_LONGPRESS_HINT: "Segure Confirmar para abrir com sincronização"
|
STR_KO_LONGPRESS_HINT: "Segure Confirmar para abrir com sincronização"
|
||||||
STR_INSTALLED: "Instalado"
|
STR_INSTALLED: "Instalado"
|
||||||
STR_DOWNLOAD_ALL: "Baixar / atualizar tudo"
|
STR_DOWNLOAD_ALL: "Baixar tudo"
|
||||||
|
STR_UPDATE_ALL: "Atualizar tudo"
|
||||||
STR_UPDATE_AVAILABLE: "Atualizar"
|
STR_UPDATE_AVAILABLE: "Atualizar"
|
||||||
STR_TINY: "Minúsculo"
|
STR_TINY: "Minúsculo"
|
||||||
STR_NO_FONTS_AVAILABLE: "Nenhuma fonte disponível"
|
STR_NO_FONTS_AVAILABLE: "Nenhuma fonte disponível"
|
||||||
|
|||||||
@@ -576,7 +576,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Envio automático ao fechar o livro"
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Remoto à frente, a ignorar"
|
STR_KO_AUTO_SYNC_SKIPPED: "Remoto à frente, a ignorar"
|
||||||
STR_KO_LONGPRESS_HINT: "Mantenha Confirmar para abrir com sincronização"
|
STR_KO_LONGPRESS_HINT: "Mantenha Confirmar para abrir com sincronização"
|
||||||
STR_INSTALLED: "Instalado"
|
STR_INSTALLED: "Instalado"
|
||||||
STR_DOWNLOAD_ALL: "Transferir / atualizar tudo"
|
STR_DOWNLOAD_ALL: "Transferir tudo"
|
||||||
|
STR_UPDATE_ALL: "Atualizar tudo"
|
||||||
STR_UPDATE_AVAILABLE: "Atualizar"
|
STR_UPDATE_AVAILABLE: "Atualizar"
|
||||||
STR_TINY: "Minúsculo"
|
STR_TINY: "Minúsculo"
|
||||||
STR_NO_FONTS_AVAILABLE: "Nenhuma fonte disponível"
|
STR_NO_FONTS_AVAILABLE: "Nenhuma fonte disponível"
|
||||||
|
|||||||
@@ -577,7 +577,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Авто-отправка при закрытии к
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Удаленный впереди, пропускаю"
|
STR_KO_AUTO_SYNC_SKIPPED: "Удаленный впереди, пропускаю"
|
||||||
STR_KO_LONGPRESS_HINT: "Удерживайте Подтвердить, чтобы открыть с синхронизацией"
|
STR_KO_LONGPRESS_HINT: "Удерживайте Подтвердить, чтобы открыть с синхронизацией"
|
||||||
STR_INSTALLED: "Установлено"
|
STR_INSTALLED: "Установлено"
|
||||||
STR_DOWNLOAD_ALL: "Загрузить / обновить все"
|
STR_DOWNLOAD_ALL: "Загрузить все"
|
||||||
|
STR_UPDATE_ALL: "Обновить все"
|
||||||
STR_UPDATE_AVAILABLE: "Обновить"
|
STR_UPDATE_AVAILABLE: "Обновить"
|
||||||
STR_TINY: "Крошечный"
|
STR_TINY: "Крошечный"
|
||||||
STR_NO_FONTS_AVAILABLE: "Шрифты недоступны"
|
STR_NO_FONTS_AVAILABLE: "Шрифты недоступны"
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ STR_REGISTER_FAILED: "Registracija ni uspela"
|
|||||||
STR_USERNAME_TAKEN: "Uporabniško ime je že zasedeno"
|
STR_USERNAME_TAKEN: "Uporabniško ime je že zasedeno"
|
||||||
STR_DONE: "Končano"
|
STR_DONE: "Končano"
|
||||||
STR_INSTALLED: "Nameščeno"
|
STR_INSTALLED: "Nameščeno"
|
||||||
STR_DOWNLOAD_ALL: "Prenesi / posodobi vse"
|
STR_DOWNLOAD_ALL: "Prenesi vse"
|
||||||
|
STR_UPDATE_ALL: "Posodobi vse"
|
||||||
STR_UPDATE_AVAILABLE: "Posodobi"
|
STR_UPDATE_AVAILABLE: "Posodobi"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "To bo izbrisalo vse predpomnjene podatke."
|
STR_CLEAR_CACHE_WARNING_1: "To bo izbrisalo vse predpomnjene podatke."
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Ves napredek branja bo izgubljen!"
|
STR_CLEAR_CACHE_WARNING_2: "Ves napredek branja bo izgubljen!"
|
||||||
|
|||||||
@@ -577,7 +577,8 @@ STR_KO_SYNC_ON_BOOK_CLOSE: "Empuje automático al cerrar el libro"
|
|||||||
STR_KO_AUTO_SYNC_SKIPPED: "Remoto adelantado, omitiendo"
|
STR_KO_AUTO_SYNC_SKIPPED: "Remoto adelantado, omitiendo"
|
||||||
STR_KO_LONGPRESS_HINT: "Mantén pulsado Confirmar para abrir con sincronización"
|
STR_KO_LONGPRESS_HINT: "Mantén pulsado Confirmar para abrir con sincronización"
|
||||||
STR_INSTALLED: "Instalado"
|
STR_INSTALLED: "Instalado"
|
||||||
STR_DOWNLOAD_ALL: "Descargar / actualizar todo"
|
STR_DOWNLOAD_ALL: "Descargar todo"
|
||||||
|
STR_UPDATE_ALL: "Actualizar todo"
|
||||||
STR_UPDATE_AVAILABLE: "Actualizar"
|
STR_UPDATE_AVAILABLE: "Actualizar"
|
||||||
STR_TINY: "Minúsculo"
|
STR_TINY: "Minúsculo"
|
||||||
STR_NO_FONTS_AVAILABLE: "No hay fuentes disponibles"
|
STR_NO_FONTS_AVAILABLE: "No hay fuentes disponibles"
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ STR_REGISTER_FAILED: "Registrering misslyckades"
|
|||||||
STR_USERNAME_TAKEN: "Användarnamn upptaget"
|
STR_USERNAME_TAKEN: "Användarnamn upptaget"
|
||||||
STR_DONE: "Klar"
|
STR_DONE: "Klar"
|
||||||
STR_INSTALLED: "Installerat"
|
STR_INSTALLED: "Installerat"
|
||||||
STR_DOWNLOAD_ALL: "Ladda ner / uppdatera allt"
|
STR_DOWNLOAD_ALL: "Ladda ner allt"
|
||||||
|
STR_UPDATE_ALL: "Uppdatera allt"
|
||||||
STR_UPDATE_AVAILABLE: "Uppdatera"
|
STR_UPDATE_AVAILABLE: "Uppdatera"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Detta rensar all cachad bokdata"
|
STR_CLEAR_CACHE_WARNING_1: "Detta rensar all cachad bokdata"
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Alla läsframsteg kommer att försvinna!"
|
STR_CLEAR_CACHE_WARNING_2: "Alla läsframsteg kommer att försvinna!"
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ STR_REGISTER_FAILED: "Kayıt başarısız"
|
|||||||
STR_USERNAME_TAKEN: "Kullanıcı adı zaten alınmış"
|
STR_USERNAME_TAKEN: "Kullanıcı adı zaten alınmış"
|
||||||
STR_DONE: "Tamamlandı"
|
STR_DONE: "Tamamlandı"
|
||||||
STR_INSTALLED: "Yüklendi"
|
STR_INSTALLED: "Yüklendi"
|
||||||
STR_DOWNLOAD_ALL: "Tümünü İndir / Güncelle"
|
STR_DOWNLOAD_ALL: "Tümünü indir"
|
||||||
|
STR_UPDATE_ALL: "Tümünü güncelle"
|
||||||
STR_UPDATE_AVAILABLE: "Güncelle"
|
STR_UPDATE_AVAILABLE: "Güncelle"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Bu işlem tüm önbelleğe alınmış verileri siler."
|
STR_CLEAR_CACHE_WARNING_1: "Bu işlem tüm önbelleğe alınmış verileri siler."
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Tüm okuma ilerlemesi kaybolacak!"
|
STR_CLEAR_CACHE_WARNING_2: "Tüm okuma ilerlemesi kaybolacak!"
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ STR_REGISTER_FAILED: "Помилка реєстрації"
|
|||||||
STR_USERNAME_TAKEN: "Ім'я користувача вже зайнято"
|
STR_USERNAME_TAKEN: "Ім'я користувача вже зайнято"
|
||||||
STR_DONE: "Готово"
|
STR_DONE: "Готово"
|
||||||
STR_INSTALLED: "Встановлено"
|
STR_INSTALLED: "Встановлено"
|
||||||
STR_DOWNLOAD_ALL: "Завантажити / Оновити все"
|
STR_DOWNLOAD_ALL: "Завантажити все"
|
||||||
|
STR_UPDATE_ALL: "Оновити все"
|
||||||
STR_UPDATE_AVAILABLE: "Оновити"
|
STR_UPDATE_AVAILABLE: "Оновити"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Це очистить усі кешовані дані книг."
|
STR_CLEAR_CACHE_WARNING_1: "Це очистить усі кешовані дані книг."
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Весь прогрес читання буде втрачено!"
|
STR_CLEAR_CACHE_WARNING_2: "Весь прогрес читання буде втрачено!"
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ STR_REGISTER_FAILED: "Đăng ký thất bại"
|
|||||||
STR_USERNAME_TAKEN: "Tên đăng nhập đã được dùng"
|
STR_USERNAME_TAKEN: "Tên đăng nhập đã được dùng"
|
||||||
STR_DONE: "Xong"
|
STR_DONE: "Xong"
|
||||||
STR_INSTALLED: "Đã cài đặt"
|
STR_INSTALLED: "Đã cài đặt"
|
||||||
STR_DOWNLOAD_ALL: "Tải xuống / Cập nhật tất cả"
|
STR_DOWNLOAD_ALL: "Tải xuống tất cả"
|
||||||
|
STR_UPDATE_ALL: "Cập nhật tất cả"
|
||||||
STR_UPDATE_AVAILABLE: "Cập nhật"
|
STR_UPDATE_AVAILABLE: "Cập nhật"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Thao tác này sẽ xóa toàn bộ dữ liệu sách đã lưu."
|
STR_CLEAR_CACHE_WARNING_1: "Thao tác này sẽ xóa toàn bộ dữ liệu sách đã lưu."
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Tất cả tiến trình đọc sẽ bị mất!"
|
STR_CLEAR_CACHE_WARNING_2: "Tất cả tiến trình đọc sẽ bị mất!"
|
||||||
|
|||||||
@@ -322,6 +322,8 @@ void OpdsBookBrowserActivity::render(RenderLock&&) {
|
|||||||
const int barY = midY + 20;
|
const int barY = midY + 20;
|
||||||
GUI.drawProgressBar(renderer, Rect{barX, barY, barWidth, barHeight}, downloadProgress, downloadTotal);
|
GUI.drawProgressBar(renderer, Rect{barX, barY, barWidth, barHeight}, downloadProgress, downloadTotal);
|
||||||
}
|
}
|
||||||
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||||
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -535,10 +537,12 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book, const OpdsAcqu
|
|||||||
|
|
||||||
const auto result = HttpDownloader::downloadToFile(
|
const auto result = HttpDownloader::downloadToFile(
|
||||||
downloadUrl, filename,
|
downloadUrl, filename,
|
||||||
[this](const size_t downloaded, const size_t total) {
|
[this](const unsigned int downloaded, const unsigned int total) {
|
||||||
|
mappedInput.update();
|
||||||
downloadProgress = downloaded;
|
downloadProgress = downloaded;
|
||||||
downloadTotal = total;
|
downloadTotal = total;
|
||||||
requestUpdate(true);
|
requestUpdate(true);
|
||||||
|
return !mappedInput.wasPressed(MappedInputManager::Button::Back);
|
||||||
},
|
},
|
||||||
server.username, server.password);
|
server.username, server.password);
|
||||||
|
|
||||||
@@ -583,8 +587,13 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book, const OpdsAcqu
|
|||||||
std::string sidecarPath = baseFilename + ext;
|
std::string sidecarPath = baseFilename + ext;
|
||||||
|
|
||||||
const auto coverDlResult = HttpDownloader::downloadToFile(
|
const auto coverDlResult = HttpDownloader::downloadToFile(
|
||||||
coverUrl, sidecarPath, [this](const size_t, const size_t) { requestUpdate(true); }, server.username,
|
coverUrl, sidecarPath,
|
||||||
server.password);
|
[this](const unsigned int, const unsigned int) {
|
||||||
|
mappedInput.update();
|
||||||
|
requestUpdate(true);
|
||||||
|
return !mappedInput.wasPressed(MappedInputManager::Button::Back);
|
||||||
|
},
|
||||||
|
server.username, server.password);
|
||||||
if (coverDlResult != HttpDownloader::OK) {
|
if (coverDlResult != HttpDownloader::OK) {
|
||||||
LOG_ERR("OPDS", "Failed to download cover from %s (err %d)", coverUrl.c_str(), (int)coverDlResult);
|
LOG_ERR("OPDS", "Failed to download cover from %s (err %d)", coverUrl.c_str(), (int)coverDlResult);
|
||||||
Storage.remove(sidecarPath.c_str());
|
Storage.remove(sidecarPath.c_str());
|
||||||
@@ -600,6 +609,11 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book, const OpdsAcqu
|
|||||||
formatSelectionLabels.clear();
|
formatSelectionLabels.clear();
|
||||||
state = BrowserState::BROWSING;
|
state = BrowserState::BROWSING;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
|
} else if (result == HttpDownloader::ABORTED) {
|
||||||
|
selectedBookIndex = -1;
|
||||||
|
formatSelectionLabels.clear();
|
||||||
|
state = BrowserState::BROWSING;
|
||||||
|
requestUpdate();
|
||||||
} else {
|
} else {
|
||||||
selectedBookIndex = -1;
|
selectedBookIndex = -1;
|
||||||
formatSelectionLabels.clear();
|
formatSelectionLabels.clear();
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ class OpdsBookBrowserActivity final : public Activity {
|
|||||||
std::string currentPath;
|
std::string currentPath;
|
||||||
std::string searchTemplate;
|
std::string searchTemplate;
|
||||||
bool consumeConfirm = false;
|
bool consumeConfirm = false;
|
||||||
bool consumeBack = false; // Added missing member
|
bool consumeBack = false;
|
||||||
|
bool cancelDownloadRequested = false;
|
||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
int selectedBookIndex = -1;
|
int selectedBookIndex = -1;
|
||||||
int formatSelectorIndex = 0;
|
int formatSelectorIndex = 0;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ void FontDownloadActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
state_ = FAMILY_LIST;
|
state_ = FAMILY_LIST;
|
||||||
selectedIndex_ = 0;
|
selectedIndex_ = 0;
|
||||||
|
previousActionCount_ = actionCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,10 +164,23 @@ bool FontDownloadActivity::fetchAndParseManifest() {
|
|||||||
// --- Download ---
|
// --- Download ---
|
||||||
|
|
||||||
void FontDownloadActivity::downloadAll() {
|
void FontDownloadActivity::downloadAll() {
|
||||||
|
cancelRequested_ = false;
|
||||||
for (size_t i = 0; i < families_.size(); i++) {
|
for (size_t i = 0; i < families_.size(); i++) {
|
||||||
if (families_[i].installed && !families_[i].hasUpdate) continue;
|
if (families_[i].installed) continue;
|
||||||
downloadFamily(families_[i]);
|
downloadFamily(families_[i]);
|
||||||
if (state_ == ERROR) return;
|
if (state_ == ERROR || cancelRequested_) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderLock lock(*this);
|
||||||
|
state_ = COMPLETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontDownloadActivity::updateAll() {
|
||||||
|
cancelRequested_ = false;
|
||||||
|
for (size_t i = 0; i < families_.size(); i++) {
|
||||||
|
if (!families_[i].installed || !families_[i].hasUpdate) continue;
|
||||||
|
downloadFamily(families_[i]);
|
||||||
|
if (state_ == ERROR || cancelRequested_) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
@@ -176,12 +190,57 @@ void FontDownloadActivity::downloadAll() {
|
|||||||
size_t FontDownloadActivity::totalUninstalledSize() const {
|
size_t FontDownloadActivity::totalUninstalledSize() const {
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
for (const auto& f : families_) {
|
for (const auto& f : families_) {
|
||||||
if (!f.installed || f.hasUpdate) total += f.totalSize;
|
if (!f.installed) total += f.totalSize;
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t FontDownloadActivity::totalUpdateSize() const {
|
||||||
|
size_t total = 0;
|
||||||
|
for (const auto& f : families_) {
|
||||||
|
if (f.installed && f.hasUpdate) total += f.totalSize;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontDownloadActivity::syncSelectedIndexForNewActionCount() {
|
||||||
|
const int currentActionCount = actionCount();
|
||||||
|
if (currentActionCount == previousActionCount_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newIndex = selectedIndex_;
|
||||||
|
if (selectedIndex_ >= previousActionCount_) {
|
||||||
|
const int familyIndex = selectedIndex_ - previousActionCount_;
|
||||||
|
newIndex = familyIndex + currentActionCount;
|
||||||
|
} else if (selectedIndex_ >= currentActionCount) {
|
||||||
|
newIndex = currentActionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newIndex >= listItemCount()) {
|
||||||
|
newIndex = std::max(0, listItemCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIndex_ = newIndex;
|
||||||
|
previousActionCount_ = currentActionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontDownloadActivity::hasDownloadCandidates() const {
|
||||||
|
for (const auto& f : families_) {
|
||||||
|
if (!f.installed) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontDownloadActivity::hasUpdateCandidates() const {
|
||||||
|
for (const auto& f : families_) {
|
||||||
|
if (f.installed && f.hasUpdate) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
||||||
|
cancelRequested_ = false;
|
||||||
{
|
{
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
state_ = DOWNLOADING;
|
state_ = DOWNLOADING;
|
||||||
@@ -228,6 +287,8 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
|||||||
currentFileIndex_ = i;
|
currentFileIndex_ = i;
|
||||||
fileProgress_ = 0;
|
fileProgress_ = 0;
|
||||||
fileTotal_ = file.size;
|
fileTotal_ = file.size;
|
||||||
|
lastProgressPercent_ = -1;
|
||||||
|
lastProgressUpdateMs_ = 0;
|
||||||
}
|
}
|
||||||
requestUpdateAndWait();
|
requestUpdateAndWait();
|
||||||
|
|
||||||
@@ -249,12 +310,36 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
|||||||
|
|
||||||
std::string url = baseUrl_ + file.name;
|
std::string url = baseUrl_ + file.name;
|
||||||
|
|
||||||
auto result = HttpDownloader::downloadToFile(url, stagedPath, [this](size_t downloaded, size_t total) {
|
auto result = HttpDownloader::downloadToFile(url, stagedPath, [this](unsigned int downloaded, unsigned int total) {
|
||||||
|
mappedInput.update();
|
||||||
fileProgress_ = downloaded;
|
fileProgress_ = downloaded;
|
||||||
fileTotal_ = total;
|
fileTotal_ = total;
|
||||||
requestUpdate(true);
|
|
||||||
|
const unsigned long now = millis();
|
||||||
|
int percent = 0;
|
||||||
|
if (total > 0) {
|
||||||
|
percent = static_cast<int>((static_cast<unsigned long long>(downloaded) * 100ULL + total / 2) / total);
|
||||||
|
}
|
||||||
|
const bool percentChanged = percent != lastProgressPercent_;
|
||||||
|
const bool timeElapsed = lastProgressUpdateMs_ == 0 || now - lastProgressUpdateMs_ > 2000;
|
||||||
|
if ((percentChanged && timeElapsed) || downloaded == total) {
|
||||||
|
requestUpdate(true);
|
||||||
|
lastProgressPercent_ = percent;
|
||||||
|
lastProgressUpdateMs_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !mappedInput.wasPressed(MappedInputManager::Button::Back);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result == HttpDownloader::ABORTED) {
|
||||||
|
LOG_INF("FONT", "Download cancelled: %s", file.name.c_str());
|
||||||
|
Storage.removeDir(stagingDir);
|
||||||
|
cancelRequested_ = true;
|
||||||
|
RenderLock lock(*this);
|
||||||
|
state_ = FAMILY_LIST;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result != HttpDownloader::OK) {
|
if (result != HttpDownloader::OK) {
|
||||||
LOG_ERR("FONT", "Download failed: %s (%d)", file.name.c_str(), result);
|
LOG_ERR("FONT", "Download failed: %s (%d)", file.name.c_str(), result);
|
||||||
Storage.removeDir(stagingDir);
|
Storage.removeDir(stagingDir);
|
||||||
@@ -323,6 +408,7 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
|||||||
fontInstaller_.refreshRegistry();
|
fontInstaller_.refreshRegistry();
|
||||||
family.installed = true;
|
family.installed = true;
|
||||||
family.hasUpdate = false;
|
family.hasUpdate = false;
|
||||||
|
syncSelectedIndexForNewActionCount();
|
||||||
|
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
state_ = COMPLETE;
|
state_ = COMPLETE;
|
||||||
@@ -349,6 +435,7 @@ void FontDownloadActivity::deleteFamilyAtIndex(int familyIndex) {
|
|||||||
fontInstaller_.refreshRegistry();
|
fontInstaller_.refreshRegistry();
|
||||||
family.installed = false;
|
family.installed = false;
|
||||||
family.hasUpdate = false;
|
family.hasUpdate = false;
|
||||||
|
syncSelectedIndexForNewActionCount();
|
||||||
pendingErrorAction_ = PendingFontAction::None;
|
pendingErrorAction_ = PendingFontAction::None;
|
||||||
errorMessage_.clear();
|
errorMessage_.clear();
|
||||||
|
|
||||||
@@ -377,6 +464,7 @@ void FontDownloadActivity::deleteFamilyAtIndex(int familyIndex) {
|
|||||||
std::string FontDownloadActivity::confirmButtonLabel() const {
|
std::string FontDownloadActivity::confirmButtonLabel() const {
|
||||||
if (families_.empty()) return tr(STR_DOWNLOAD);
|
if (families_.empty()) return tr(STR_DOWNLOAD);
|
||||||
if (isDownloadAllSelected()) return tr(STR_DOWNLOAD);
|
if (isDownloadAllSelected()) return tr(STR_DOWNLOAD);
|
||||||
|
if (isUpdateAllSelected()) return tr(STR_UPDATE);
|
||||||
const auto& family = families_[familyIndexFromList(selectedIndex_)];
|
const auto& family = families_[familyIndexFromList(selectedIndex_)];
|
||||||
if (family.installed && !family.hasUpdate) return tr(STR_DELETE);
|
if (family.installed && !family.hasUpdate) return tr(STR_DELETE);
|
||||||
if (family.hasUpdate) return tr(STR_UPDATE);
|
if (family.hasUpdate) return tr(STR_UPDATE);
|
||||||
@@ -387,6 +475,7 @@ std::string FontDownloadActivity::confirmButtonLabel() const {
|
|||||||
|
|
||||||
void FontDownloadActivity::loop() {
|
void FontDownloadActivity::loop() {
|
||||||
if (state_ == FAMILY_LIST) {
|
if (state_ == FAMILY_LIST) {
|
||||||
|
syncSelectedIndexForNewActionCount();
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
@@ -411,6 +500,9 @@ void FontDownloadActivity::loop() {
|
|||||||
if (isDownloadAllSelected()) {
|
if (isDownloadAllSelected()) {
|
||||||
downloadAll();
|
downloadAll();
|
||||||
requestUpdateAndWait();
|
requestUpdateAndWait();
|
||||||
|
} else if (isUpdateAllSelected()) {
|
||||||
|
updateAll();
|
||||||
|
requestUpdateAndWait();
|
||||||
} else {
|
} else {
|
||||||
const int familyIndex = familyIndexFromList(selectedIndex_);
|
const int familyIndex = familyIndexFromList(selectedIndex_);
|
||||||
const auto& family = families_[familyIndex];
|
const auto& family = families_[familyIndex];
|
||||||
@@ -488,6 +580,7 @@ void FontDownloadActivity::render(RenderLock&&) {
|
|||||||
if (state_ == LOADING_MANIFEST) {
|
if (state_ == LOADING_MANIFEST) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_LOADING_FONT_LIST));
|
renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_LOADING_FONT_LIST));
|
||||||
} else if (state_ == FAMILY_LIST) {
|
} else if (state_ == FAMILY_LIST) {
|
||||||
|
syncSelectedIndexForNewActionCount();
|
||||||
if (families_.empty()) {
|
if (families_.empty()) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_NO_FONTS_AVAILABLE));
|
renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_NO_FONTS_AVAILABLE));
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||||
@@ -498,18 +591,39 @@ void FontDownloadActivity::render(RenderLock&&) {
|
|||||||
Rect{0, contentTop, pageWidth, pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing},
|
Rect{0, contentTop, pageWidth, pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing},
|
||||||
listItemCount(), selectedIndex_,
|
listItemCount(), selectedIndex_,
|
||||||
[this](int index) -> std::string {
|
[this](int index) -> std::string {
|
||||||
if (index == 0) {
|
if (hasDownloadCandidates()) {
|
||||||
return std::string(tr(STR_DOWNLOAD_ALL)) + " (" + formatSize(totalUninstalledSize()) + ")";
|
if (index == 0) {
|
||||||
|
return std::string(tr(STR_DOWNLOAD_ALL)) + " (" + formatSize(totalUninstalledSize()) + ")";
|
||||||
|
}
|
||||||
|
if (hasUpdateCandidates() && index == 1) {
|
||||||
|
return std::string(tr(STR_UPDATE_ALL)) + " (" + formatSize(totalUpdateSize()) + ")";
|
||||||
|
}
|
||||||
|
} else if (hasUpdateCandidates() && index == 0) {
|
||||||
|
return std::string(tr(STR_UPDATE_ALL)) + " (" + formatSize(totalUpdateSize()) + ")";
|
||||||
}
|
}
|
||||||
return families_[familyIndexFromList(index)].name;
|
return families_[familyIndexFromList(index)].name;
|
||||||
},
|
},
|
||||||
nullptr, nullptr,
|
|
||||||
[this](int index) -> std::string {
|
[this](int index) -> std::string {
|
||||||
if (index == 0) return "";
|
if (hasDownloadCandidates()) {
|
||||||
|
if (index == 0) return "";
|
||||||
|
if (hasUpdateCandidates() && index == 1) return "";
|
||||||
|
} else if (hasUpdateCandidates() && index == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return families_[familyIndexFromList(index)].description;
|
||||||
|
},
|
||||||
|
nullptr,
|
||||||
|
[this](int index) -> std::string {
|
||||||
|
if (hasDownloadCandidates()) {
|
||||||
|
if (index == 0) return "";
|
||||||
|
if (hasUpdateCandidates() && index == 1) return "";
|
||||||
|
} else if (hasUpdateCandidates() && index == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
const auto& f = families_[familyIndexFromList(index)];
|
const auto& f = families_[familyIndexFromList(index)];
|
||||||
if (f.hasUpdate) return tr(STR_UPDATE_AVAILABLE);
|
if (f.hasUpdate) return tr(STR_UPDATE_AVAILABLE);
|
||||||
if (f.installed) return tr(STR_INSTALLED);
|
if (f.installed) return tr(STR_INSTALLED);
|
||||||
return f.description;
|
return "";
|
||||||
},
|
},
|
||||||
true);
|
true);
|
||||||
|
|
||||||
@@ -538,6 +652,9 @@ void FontDownloadActivity::render(RenderLock&&) {
|
|||||||
int percentY = barY + metrics.progressBarHeight + metrics.verticalSpacing;
|
int percentY = barY + metrics.progressBarHeight + metrics.verticalSpacing;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, percentY,
|
renderer.drawCenteredText(UI_10_FONT_ID, percentY,
|
||||||
(std::to_string(static_cast<int>(progress * 100)) + "%").c_str());
|
(std::to_string(static_cast<int>(progress * 100)) + "%").c_str());
|
||||||
|
|
||||||
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||||
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
} else if (state_ == COMPLETE) {
|
} else if (state_ == COMPLETE) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_FONT_INSTALLED), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_FONT_INSTALLED), true, EpdFontFamily::BOLD);
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||||
|
|||||||
@@ -69,15 +69,31 @@ class FontDownloadActivity : public Activity {
|
|||||||
int downloadingFamilyIndex_ = 0;
|
int downloadingFamilyIndex_ = 0;
|
||||||
PendingFontAction pendingErrorAction_ = PendingFontAction::None;
|
PendingFontAction pendingErrorAction_ = PendingFontAction::None;
|
||||||
std::string errorMessage_;
|
std::string errorMessage_;
|
||||||
|
bool cancelRequested_ = false;
|
||||||
|
int previousActionCount_ = 0;
|
||||||
|
int lastProgressPercent_ = -1;
|
||||||
|
unsigned long lastProgressUpdateMs_ = 0;
|
||||||
|
|
||||||
void onWifiSelectionComplete(bool success);
|
void onWifiSelectionComplete(bool success);
|
||||||
bool fetchAndParseManifest();
|
bool fetchAndParseManifest();
|
||||||
void downloadFamily(ManifestFamily& family);
|
void downloadFamily(ManifestFamily& family);
|
||||||
void downloadAll();
|
void downloadAll();
|
||||||
bool isDownloadAllSelected() const { return selectedIndex_ == 0 && !families_.empty(); }
|
void updateAll();
|
||||||
int familyIndexFromList(int listIndex) const { return listIndex - 1; }
|
bool isDownloadAllSelected() const { return hasDownloadCandidates() && selectedIndex_ == 0; }
|
||||||
int listItemCount() const { return families_.empty() ? 0 : static_cast<int>(families_.size()) + 1; }
|
bool isUpdateAllSelected() const {
|
||||||
|
if (!hasUpdateCandidates()) return false;
|
||||||
|
return selectedIndex_ == (hasDownloadCandidates() ? 1 : 0);
|
||||||
|
}
|
||||||
|
bool hasDownloadCandidates() const;
|
||||||
|
bool hasUpdateCandidates() const;
|
||||||
|
int actionCount() const { return (hasDownloadCandidates() ? 1 : 0) + (hasUpdateCandidates() ? 1 : 0); }
|
||||||
|
int familyIndexFromList(int listIndex) const {
|
||||||
|
return listIndex > actionCount() - 1 ? listIndex - actionCount() : -1;
|
||||||
|
}
|
||||||
|
int listItemCount() const { return families_.empty() ? 0 : static_cast<int>(families_.size()) + actionCount(); }
|
||||||
size_t totalUninstalledSize() const;
|
size_t totalUninstalledSize() const;
|
||||||
|
size_t totalUpdateSize() const;
|
||||||
|
void syncSelectedIndexForNewActionCount();
|
||||||
|
|
||||||
std::string confirmButtonLabel() const;
|
std::string confirmButtonLabel() const;
|
||||||
void promptDeleteFamily(int familyIndex);
|
void promptDeleteFamily(int familyIndex);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace {
|
|||||||
class FileWriteStream final : public Stream {
|
class FileWriteStream final : public Stream {
|
||||||
public:
|
public:
|
||||||
FileWriteStream(FsFile& file, size_t total, HttpDownloader::ProgressCallback progress)
|
FileWriteStream(FsFile& file, size_t total, HttpDownloader::ProgressCallback progress)
|
||||||
: file_(file), total_(total), progress_(std::move(progress)) {}
|
: file_(file), total_(total), progress_(std::move(progress)), abortRequested_(false) {}
|
||||||
|
|
||||||
size_t write(uint8_t byte) override { return write(&byte, 1); }
|
size_t write(uint8_t byte) override { return write(&byte, 1); }
|
||||||
|
|
||||||
@@ -28,8 +28,11 @@ class FileWriteStream final : public Stream {
|
|||||||
writeOk_ = false;
|
writeOk_ = false;
|
||||||
}
|
}
|
||||||
downloaded_ += written;
|
downloaded_ += written;
|
||||||
if (progress_ && total_ > 0) {
|
if (progress_) {
|
||||||
progress_(downloaded_, total_);
|
if (!progress_(downloaded_, total_)) {
|
||||||
|
abortRequested_ = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
@@ -41,12 +44,14 @@ class FileWriteStream final : public Stream {
|
|||||||
|
|
||||||
size_t downloaded() const { return downloaded_; }
|
size_t downloaded() const { return downloaded_; }
|
||||||
bool ok() const { return writeOk_; }
|
bool ok() const { return writeOk_; }
|
||||||
|
bool aborted() const { return abortRequested_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FsFile& file_;
|
FsFile& file_;
|
||||||
size_t total_;
|
size_t total_;
|
||||||
size_t downloaded_ = 0;
|
size_t downloaded_ = 0;
|
||||||
bool writeOk_ = true;
|
bool writeOk_ = true;
|
||||||
|
bool abortRequested_ = false;
|
||||||
HttpDownloader::ProgressCallback progress_;
|
HttpDownloader::ProgressCallback progress_;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -126,7 +131,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
http.begin(*client, url.c_str());
|
http.begin(*client, url.c_str());
|
||||||
http.setReuse(false);
|
http.setReuse(false);
|
||||||
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||||
http.setTimeout(65535); // max uint16_t (~65s) — HTTPClient::setTimeout takes uint16_t ms
|
http.setTimeout(30000);
|
||||||
http.addHeader("User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION);
|
http.addHeader("User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION);
|
||||||
http.addHeader("Connection", "close");
|
http.addHeader("Connection", "close");
|
||||||
|
|
||||||
@@ -160,23 +165,26 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
// Open file for writing
|
// Open file for writing
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) {
|
if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) {
|
||||||
LOG_ERR("HTTP", "Failed to open file for writing");
|
LOG_ERR("HTTP", "Failed to open file for writing: %s", destPath.c_str());
|
||||||
http.end();
|
http.end();
|
||||||
return FILE_ERROR;
|
return FILE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_DBG("HTTP", "Opened destination file for writing: %s", destPath.c_str());
|
||||||
|
|
||||||
int writeResult = -1;
|
int writeResult = -1;
|
||||||
size_t downloaded = 0;
|
size_t downloaded = 0;
|
||||||
bool writeOk = true;
|
bool writeOk = true;
|
||||||
|
|
||||||
// Let HTTPClient handle chunked decoding and stream body bytes into the file.
|
|
||||||
// For known sizes (Content-Length), we can stream it manually to save RAM!
|
|
||||||
// HTTPClient::writeToStream allocates a 4096-byte chunk on the heap which can
|
|
||||||
// fail (-8 / HTTPC_ERROR_TOO_LESS_RAM) if the heap is fragmented or depleted.
|
|
||||||
if (contentLength > 0) {
|
if (contentLength > 0) {
|
||||||
NetworkClient& stream = http.getStream();
|
NetworkClient& stream = http.getStream();
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
writeResult = 1;
|
writeResult = 1;
|
||||||
|
bool aborted = false;
|
||||||
|
unsigned long lastAvailLog = millis();
|
||||||
|
unsigned long startMs = millis();
|
||||||
|
unsigned long lastProgressPoll = millis();
|
||||||
|
|
||||||
while (http.connected() && downloaded < contentLength) {
|
while (http.connected() && downloaded < contentLength) {
|
||||||
size_t available = stream.available();
|
size_t available = stream.available();
|
||||||
if (available > 0) {
|
if (available > 0) {
|
||||||
@@ -184,31 +192,69 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
if (downloaded + toRead > contentLength) {
|
if (downloaded + toRead > contentLength) {
|
||||||
toRead = contentLength - downloaded;
|
toRead = contentLength - downloaded;
|
||||||
}
|
}
|
||||||
int readSize = stream.readBytes(buffer, toRead);
|
int readSize = stream.readBytes(reinterpret_cast<char*>(buffer), toRead);
|
||||||
if (readSize > 0) {
|
if (readSize > 0) {
|
||||||
if (file.write(buffer, readSize) != static_cast<size_t>(readSize)) {
|
if (file.write(buffer, readSize) != static_cast<size_t>(readSize)) {
|
||||||
|
LOG_ERR("HTTP", "File write failed: wrote %d/%zu bytes to %s", readSize, toRead, destPath.c_str());
|
||||||
writeOk = false;
|
writeOk = false;
|
||||||
writeResult = -1;
|
writeResult = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
downloaded += readSize;
|
downloaded += readSize;
|
||||||
if (progress) progress(downloaded, contentLength);
|
if (progress && !progress(downloaded, contentLength)) {
|
||||||
|
LOG_DBG("HTTP", "Download aborted by callback at %zu/%zu", downloaded, contentLength);
|
||||||
|
aborted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
LOG_ERR("HTTP", "Stream readBytes returned %d after %zu bytes", readSize, downloaded);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (millis() - lastProgressPoll > 100) {
|
||||||
|
if (progress && !progress(downloaded, contentLength)) {
|
||||||
|
LOG_DBG("HTTP", "Download aborted by callback while waiting for data at %zu/%zu", downloaded,
|
||||||
|
contentLength);
|
||||||
|
aborted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastProgressPoll = millis();
|
||||||
|
}
|
||||||
|
if (millis() - lastAvailLog > 2000) {
|
||||||
|
LOG_DBG("HTTP", "Waiting for available data: downloaded=%zu connected=%d elapsed=%lums", downloaded,
|
||||||
|
http.connected(), millis() - startMs);
|
||||||
|
lastAvailLog = millis();
|
||||||
|
}
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aborted) {
|
||||||
|
file.flush();
|
||||||
|
file.close();
|
||||||
|
http.end();
|
||||||
|
client->stop();
|
||||||
|
Storage.remove(destPath.c_str());
|
||||||
|
return ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
if (downloaded != contentLength) {
|
if (downloaded != contentLength) {
|
||||||
|
LOG_ERR("HTTP", "Download size mismatch after loop: got %zu expected %zu", downloaded, contentLength);
|
||||||
writeResult = -1;
|
writeResult = -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Chunked or unknown length fallback
|
|
||||||
FileWriteStream fileStream(file, contentLength, progress);
|
FileWriteStream fileStream(file, contentLength, progress);
|
||||||
writeResult = http.writeToStream(&fileStream);
|
writeResult = http.writeToStream(&fileStream);
|
||||||
downloaded = fileStream.downloaded();
|
downloaded = fileStream.downloaded();
|
||||||
writeOk = fileStream.ok();
|
writeOk = fileStream.ok();
|
||||||
|
if (fileStream.aborted()) {
|
||||||
|
file.flush();
|
||||||
|
file.close();
|
||||||
|
http.end();
|
||||||
|
client->stop();
|
||||||
|
Storage.remove(destPath.c_str());
|
||||||
|
return ABORTED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush before closing to ensure data is written to the SD card.
|
// Flush before closing to ensure data is written to the SD card.
|
||||||
@@ -220,7 +266,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
client->stop();
|
client->stop();
|
||||||
|
|
||||||
if (writeResult < 0) {
|
if (writeResult < 0) {
|
||||||
LOG_ERR("HTTP", "writeToStream error: %d", writeResult);
|
LOG_ERR("HTTP", "writeToStream error: %d (downloaded %zu)", writeResult, downloaded);
|
||||||
Storage.remove(destPath.c_str());
|
Storage.remove(destPath.c_str());
|
||||||
return HTTP_ERROR;
|
return HTTP_ERROR;
|
||||||
}
|
}
|
||||||
@@ -229,7 +275,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
|
|
||||||
// Guard against partial writes even if HTTPClient completes.
|
// Guard against partial writes even if HTTPClient completes.
|
||||||
if (!writeOk) {
|
if (!writeOk) {
|
||||||
LOG_ERR("HTTP", "Write failed during download");
|
LOG_ERR("HTTP", "Write failed during download (downloaded %zu)", downloaded);
|
||||||
Storage.remove(destPath.c_str());
|
Storage.remove(destPath.c_str());
|
||||||
return FILE_ERROR;
|
return FILE_ERROR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
class HttpDownloader {
|
class HttpDownloader {
|
||||||
public:
|
public:
|
||||||
using ProgressCallback = std::function<void(size_t downloaded, size_t total)>;
|
using ProgressCallback = std::function<bool(unsigned int downloaded, unsigned int total)>;
|
||||||
|
|
||||||
enum DownloadError {
|
enum DownloadError {
|
||||||
OK = 0,
|
OK = 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user