A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: use HTTPClient::writeToStream for downloading files from OPDS (#1207)

## Summary

* Refactored `HttpDownloader::downloadToFile` to use `FileWriteStream`
and `HTTPClient::writeToStream`, removing manual chunked downloading
logic, which was error-prone.
* Fixes
https://github.com/crosspoint-reader/crosspoint-reader/issues/632

## Additional Context

* Tested downloading files from OPDS with a size up to 10 mb.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_

authored by

Arthur Tazhitdinov and committed by
GitHub
45a228a6 6ff5fcd9

+71 -48
+4 -1
src/activities/browser/OpdsBookBrowserActivity.cpp
··· 171 171 172 172 if (state == BrowserState::DOWNLOADING) { 173 173 renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, tr(STR_DOWNLOADING)); 174 - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, statusMessage.c_str()); 174 + const auto maxWidth = pageWidth - 40; 175 + // Trim long titles to keep them within the screen bounds. 176 + auto title = renderer.truncatedText(UI_10_FONT_ID, statusMessage.c_str(), maxWidth); 177 + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, title.c_str()); 175 178 if (downloadTotal > 0) { 176 179 const int barWidth = pageWidth - 100; 177 180 constexpr int barHeight = 20;
+67 -44
src/network/HttpDownloader.cpp
··· 9 9 10 10 #include <cstring> 11 11 #include <memory> 12 + #include <utility> 12 13 13 14 #include "CrossPointSettings.h" 14 15 #include "util/UrlUtils.h" 16 + 17 + namespace { 18 + class FileWriteStream final : public Stream { 19 + public: 20 + FileWriteStream(FsFile& file, size_t total, HttpDownloader::ProgressCallback progress) 21 + : file_(file), total_(total), progress_(std::move(progress)) {} 22 + 23 + size_t write(uint8_t byte) override { return write(&byte, 1); } 24 + 25 + size_t write(const uint8_t* buffer, size_t size) override { 26 + // Write-through stream for HTTPClient::writeToStream with progress tracking. 27 + const size_t written = file_.write(buffer, size); 28 + if (written != size) { 29 + writeOk_ = false; 30 + } 31 + downloaded_ += written; 32 + if (progress_ && total_ > 0) { 33 + progress_(downloaded_, total_); 34 + } 35 + return written; 36 + } 37 + 38 + int available() override { return 0; } 39 + int read() override { return -1; } 40 + int peek() override { return -1; } 41 + void flush() override { file_.flush(); } 42 + 43 + size_t downloaded() const { return downloaded_; } 44 + bool ok() const { return writeOk_; } 45 + 46 + private: 47 + FsFile& file_; 48 + size_t total_; 49 + size_t downloaded_ = 0; 50 + bool writeOk_ = true; 51 + HttpDownloader::ProgressCallback progress_; 52 + }; 53 + } // namespace 15 54 16 55 bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) { 17 56 // Use NetworkClientSecure for HTTPS, regular NetworkClient for HTTP ··· 96 135 return HTTP_ERROR; 97 136 } 98 137 99 - const size_t contentLength = http.getSize(); 100 - LOG_DBG("HTTP", "Content-Length: %zu", contentLength); 138 + const int64_t reportedLength = http.getSize(); 139 + const size_t contentLength = reportedLength > 0 ? static_cast<size_t>(reportedLength) : 0; 140 + if (contentLength > 0) { 141 + LOG_DBG("HTTP", "Content-Length: %zu", contentLength); 142 + } else { 143 + LOG_DBG("HTTP", "Content-Length: unknown"); 144 + } 101 145 102 146 // Remove existing file if present 103 147 if (Storage.exists(destPath.c_str())) { ··· 112 156 return FILE_ERROR; 113 157 } 114 158 115 - // Get the stream for chunked reading 116 - NetworkClient* stream = http.getStreamPtr(); 117 - if (!stream) { 118 - LOG_ERR("HTTP", "Failed to get stream"); 119 - file.close(); 159 + // Let HTTPClient handle chunked decoding and stream body bytes into the file. 160 + FileWriteStream fileStream(file, contentLength, progress); 161 + const int writeResult = http.writeToStream(&fileStream); 162 + 163 + file.close(); 164 + http.end(); 165 + 166 + if (writeResult < 0) { 167 + LOG_ERR("HTTP", "writeToStream error: %d", writeResult); 120 168 Storage.remove(destPath.c_str()); 121 - http.end(); 122 169 return HTTP_ERROR; 123 170 } 124 171 125 - // Download in chunks 126 - uint8_t buffer[DOWNLOAD_CHUNK_SIZE]; 127 - size_t downloaded = 0; 128 - const size_t total = contentLength > 0 ? contentLength : 0; 172 + const size_t downloaded = fileStream.downloaded(); 173 + LOG_DBG("HTTP", "Downloaded %zu bytes", downloaded); 129 174 130 - while (http.connected() && (contentLength == 0 || downloaded < contentLength)) { 131 - const size_t available = stream->available(); 132 - if (available == 0) { 133 - delay(1); 134 - continue; 135 - } 175 + // Guard against partial writes even if HTTPClient completes. 176 + if (!fileStream.ok()) { 177 + LOG_ERR("HTTP", "Write failed during download"); 178 + Storage.remove(destPath.c_str()); 179 + return FILE_ERROR; 180 + } 136 181 137 - const size_t toRead = available < DOWNLOAD_CHUNK_SIZE ? available : DOWNLOAD_CHUNK_SIZE; 138 - const size_t bytesRead = stream->readBytes(buffer, toRead); 139 - 140 - if (bytesRead == 0) { 141 - break; 142 - } 143 - 144 - const size_t written = file.write(buffer, bytesRead); 145 - if (written != bytesRead) { 146 - LOG_ERR("HTTP", "Write failed: wrote %zu of %zu bytes", written, bytesRead); 147 - file.close(); 148 - Storage.remove(destPath.c_str()); 149 - http.end(); 150 - return FILE_ERROR; 151 - } 152 - 153 - downloaded += bytesRead; 154 - 155 - if (progress && total > 0) { 156 - progress(downloaded, total); 157 - } 182 + if (contentLength == 0 && downloaded == 0) { 183 + LOG_ERR("HTTP", "Download failed: no data received"); 184 + Storage.remove(destPath.c_str()); 185 + return HTTP_ERROR; 158 186 } 159 - 160 - file.close(); 161 - http.end(); 162 - 163 - LOG_DBG("HTTP", "Downloaded %zu bytes", downloaded); 164 187 165 188 // Verify download size if known 166 189 if (contentLength > 0 && downloaded != contentLength) {
-3
src/network/HttpDownloader.h
··· 38 38 */ 39 39 static DownloadError downloadToFile(const std::string& url, const std::string& destPath, 40 40 ProgressCallback progress = nullptr); 41 - 42 - private: 43 - static constexpr size_t DOWNLOAD_CHUNK_SIZE = 1024; 44 41 };