Ctrl K
ComunidadGuías…

koreader-plugin/bookorbit.koplugin/bookorbit_menu_pin.lua

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
--[[--
Pins the BookOrbit menu entry onto the first page of the Tools menu, right
below calibre, and puts BookOrbit's icon in KOReader's menu bar just before
the main menu.

KOReader caches its bundled menu-order tables and lets plugins extend them
before the menu is built. Mutating those shared tables preserves entries added
by every plugin regardless of load order. Older BookOrbit releases instead
wrote complete user override lists, which hid entries added by later plugins;
those marker-owned files are removed during migration. A hand-maintained file
keeps its other entries and only has BookOrbit ids normalized.
]]

local DataStorage = require("datastorage")
local dump = require("dump")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local util = require("util")

local MARKER = "-- bookorbit-menu-pin v3 (generated; delete this file to reset menu order)"
local OLD_MARKERS = {
    "-- bookorbit-sync-pin v1 (generated; delete this file to reset menu order)",
    "-- bookorbit-menu-pin v2 (generated; delete this file to reset menu order)",
}
local TAB_ID = "bookorbit_tab"
local BUTTONS = "KOMenu:menu_buttons"
local MENU_ID = "bookorbit"
local OLD_MENU_IDS = {
    "bookorbit_browse",
    "bookorbit_sync",
}

local PREFIXES = {
    { prefix = "reader", order_module = "ui/elements/reader_menu_order" },
    { prefix = "filemanager", order_module = "ui/elements/filemanager_menu_order" },
}

local EMPTY_OVERRIDE = MARKER .. "\nreturn {}\n"

local BookOrbitMenuPin = {
    done = false,
    -- Whether BookOrbit's icon belongs in the menu bar; set from the
    -- plugin's setting before ensure or refresh runs.
    tab = true,
}

local function removeId(tools, menu_id)
    local removed = false
    for i = #tools, 1, -1 do
        if tools[i] == menu_id then
            table.remove(tools, i)
            removed = true
        end
    end
    return removed
end

local function removeOldIds(tools)
    local removed = false
    for _, menu_id in ipairs(OLD_MENU_IDS) do
        removed = removeId(tools, menu_id) or removed
    end
    return removed
end

local function normalizeBookOrbitIds(tools)
    removeOldIds(tools)
    removeId(tools, MENU_ID)

    local insert_at = #tools
    for i, id in ipairs(tools) do
        if id == "calibre" then
            insert_at = i
            break
        end
    end

    table.insert(tools, insert_at + 1, MENU_ID)
end

-- The menu bar with BookOrbit's tab just before the main menu (the last
-- button), or without it.
local function normalizeButtons(buttons, with_tab)
    removeId(buttons, TAB_ID)
    if not with_tab then return end
    local insert_at = #buttons + 1
    for i, id in ipairs(buttons) do
        if id == "main" then
            insert_at = i
            break
        end
    end
    table.insert(buttons, insert_at, TAB_ID)
end
BookOrbitMenuPin.normalizeButtons = normalizeButtons

local function normalizeRuntimeOrder(order)
    if type(order) ~= "table" or type(order.tools) ~= "table" then return end
    normalizeBookOrbitIds(order.tools)
    if type(order[BUTTONS]) == "table" then
        normalizeButtons(order[BUTTONS], BookOrbitMenuPin.tab)
        -- MenuSorter treats a button as a tab only when it has an order list.
        order[TAB_ID] = BookOrbitMenuPin.tab and {} or nil
    end
end

local function isGeneratedByUs(existing)
    if existing:sub(1, #MARKER) == MARKER then
        return true
    end
    for _, marker in ipairs(OLD_MARKERS) do
        if existing:sub(1, #marker) == marker then
            return true
        end
    end
    return false
end

local function migrateOverride(prefix)
    local path = DataStorage:getSettingsDir() .. "/" .. prefix .. "_menu_order.lua"
    if not lfs.attributes(path) then return end

    local existing = util.readFromFile(path, "r")
    if not existing then
        logger.warn("BookOrbit: cannot read menu order file, leaving it alone:", path)
        return
    end

    if isGeneratedByUs(existing) then
        local ok, err = os.remove(path)
        if not ok then
            local cleared, clear_err = util.writeToFile(EMPTY_OVERRIDE, path, true)
            if not cleared then
                logger.warn("BookOrbit: cannot clear generated menu order file", path, err, clear_err)
            end
        end
        return
    end

    -- Hand-maintained file: replace stale BookOrbit ids without touching other entries.
    local parsed, user = pcall(dofile, path)
    if not parsed or type(user) ~= "table" then
        logger.warn("BookOrbit: cannot parse user menu order file, leaving it alone:", path)
        return
    end
    if type(user.tools) == "table" then
        normalizeBookOrbitIds(user.tools)
    end
    if type(user[BUTTONS]) == "table" then
        normalizeButtons(user[BUTTONS], BookOrbitMenuPin.tab)
        user[TAB_ID] = BookOrbitMenuPin.tab and {} or nil
    else
        user[TAB_ID] = nil
    end
    local ok, err = util.writeToFile(dump(user, nil, true), path, true, true)
    if not ok then
        logger.warn("BookOrbit: cannot update user menu order file", path, err)
    end
end

local function ensureOne(prefix, order_module)
    local ok, order = pcall(require, order_module)
    if not ok then
        logger.warn("BookOrbit: cannot load menu order", order_module, order)
        return
    end
    normalizeRuntimeOrder(order)
    migrateOverride(prefix)
end

local function ensureAll()
    for _, entry in ipairs(PREFIXES) do
        local ok, err = pcall(ensureOne, entry.prefix, entry.order_module)
        if not ok then
            logger.warn("BookOrbit: menu pin failed for", entry.prefix, err)
        end
    end
end

function BookOrbitMenuPin.ensure(with_tab)
    if BookOrbitMenuPin.done then return end
    BookOrbitMenuPin.done = true
    if with_tab ~= nil then BookOrbitMenuPin.tab = with_tab end
    ensureAll()
end

-- Updates both cached orders after the menu bar setting changes.
function BookOrbitMenuPin.refresh(with_tab)
    BookOrbitMenuPin.tab = with_tab ~= false
    BookOrbitMenuPin.done = true
    ensureAll()
end

return BookOrbitMenuPin