diff --git a/apps/readest.koplugin/main.lua b/apps/readest.koplugin/main.lua index 83644ff7..ef352a4c 100644 --- a/apps/readest.koplugin/main.lua +++ b/apps/readest.koplugin/main.lua @@ -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 \ No newline at end of file