feat(koplugin): add self-update check and install from menu (#3575)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-03-21 15:16:50 +08:00
committed by GitHub
parent 0e8605d298
commit 2f430bf2ff
+189
View File
@@ -37,10 +37,22 @@ ReadestSync.default_settings = {
last_sync_at = nil,
}
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 ReadestSync:init()
self.last_sync_timestamp = 0
self.settings = G_reader_settings:readSetting("readest_sync", self.default_settings)
local meta = dofile(self.path .. "/_meta.lua")
self.installed_version = meta and meta.version and tostring(meta.version)
self.ui.menu:registerToMainMenu(self)
end
@@ -110,6 +122,18 @@ function ReadestSync:addToMainMenu(menu_items)
callback = function()
self:pullBookConfig(true)
end,
separator = true,
},
{
text_func = function()
if self.installed_version then
return T(_("Check for update (v%1)"), self.installed_version)
end
return _("Check for update")
end,
callback = function()
self:checkForUpdate()
end,
},
}
}
@@ -653,4 +677,169 @@ function ReadestSync:onCloseWidget()
end
end
function ReadestSync: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 ReadestSync: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 ReadestSync:checkForUpdate()
local ConfirmBox = require("ui/widget/confirmbox")
if NetworkMgr:willRerunWhenOnline(function() self:checkForUpdate() end) then
return
end
UIManager:show(InfoMessage:new{
text = _("Checking for update…"),
timeout = 1,
})
local current_version = self.installed_version
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 current_version or self:compareVersions(current_version, latest_version) < 0 then
UIManager:show(ConfirmBox:new{
text = current_version
and T(_("A new version is available: v%1 (current: v%2).\n\nDo you want to update now?"), latest_version, current_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(latest_version)
end,
})
else
UIManager:show(InfoMessage:new{
text = T(_("You are up to date (v%1)."), current_version),
timeout = 3,
})
end
end
function ReadestSync:downloadAndInstall(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(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 plugin_dir = self.path
local parent_dir = plugin_dir: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 ReadestSync