First working commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
return {
|
||||
server_url = "http://fetch.example.com/get",
|
||||
auth_token = "fill-in-with-token"
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
--[[
|
||||
KOReader Fetch Plugin
|
||||
|
||||
Fetches files from a remote server endpoint and saves them to a configured directory.
|
||||
|
||||
Installation:
|
||||
1. Copy this file to: koreader/plugins/fetch.koplugin/main.lua
|
||||
2. Create config file at: koreader/plugins/fetch.koplugin/config.lua with:
|
||||
return {
|
||||
server_url = "http://example.com/get",
|
||||
auth_token = "your-secret-token-here-change-me"
|
||||
}
|
||||
3. Restart KOReader
|
||||
]]
|
||||
|
||||
local DataStorage = require("datastorage")
|
||||
local Dispatcher = require("dispatcher")
|
||||
local FFIUtil = require("ffi/util")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local PathChooser = require("ui/widget/pathchooser")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
||||
local http = require("socket.http")
|
||||
local ltn12 = require("ltn12")
|
||||
local logger = require("logger")
|
||||
local _ = require("gettext")
|
||||
local T = FFIUtil.template
|
||||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
-- Load configuration
|
||||
local function loadConfig()
|
||||
local config_path = FFIUtil.joinPath(
|
||||
FFIUtil.joinPath(DataStorage:getDataDir(), "plugins"),
|
||||
"fetch.koplugin/config.lua"
|
||||
)
|
||||
|
||||
local ok, config = pcall(dofile, config_path)
|
||||
if ok and config then
|
||||
return config
|
||||
else
|
||||
logger.warn("Fetch plugin: Could not load config file, using defaults")
|
||||
return {
|
||||
server_url = "http://192.168.1.100:8000/get",
|
||||
auth_token = "your-secret-token-here"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local Fetch = WidgetContainer:extend{
|
||||
name = "fetch",
|
||||
is_doc_only = false,
|
||||
}
|
||||
|
||||
function Fetch:init()
|
||||
-- Load server configuration
|
||||
self.config = loadConfig()
|
||||
|
||||
-- Load or set default home directory
|
||||
self.settings = G_reader_settings:readSetting("fetch_plugin") or {}
|
||||
self.home_directory = self.settings.home_directory or "/mnt/onboard/fetched"
|
||||
|
||||
-- Add menu items
|
||||
self.ui.menu:registerToMainMenu(self)
|
||||
|
||||
logger.info("Fetch plugin initialized")
|
||||
end
|
||||
|
||||
function Fetch:addToMainMenu(menu_items)
|
||||
menu_items.fetch = {
|
||||
text = _("Fetch Files"),
|
||||
sub_item_table = {
|
||||
{
|
||||
text = _("Fetch Now"),
|
||||
callback = function()
|
||||
self:fetchFiles()
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Configure Home Directory"),
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
self:showDirectoryDialog()
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("View Settings"),
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
self:showSettings()
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
function Fetch:showSettings()
|
||||
local text = T(_(
|
||||
"Server URL: %1\n" ..
|
||||
"Auth Token: %2\n" ..
|
||||
"Home Directory: %3\n\n" ..
|
||||
"To change server settings, edit:\n" ..
|
||||
"koreader/plugins/fetch.koplugin/config.lua"
|
||||
),
|
||||
self.config.server_url,
|
||||
string.sub(self.config.auth_token, 1, 10) .. "...",
|
||||
self.home_directory
|
||||
)
|
||||
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = text,
|
||||
timeout = 5,
|
||||
})
|
||||
end
|
||||
|
||||
function Fetch:showDirectoryDialog()
|
||||
local path_chooser = PathChooser:new{
|
||||
title = _("Choose Home Directory"),
|
||||
path = self.home_directory,
|
||||
show_files = false,
|
||||
onConfirm = function(new_dir)
|
||||
self.home_directory = new_dir
|
||||
self.settings.home_directory = new_dir
|
||||
G_reader_settings:saveSetting("fetch_plugin", self.settings)
|
||||
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("Home directory set to:\n%1"), new_dir),
|
||||
timeout = 2,
|
||||
})
|
||||
end,
|
||||
}
|
||||
UIManager:show(path_chooser)
|
||||
end
|
||||
|
||||
function Fetch:fetchFiles()
|
||||
logger.info("Fetch: Starting file fetch from " .. self.config.server_url)
|
||||
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Fetching files from server..."),
|
||||
timeout = 1,
|
||||
})
|
||||
|
||||
-- Ensure home directory exists
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
local attributes = lfs.attributes(self.home_directory)
|
||||
if not attributes or attributes.mode ~= "directory" then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("Home directory does not exist:\n%1"), self.home_directory),
|
||||
timeout = 3,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Generate temporary file path for downloaded zip
|
||||
local temp_zip = FFIUtil.joinPath(
|
||||
DataStorage:getDataDir(),
|
||||
"fetch_temp_" .. os.time() .. ".zip"
|
||||
)
|
||||
|
||||
-- Download file using socket.http
|
||||
local response_body = {}
|
||||
local request_body = ""
|
||||
|
||||
local result, status_code, headers = http.request{
|
||||
url = self.config.server_url,
|
||||
method = "POST",
|
||||
headers = {
|
||||
["Authorization"] = "Bearer " .. self.config.auth_token,
|
||||
["Content-Length"] = "0",
|
||||
},
|
||||
sink = ltn12.sink.table(response_body)
|
||||
}
|
||||
|
||||
logger.info("Fetch: HTTP result=" .. tostring(result) .. ", status=" .. tostring(status_code))
|
||||
|
||||
if not result or status_code ~= 200 then
|
||||
local error_msg = _("Failed to fetch files from server")
|
||||
if status_code == 401 then
|
||||
error_msg = _("Authentication failed. Check your token.")
|
||||
elseif status_code == 404 then
|
||||
error_msg = _("No files available on server")
|
||||
elseif not result then
|
||||
error_msg = _("Could not connect to server")
|
||||
end
|
||||
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = error_msg,
|
||||
timeout = 3,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Concatenate response body
|
||||
local zip_data = table.concat(response_body)
|
||||
|
||||
if #zip_data == 0 then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("No files received from server"),
|
||||
timeout = 3,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
logger.info("Fetch: Downloaded " .. #zip_data .. " bytes")
|
||||
|
||||
-- Write zip data to temp file
|
||||
local file = io.open(temp_zip, "wb")
|
||||
if not file then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Failed to save downloaded file"),
|
||||
timeout = 3,
|
||||
})
|
||||
return
|
||||
end
|
||||
file:write(zip_data)
|
||||
file:close()
|
||||
|
||||
-- Extract zip file
|
||||
local extract_success, file_count = self:extractZip(temp_zip, self.home_directory)
|
||||
|
||||
-- Cleanup temp file
|
||||
self:cleanup(temp_zip)
|
||||
|
||||
if extract_success then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("Successfully fetched %1 file(s) to:\n%2"), file_count, self.home_directory),
|
||||
timeout = 3,
|
||||
})
|
||||
logger.info("Fetch: Successfully extracted " .. file_count .. " files")
|
||||
else
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Failed to extract downloaded files"),
|
||||
timeout = 3,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Fetch:extractZip(zip_path, destination)
|
||||
logger.info("Fetch: Extracting zip to " .. destination)
|
||||
|
||||
-- Use system unzip command
|
||||
local unzip_cmd = string.format(
|
||||
'unzip -o "%s" -d "%s" 2>&1',
|
||||
zip_path,
|
||||
destination
|
||||
)
|
||||
|
||||
logger.dbg("Fetch: Running unzip command")
|
||||
local handle = io.popen(unzip_cmd)
|
||||
if not handle then
|
||||
logger.err("Fetch: Failed to execute unzip command")
|
||||
return false, 0
|
||||
end
|
||||
|
||||
local output = handle:read("*a")
|
||||
local success = handle:close()
|
||||
|
||||
logger.info("Fetch: Unzip output: " .. output)
|
||||
|
||||
if not success then
|
||||
logger.err("Fetch: Failed to extract zip file")
|
||||
return false, 0
|
||||
end
|
||||
|
||||
-- Count extracted files from output
|
||||
local file_count = 0
|
||||
for line in output:gmatch("[^\r\n]+") do
|
||||
if line:match("inflating:") or line:match("extracting:") then
|
||||
file_count = file_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- If count is 0, try counting files in directory
|
||||
if file_count == 0 then
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
for file in lfs.dir(destination) do
|
||||
if file ~= "." and file ~= ".." then
|
||||
local attr = lfs.attributes(FFIUtil.joinPath(destination, file))
|
||||
if attr and attr.mode == "file" then
|
||||
file_count = file_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true, file_count
|
||||
end
|
||||
|
||||
function Fetch:cleanup(file_path)
|
||||
if file_path then
|
||||
os.remove(file_path)
|
||||
logger.dbg("Fetch: Cleaned up temporary file")
|
||||
end
|
||||
end
|
||||
|
||||
function Fetch:onDispatcherRegisterActions()
|
||||
Dispatcher:registerAction("fetch_files", {
|
||||
category = "none",
|
||||
event = "FetchFiles",
|
||||
title = _("Fetch files from server"),
|
||||
general = true,
|
||||
})
|
||||
end
|
||||
|
||||
function Fetch:onFetchFiles()
|
||||
self:fetchFiles()
|
||||
end
|
||||
|
||||
return Fetch
|
||||
Reference in New Issue
Block a user