Files
readest/apps/readest.koplugin/selfupdate.lua
T
Huang Xin c59097b0ac feat(koplugin): add i18n catalog and sync info dialog (#4050)
- i18n loader at apps/readest.koplugin/i18n.lua: isolated callable,
  falls back to KOReader's gettext when a string is untranslated
- Translation catalog at locales/<i18next-code>/translation.po for 31
  languages, mirroring apps/readest-app/public/locales/
- scripts/extract-i18n.js (scan _("...") and _([[...]]); preserve
  existing, drop obsolete, add new) and scripts/apply-translations.js
  (bulk import from /tmp/koplugin-translations/<lang>.json)
- Mirror apps/readest-app SyncInfoDialog: rename showMetaHashInfo to
  showSyncInfo, dialog title "Sync Info", new Last Synced row computed
  as max(last_synced_at_config, last_synced_at_notes) from doc_settings
- syncconfig.lua / syncannotations.lua mark per-book sync timestamps
  on push/pull success
- Rename "Meta Hash" -> "Book Fingerprint" in koplugin and
  apps/readest-app SyncInfoDialog.tsx; translations propagated to
  all readest-app locales
- "book config" -> "reading progress" wording across user-facing
  strings (matches QiuYukang fork terminology)
- Replace "Log out as " / "Login failed: " concat prefixes with
  T(_("...%1..."), arg) placeholder pattern (RTL / verb-final friendly)
- pnpm lint:lua: luajit -b syntax check across koplugin .lua files;
  soft-skips when luajit is missing locally; CI installs luajit and
  runs the check unconditionally

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 05:35:36 +02:00

183 lines
5.7 KiB
Lua

local Device = require("device")
local InfoMessage = require("ui/widget/infomessage")
local NetworkMgr = require("ui/network/manager")
local UIManager = require("ui/uimanager")
local logger = require("logger")
local T = require("ffi/util").template
local _ = require("i18n")
local SelfUpdate = {}
local UPDATE_URLS = {
"https://download.readest.com/releases/latest.json",
"https://github.com/readest/readest/releases/latest/download/latest.json",
}
local DOWNLOAD_URLS = {
"https://download.readest.com/releases/%s/Readest-%s-1.koplugin.zip",
"https://github.com/readest/readest/releases/download/%s/Readest-%s-1.koplugin.zip",
}
function SelfUpdate:compareVersions(v1, v2)
local function parse(v)
local parts = {}
for num in tostring(v):gmatch("(%d+)") do
table.insert(parts, tonumber(num))
end
return parts
end
local p1, p2 = parse(v1), parse(v2)
local len = math.max(#p1, #p2)
for i = 1, len do
local a, b = p1[i] or 0, p2[i] or 0
if a < b then return -1 end
if a > b then return 1 end
end
return 0
end
function SelfUpdate:fetchLatestVersion()
local http = require("socket.http")
local ltn12 = require("ltn12")
local socket = require("socket")
local socketutil = require("socketutil")
local json = require("json")
for _, url in ipairs(UPDATE_URLS) do
local sink = {}
socketutil:set_timeout(socketutil.LARGE_BLOCK_TIMEOUT, socketutil.LARGE_TOTAL_TIMEOUT)
local code = socket.skip(1, http.request{
url = url,
sink = ltn12.sink.table(sink),
})
socketutil:reset_timeout()
if code == 200 then
local ok, data = pcall(json.decode, table.concat(sink))
if ok and data and data.version then
return data.version
end
end
logger.dbg("ReadestSync: failed to fetch latest.json from", url, "code:", code)
end
return nil
end
function SelfUpdate:checkForUpdate(plugin_path, installed_version)
local ConfirmBox = require("ui/widget/confirmbox")
if NetworkMgr:willRerunWhenOnline(function() self:checkForUpdate(plugin_path, installed_version) end) then
return
end
UIManager:show(InfoMessage:new{
text = _("Checking for update…"),
timeout = 1,
})
Device:setIgnoreInput(true)
local latest_version = self:fetchLatestVersion()
Device:setIgnoreInput(false)
if not latest_version then
UIManager:show(InfoMessage:new{
text = _("Failed to check for update. Please try again later."),
timeout = 3,
})
return
end
if not installed_version or self:compareVersions(installed_version, latest_version) < 0 then
UIManager:show(ConfirmBox:new{
text = installed_version
and T(_("A new version is available: v%1 (current: v%2).\n\nDo you want to update now?"), latest_version, installed_version)
or T(_("A new version is available: v%1.\n\nDo you want to update now?"), latest_version),
ok_text = _("Update"),
ok_callback = function()
self:downloadAndInstall(plugin_path, latest_version)
end,
})
else
UIManager:show(InfoMessage:new{
text = T(_("You are up to date (v%1)."), installed_version),
timeout = 3,
})
end
end
function SelfUpdate:downloadAndInstall(plugin_path, version)
local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local http = require("socket.http")
local ltn12 = require("ltn12")
local socket = require("socket")
local socketutil = require("socketutil")
if NetworkMgr:willRerunWhenOnline(function() self:downloadAndInstall(plugin_path, version) end) then
return
end
local tag = "v" .. version
local zip_name = "Readest-" .. version .. "-1.koplugin.zip"
local tmp_path = DataStorage:getDataDir() .. "/" .. zip_name
UIManager:show(InfoMessage:new{
text = _("Downloading update…"),
timeout = 1,
})
Device:setIgnoreInput(true)
local download_ok = false
for _, url_template in ipairs(DOWNLOAD_URLS) do
local url = string.format(url_template, tag, version)
logger.dbg("ReadestSync: downloading from", url)
socketutil:set_timeout(socketutil.FILE_BLOCK_TIMEOUT, socketutil.FILE_TOTAL_TIMEOUT)
local code = socket.skip(1, http.request{
url = url,
sink = ltn12.sink.file(io.open(tmp_path, "w")),
})
socketutil:reset_timeout()
if code == 200 then
download_ok = true
break
end
logger.dbg("ReadestSync: download failed from", url, "code:", code)
end
Device:setIgnoreInput(false)
if not download_ok then
os.remove(tmp_path)
UIManager:show(InfoMessage:new{
text = _("Failed to download update. Please try again later."),
timeout = 3,
})
return
end
local parent_dir = plugin_path:match("(.*/)")
local ok, err = Device:unpackArchive(tmp_path, parent_dir)
os.remove(tmp_path)
if ok then
UIManager:show(ConfirmBox:new{
text = T(_("Readest plugin updated to v%1.\n\nPlease restart KOReader to apply the update."), version),
ok_text = _("Restart now"),
ok_callback = function()
UIManager:restartKOReader()
end,
cancel_text = _("Later"),
})
else
UIManager:show(InfoMessage:new{
text = T(_("Failed to install update: %1"), err or _("unknown error")),
timeout = 5,
})
end
end
return SelfUpdate