forked from akai/readest
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:
@@ -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
|
||||
Reference in New Issue
Block a user