diff --git a/frontend/device/android/device.lua b/frontend/device/android/device.lua index ad5e2267c..dcc855c03 100644 --- a/frontend/device/android/device.lua +++ b/frontend/device/android/device.lua @@ -21,7 +21,7 @@ local Device = Generic:new{ display_dpi = android.lib.AConfiguration_getDensity(android.app.config), hasClipboard = yes, hasColorScreen = yes, - hasOTAUpdates = yes, + hasOTAUpdates = no, } function Device:init() diff --git a/frontend/device/sdl/device.lua b/frontend/device/sdl/device.lua index 4d274d6a3..1fcad8d0c 100644 --- a/frontend/device/sdl/device.lua +++ b/frontend/device/sdl/device.lua @@ -21,6 +21,7 @@ local Device = Generic:new{ local AppImage = Device:new{ model = "AppImage", + hasOTAUpdates = yes, } local Emulator = Device:new{ diff --git a/frontend/ui/otamanager.lua b/frontend/ui/otamanager.lua index be723e3f8..35a5224e6 100644 --- a/frontend/ui/otamanager.lua +++ b/frontend/ui/otamanager.lua @@ -33,6 +33,7 @@ local OTAManager = { "stable", "nightly", }, + link_template = "koreader-%s-latest-%s", zsync_template = "koreader-%s-latest-%s.zsync", installed_package = ota_dir .. "koreader.installed.tar", package_indexfile = "ota/package.index", @@ -79,6 +80,8 @@ function OTAManager:getOTAModel() return "android-x86" end return "android" + elseif Device:isSDL() then + return "appimage" elseif Device:isCervantes() then return "cervantes" elseif Device:isKindle() then @@ -102,6 +105,18 @@ function OTAManager:getOTAModel() end end +function OTAManager:getOTAType() + local ota_model = self:getOTAModel() + + if ota_model == "" then return end + + if ota_model:find("android") or ota_model:find("appimage") then + return "link" + end + + return "ota" +end + function OTAManager:getOTAServer() return G_reader_settings:readSetting("ota_server") or self.ota_servers[1] end @@ -120,6 +135,10 @@ function OTAManager:setOTAChannel(channel) G_reader_settings:saveSetting("ota_channel", channel) end +function OTAManager:getLinkFilename() + return self.link_template:format(self:getOTAModel(), self:getOTAChannel()) +end + function OTAManager:getZsyncFilename() return self.zsync_template:format(self:getOTAModel(), self:getOTAChannel()) end @@ -128,27 +147,39 @@ function OTAManager:checkUpdate() local http = require("socket.http") local ltn12 = require("ltn12") - local zsync_file = self:getZsyncFilename() - local ota_zsync_file = self:getOTAServer() .. zsync_file - local local_zsync_file = ota_dir .. zsync_file + local update_file = (self:getOTAType() == "link") and self:getLinkFilename() or self:getZsyncFilename() + + local ota_update_file = self:getOTAServer() .. update_file + local local_update_file = ota_dir .. update_file -- download zsync file from OTA server - logger.dbg("downloading zsync file", ota_zsync_file) + logger.dbg("downloading update file", ota_update_file) local _, c, _ = http.request{ - url = ota_zsync_file, - sink = ltn12.sink.file(io.open(local_zsync_file, "w"))} + url = ota_update_file, + sink = ltn12.sink.file(io.open(local_update_file, "w"))} if c ~= 200 then - logger.warn("cannot find zsync file", c) + logger.warn("cannot find update file", c) return end -- parse OTA package version - local ota_package = nil - local zsync = io.open(local_zsync_file, "r") - if zsync then - for line in zsync:lines() do - ota_package = line:match("^Filename:%s*(.-)%s*$") - if ota_package then break end + local link, ota_package + local update_info = io.open(local_update_file, "r") + if update_info then + if OTAManager:getOTAType() == "link" then + local i = 0 + for line in update_info:lines() do + i = i + 1 + if i == 1 then + ota_package = line + link = self:getOTAServer() .. ota_package + end + end + else + for line in update_info:lines() do + ota_package = line:match("^Filename:%s*(.-)%s*$") + if ota_package then break end + end end - zsync:close() + update_info:close() end local local_ok, local_version = pcall(function() local rev = Version:getCurrentRevision() @@ -161,14 +192,15 @@ function OTAManager:checkUpdate() -- larger than the local package version if local_ok and ota_ok and ota_version and local_version and ota_version ~= local_version then - return ota_version, local_version + return ota_version, local_version, link elseif ota_version and ota_version == local_version then return 0 end end function OTAManager:fetchAndProcessUpdate() - local ota_version, local_version = OTAManager:checkUpdate() + local ota_version, local_version, link = OTAManager:checkUpdate() + if ota_version == 0 then UIManager:show(InfoMessage:new{ text = _("KOReader is up to date."), @@ -176,79 +208,95 @@ function OTAManager:fetchAndProcessUpdate() elseif ota_version == nil then local channel = ota_channels[OTAManager:getOTAChannel()] UIManager:show(InfoMessage:new{ - text = T(_("OTA package is not available on %1 channel."), channel), + text = T(_("Update information not available on %1 channel."), channel), }) elseif ota_version then - UIManager:show(ConfirmBox:new{ - text = T( - _("Do you want to update?\nInstalled version: %1\nAvailable version: %2"), - local_version, - ota_version - ), - ok_text = _("Update"), - ok_callback = function() - UIManager:show(InfoMessage:new{ - text = _("Downloading may take several minutes…"), - timeout = 3, - }) - UIManager:scheduleIn(1, function() - if OTAManager:zsync() == 0 then - UIManager:show(InfoMessage:new{ - text = _("KOReader will be updated on next restart."), - }) - -- Make it clear that zsync is done - if self.can_pretty_print then - os.execute("./fbink -q -y -7 -pm ' ' ' '") - end - else - -- Make it clear that zsync is done - if self.can_pretty_print then - os.execute("./fbink -q -y -7 -pm ' ' ' '") - end - UIManager:show(MultiConfirmBox:new{ - text = _("Failed to update KOReader.\n\nYou can:\nCancel, keeping temporary files.\nRetry the update process with a full download.\nAbort and cleanup all temporary files."), - choice1_text = _("Retry"), - choice1_callback = function() - UIManager:show(InfoMessage:new{ - text = _("Downloading may take several minutes…"), - timeout = 3, - }) - -- Clear the installed package, as well as the complete/incomplete update download - os.execute("rm " .. self.installed_package) - os.execute("rm " .. self.updated_package .. "*") - -- And then relaunch zsync in full download mode... - UIManager:scheduleIn(1, function() - if OTAManager:zsync(true) == 0 then - UIManager:show(InfoMessage:new{ - text = _("KOReader will be updated on next restart."), - }) - -- Make it clear that zsync is done - if self.can_pretty_print then - os.execute("./fbink -q -y -7 -pm ' ' ' '") - end - else - -- Make it clear that zsync is done - if self.can_pretty_print then - os.execute("./fbink -q -y -7 -pm ' ' ' '") - end - UIManager:show(ConfirmBox:new{ - text = _("Error updating KOReader. Would you like to delete temporary files?"), - ok_callback = function() - os.execute("rm " .. ota_dir .. "ko*") - end, - }) - end - end) - end, - choice2_text = _("Abort"), - choice2_callback = function() - os.execute("rm " .. ota_dir .. "ko*") - end, - }) + if OTAManager:getOTAType() == "link" then + UIManager:show(ConfirmBox:new{ + text = T( + _("Do you want to update?\nInstalled version: %1\nAvailable version: %2"), + local_version, + ota_version + ), + ok_text = _("Update"), + ok_callback = function() + if Device:isSDL() then + os.execute("xdg-open '"..link.."'") end - end) - end - }) + end + }) + else + UIManager:show(ConfirmBox:new{ + text = T( + _("Do you want to update?\nInstalled version: %1\nAvailable version: %2"), + local_version, + ota_version + ), + ok_text = _("Update"), + ok_callback = function() + UIManager:show(InfoMessage:new{ + text = _("Downloading may take several minutes…"), + timeout = 3, + }) + UIManager:scheduleIn(1, function() + if OTAManager:zsync() == 0 then + UIManager:show(InfoMessage:new{ + text = _("KOReader will be updated on next restart."), + }) + -- Make it clear that zsync is done + if self.can_pretty_print then + os.execute("./fbink -q -y -7 -pm ' ' ' '") + end + else + -- Make it clear that zsync is done + if self.can_pretty_print then + os.execute("./fbink -q -y -7 -pm ' ' ' '") + end + UIManager:show(MultiConfirmBox:new{ + text = _("Failed to update KOReader.\n\nYou can:\nCancel, keeping temporary files.\nRetry the update process with a full download.\nAbort and cleanup all temporary files."), + choice1_text = _("Retry"), + choice1_callback = function() + UIManager:show(InfoMessage:new{ + text = _("Downloading may take several minutes…"), + timeout = 3, + }) + -- Clear the installed package, as well as the complete/incomplete update download + os.execute("rm " .. self.installed_package) + os.execute("rm " .. self.updated_package .. "*") + -- And then relaunch zsync in full download mode... + UIManager:scheduleIn(1, function() + if OTAManager:zsync(true) == 0 then + UIManager:show(InfoMessage:new{ + text = _("KOReader will be updated on next restart."), + }) + -- Make it clear that zsync is done + if self.can_pretty_print then + os.execute("./fbink -q -y -7 -pm ' ' ' '") + end + else + -- Make it clear that zsync is done + if self.can_pretty_print then + os.execute("./fbink -q -y -7 -pm ' ' ' '") + end + UIManager:show(ConfirmBox:new{ + text = _("Error updating KOReader. Would you like to delete temporary files?"), + ok_callback = function() + os.execute("rm " .. ota_dir .. "ko*") + end, + }) + end + end) + end, + choice2_text = _("Abort"), + choice2_callback = function() + os.execute("rm " .. ota_dir .. "ko*") + end, + }) + end + end) + end + }) + end end end @@ -262,44 +310,39 @@ function OTAManager:_buildLocalPackage() logger.err("Missing ota metadata:", self.package_indexfile) return nil end - if Device:isAndroid() then - return os.execute(string.format( - "./tar --no-recursion -cf %s -T %s", - self.installed_package, self.package_indexfile)) - else - -- With visual feedback if supported... - if self.can_pretty_print then - os.execute("./fbink -q -y -7 -pmh 'Preparing local OTA package'") - -- We need a vague idea of how much space the tarball we're creating will take to compute a proper percentage... - -- Get the size from the latest zsync package, which'll be a closer match than anything else we might come up with. - local zsync_file = self:getZsyncFilename() - local local_zsync_file = ota_dir .. zsync_file - local tarball_size = nil - local zsync = io.open(local_zsync_file, "r") - if zsync then - for line in zsync:lines() do - tarball_size = line:match("^Length: (%d*)$") - if tarball_size then break end - end - zsync:close() - end - -- Next, we need to compute the amount of tar blocks that'll take, knowing that tar's default blocksize is 20 * 512 bytes. - -- c.f., https://superuser.com/questions/168749 & http://www.noah.org/wiki/tar_notes - -- Defaults to a sane-ish value as-of now, in case shit happens... - local blocks = 6405 - if tarball_size then - blocks = tarball_size / (512 * 20) + + -- With visual feedback if supported... + if self.can_pretty_print then + os.execute("./fbink -q -y -7 -pmh 'Preparing local OTA package'") + -- We need a vague idea of how much space the tarball we're creating will take to compute a proper percentage... + -- Get the size from the latest zsync package, which'll be a closer match than anything else we might come up with. + local update_file = self:getZsyncFilename() + local local_update_file = ota_dir .. update_file + local tarball_size = nil + local zsync = io.open(local_update_file, "r") + if zsync then + for line in zsync:lines() do + tarball_size = line:match("^Length: (%d*)$") + if tarball_size then break end end - -- And since we want a percentage, devise the exact value we need for tar to spit out exactly 100 checkpoints ;). - local cpoints = blocks / 100 - return os.execute(string.format( - "./tar --no-recursion -cf %s -C .. -T %s --checkpoint=%d --checkpoint-action=exec='./fbink -q -y -6 -P $(($TAR_CHECKPOINT/%d))'", - self.installed_package, self.package_indexfile, cpoints, cpoints)) - else - return os.execute(string.format( - "./tar --no-recursion -cf %s -C .. -T %s", - self.installed_package, self.package_indexfile)) + zsync:close() + end + -- Next, we need to compute the amount of tar blocks that'll take, knowing that tar's default blocksize is 20 * 512 bytes. + -- c.f., https://superuser.com/questions/168749 & http://www.noah.org/wiki/tar_notes + -- Defaults to a sane-ish value as-of now, in case shit happens... + local blocks = 6405 + if tarball_size then + blocks = tarball_size / (512 * 20) end + -- And since we want a percentage, devise the exact value we need for tar to spit out exactly 100 checkpoints ;). + local cpoints = blocks / 100 + return os.execute(string.format( + "./tar --no-recursion -cf %s -C .. -T %s --checkpoint=%d --checkpoint-action=exec='./fbink -q -y -6 -P $(($TAR_CHECKPOINT/%d))'", + self.installed_package, self.package_indexfile, cpoints, cpoints)) + else + return os.execute(string.format( + "./tar --no-recursion -cf %s -C .. -T %s", + self.installed_package, self.package_indexfile)) end end @@ -362,7 +405,14 @@ end function OTAManager:getOTAMenuTable() return { - text = _("OTA update"), + text = _("Update"), + hold_callback = function() + if not NetworkMgr:isOnline() then + NetworkMgr:promptWifiOn() + else + OTAManager:fetchAndProcessUpdate() + end + end, sub_item_table = { { text = _("Check for update"), @@ -378,11 +428,11 @@ function OTAManager:getOTAMenuTable() text = _("Settings"), sub_item_table = { { - text = _("OTA server"), + text = _("Update server"), sub_item_table = self:genServerList() }, { - text = _("OTA channel"), + text = _("Update channel"), sub_item_table = self:genChannelList() }, }