mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -200,7 +201,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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
174
frontend/ui/widget/progressbardialog.lua
Normal file
174
frontend/ui/widget/progressbardialog.lua
Normal file
@@ -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( <progress value> )
|
||||
|
||||
-- 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
|
||||
1
kodev
1
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}"
|
||||
}
|
||||
|
||||
|
||||
Submodule platform/android/luajit-launcher updated: ab7a280f4b...b0fd95f289
@@ -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
|
||||
|
||||
@@ -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', {})
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user