mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
userpatch: allow monkey-patching KOReader (#9104)
Supersede old android-only patch.lua.
This commit is contained in:
4
Makefile
4
Makefile
@@ -103,9 +103,9 @@ endif
|
||||
ifdef ANDROID
|
||||
cd $(INSTALL_DIR)/koreader && \
|
||||
ln -sf ../../$(ANDROID_DIR)/*.lua .
|
||||
@echo "[*] Install afterupdate marker"
|
||||
@echo "# If this file is here, there are no afterupdate scripts in /sdcard/koreader/scripts/afterupdate." > $(INSTALL_DIR)/koreader/afterupdate.marker
|
||||
endif
|
||||
@echo "[*] Install update once marker"
|
||||
@echo "# This file indicates that update once patches have not been applied yet." > $(INSTALL_DIR)/koreader/update_once.marker
|
||||
ifdef WIN32
|
||||
@echo "[*] Install runtime libraries for win32..."
|
||||
cd $(INSTALL_DIR)/koreader && cp ../../$(WIN32_DIR)/*.dll .
|
||||
|
||||
@@ -460,7 +460,7 @@ end
|
||||
|
||||
function Device:canExecuteScript(file)
|
||||
local file_ext = string.lower(util.getFileNameSuffix(file))
|
||||
if android.prop.flavor ~= "fdroid" and file_ext == "sh" then
|
||||
if android.prop.flavor ~= "fdroid" and file_ext == "sh" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ local lfs = require("libs/libkoreader-lfs")
|
||||
local logger = require("logger")
|
||||
|
||||
-- Date at which the last migration snippet was added
|
||||
local CURRENT_MIGRATION_DATE = 20220523
|
||||
local CURRENT_MIGRATION_DATE = 20220625
|
||||
|
||||
-- Retrieve the date of the previous migration, if any
|
||||
local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0)
|
||||
@@ -400,5 +400,22 @@ if last_migration_date < 20220523 then
|
||||
migrateSettingsName("highlight_long_hold_threshold", "highlight_long_hold_threshold_s")
|
||||
end
|
||||
|
||||
-- #9104
|
||||
if last_migration_date < 20220625 then
|
||||
os.remove("afterupdate.marker")
|
||||
|
||||
-- Move an existing `koreader/patch.lua` to `koreader/patches/1-patch.lua` (-> will be excuted in `early`)
|
||||
local data_dir = DataStorage:getDataDir()
|
||||
local patch_dir = data_dir .. "/patches"
|
||||
if lfs.attributes(data_dir .. "/patch.lua", "mode") == "file" then
|
||||
if lfs.attributes(patch_dir, "mode") == nil then
|
||||
if not lfs.mkdir(patch_dir, "mode") then
|
||||
logger.err("User patch error creating directory", patch_dir)
|
||||
end
|
||||
end
|
||||
os.rename(data_dir .. "/patch.lua", patch_dir .. "/1-patch.lua")
|
||||
end
|
||||
end
|
||||
|
||||
-- We're done, store the current migration date
|
||||
G_reader_settings:saveSetting("last_migration_date", CURRENT_MIGRATION_DATE)
|
||||
|
||||
108
frontend/userpatch.lua
Normal file
108
frontend/userpatch.lua
Normal file
@@ -0,0 +1,108 @@
|
||||
--[[--
|
||||
Allows applying developer patches while running KOReader.
|
||||
|
||||
The contents in `koreader/patches/` are applied on calling `userpatch.applyPatches(priority)`.
|
||||
--]]--
|
||||
|
||||
local isAndroid, android = pcall(require, "android")
|
||||
|
||||
local userpatch = {
|
||||
-- priorities for user patches,
|
||||
early_once = "0", -- to be started early on startup (once after an update)
|
||||
early = "1", -- to be started early on startup (always, but after an `early_once`)
|
||||
late = "2", -- to be started after UIManager is ready (always)
|
||||
-- 3-7 are reserved for later use
|
||||
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)
|
||||
|
||||
-- the patch function itself
|
||||
applyPatches = function(priority) end, -- to be overwritten, if the device allows it.
|
||||
}
|
||||
|
||||
if isAndroid and android.prop.flavor == "fdroid" then
|
||||
return userpatch -- allows to use applyPatches as a no-op on F-Droid flavor
|
||||
end
|
||||
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
local logger = require("logger")
|
||||
local DataStorage = require("datastorage")
|
||||
|
||||
-- the directory KOReader is installed in (and runs from)
|
||||
local package_dir = lfs.currentdir()
|
||||
-- the directory where KOReader stores user data
|
||||
local data_dir = DataStorage:getDataDir()
|
||||
|
||||
--- Run lua patches
|
||||
-- Execution order order is alphanum-sort for humans version 4: `1-patch.lua` is executed before `10-patch.lua`
|
||||
-- (see http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua)
|
||||
-- string directory ... to scan through (flat no recursion)
|
||||
-- string priority ... only files starting with `priority` followed by digits and a '-' will be processed.
|
||||
-- return true if a patch was executed
|
||||
local function runUserPatchTasks(dir, priority)
|
||||
if lfs.attributes(dir, "mode") ~= "directory" then
|
||||
return
|
||||
end
|
||||
|
||||
local patches = {}
|
||||
for entry in lfs.dir(dir) do
|
||||
local mode = lfs.attributes(dir .. "/" .. entry, "mode")
|
||||
if entry and mode == "file" and entry:match("^" .. priority .. "%d*%-") then
|
||||
table.insert(patches, entry)
|
||||
end
|
||||
end
|
||||
|
||||
if #patches == 0 then
|
||||
return -- nothing to do
|
||||
end
|
||||
|
||||
local function addLeadingZeroes(d)
|
||||
local dec, n = string.match(d, "(%.?)0*(.+)")
|
||||
return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
|
||||
end
|
||||
local function sorting(a, b)
|
||||
return tostring(a):gsub("%.?%d+", addLeadingZeroes)..("%3d"):format(#b)
|
||||
< tostring(b):gsub("%.?%d+", addLeadingZeroes)..("%3d"):format(#a)
|
||||
end
|
||||
|
||||
table.sort(patches, sorting)
|
||||
|
||||
for i, entry in ipairs(patches) do
|
||||
local fullpath = dir .. "/" .. entry
|
||||
if lfs.attributes(fullpath, "mode") == "file" then
|
||||
if fullpath:match("%.lua$") then -- execute patch-files first
|
||||
logger.info("Applying patch:", fullpath)
|
||||
local ok, err = pcall(dofile, fullpath)
|
||||
if not ok then
|
||||
logger.warn("Patching failed:", err)
|
||||
-- Only show InfoMessage, when UIManager is working
|
||||
if priority >= userpatch.late and priority < userpatch.before_exit then
|
||||
-- Only developers (advanced users) will use this mechanism.
|
||||
-- A warning on a patch failure after an OTA update will simplify troubleshooting.
|
||||
local UIManager = require("ui/uimanager")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
UIManager:show(InfoMessage:new{text = "Error applying patch:\n" .. fullpath}) -- no translate
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- This function applies lua patches from `/koreader/patches`
|
||||
---- @string priority ... one of the defined priorities in the userpatch hashtable
|
||||
function userpatch.applyPatches(priority)
|
||||
local patch_dir = data_dir .. "/patches"
|
||||
local update_once_marker = package_dir .. "/update_once.marker"
|
||||
local update_once_pending = lfs.attributes(update_once_marker, "mode") == "file"
|
||||
|
||||
if priority >= userpatch.early or update_once_pending then
|
||||
local executed_something = runUserPatchTasks(patch_dir, priority)
|
||||
if executed_something and update_once_pending then
|
||||
-- Only delete update once marker if `early_once` updates have been applied.
|
||||
os.remove(update_once_marker) -- Prevent another execution on a further starts.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return userpatch
|
||||
@@ -16,54 +16,6 @@ end
|
||||
-- path to primary external storage partition
|
||||
local path = android.getExternalStoragePath()
|
||||
|
||||
-- run user shell scripts or recursive migration of user data
|
||||
local function runUserScripts(dir, migration, parent)
|
||||
for entry in lfs.dir(dir) do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
local fullpath = dir .. "/" .. entry
|
||||
local mode = lfs.attributes(fullpath).mode
|
||||
if mode == "file" and migration then
|
||||
if entry ~= "migrate" and not fullpath:match(".sh$") then
|
||||
local destdir = parent and android.dir .. "/" .. parent or android.dir
|
||||
-- we cannot create new directories on asset storage.
|
||||
-- trying to do that crashes the VM with error=13, Permission Denied
|
||||
android.execute("cp", fullpath, destdir .."/".. entry)
|
||||
end
|
||||
elseif mode == "file" and fullpath:match(".sh$") then
|
||||
android.execute("sh", fullpath, path .. "/koreader", android.dir)
|
||||
elseif mode == "directory" then
|
||||
runUserScripts(fullpath, migration, parent and parent .. "/" .. entry or entry) -- recurse into next directory
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if android.prop.runtimeChanges then
|
||||
-- run scripts once after an update of koreader,
|
||||
-- it can also trigger a recursive migration of user data
|
||||
local run_once_scripts = path .. "/koreader/scripts.afterupdate"
|
||||
if lfs.attributes(run_once_scripts, "mode") == "directory" then
|
||||
local afterupdate_marker = android.dir .. "/afterupdate.marker"
|
||||
if lfs.attributes(afterupdate_marker, "mode") ~= nil then
|
||||
if lfs.attributes(run_once_scripts .. "/migrate", "mode") ~= nil then
|
||||
android.LOGI("after-update: running migration")
|
||||
runUserScripts(run_once_scripts, true)
|
||||
else
|
||||
android.LOGI("after-update: running shell scripts")
|
||||
runUserScripts(run_once_scripts)
|
||||
end
|
||||
android.execute("rm", afterupdate_marker)
|
||||
end
|
||||
end
|
||||
-- scripts executed every start of koreader, no migration here
|
||||
local run_always_scripts = path .. "/koreader/scripts.always"
|
||||
if lfs.attributes(run_always_scripts, "mode") == "directory" then
|
||||
runUserScripts(run_always_scripts)
|
||||
end
|
||||
-- run koreader patch before koreader startup
|
||||
pcall(dofile, path.."/koreader/patch.lua")
|
||||
end
|
||||
|
||||
-- set TESSDATA_PREFIX env var
|
||||
C.setenv("TESSDATA_PREFIX", path.."/koreader/data", 1)
|
||||
|
||||
|
||||
24
reader.lua
24
reader.lua
@@ -13,13 +13,19 @@ io.stdout:write([[
|
||||
[*] Current time: ]], os.date("%x-%X"), "\n")
|
||||
io.stdout:flush()
|
||||
|
||||
-- Set up Lua and ffi search paths
|
||||
require("setupkoenv")
|
||||
|
||||
-- Apply startup user patches and execute startup user scripts
|
||||
local userpatch = require("userpatch")
|
||||
userpatch.applyPatches(userpatch.early_once)
|
||||
userpatch.applyPatches(userpatch.early)
|
||||
|
||||
-- Load default settings
|
||||
require("defaults")
|
||||
local DataStorage = require("datastorage")
|
||||
pcall(dofile, DataStorage:getDataDir() .. "/defaults.persistent.lua")
|
||||
|
||||
-- Set up Lua and ffi search paths
|
||||
require("setupkoenv")
|
||||
|
||||
io.stdout:write(" [*] Version: ", require("version"):getCurrentRevision(), "\n\n")
|
||||
io.stdout:flush()
|
||||
@@ -205,6 +211,9 @@ end
|
||||
|
||||
local UIManager = require("ui/uimanager")
|
||||
|
||||
-- Apply developer patches
|
||||
userpatch.applyPatches(userpatch.late)
|
||||
|
||||
-- Inform once about color rendering on newly supported devices
|
||||
-- (there are some android devices that may not have a color screen,
|
||||
-- and we are not (yet?) able to guess that fact)
|
||||
@@ -363,6 +372,13 @@ local function exitReader()
|
||||
end
|
||||
end
|
||||
|
||||
local ret = exitReader()
|
||||
-- Apply before_exit patches and execute user scripts
|
||||
userpatch.applyPatches(userpatch.before_exit)
|
||||
|
||||
local reader_retval = exitReader()
|
||||
|
||||
-- Apply exit user patches and execute user scripts
|
||||
userpatch.applyPatches(userpatch.on_exit)
|
||||
|
||||
-- Close the Lua state on exit
|
||||
os.exit(ret, true)
|
||||
os.exit(reader_retval, true)
|
||||
|
||||
Reference in New Issue
Block a user