patch management

Use texteditor if available

Auto disable if patches folder doesnt exist

And show a restart message after file edit

Sort menu entries in execution order

Warning sign for failing patches
reviewable/pr10088/r1
zwim 1 year ago committed by zwim
parent cf9d3a0d70
commit 4e944dc918

@ -135,6 +135,7 @@ local order = {
"terminal",
"----------------------------",
"plugin_management",
"patch_management",
"advanced_settings",
"developer_options",
},

@ -185,6 +185,7 @@ local order = {
"terminal",
"----------------------------",
"plugin_management",
"patch_management",
},
search = {
"dictionary_lookup",

@ -15,6 +15,11 @@ local userpatch = {
before_exit = "8", -- to be started a bit before exit before settings are saved (always)
on_exit = "9", -- to be started right before exit (always)
-- hash table for patch execution status
-- key: name of the patch
-- value: true (success), false (failure), nil (not executed)
execution_status = {},
-- the patch function itself
applyPatches = function(priority) end, -- to be overwritten, if the device allows it.
}
@ -64,6 +69,7 @@ local function runUserPatchTasks(dir, priority)
if fullpath:match("%.lua$") then -- execute patch-files first
logger.info("Applying patch:", fullpath)
local ok, err = pcall(dofile, fullpath)
userpatch.execution_status[entry] = ok
if not ok then
logger.warn("Patching failed:", err)
-- Only show InfoMessage, when UIManager is working

@ -0,0 +1,6 @@
local _ = require("gettext")
return {
name = "patch_management",
fullname = _("Patch management"),
description = _("This plugin allows enabling, disabling or editing user patches."),
}

@ -0,0 +1,189 @@
--[[--
Plugin for managing user patches
@module koplugin.patchmanagement
--]]--
local DataStorage = require("datastorage")
local lfs = require("libs/libkoreader-lfs")
if lfs.attributes(DataStorage:getDataDir() .. "/patches", "mode") ~= "directory" then
return { disabled = true }
end
local Device = require("device")
local InfoMessage = require("ui/widget/infomessage")
local TextViewer = require("ui/widget/textviewer")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local sort = require("sort")
local userPatch = require("userpatch")
local _ = require("gettext")
local Screen = Device.screen
local PatchManagement = WidgetContainer:extend{
name = "patch_management",
}
function PatchManagement:init()
self.patch_dir = DataStorage:getDataDir() .. "/patches"
self.disable_ext = ".disabled"
self.patches = nil
self:getAvailablePatches()
self.ui.menu:registerToMainMenu(self)
end
function PatchManagement:getAvailablePatches()
self.patches = {}
for priority = tonumber(userPatch.early_once), tonumber(userPatch.on_exit) do
self.patches[priority] = {}
end
for entry in lfs.dir(self.patch_dir) do
local mode = lfs.attributes(self.patch_dir .. "/" .. entry, "mode")
if mode == "file" then
for priority = tonumber(userPatch.early_once), tonumber(userPatch.on_exit) do
if entry:match("^" .. priority .. "%d*%-") and entry:find(".lua", 1, true) then
-- only lua files, starting with "%d+%-"
table.insert(self.patches[priority], entry)
break
end
end
end
end
for priority = tonumber(userPatch.early_once), tonumber(userPatch.on_exit) do
table.sort(self.patches[priority], sort.natsort_cmp())
end
end
function PatchManagement:getSubMenu(priority)
if self.patches == nil then
return {}
end
local function getExecutionStatus(patch_name)
return userPatch.execution_status[patch_name] == false and "" or ""
end
local sub_menu = {}
for i, patch in ipairs(self.patches[priority]) do
local ext = ".lua"
-- strip anything after ".lua" in patch_name
local patch_name = patch
patch_name = patch_name:sub(1, patch_name:find(ext, 1, true) + ext:len() - 1)
table.insert(sub_menu, {
text = patch_name .. getExecutionStatus(patch_name),
checked_func = function()
return patch:find("%.lua$") ~= nil
end,
callback = function()
local extension_pos = patch:find(ext, 1, true)
if extension_pos then
local is_patch_enabled = extension_pos == patch:len() - (ext:len() - 1)
if is_patch_enabled then -- patch name ends with ".lua"
local disabled_name = patch .. self.disable_ext
os.rename(self.patch_dir .. "/" .. patch,
self.patch_dir .. "/" .. disabled_name)
patch = disabled_name
else -- patch name name contains ".lua"
local enabled_name = patch:sub(1, extension_pos + ext:len() - 1)
os.rename(self.patch_dir .. "/" .. patch,
self.patch_dir .. "/" .. enabled_name)
patch = enabled_name
end
end
UIManager:askForRestart(
_("Patches changed. Current set of patches will be applied on next restart."))
end,
hold_callback = function()
local patch_fullpath = self.patch_dir .. "/" .. patch
if self.ui.texteditor then
local function done_callback()
UIManager:askForRestart(
_("Patches might have changed. Current set of patches will be applied on next restart."))
end
self.ui.texteditor:quickEditFile(patch_fullpath, done_callback, false)
else -- fallback to show only the first lines
local file = io.open(patch_fullpath, "rb")
if not file then
return ""
end
local patch_content = file:read("*all")
file:close()
local textviewer
textviewer = TextViewer:new{
title = patch,
text = patch_content,
}
UIManager:show(textviewer)
end
end,
})
end
return sub_menu
end
local about_text = _([[Patch management allows enabling, disabling or editing user provided patches.
The runlevel and priority of a patch can not be modified here. This has to be done manually by renaming the patch prefix.
For more information about user patches, see
https://github.com/koreader/koreader/wiki/User-patches
Patches are an advanced feature, so be careful what you do!]])
function PatchManagement:addToMainMenu(menu_items)
menu_items.patch_management = {
text = _("Patch management"),
enabled_func = function()
if self.patches == nil then
return false
end
for i = tonumber(userPatch.early_once), tonumber(userPatch.on_exit) do
if #self.patches[i] > 0 then
return true -- we have at least one patch in the patches folder
end
end
return false
end,
sub_item_table = {
{
text = _("About patch management"),
callback = function()
UIManager:show(InfoMessage:new{
text = about_text,
width = math.floor(Screen:getWidth() * 0.9),
})
end,
separator = true,
keep_menu_open = true,
},
{
text = _("Patches executed:"),
enabled = false,
},
}
}
local sub_menu_text = {}
sub_menu_text[tonumber(userPatch.early_once)] = _("On startup, only after update")
sub_menu_text[tonumber(userPatch.early)] = _("On startup")
sub_menu_text[tonumber(userPatch.late)] = _("After setup")
sub_menu_text[tonumber(userPatch.before_exit)] = _("Before exit")
sub_menu_text[tonumber(userPatch.on_exit)] = _("On exit")
for i = tonumber(userPatch.early_once), tonumber(userPatch.on_exit) do
if sub_menu_text[i] then
table.insert(menu_items.patch_management.sub_item_table,
{
text = sub_menu_text[i],
enabled_func = function()
return #self.patches[i] > 0
end,
sub_item_table = self:getSubMenu(i)
})
end
end
end
return PatchManagement
Loading…
Cancel
Save