Skip to content

core: apply patches (was monkey patches)#9104

Merged
poire-z merged 29 commits into
masterfrom
unknown repository
Jun 25, 2022
Merged

core: apply patches (was monkey patches)#9104
poire-z merged 29 commits into
masterfrom
unknown repository

Conversation

@ghost

@ghost ghost commented May 17, 2022

Copy link
Copy Markdown

This PR allows to apply developer patches. See #9058 (comment) and onward.

The patches directory is not created by default, so a regular user won't see any change.

Don't allow patching on FDroid.


This change is Reviewable

@poire-z

poire-z commented May 17, 2022

Copy link
Copy Markdown
Contributor

I'm ok with the code, but I wonder if it would better be made into its own file (frontent/livepatch.lua or startuppatch.lua or readerpatch.lua ?) to save 30 lines from the small and essential reader.lua where these 30 lines are a bit distracting :)
(I also have no idea why we would disable this for fdroid build - and why we already forbid scripts execution.)

@ghost

ghost commented May 17, 2022

Copy link
Copy Markdown
Author

+1 for outsourcing the patch thing.

For the scripts execution question there is #6297.

@Frenzie Frenzie added this to the 2022.06 milestone May 17, 2022
@pazos

pazos commented May 17, 2022

Copy link
Copy Markdown
Member

@ghost

ghost commented May 17, 2022

Copy link
Copy Markdown
Author

@pazos: If we drop https://github.com/koreader/koreader/blob/master/platform/android/llapp_main.lua#L19-L65 completely, we would lose some functionality.

I will see if I can move the functionality to livepatch.lua.

At a first glance: I see the main difference, that livepatch.lua is called later than patch.lua. I can not decide if this would be a problem with some existing patch.lua.

@ghost

ghost commented May 17, 2022

Copy link
Copy Markdown
Author

At a second glance I have found a minor problem. With the migrating function we can replace base libraries (crengine ...). If we do that later (in reader.lua), we probably have to restart KOReader to accept them.

@ghost ghost requested a review from Frenzie as a code owner May 17, 2022 19:00
@ghost

ghost commented May 17, 2022

Copy link
Copy Markdown
Author

So far only tested on the emulator. Will test on Kobo tomorrow.
Can not test on the other platforms.

Comment thread frontend/device/android/device.lua Outdated
Comment thread frontend/util.lua Outdated
end

--- A wrapper to os.execute
function util.execute(...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a util.execute() in base/ffi/util.lua - so even if this one's argument may be different, this could become confusing.
Also see FileManager:moveFile() in frontend/apps/filemanager/filemanager.lua.
We also have a util.shell_escape(args) used in readerdictionary.lua, which might be better at handling the quoting you do below (or not, dunno :).
Anyway, using the shell like you do here (and like we do elsewhere, because nobody cleaned that up :) is a bit odd and ugly, when Lua has a os.rename().

Comment thread reader.lua
Comment on lines +208 to +216
-- Apply developer patches
require("livepatch")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know more than us about what kind of stuff can be in your scripts.always/afterupdate, and if they are fine being executed that late.
But I feel the Android patch.lua might be used to tweak the framebuffer dimensions (@pazos ? @NiLuJe ) , and I guess it needs to be run before reader.lua - or may be at its very top. So, you might need to stage in live.patch.lua, that you could import early and run the 0* scripts early, and others later ?
Or local livepatch = require("livepatch") at top and have it run any existing patch.lua (without moving it) at require time - and for the later stuff, here do livepatch.executeLivePatches()

@ghost

ghost commented May 18, 2022

Copy link
Copy Markdown
Author

So now we have

  • and master-patch.lua (the moved and renamed patch.lua, for consistency) and
  • the folders scripts.always, scripts.afterupdate and patches.

In all tree folders there might be shell-scripts, lua patches and the migrate funcitonallity. (Not tested for now if lua-patches and migrate bite each other, without migrate it works.)

The contents of scripts-directories and master-patch.lua are processed almost on start of reader.lua. This should allow the same functionality as the deleted code in llapp_main.lua (but now on almost all devices).

Te contents of the patches folder are processed later, to allow monky-patching.

Tested on the Emulator, Kobo Sage and on my mobile.

Comment thread reader.lua Outdated
Comment on lines 16 to 26
-- Set up ffi search paths
require("setupkopaths")

-- Apply `patches/patch.lua` and execute startup user scripts and perform user data migration
local livepatch = require("livepatch")
livepatch.executeScriptsAndMigrate()

-- Load default settings
require("defaults")
local DataStorage = require("datastorage")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this split of setupkoenvs into setupkopaths is really needed - as setupkoenv doesn't seem to use anything from the DataStorage stuff (and the loading of defaults.persistent.lua that happens just after what I could select to comment on :) and the require("setupkoenv") that happens just after.
So, I guess you could just move the require("setupkoenv") where you put your require("setupkopaths").
I also guess no patch/livepatch stuff would need to tweak the ffi load stuff hat happens in setupkoenv, and this patching stuff might actually be happy to have the ffi loading stuff available.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the current implementation is as close to the deleted Android one as possible. Splitting setupkoenv in two parts allows:
1.) to patch the search paths (e.g. a custom plugin) and
2.) copy some .so libraries before they are used (e.g. crengine).
(So a really developer, testing thing).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2nd part of setupkoenv.lua does not load any library (I think, @NiLuJe ?), it just redefines ffi.load, so I think this won't hurt if done early if you just move the require("setupkoenv") where you put your require("setupkopaths").

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's wait on @NiLuJe.

Comment thread frontend/livepatch.lua Outdated
Comment thread frontend/livepatch.lua Outdated
Comment on lines +110 to +111
-- If an older `patch.lua` is found it is moved to `patches/master-patch.lua`. This has to be done here,
-- as onetime_migration gets called to late.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"master-patch.lua" feels a bit unprecise (why "master" ?). "early-patch.lua" would sound more true.
And if we allow multiple "late" patch files, why only a single "early" file ?
Also, dunno about moving an existing patch.lua (I would just run it early if it exists).

@ghost ghost May 18, 2022

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to bundle all developer scripts and patches in as least places as possible. IMHO an additional patch.lua feels weird.

And I did not want to change existing behaviour, so we already have scripts.always and scripts.afterupdate. But you could complain that patches does not fit in that row. So what about renaming patches-> scripts.late (And I move the early-patch.lua into scripts.always).

Note to self: Check if folder exists, before moving.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO an additional patch.lua feels weird.

I mean, don't advertise about it, but if there is one (legacy user), run it if found.
But advertise the new common way to do that.

I wanted to bundle all developer scripts and patches in as least places as possible

So do I :)

And I did not want to change existing behaviour

I have no problem with changeing it, current users are rare (mostly us), and will notice it and update it according to the new way/documention :) I don't think we would need to do any migration about them.
(Except for patch.lua that some old users may have added one with viewport stuff specific to their devices, and that could have forgotten about it - so either keep it in its original place and use it if found - or move it as you did.)

If we don't change anything, these directory names will sound inconsistent:

scripts.always/       (ok, always, but late or early ?)
scripts.afterupdate/  (ok, only after update, but always or early ? :)
scripts.late/         (ok, late, but always I guess)

So, we might want to decide about a generic futureproof scheme. Directories (but they would clutter koreader/ directory), convention about filenames (like /etc/rc1.d/ ... rc6.d/ on old Unix) in a single directory (patch/ livepatch/ userscripts/?) ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep inconsistent. May I propose:

livepatches/early_afterupdate  (early and after an update)
livepatches/early              (early independend to updates)
livepatches/late               (late independend to updates)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These would be subdirectories ? or filenames ? or filename prefixes ?

Also, livepatches and livepatch.lua may better be named userpatch.lua, to make it obvious it's for handling... users' patches :) (or is the notion of "live" patch to distinguish from manual code patching is important ?)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thees would be subdirectories, with files and folders in them.

userpatches/early_afterupdate/  (early and after an update)
userpatches/early/              (early independend to updates)
userpatches/late/               (late independend to updates)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with files and folders in them.

Felt like overengineering :) but I guess "folders in them" is just for content that should be copied into koreader's own tree, like hyphenation files ? (and you would not want a tweaked translator.lua - to be copied over koreader's one - to lie into userpatches/early/ and be considered a patch itself).

I guess personally, I would have gone with:
file starting with 0-* : early patch
file starting with 1-* : later patch
file starting with 0somethingelse-* afterupdate early patch (no real idea for a prefix :)
anything else: ignore.
So everything is quite visible in that layout, and you can just rename a file (add a NOT_0-test.lua prefix) to have it not considered.

Comment thread frontend/livepatch.lua Outdated
Comment on lines +116 to +118
if lfs.attributes(afterupdate_marker, "mode") == "file" then
local migrate = lfs.attributes(run_once_scripts .. "/migrate", "mode") ~= nil
logger.info("after-update: running", migrate and "migration" or "shell scripts")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What for do you use this "/migrate/" stuff ? For your german hyphenation test file ? Anything else ?
Dunno if other users than you do use this feature - but may be we could simplify the amount of available tricks :) and just use early and last lua scripts. You could do any move/migration from a Lua script (even if shell scripts might be easier to write for that kind of thing), it would feel cleaner to me.
You could add helpers methods in this livepatch.lua so shell stuff like cp can be easily done in a lua file.

(I'm having a hard time understanding what's possible and what happens when reading this new linvepatch.lua.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if "migrate" stuff only do copy of files from the user dir into koreader distributed dir, I would at least don't use the word "migrate" in the code/comments, and it feels to me you are migrating some files/settings. Just "copy" would work and read clearer.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I thought that it was you, who wanted and implemented the migrate thing ;) But after studying the old commits I believe @pazos introduced the migrate functionality.
I don't use migrate at all. So, if we want to drop it, I would be glad ;)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question concerning the execute function. Now it uses os.execute. Would it be of any benefit to use os.rename and os.remove where possible?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pure Lua for clarity, and it would avoid some issues with executing /bin/mv via the shell and the need to escape filenames properly.

@ghost

ghost commented May 18, 2022

Copy link
Copy Markdown
Author

Changes to setupkoenv not taken into account, yet.

Comment thread frontend/livepatch.lua Outdated
Comment on lines +11 to +13
early_afterupdate = "0",
early = "1",
late = "2",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, looks logical :)

Comment thread frontend/livepatch.lua Outdated
if mode == "file" and fullpath:match("%.sh$") then -- execute shell scripts
execute("sh", fullpath, home_dir .. "/koreader", package_dir)
elseif mode == "file" and fullpath:match("%.lua$")
and not fullpath:match("early%-patch%.lua$") then -- execute patch-files

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that early%-patch%.lua check still needed ? or leftover ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a harmless leftover -> to be removed.

@ghost ghost changed the title core: apply patches core: apply patches (was monkey patches) May 19, 2022
@ghost

ghost commented May 19, 2022

Copy link
Copy Markdown
Author

@pazos:

Do you see any problems with removing the migrate option (IMHO any script or lua-patch can do that if someone really needs this).

Is this solution to apply (0000-patch.lua) very early in reader.lua sufficient to replace the old patch.lua?

@poire-z

poire-z commented May 19, 2022

Copy link
Copy Markdown
Contributor

@NiLuJe : we need some feedback from you (questions above, and general feeling about this PR).


Just pasting here some very old patch.lua I had around, with various experiments of mine, so we can see what kind of trick people could have done (and might still be doing):

-- Android Koreader patches

-- Avoid koreader stuck and crashes
require("jit.opt").start("sizemcode=64","maxmcode=512", "recunroll=8", "callunroll=12", "loopunroll=60", "instunroll=16", "tryside=16", "hotexit=160", "hotloop=1792", "maxsnap=160000", "maxside=32000", "maxirconst=160000", "maxrecord=1280000", "maxtrace=320000")
for i=1,1000 do end  -- Force allocation of one large segment

-- Remove this plugin
local A = require("android")
A.execute("rm", "-rf", "plugins/backgroundrunner.koplugin")

-- Set CREngine documents background color
local called_once = false
local fba = require("ffi/framebuffer_android")
fba.refreshFullImp_orig = fba.refreshFullImp
fba.refreshFullImp = function(self)
    fba.refreshFullImp_orig(self)
    if not called_once then
        called_once = true
        return
    end
    fba.refreshFullImp = fba.refreshFullImp_orig
    local CreDocument = require("document/credocument")
    CreDocument.initOrig = CreDocument.init
    CreDocument.init = function(self)
        CreDocument.initOrig(self)
        self._document:setStringProperty("background.color.default", "#eeeeee")
    end
end
-- Mais en fait non
fba.refreshFullImp = fba.refreshFullImp_orig

-- Make white be gray
-- local BB = require("ffi/blitbuffer")
-- BB.COLOR_WHITE = BB.Color8(0xCC)

-- OLD, no more needed:
-- fba.init = function(self)
--     self.bb = BB.new(android.screen.width, android.screen.height, BB.TYPE_BBRGB32)
--     self.bb:fill(BB.COLOR_WHITE)
--     self:refreshFull()
--     fba.parent.init(self)
-- end

-- OLD, no more needed:
-- local android = require("android")
-- android.setScreenBrightnessOrig = android.setScreenBrightness
-- android.setScreenBrightness = function()
--     android.setScreenBrightness = android.setScreenBrightnessOrig
-- end

@NiLuJe

NiLuJe commented May 19, 2022

Copy link
Copy Markdown
Member

I might have some time to look at it tomorrow evening. If not, ping me again on Sunday ;).

@pazos

pazos commented May 20, 2022

Copy link
Copy Markdown
Member

@zwim: I'm sorry. Was so busy this week :p.

Do you see any problems with removing the migrate option (IMHO any script or lua-patch can do that if someone really needs this).

Not really, I coded it as a friendlier manner of copying stuff to /data/data/org.koreader.launcher than shell scripts.

I would also like the shell scripts patch refactored.

I wrote something a few days ago but I had no time to finish it. (and a lot of commits happened since then). I'm hope it is still relevant:

My two cents based on @poire-z feedback and @zwim code/feedback

1. Keep `patch.lua` logic in `llapp_main.lua`.

2. Make `LivePatch` the full fledged thing:

    - The code that runs scripts can be factorized as ` LivePatch:runScript(script, arg1, arg2)` and invoked from patches.
    - 
    - (recursive) copy can be factorized as well.
    - write hooks and call them on different stages of `reader.lua`. (ie: `LivePatch:onLaunch()` will try to pcall `data_dir`/patches/launch.lua. Make as many hooks as you might find useful. (One or two look enough to me)
    - the specific android condition where we need to copy some data to internal app storage after each update can be renamed as LivePatch:requiresOneTime()` and return false everywhere except in android, after an update.

3. Move all custom logic to dev patches. (like checking if we're just updated and copying files or executing shell scripts based on that)


Also we need to assume we can't live-patch some device stuff without a companion `Device:reload()` function.

@pazos

pazos commented May 20, 2022

Copy link
Copy Markdown
Member

Anyhow, I'll plan to review this on sunday too :)

@ghost

ghost commented May 21, 2022

Copy link
Copy Markdown
Author
- the specific android condition where we need to copy some data to internal app storage after each update can be renamed as LivePatch:requiresOneTime()` and return false everywhere except in android, after an update.

Should work with userpatches/0000-patch.lua (which is what the "old" patch.lua gets renamed to).

  1. Move all custom logic to dev patches. (like checking if we're just updated and copying files or executing shell scripts based on that)

Yep, we can have *.sh and *.lua patches now.

Also we need to assume we can't live-patch some device stuff without a companion Device:reload() function.

No need for that, if we the patches are in level 0 or 1.

@pazos pazos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the current state of affairs (at all!)

Looks utterly complicated for something that should be way simpler: provide different hooks/entry points for developers to pcall stuff and modify the behaviour/logic of the program during runtime execution.

Since I'm going to comment on certain issues that I see on the code I'm trying to put my global impression as a general comment here: it's not a matter of fixing the specific issues but a design decision:

do we need all these priorities, all those hooks and the ability to execute shell scripts buried inside this part of the logic?

Comment thread Makefile Outdated
Comment thread frontend/userpatch.lua Outdated
}

if isAndroid and android.prop.flavor == "fdroid" then
return userpatch

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return nil?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No!. Returning the whole field, will allow us to call the no-op applyPatches even on F-Droid -> see below.

Comment thread frontend/userpatch.lua Outdated
Comment thread frontend/userpatch.lua Outdated
Comment thread frontend/userpatch.lua Outdated
return userpatch -- live patching is not supported
end

logger.info("Live update using package_dir:", package_dir, "; home_dir:", home_dir)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want this logged unconditionally each time the program starts on devices/users that don't use this feature.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yepp, that's just for testing this PR -> will be removed.

Comment thread reader.lua
Comment thread frontend/userpatch.lua Outdated
return os.execute(table.concat(command))
end

--- Run user shell scripts or lua patches

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of running arbitrary shell scripts?

Now that you have hooks available to run arbitrary lua code sounds easier to just call shell/python or perl scripts from your own lua patches.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing shell scripts is a 4-liner and why not use it. If you want execute patch .... it would be clumsy to write a patch.lua and execute the patch command there.

@NiLuJe NiLuJe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 8 of 11 files at r5, 3 of 3 files at r10, all commit messages.
Reviewable status: all files reviewed, 17 unresolved discussions (waiting on @Frenzie, @pazos, @poire-z, and @zwim)


reader.lua line 25 at r5 (raw file):

Previously, zwim wrote…

OK, let's wait on @NiLuJe.

Yep, it just sets up the searchpaths for ffi.load.

I have no idea what the original impelmentation was used for, but touching ffi.load seems like a terribad idea ;).

(You can just pass a path to ffi.load if you ever need to load something in a plugin).


reader.lua line 384 at r10 (raw file):

-- Apply exit user patches and execute user scripts
userpatch.applyPatches(userpatch.on_exit)

Kind of the same question as @poire-z above:
What's the use-case for those?

Code quote:

-- 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)

@NiLuJe

NiLuJe commented May 22, 2022

Copy link
Copy Markdown
Member

Same general feeling as @pazos, that it feels a bit cthulhuesque for no good reason.

Then again, I'm not the target audience, so that doesn't really help getting into it ;).

Comment thread frontend/ui/data/onetime_migration.lua Outdated

-- Date at which the last migration snippet was added
local CURRENT_MIGRATION_DATE = 20220523
local CURRENT_MIGRATION_DATE = 20220615

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last update of this date :) and we're good to go.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a long birth. But it looks good ;)

@poire-z poire-z merged commit 41e78b6 into koreader:master Jun 25, 2022
@ghost ghost deleted the monkeyPatch branch June 25, 2022 20:56
@ghost ghost mentioned this pull request Jun 28, 2022
@ghost

ghost commented Aug 12, 2022

Copy link
Copy Markdown
Author

For documentation: A small patch to add a menu entry for USBNet can be found here https://gist.github.com/zwim/b0d46fa83d9dbb853324f3e0c17562b5

Works on a Kobo.

@NiLuJe

NiLuJe commented Oct 14, 2022

Copy link
Copy Markdown
Member

Speaking of documentation: this should probably be documented somewhere on the Wiki? ;).

@ghost

ghost commented Oct 14, 2022

Copy link
Copy Markdown
Author

Do you mean the userpatch-feature itself, or a collection of available patches or the USBNet-specific patch?

For general documentation of the userpatch option I would ping @offset-torque,
for documenting available (untested) userpatches I could imagine a Wiki-page (maybe witch links to gists for downloading)
and the special USBNet-patch one I don't know the expected audience.

I would vote for the Wiki-page.

@NiLuJe

NiLuJe commented Oct 14, 2022 via email

Copy link
Copy Markdown
Member

@ghost

ghost commented Oct 14, 2022

Copy link
Copy Markdown
Author

:+1

If nobody else wants to to this, I will try to include this in the wiki. Maybe between Plugins and Development there would be place to add a section. I would not put it into Tipps&Tricks as this 1st is to hidden and 2nd it is too complicated for a novice.

  • offset-torque is notified
  • documenation
  • Wiki-page

@offset-torque

Copy link
Copy Markdown

Yep, this was on my radar which I was going to ask about.
I just wasn't sure that if userpatch is a temporary fix reserved for developer usage or it is an official feature that we want to introduce to all users.

For general documentation of the userpatch option I would ping offset-torque,
for documenting available (untested) userpatches I could imagine a Wiki-page (maybe witch links to gists for downloading)

Exactly what I imagined. I can explain this feature in the User Guide and direct users to the wiki for checking the available patches. When there is a new patch, its author can add it to the wiki with a short explanation.

I was in the process of adding an Advanced Operations section/tag to the User Guide so this feature already has a place to go.

@hius07

hius07 commented Oct 15, 2022

Copy link
Copy Markdown
Member

direct users to the wiki for checking the available patches.

And there is a special label:
https://github.com/koreader/koreader/issues?q=label%3A%22User+patch+available%22

@ghost

ghost commented Oct 16, 2022

Copy link
Copy Markdown
Author

I have done a preliminary Wiki entry. Please feel free to change/update.

rjd22 pushed a commit that referenced this pull request Nov 7, 2022
Supersede old android-only patch.lua.
@poire-z

poire-z commented Apr 14, 2023

Copy link
Copy Markdown
Contributor

For reference and for @kaznelson, regarding https://www.mobileread.com/forums/showthread.php?p=4314627#post4314627 and patching gesturedetector.lua's private module-local variable object Contact, which I thought would be impossible without modifying our code to make this Contact object accessible by referencing it as a member of the public GestureDetector obeject.

-- Contact object, it'll keep track of everything we need for a single contact across its lifetime
-- i.e., from this contact's down to up (or its *effective* up for double-taps, e.g., when the tap or double_tap is emitted).
-- We'll identify contacts by their slot numbers, and store 'em in GestureDetector's active_contacts table (hash).
local Contact = {} -- Class object is empty, as we do *NOT* want inheritance outside of methods.
function Contact:new(o)
setmetatable(o, self)
self.__index = self
return o
end

I wondered if with some Lua magic we could reach it, and it happens we can, so sharing this useful trick:

local GestureDetector = require("device/gesturedetector")

-- local logger = require("logger")
-- local z = debug.getinfo(GestureDetector.newContact)
-- logger.warn(z)

-- Find the Contact object, which is local and private to gesturedetector.lua.
-- But as the public GestureDetector.newContact uses it, we can fetch it from
-- its upvalues!
local Contact
local n = 1
while true do
    local name, value = debug.getupvalue(GestureDetector.newContact, n)
    if not name then break end
    if name == "Contact" then
        Contact = value
        break
    end
    n = n + 1
end
if not Contact then return end
-- logger.warn("found:", Contact.getPath)

-- Now we can override that private object method
Contact.getPath = function(self, simple, diagonal, initial_tev)
    return "west", 123 -- replace that with your code
end

@NiLuJe

NiLuJe commented Apr 14, 2023

Copy link
Copy Markdown
Member

Oops, I'd forgotten that Contact was entirely private ;p.

(Usual caveat that anything from the debug module is slow and should be avoided unless you really really need it ;)).

@poire-z

poire-z commented Apr 14, 2023

Copy link
Copy Markdown
Contributor

(The debug.stuff would just be used once, just when executing the patch to setup things - and indeed, in the things it setups, it better be classic lua, and optimized if run a lot.)

@NiLuJe

NiLuJe commented Apr 14, 2023

Copy link
Copy Markdown
Member

Oh, yeah, it's definitely harmless here, that was just a preemptive warning about its usage in general ;o).

@kaznelson

kaznelson commented Apr 15, 2023

Copy link
Copy Markdown

@NiLuJe, @poire-z
Thanks! It works!
True, I adjusted the swipe angles to my liking - when the diagonal swipe is not used. But now it’s easy (hmm... relatively) to do it as convenient.

-- Override angles of swipes (in paging favor):
-- sector 15° for diagonal swipe,
-- sector ±60° for horizontal swipe,
-- sector ±15° for vertical swipe.

-- Source:
-- https://github.com/koreader/koreader/pull/9104

-- Triangle calculation:
-- https://www.calculator.net/triangle-calculator.html
-- https://www.calc.ru/raschet-treugolnika.html

local GestureDetector = require("device/gesturedetector")

-- local logger = require("logger")
-- local z = debug.getinfo(GestureDetector.newContact)
-- logger.warn(z)

-- Find the Contact object, which is local and private to gesturedetector.lua.
-- But as the public GestureDetector.newContact uses it, we can fetch it from
-- its upvalues!
local Contact
local n = 1
while true do
    local name, value = debug.getupvalue(GestureDetector.newContact, n)
    if not name then break end
    if name == "Contact" then
        Contact = value
        break
    end
    n = n + 1
end
if not Contact then return end
-- logger.warn("found:", Contact.getPath)

-- Now we can override that private object method
Contact.getPath = function(self, simple, diagonal, initial_tev)
    -- Replace that with your code
    initial_tev = initial_tev or self.initial_tev

    local x_diff = self.current_tev.x - initial_tev.x
    local y_diff = self.current_tev.y - initial_tev.y
    local direction = nil
    local distance = math.sqrt(x_diff*x_diff + y_diff*y_diff)
    if x_diff ~= 0 or y_diff ~= 0 then
        local v_direction = y_diff < 0 and "north" or "south"
        local h_direction = x_diff < 0 and "west" or "east"
        if (not simple
            -- Sector 15° for diagonal swipe (as "from:to")
            and math.abs(y_diff) > 1.732*math.abs(x_diff)
            and math.abs(y_diff) < 3.732*math.abs(x_diff))
           or (simple and diagonal)
        then
            direction = v_direction .. h_direction
        -- Sector ±60° for horizontal swipe
        elseif (math.abs(x_diff) > 0.577*math.abs(y_diff)) then
            direction = h_direction
        else
            -- Remaining sector ±15° for vertical swipe
            direction = v_direction
        end
    end
    return direction, distance
end

2-override-angles-of-swipes.zip

@poire-z

poire-z commented May 19, 2024

Copy link
Copy Markdown
Contributor

For reference, another trick (following the one above about reaching local variables), to allow reaching and user patching plugins:

local PluginLoader = require("pluginloader")
-- On each new Reader/FileManager, plugins are dofile()d, and then
-- instantiated thru createPluginInstance: catch there ere.
local patch_plugin_func -- defined below
local orig_PluginLoader_createPluginInstance = PluginLoader.createPluginInstance
PluginLoader.createPluginInstance = function(self, plugin, attr)
    local ok, plugin_or_err = orig_PluginLoader_createPluginInstance(self, plugin, attr)
    if ok and plugin.name == "your_target_plugin_name" then -- ie. "coverbrowser"
        patch_plugin_func(plugin)
    end
    return ok, plugin_or_err
end

patch_plugin_func = function(plugin)
    -- do your patching to the plugin object here
end

Some hardcore example combining these 2 tricks at #11838 (comment).

@poire-z

poire-z commented May 24, 2024

Copy link
Copy Markdown
Contributor

Added some helpers functions with these tricks in the userpatch module in #11873, fd7e224 .

0xstillb pushed a commit to 0xstillb/koreader-thai that referenced this pull request May 9, 2026
Supersede old android-only patch.lua.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants