From e41d00fca1db1ad1d3a9d4355e8a37cb11bc36ca Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Sat, 14 Jun 2025 10:49:43 +0300 Subject: [PATCH 01/11] bookstatuswidget: minor fixes (#13943) --- frontend/ui/widget/bookstatuswidget.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/ui/widget/bookstatuswidget.lua b/frontend/ui/widget/bookstatuswidget.lua index 73c58ea05..8daf09981 100644 --- a/frontend/ui/widget/bookstatuswidget.lua +++ b/frontend/ui/widget/bookstatuswidget.lua @@ -221,6 +221,7 @@ function BookStatusWidget:setStar(num) local stars_group = HorizontalGroup:new{ align = "center" } local row = {} if num then + num = (num == 1 and self.summary.rating == 1) and 0 or num self.summary.rating = num self.updated = true @@ -454,7 +455,7 @@ function BookStatusWidget:genSummaryGroup(width) padding = text_padding, parent = self, readonly = self.readonly, - hint = _("A few words about the book"), + hint = not self.readonly and _("A few words about the book"), } table.insert(self.layout, {self.input_note}) From b8369d743a93967d71d6036d9c91827d276e8303 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 14 Jun 2025 21:14:25 +0200 Subject: [PATCH 02/11] tests: fix newsdownloader tests isolation (#13954) Cf. https://github.com/koreader/koreader/pull/13953#issuecomment-2972614736. --- spec/unit/newsdownloader_spec.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/unit/newsdownloader_spec.lua b/spec/unit/newsdownloader_spec.lua index feb3b5f88..d98f2b4a0 100644 --- a/spec/unit/newsdownloader_spec.lua +++ b/spec/unit/newsdownloader_spec.lua @@ -6,8 +6,11 @@ describe("NewsDownloader module", function() local NewsDownloader setup(function() - package.path = "plugins/newsdownloader.koplugin/?.lua;" .. package.path + local plugin_path = "plugins/newsdownloader.koplugin" + package.path = plugin_path.."/?.lua;" .. package.path NewsDownloader = require("main") + NewsDownloader.path = plugin_path + NewsDownloader:lazyInitialization() end) describe("RSS feed parsing", function() From b373941a6fd64eac6e298eede4f2d7dcfa157f2e Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 14 Jun 2025 12:36:13 +0200 Subject: [PATCH 03/11] doc: update building instructions - remove unnecessary LUA requirements already provided by the build system - clarify necessary vs optional requirements - simplify macOS instructions around the minimum targeted deployment version --- doc/Building.md | 66 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/doc/Building.md b/doc/Building.md index 7f22a01a4..bfd47937b 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -14,9 +14,7 @@ You can skip most of the following instructions if desired, and use our premade To get and compile the source you must have: - `autoconf`: version greater than 2.64 - `bash`: version 4.0 or greater -- `ccache`: optional, but recommended - `cmake`: version 3.17.5 or greater -- `gettext` - `gcc/g++` or `clang/clang++`: with C11 & C++17 support - `git` - `make`: version 4.1 or greater @@ -29,33 +27,45 @@ To get and compile the source you must have: - `unzip` - `wget` -For testing: -- `busted` -- `lua`: version 5.1 -- `luarocks` +For running the emulator / tests: - `SDL2` +Optional: +- `7z`: for packing releases +- `ccache`: recommended for faster recompilation times +- `gettext`: for updating translations +- `luacheck`: for linting the codebase with `./kodev check` + ### Alpine Linux Install the prerequisites using apk: ``` sudo apk add autoconf automake bash cmake coreutils curl diffutils \ - findutils g++ gcc gettext-dev git grep gzip libtool linux-headers \ - lua5.1-busted luarocks5.1 make meson nasm ninja-build patch perl \ - pkgconf procps-ng sdl2 tar unzip wget + findutils g++ gcc git grep gzip libtool linux-headers make meson \ + nasm ninja-build patch perl pkgconf procps-ng sdl2 tar unzip wget ``` **Note:** don't forget to add `/usr/lib/ninja-build/bin` to `$PATH` so the real ninja is used (and not the binary provided by samurai). +Optional: +``` +sudo apk add 7zip ccache gettext-dev luacheck +``` + ### Arch Linux Install the prerequisites using pacman: ``` run0 pacman -S base-devel ca-certificates cmake gcc-libs git \ - lua51-busted luarocks meson nasm ninja perl sdl2 unzip wget + meson nasm ninja perl sdl2 unzip wget +``` + +Optional: +``` +run0 pacman -S 7zip ccache luacheck ``` ### Debian/Ubuntu @@ -63,9 +73,9 @@ run0 pacman -S base-devel ca-certificates cmake gcc-libs git \ Install the prerequisites using APT: ``` -sudo apt-get install autoconf automake build-essential ca-certificates cmake \ - gcc-multilib gettext git libsdl2-2.0-0 libtool libtool-bin lua-busted \ - lua5.1 luarocks meson nasm ninja-build patch perl pkg-config unzip wget +sudo apt install autoconf automake build-essential ca-certificates cmake \ + gcc-multilib git libsdl2-2.0-0 libtool libtool-bin meson nasm ninja-build \ + patch perl pkg-config unzip wget ``` **Note:** Debian distributions might need `meson` to be installed from `bookworm-backports`) because the version provided by the default repositories is too old: @@ -75,19 +85,28 @@ sudo apt install meson/bookworm-backports The bookworm-backports repository was already included on Linux Mint Dedian Edition 6. Otherwise, follow full up-to-date instructions from here: https://wiki.debian.org/Backports. +Optional: +``` +sudo apt install ccache gettext lua-check p7zip-full +``` + ### Fedora/Red Hat Install the prerequisites using DNF: ``` -sudo dnf install autoconf automake cmake gettext gcc gcc-c++ git libtool \ - lua5.1 luarocks meson nasm ninja-build patch perl-FindBin procps-ng \ - SDL2 unzip wget +sudo dnf install autoconf automake cmake gcc gcc-c++ git libtool meson nasm \ + ninja-build patch perl-FindBin procps-ng SDL2 unzip wget ``` -And for busted: +Optional: ``` -luarocks --lua-version=5.1 --local install busted +sudo dnf install ccache gettext p7zip +``` +And for luacheck: +``` +sudo dnf install lua-argparse lua-filesystem luarocks +luarocks install luacheck ``` ### macOS @@ -96,7 +115,7 @@ Install the prerequisites using [Homebrew](https://brew.sh/): ``` brew install autoconf automake bash binutils cmake coreutils findutils \ - gnu-getopt libtool make meson nasm ninja p7zip pkg-config sdl2 util-linux + gnu-getopt libtool make meson nasm ninja pkg-config sdl2 util-linux ``` You will also have to ensure Homebrew's findutils, gnu-getopt, make & util-linux are in your path, e.g., via @@ -104,12 +123,15 @@ You will also have to ensure Homebrew's findutils, gnu-getopt, make & util-linux export PATH="$(brew --prefix)/opt/findutils/libexec/gnubin:$(brew --prefix)/opt/gnu-getopt/bin:$(brew --prefix)/opt/make/libexec/gnubin:$(brew --prefix)/opt/util-linux/bin:${PATH}" ``` -*Note:* With current XCode versions, you *will* need to set a minimum deployment version higher than `10.04`. Otherwise, you'll hit various linking errors related to missing unwinding libraries/symbols. -On Mojave, `10.09` has been known to behave with XCode 10, And `10.14` with XCode 11. When in doubt, go with your current macOS version. +Optional: +``` +brew install ccache gettext luacheck p7zip +``` + +*Note:* You can override the default targeted minimum deployment version by setting `MACOSX_DEPLOYMENT_TARGET`: ``` export MACOSX_DEPLOYMENT_TARGET=10.09 ``` -*Note:* On Catalina (10.15), you will currently *NOT* want to deploy for `10.15`, as [XCode is currently broken in that configuration](https://forums.developer.apple.com/thread/121887)! (i.e., deploy for `10.14` instead). ## Getting the source From ce8d338d949eb962e10c877d7b0271f5e4b2bcf4 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 14 Jun 2025 12:41:36 +0200 Subject: [PATCH 04/11] kodev: update `activate` command There's no need for messing with the LUA environment using luarocks anymore, since all testing requirements are already provided by the build system, and luacheck can be run with a different LUA version than the one being checked. --- kodev | 1 - 1 file changed, 1 deletion(-) diff --git a/kodev b/kodev index b230bc1de..d492c1f0a 100755 --- a/kodev +++ b/kodev @@ -311,7 +311,6 @@ function kodev-activate() { parse_options '' '' '0' "$@" info "adding ${CURDIR} to \$PATH..." export PATH="${PATH}:${CURDIR}" - eval "$(luarocks --lua-version=5.1 path --bin)" exec "${SHELL}" } From 4dc4339bc7e4c767954f4ebb70a00ea33b52de18 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 14 Jun 2025 12:57:20 +0200 Subject: [PATCH 05/11] doc: update testing instructions --- doc/Unit_tests.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/doc/Unit_tests.md b/doc/Unit_tests.md index 4aa357c42..5a62df088 100644 --- a/doc/Unit_tests.md +++ b/doc/Unit_tests.md @@ -1,16 +1,14 @@ # Unit Tests -Unit tests are automatically performed using [busted](http://olivinelabs.com/busted/). It depends on `luarocks`. +Unit tests are written for [busted](https://lunarmodules.github.io/busted/) +(a version is automatically provided by the build system), and executed in +parallel with the meson test runner. -To grab busted, install the same version [as used in the automated tests](https://github.com/koreader/koreader/blob/master/.ci/install.sh). At the time of writing that is 2.0.0-1: -```bash -mkdir $HOME/.luarocks -cp /etc/luarocks/config.lua $HOME/.luarocks/config.lua -echo "wrap_bin_scripts = false" >> $HOME/.luarocks/config.lua -luarocks --local install busted 2.0.0-1 -``` -Then you can set up the environment variables with `./kodev activate`. +You can run them with `./kodev test`, examples: -If all went well, you'll now be able to run `./kodev test front` (for the frontend) or `./kodev test base` (for koreader-base). +- to run all tests (frontend & base): `./kodev test` +- frontend only: `./kodev test front` +- to run one specific base test: `./kodev test base util` +- to list available tests: `./kodev test -l` -You can run individual tests using `./kodev test front testname_spec.lua`. +Check the output of `./kodev test -h` for the full usage. From 72573fb39314d8def42e3fbfb1afd4e0f1a216bd Mon Sep 17 00:00:00 2001 From: Emre Date: Tue, 17 Jun 2025 12:27:11 +0200 Subject: [PATCH 06/11] [Calibre] React to SEND_BOOK_METADATA opcode (#13920) Fixes #12922. --- plugins/calibre.koplugin/metadata.lua | 11 +++++++++-- plugins/calibre.koplugin/wireless.lua | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/calibre.koplugin/metadata.lua b/plugins/calibre.koplugin/metadata.lua index 8ca7a9b0e..0d4e72d59 100644 --- a/plugins/calibre.koplugin/metadata.lua +++ b/plugins/calibre.koplugin/metadata.lua @@ -134,12 +134,19 @@ end -- add a book to our books table function CalibreMetadata:addBook(book) -- prevent duplicate entries + if not self:updateBook(book) then + table.insert(self.books, #self.books + 1, slim(book)) + end +end + +-- update a book in our books table if exists +function CalibreMetadata:updateBook(book) local _, index = self:getBookUuid(book.lpath) if index then self.books[index] = slim(book) - else - table.insert(self.books, #self.books + 1, slim(book)) + return true end + return false end -- remove a book from our books table diff --git a/plugins/calibre.koplugin/wireless.lua b/plugins/calibre.koplugin/wireless.lua index c972ff947..a0a4f8293 100644 --- a/plugins/calibre.koplugin/wireless.lua +++ b/plugins/calibre.koplugin/wireless.lua @@ -430,6 +430,8 @@ function CalibreWireless:onReceiveJSON(data) self:getBookCount(arg) elseif opcode == OPCODES.SEND_BOOK then self:sendBook(arg) + elseif opcode == OPCODES.SEND_BOOK_METADATA then + self:sendBookMetadata(arg) elseif opcode == OPCODES.DELETE_BOOK then self:deleteBook(arg) elseif opcode == OPCODES.GET_BOOK_FILE_SEGMENT then @@ -707,6 +709,16 @@ function CalibreWireless:sendBook(arg) end end +function CalibreWireless:sendBookMetadata(arg) + logger.dbg("SEND_BOOK_METADATA", arg) + + CalibreMetadata:updateBook(arg.data) + + if (arg.index + 1) == arg.count then + CalibreMetadata:saveBookList() + end +end + function CalibreWireless:deleteBook(arg) logger.dbg("DELETE_BOOK", arg) self:sendJsonData('OK', {}) From e9e2de27c51a210c0aa9fc2d20b8a887468a4f5a Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Thu, 19 Jun 2025 08:15:48 +0300 Subject: [PATCH 07/11] readcollection: minor speedup (#13958) --- frontend/readcollection.lua | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/frontend/readcollection.lua b/frontend/readcollection.lua index 910bb61cf..3f10d7243 100644 --- a/frontend/readcollection.lua +++ b/frontend/readcollection.lua @@ -148,10 +148,12 @@ end function ReadCollection:addRemoveItemMultiple(file, collections_to_add) file = ffiUtil.realpath(file) or file + local attr for coll_name, coll in pairs(self.coll) do if collections_to_add[coll_name] then if not coll[file] then - coll[file] = buildEntry(file, self:getCollectionNextOrder(coll_name)) + attr = attr or lfs.attributes(file) + coll[file] = buildEntry(file, self:getCollectionNextOrder(coll_name), attr) end else if coll[file] then @@ -165,10 +167,12 @@ function ReadCollection:addItemsMultiple(files, collections_to_add) local count = 0 for file in pairs(files) do file = ffiUtil.realpath(file) or file + local attr for coll_name in pairs(collections_to_add) do local coll = self.coll[coll_name] if not coll[file] then - coll[file] = buildEntry(file, self:getCollectionNextOrder(coll_name)) + attr = attr or lfs.attributes(file) + coll[file] = buildEntry(file, self:getCollectionNextOrder(coll_name), attr) count = count + 1 end end @@ -290,20 +294,22 @@ function ReadCollection:updateCollectionFromFolder(collection_name, folders) local count = 0 if folders then local coll = self.coll[collection_name] - local filetypes = util.tableGetValue(self.coll_settings[collection_name], "filter", "add", "filetype") + local filetypes + local str = util.tableGetValue(self.coll_settings[collection_name], "filter", "add", "filetype") + if str then -- string of comma separated file types + filetypes = {} + for filetype in util.gsplit(str, ",") do + filetypes[util.trim(filetype)] = true + end + end local function add_item_callback(file, f, attr) file = ffiUtil.realpath(file) - local does_match = coll[file] == nil and not util.stringStartsWith(f, "._") and DocumentRegistry:hasProvider(file) + local does_match = coll[file] == nil and not util.stringStartsWith(f, "._") + and (filetypes or DocumentRegistry:hasProvider(file)) if does_match then if filetypes then - does_match = false local _, fileext = require("apps/filemanager/filemanagerutil").splitFileNameType(file) - for filetype in util.gsplit(filetypes, ",") do - if util.trim(filetype) == fileext then - does_match = true - break - end - end + does_match = filetypes[fileext] end if does_match then self:addItem(file, collection_name, attr) From 3f19a2a05e2035c5cf37e3a33582c162dfbaea6f Mon Sep 17 00:00:00 2001 From: Linus045 Date: Thu, 19 Jun 2025 10:15:40 +0200 Subject: [PATCH 08/11] feat: Adds progress bar to cloud storage downloads (#13650) --- frontend/apps/cloudstorage/cloudstorage.lua | 31 ++-- frontend/apps/cloudstorage/dropbox.lua | 4 +- frontend/apps/cloudstorage/dropboxapi.lua | 10 +- frontend/apps/cloudstorage/ftp.lua | 9 +- frontend/apps/cloudstorage/webdav.lua | 11 +- frontend/apps/cloudstorage/webdavapi.lua | 10 +- frontend/socketutil.lua | 18 ++ frontend/ui/widget/progressbardialog.lua | 174 ++++++++++++++++++++ 8 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 frontend/ui/widget/progressbardialog.lua diff --git a/frontend/apps/cloudstorage/cloudstorage.lua b/frontend/apps/cloudstorage/cloudstorage.lua index b13ddf7c6..0c918b4cc 100644 --- a/frontend/apps/cloudstorage/cloudstorage.lua +++ b/frontend/apps/cloudstorage/cloudstorage.lua @@ -12,6 +12,7 @@ local LuaSettings = require("luasettings") local Menu = require("ui/widget/menu") local NetworkMgr = require("ui/network/manager") local PathChooser = require("ui/widget/pathchooser") +local ProgressbarDialog = require("ui/widget/progressbardialog") local UIManager = require("ui/uimanager") local WebDav = require("apps/cloudstorage/webdav") local lfs = require("libs/libkoreader-lfs") @@ -213,19 +214,29 @@ end function CloudStorage:downloadFile(item) local function startDownloadFile(unit_item, address, username, password, path_dir, callback_close) + local progressbar_dialog = ProgressbarDialog:new { + title = _("Downloading…"), + subtitle = unit_item.text, + progress_max = unit_item.filesize, + } + UIManager:scheduleIn(1, function() - if self.type == "dropbox" then - DropBox:downloadFile(unit_item, password, path_dir, callback_close) - elseif self.type == "ftp" then - Ftp:downloadFile(unit_item, address, username, password, path_dir, callback_close) - elseif self.type == "webdav" then - WebDav:downloadFile(unit_item, address, username, password, path_dir, callback_close) + local progress_callback = function(progress) + progressbar_dialog:reportProgress(progress) end + + if self.type == "dropbox" then + DropBox:downloadFile(unit_item, password, path_dir, callback_close, progress_callback) + elseif self.type == "ftp" then + Ftp:downloadFile(unit_item, address, username, password, path_dir, callback_close, nil) + elseif self.type == "webdav" then + WebDav:downloadFile(unit_item, address, username, password, path_dir, callback_close, progress_callback) + end + + progressbar_dialog:close() end) - UIManager:show(InfoMessage:new{ - text = _("Downloading. This might take a moment."), - timeout = 1, - }) + + progressbar_dialog:show() end local function createTitle(filename_orig, filesize, filename, path) -- title for ButtonDialog diff --git a/frontend/apps/cloudstorage/dropbox.lua b/frontend/apps/cloudstorage/dropbox.lua index c3c3d8968..6d6eaf01e 100644 --- a/frontend/apps/cloudstorage/dropbox.lua +++ b/frontend/apps/cloudstorage/dropbox.lua @@ -24,8 +24,8 @@ function DropBox:showFiles(url, password) return DropBoxApi:showFiles(url, password) end -function DropBox:downloadFile(item, password, path, callback_close) - local code_response = DropBoxApi:downloadFile(item.url, password, path) +function DropBox:downloadFile(item, password, path, callback_close, progress_callback) + local code_response = DropBoxApi:downloadFile(item.url, password, path, progress_callback) if code_response == 200 then local __, filename = util.splitFilePathName(path) if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then diff --git a/frontend/apps/cloudstorage/dropboxapi.lua b/frontend/apps/cloudstorage/dropboxapi.lua index 6f06da304..0364b594f 100644 --- a/frontend/apps/cloudstorage/dropboxapi.lua +++ b/frontend/apps/cloudstorage/dropboxapi.lua @@ -106,9 +106,13 @@ function DropBoxApi:fetchListFolders(path, token) logger.warn("DropBoxApi: error:", result_response) end -function DropBoxApi:downloadFile(path, token, local_path) +function DropBoxApi:downloadFile(path, token, local_path, progress_callback) local data1 = "{\"path\": \"" .. path .. "\"}" socketutil:set_timeout(socketutil.FILE_BLOCK_TIMEOUT, socketutil.FILE_TOTAL_TIMEOUT) + + local handle = ltn12.sink.file(io.open(local_path, "w")) + handle = socketutil.chainSinkWithProgressCallback(handle, progress_callback) + local code, headers, status = socket.skip(1, http.request{ url = API_DOWNLOAD_FILE, method = "GET", @@ -116,7 +120,7 @@ function DropBoxApi:downloadFile(path, token, local_path) ["Authorization"] = "Bearer ".. token, ["Dropbox-API-Arg"] = data1, }, - sink = ltn12.sink.file(io.open(local_path, "w")), + sink = handle, }) socketutil:reset_timeout() if code ~= 200 then @@ -195,6 +199,7 @@ function DropBoxApi:listFolder(path, token, folder_mode) table.insert(dropbox_file, { text = text, mandatory = util.getFriendlySize(files.size), + filesize = files.size, url = files.path_display, type = tag, }) @@ -221,6 +226,7 @@ function DropBoxApi:listFolder(path, token, folder_mode) mandatory = files.mandatory, url = files.url, type = files.type, + filesize = files.filesize, }) end return dropbox_list diff --git a/frontend/apps/cloudstorage/ftp.lua b/frontend/apps/cloudstorage/ftp.lua index 5cb47c5e2..06239521e 100644 --- a/frontend/apps/cloudstorage/ftp.lua +++ b/frontend/apps/cloudstorage/ftp.lua @@ -8,6 +8,7 @@ local ReaderUI = require("apps/reader/readerui") local UIManager = require("ui/uimanager") local ltn12 = require("ltn12") local logger = require("logger") +local socketutil = require("socketutil") local util = require("util") local _ = require("gettext") local T = require("ffi/util").template @@ -19,7 +20,7 @@ function Ftp:run(address, user, pass, path) return FtpApi:listFolder(url, path) end -function Ftp:downloadFile(item, address, user, pass, path, callback_close) +function Ftp:downloadFile(item, address, user, pass, path, callback_close, progress_callback) local url = FtpApi:generateUrl(address, util.urlEncode(user), util.urlEncode(pass)) .. item.url logger.dbg("downloadFile url", url) path = util.fixUtf8(path, "_") @@ -30,7 +31,11 @@ function Ftp:downloadFile(item, address, user, pass, path, callback_close) }) return end - local response = FtpApi:ftpGet(url, "retr", ltn12.sink.file(file)) + + local handle = ltn12.sink.file(file) + handle = socketutil.chainSinkWithProgressCallback(handle, progress_callback) + + local response = FtpApi:ftpGet(url, "retr", handle) if response ~= nil then local __, filename = util.splitFilePathName(path) if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then diff --git a/frontend/apps/cloudstorage/webdav.lua b/frontend/apps/cloudstorage/webdav.lua index be2a23e85..69ce6c808 100644 --- a/frontend/apps/cloudstorage/webdav.lua +++ b/frontend/apps/cloudstorage/webdav.lua @@ -17,8 +17,15 @@ function WebDav:run(address, user, pass, path, folder_mode) return WebDavApi:listFolder(address, user, pass, path, folder_mode) end -function WebDav:downloadFile(item, address, username, password, local_path, callback_close) - local code_response = WebDavApi:downloadFile(WebDavApi:getJoinedPath(address, item.url), username, password, local_path) +function WebDav:downloadFile(item, address, username, password, local_path, callback_close, progress_callback) + local code_response = WebDavApi:downloadFile( + WebDavApi:getJoinedPath(address, item.url), + username, + password, + local_path, + progress_callback + ) + if code_response == 200 then local __, filename = util.splitFilePathName(local_path) if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then diff --git a/frontend/apps/cloudstorage/webdavapi.lua b/frontend/apps/cloudstorage/webdavapi.lua index 3865d6163..5eff429bc 100644 --- a/frontend/apps/cloudstorage/webdavapi.lua +++ b/frontend/apps/cloudstorage/webdavapi.lua @@ -162,13 +162,17 @@ function WebDavApi:listFolder(address, user, pass, folder_path, folder_mode) return webdav_list end -function WebDavApi:downloadFile(file_url, user, pass, local_path) +function WebDavApi:downloadFile(file_url, user, pass, local_path, progress_callback) socketutil:set_timeout(socketutil.FILE_BLOCK_TIMEOUT, socketutil.FILE_TOTAL_TIMEOUT) logger.dbg("WebDavApi: downloading file: ", file_url) - local code, headers, status = socket.skip(1, http.request{ + + local handle = ltn12.sink.file(io.open(local_path, "w")) + handle = socketutil.chainSinkWithProgressCallback(handle, progress_callback) + + local code, headers, status = socket.skip(1, http.request { url = file_url, method = "GET", - sink = ltn12.sink.file(io.open(local_path, "w")), + sink = handle, user = user, password = pass, }) diff --git a/frontend/socketutil.lua b/frontend/socketutil.lua index e11745ae1..1c2c338b2 100644 --- a/frontend/socketutil.lua +++ b/frontend/socketutil.lua @@ -172,4 +172,22 @@ function socketutil.redact_request(request) return safe_request end +function socketutil.chainSinkWithProgressCallback(sink, progressCallback) + if sink == nil or progressCallback == nil then + return sink + end + + local downloaded_bytes = 0 + local progress_reporter_filter = function(chunk, err) + if chunk ~= nil then + -- accumulate the downloaded bytes so we don't need to check the actual file every time + downloaded_bytes = downloaded_bytes + chunk:len() + progressCallback(downloaded_bytes) + end + return chunk, err + end + + return ltn12.sink.chain(progress_reporter_filter, sink) +end + return socketutil diff --git a/frontend/ui/widget/progressbardialog.lua b/frontend/ui/widget/progressbardialog.lua new file mode 100644 index 000000000..ca58f8994 --- /dev/null +++ b/frontend/ui/widget/progressbardialog.lua @@ -0,0 +1,174 @@ +--[[-- +A dialog that shows a progress bar with a title and subtitle. + +@usage +local progressbar_dialog = ProgressbarDialog:new { + title = nil, + subtitle = nil, + progress_max = nil + refresh_time_seconds = 3, +} +Note: provide at least one of title, subtitle or progress_max +@param title string the title of the dialog +@param subtitle string the subtitle of the dialog +@param progress_max number the maximum progress (e.g. size of the file in bytes for file downloads) + reportProgress() should be called with the current + progress (value between 0-progress_max) to update the progress bar + optional: if `progress_max` is nil, the progress bar will be hidden +@param refresh_time_seconds number refresh time in seconds + +-- Attach progress callback and call show() +progressbar_dialog:show() + +-- Call close() when download is done +progressbar_dialog:close() + +-- To report progress, you can either: +-- manually call reportProgress with the current progress (value between 0-progress_max) +progressbar_dialog:reportProgress( ) + +-- or when using luasocket sinks, chain the callback: +local sink = ltn12.sink.file(io.open(local_path, "w")) +sink = socketutil.chainSinkWithProgressCallback(sink, function(progress) + progressbar_dialog:reportProgress(progress) +end) +--]] + +local Blitbuffer = require("ffi/blitbuffer") +local Device = require("device") +local Font = require("ui/font") +local FrameContainer = require("ui/widget/container/framecontainer") +local ProgressWidget = require("ui/widget/progresswidget") +local Size = require("ui/size") +local TextWidget = require("ui/widget/textwidget") +local UIManager = require("ui/uimanager") +local VerticalGroup = require("ui/widget/verticalgroup") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local dbg = require("dbg") +local time = require("ui/time") +local Screen = Device.screen + +local ProgressbarDialog = WidgetContainer:extend { + refresh_time_seconds = 3, +} + +function ProgressbarDialog:init() + self.align = "center" + self.dimen = Screen:getSize() + + self.progress_bar_visible = self.progress_max ~= nil and self.progress_max > 0 + + -- used for internal state + self.last_redraw_time_ms = 0 + + -- create the dialog + local progress_bar_width = Screen:getWidth() - Screen:scaleBySize(80) + local progress_bar_height = Screen:scaleBySize(18) + + -- only add relevant widgets + local vertical_group = VerticalGroup:new {} + if self.title then + vertical_group[#vertical_group + 1] = TextWidget:new { + text = self.title or "", + face = Font:getFace("ffont"), + bold = true, + max_width = progress_bar_width, + } + end + if self.subtitle then + vertical_group[#vertical_group + 1] = TextWidget:new { + text = self.subtitle or "", + face = Font:getFace("smallffont"), + max_width = progress_bar_width, + } + end + if self.progress_bar_visible then + self.progress_bar = ProgressWidget:new { + width = progress_bar_width, + height = progress_bar_height, + padding = Size.padding.large, + margin = Size.margin.tiny, + percentage = 0, + } + vertical_group[#vertical_group + 1] = self.progress_bar + end + + self[1] = FrameContainer:new { + radius = Size.radius.window, + bordersize = Size.border.window, + padding = Size.padding.large, + background = Blitbuffer.COLOR_WHITE, + vertical_group + } +end + +dbg:guard(ProgressbarDialog, "init", + nil, + function(self) + assert(self.progress_max == nil or + (type(self.progress_max) == "number" and self.progress_max > 0), + "Wrong self.progress_max type (expected nil or number greater than 0), value was: " .. + tostring(self.progress_max)) + assert(type(self.refresh_time_seconds) == "number" and self.refresh_time_seconds > 0, + "Wrong self.refresh_time_seconds type (expected number greater than 0), value was: " .. + tostring(self.refresh_time_seconds)) + assert(self.title == nil or type(self.title) == "string", + "Wrong title type (expected nil or string), value was of type: " .. type(self.title)) + assert(self.subtitle == nil or type(self.subtitle) == "string", + "Wrong subtitle type (expected nil or string), value was of type: " .. type(self.subtitle)) + assert(self.title or self.subtitle or self.progress_max, + "No values defined, dialog would be empty. Please provide at least one of title, subtitle or progress_max") + end) + +--- Updates the UI to show the current percentage of the progress bar when needed. +function ProgressbarDialog:redrawProgressbarIfNeeded() + -- grab the current percentage from the progress bar + local current_percentage = self.progress_bar.percentage + + -- if we are at 100% always redraw + if current_percentage >= 1 then + self:redrawProgressbar() + return + end + + -- check if enough time has passed + local current_time_ms = time.now() + local time_delta_ms = current_time_ms - self.last_redraw_time_ms + local refresh_time_ms = self.refresh_time_seconds * 1000 * 1000 + if time_delta_ms >= refresh_time_ms then + self.last_redraw_time_ms = current_time_ms + self:redrawProgressbar() + end +end + +function ProgressbarDialog:redrawProgressbar() + --UI is not updating during file download so force an update + UIManager:setDirty(self, function() return "fast", self.progress_bar.dimen end) + UIManager:forceRePaint() +end + +--- Used to notify about a progress update. +-- @param progress number the current progress (e.g. size of the file in bytes for file downloads) +function ProgressbarDialog:reportProgress(progress) + if not self.progress_bar_visible then + return + end + + -- set percentage of progress bar internally, this does not yet update the screen element + self.progress_bar:setPercentage(progress / self.progress_max) + + -- actually draw the progress bar update + self:redrawProgressbarIfNeeded() +end + +--- Opens dialog. +function ProgressbarDialog:show() + UIManager:show(self, "ui") +end + +---- Closes dialog. +function ProgressbarDialog:close() + UIManager:close(self, "ui") +end + +return ProgressbarDialog From f2c678c205bdf8b6ef0973c22abe9ed90199a220 Mon Sep 17 00:00:00 2001 From: hugleo Date: Thu, 19 Jun 2025 10:06:49 -0300 Subject: [PATCH 09/11] bump luajit-launcher (#13924) Removed ONYX Book GO COLOR 7 From Onyx/Qualcomm EDP (https://github.com/koreader/android-luajit-launcher/commit/b0fd95f2897ca6afae127d4f8b0bc5c15acc7913) Remove Boox Go7 from EDP driver and add support for Hisense Touch Lite (https://github.com/koreader/android-luajit-launcher/commit/3468bdbf0dd514c869c1c5066f2914c2e59336c4) JNI: always use the C interface (https://github.com/koreader/android-luajit-launcher/commit/473ccd5a93167f1c0527d7f810ae4adff2d3fc98) Add Onyx Boox Go7 and Palma to ADB for improved lighting (https://github.com/koreader/android-luajit-launcher/pull/560) --- platform/android/luajit-launcher | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/luajit-launcher b/platform/android/luajit-launcher index ab7a280f4..b0fd95f28 160000 --- a/platform/android/luajit-launcher +++ b/platform/android/luajit-launcher @@ -1 +1 @@ -Subproject commit ab7a280f4bc35fd3641a76e721bae3ea3b453a95 +Subproject commit b0fd95f2897ca6afae127d4f8b0bc5c15acc7913 From bfe329dfad6007f9be1a3c6bca7becacabb3220e Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Thu, 19 Jun 2025 23:23:29 +0200 Subject: [PATCH 10/11] gettext: filter out more empty translations --- frontend/gettext.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/gettext.lua b/frontend/gettext.lua index ba7d6dd09..896a63b73 100644 --- a/frontend/gettext.lua +++ b/frontend/gettext.lua @@ -200,7 +200,7 @@ function GetText_mt.__index.changeLang(new_lang) local n = tonumber(k:match("msgstr%[([0-9]+)%]")) local msgstr = v - if n and msgstr then + if n and msgstr and msgstr ~= "" then addTranslation(data.msgctxt, data.msgid, msgstr, n) end end From 0594f619c3d991183408ab6d6d658ba91d8c2d64 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Thu, 19 Jun 2025 23:27:52 +0200 Subject: [PATCH 11/11] gettext: fix handling of fuzzy translations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code would incorrectly load part of them, examples: - the fuzzy translation is not ignored (because of the context, which is itself ignored): ``` msgctxt "Status of group of books" msgid "All" msgstr "Kaikki" ``` - not ignored, with an invalid original string (`"Please configure it in the settings."`): ```po msgid "" "The download folder is not valid.\n" "Please configure it in the settings." msgstr "" "Le répertoire de téléchargement est invalide.\n" "Veuillez le configurer dans les paramètres." ``` - and in this case the second fuzzy entry end up overwriting the valid translation (with `"لم يتم حفظ الملف في:\n%1"`): ```po msgid "" "%1\n" "%2" msgstr "" "%1\n" "%2" msgid "" "Could not save file to:\n" "%1\n" "%2" msgstr "" "لم يتم حفظ الملف في:\n" "%1" ``` --- frontend/gettext.lua | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/frontend/gettext.lua b/frontend/gettext.lua index 896a63b73..b74ea73b8 100644 --- a/frontend/gettext.lua +++ b/frontend/gettext.lua @@ -189,6 +189,7 @@ function GetText_mt.__index.changeLang(new_lang) end local data = {} + local in_comments = false local fuzzy = false local headers local what = nil @@ -258,7 +259,18 @@ function GetText_mt.__index.changeLang(new_lang) what = nil else -- comment - if not line:match("^#") then + if line:match("^#") then + if not in_comments then + in_comments = true + fuzzy = false + end + if line:match(", fuzzy") then + fuzzy = true + end + elseif fuzzy then + in_comments = false + else + in_comments = false -- new data item (msgid, msgstr, ... local w, s = line:match("^%s*([%a_%[%]0-9]+)%s+\"(.*)\"%s*$") if w then @@ -267,7 +279,7 @@ function GetText_mt.__index.changeLang(new_lang) -- string continuation s = line:match("^%s*\"(.*)\"%s*$") end - if what and s and not fuzzy then + if what and s then -- unescape \n or msgid won't match s = s:gsub("\\n", "\n") -- unescape " or msgid won't match @@ -275,14 +287,7 @@ function GetText_mt.__index.changeLang(new_lang) -- unescape \\ or msgid won't match s = s:gsub("\\\\", "\\") data[what] = (data[what] or "") .. s - elseif what and s == "" and fuzzy then -- luacheck: ignore 542 - -- Ignore the likes of msgid "" and msgstr "" - else - -- Don't save this fuzzy string and unset fuzzy for the next one. - fuzzy = false end - elseif line:match("#, fuzzy") then - fuzzy = true end end end