mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
[feat] Update check (#4620)
The concept is quite simple: stick a file on the OTA server named something like `koreader-appimage-latest-stable` (by analogy with `koreader-cervantes-latest-stable.zsync`), which contains nothing but a filename. The difference with the zsync update is that the link is then launched in the user's browser (AppImage) or DownloadManager (Android, not yet implemented). * Android hasOTAUpdate = no for the moment
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -21,6 +21,7 @@ local Device = Generic:new{
|
||||
|
||||
local AppImage = Device:new{
|
||||
model = "AppImage",
|
||||
hasOTAUpdates = yes,
|
||||
}
|
||||
|
||||
local Emulator = Device:new{
|
||||
|
||||
@@ -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()
|
||||
|
||||
-- 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
|
||||
-- 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))
|
||||
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()
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user