Ctrl K
ComunidadGuías…

koreader-plugin/bookorbit.koplugin/bookorbit_transfer_progress.lua

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

Archivo original ↗Preguntar a KiBot
--[[--
Progress channel between a transfer subprocess and its parent.

A forked child cannot call back into KOReader's UI, so a background transfer
publishes bounded progress snapshots to a generation-scoped file instead. The
parent polls that file from short scheduled callbacks.

Each snapshot is one short line published with an atomic rename, so the parent
never reads a half-written value, malformed content decodes to nothing, and a
snapshot stamped with another generation is ignored rather than displayed.
]]

local MAGIC = "bookorbit-progress"
local VERSION = 1
local MAX_SNAPSHOT_BYTES = 128
local MIN_INTERVAL = 1
local MIN_BYTES = 128 * 1024

local TransferProgress = {}

TransferProgress.MAX_SNAPSHOT_BYTES = MAX_SNAPSHOT_BYTES

function TransferProgress.encode(snapshot)
    snapshot = snapshot or {}
    return string.format("%s %d %d %d %d %d\n",
        MAGIC,
        VERSION,
        math.max(0, math.floor(tonumber(snapshot.generation) or 0)),
        math.max(0, math.floor(tonumber(snapshot.received) or 0)),
        math.max(0, math.floor(tonumber(snapshot.total) or 0)),
        snapshot.done and 1 or 0)
end

function TransferProgress.decode(text)
    if type(text) ~= "string" or text == "" or #text > MAX_SNAPSHOT_BYTES then return nil end
    local magic, version, generation, received, total, done =
        text:match("^(%S+) (%d+) (%d+) (%d+) (%d+) (%d+)%s*$")
    if magic ~= MAGIC or tonumber(version) ~= VERSION then return nil end
    return {
        generation = tonumber(generation),
        received = tonumber(received),
        total = tonumber(total),
        done = done == "1",
    }
end

local function defaultWrite(content, path)
    local handle, err = io.open(path, "w")
    if not handle then return nil, tostring(err or "open_failed") end
    handle:write(content)
    handle:close()
    return true
end

local function defaultRead(path)
    local handle = io.open(path, "r")
    if not handle then return nil end
    local content = handle:read(MAX_SNAPSHOT_BYTES + 1)
    handle:close()
    return content
end

-- Returns writer(received, done). Snapshots are throttled so a fast transfer
-- cannot turn into a write-per-chunk storm on slow device storage; completion
-- always publishes.
function TransferProgress.writer(path, opts)
    opts = opts or {}
    local generation = opts.generation or 0
    local total = opts.total or 0
    local now = opts.now or os.time
    local write = opts.write or defaultWrite
    local rename = opts.rename or os.rename
    local remove = opts.remove or os.remove
    local min_interval = opts.min_interval or MIN_INTERVAL
    local min_bytes = opts.min_bytes or MIN_BYTES
    local temporary = path .. ".tmp"
    local last_at, last_bytes

    return function(received, done)
        received = received or 0
        local at = now()
        if not done and last_at ~= nil
                and received - last_bytes < min_bytes
                and at - last_at < min_interval then
            return false
        end
        last_at, last_bytes = at, received
        local content = TransferProgress.encode({
            generation = generation,
            received = received,
            total = total,
            done = done,
        })
        remove(temporary)
        if not write(content, temporary) then return false end
        if not rename(temporary, path) then
            remove(temporary)
            return false
        end
        return true
    end
end

-- Returns the current snapshot, or nil when the file is absent, malformed or
-- stamped with a generation the caller no longer cares about.
function TransferProgress.read(path, opts)
    opts = opts or {}
    local snapshot = TransferProgress.decode((opts.read or defaultRead)(path))
    if not snapshot then return nil end
    if opts.generation ~= nil and snapshot.generation ~= opts.generation then return nil end
    return snapshot
end

function TransferProgress.cleanup(path, opts)
    opts = opts or {}
    local remove = opts.remove or os.remove
    remove(path .. ".tmp")
    remove(path)
end

return TransferProgress