diff --git a/frontend/apps/cloudstorage/cloudstorage.lua b/frontend/apps/cloudstorage/cloudstorage.lua index a696dff27..c447e14b3 100644 --- a/frontend/apps/cloudstorage/cloudstorage.lua +++ b/frontend/apps/cloudstorage/cloudstorage.lua @@ -282,6 +282,7 @@ function CloudStorage:configCloud(type) address = fields[2], username = fields[3], password = fields[4], + folder = fields[5], type = "ftp", url = "/" }) @@ -318,6 +319,7 @@ function CloudStorage:editCloudServer(item) server.address = fields[2] server.username = fields[3] server.password = fields[4] + server.folder = fields[5] cs_servers[i] = server break end diff --git a/frontend/apps/cloudstorage/ftp.lua b/frontend/apps/cloudstorage/ftp.lua index 1c295270c..db03bd0d4 100644 --- a/frontend/apps/cloudstorage/ftp.lua +++ b/frontend/apps/cloudstorage/ftp.lua @@ -5,33 +5,21 @@ local MultiInputDialog = require("ui/widget/multiinputdialog") local ReaderUI = require("apps/reader/readerui") local Screen = require("device").screen local UIManager = require("ui/uimanager") +local logger = require("logger") local util = require("util") local _ = require("gettext") local T = require("ffi/util").template -local Ftp = { -} -local function generateUrl(address, user, pass) - local colon_sign = "" - local at_sign = "" - if user ~= "" then - at_sign = "@" - end - if pass ~= "" then - colon_sign = ":" - end - local replace = "://" .. user .. colon_sign .. pass .. at_sign - local url = string.gsub(address, "://", replace) - return url -end +local Ftp = {} function Ftp:run(address, user, pass, path) - local url = generateUrl(address, user, pass) .. path + local url = FtpApi:generateUrl(address, user, pass) .. path return FtpApi:listFolder(url, path) end function Ftp:downloadFile(item, address, user, pass, path, close) - local url = generateUrl(address, user, pass) .. item.url + local url = FtpApi:generateUrl(address, user, pass) .. item.url + logger.dbg("downloadFile url", url) local response = FtpApi:downloadFile(url) if response ~= nil then path = util.fixUtf8(path, "_") @@ -66,6 +54,8 @@ function Ftp:config(item, callback) local text_username = "" local hint_password = _("FTP password") local text_password = "" + local hint_folder = _("FTP folder") + local text_folder = "/" local title local text_button_right = _("Add") if item then @@ -75,6 +65,7 @@ function Ftp:config(item, callback) text_address = item.address text_username = item.username text_password = item.password + text_folder = item.folder else title = _("Add FTP account") end @@ -101,6 +92,11 @@ function Ftp:config(item, callback) input_type = "string", hint = hint_password, }, + { + text = text_folder, + input_type = "string", + hint = hint_folder, + }, }, buttons = { { diff --git a/frontend/apps/cloudstorage/ftpapi.lua b/frontend/apps/cloudstorage/ftpapi.lua index faca68040..74aa6579e 100644 --- a/frontend/apps/cloudstorage/ftpapi.lua +++ b/frontend/apps/cloudstorage/ftpapi.lua @@ -6,6 +6,20 @@ local url = require("socket.url") local FtpApi = { } +function FtpApi:generateUrl(address, user, pass) + local colon_sign = "" + local at_sign = "" + if user ~= "" then + at_sign = "@" + end + if pass ~= "" then + colon_sign = ":" + end + local replace = "://" .. user .. colon_sign .. pass .. at_sign + local generated_url = string.gsub(address, "://", replace) + return generated_url +end + function FtpApi:nlst(u) local t = {} local p = url.parse(u) @@ -69,4 +83,12 @@ function FtpApi:downloadFile(file_path) return ftp.get(file_path ..";type=i") end +function FtpApi:delete(file_path) + local p = url.parse(file_path) + p.argument = string.gsub(p.path, "^/", "") + p.command = "dele" + p.check = 250 + return ftp.command(p) +end + return FtpApi diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 40673349c..263508e8e 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -37,6 +37,7 @@ local order = { "cloud_storage", "read_timer", "news_downloader", + "send2ebook", "----------------------------", "more_plugins", "----------------------------", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 25b450fe4..1a811fe6b 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -58,6 +58,7 @@ local order = { "progress_sync", "zsync", "news_downloader", + "send2ebook", "----------------------------", "more_plugins", }, diff --git a/plugins/send2ebook.koplugin/main.lua b/plugins/send2ebook.koplugin/main.lua new file mode 100644 index 000000000..416995bc8 --- /dev/null +++ b/plugins/send2ebook.koplugin/main.lua @@ -0,0 +1,229 @@ +local DataStorage = require("datastorage") +local DocSettings = require("frontend/docsettings") +local ReadHistory = require("readhistory") +local FFIUtil = require("ffi/util") +local Ftp = require("apps/cloudstorage/ftp") +local FtpApi = require("apps/cloudstorage/ftpapi") +local InfoMessage = require("ui/widget/infomessage") +local LuaSettings = require("frontend/luasettings") +local UIManager = require("ui/uimanager") +local NetworkMgr = require("ui/network/manager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local logger = require("logger") +local util = require("util") +local _ = require("gettext") +local T = FFIUtil.template + +local Send2Ebook = WidgetContainer:new{} + +local initialized = false +local wifi_enabled_before_action = true +local send2ebook_config_file = "send2ebook_settings.lua" +local config_key_custom_dl_dir = "custom_dl_dir"; +local default_download_dir_name = "send2ebook" +local supported_ftp_file_type = ".epub" +local download_dir_path +local send2ebook_settings + +local function stringEnds(str,suffix) + return suffix=="" or string.sub(str,-string.len(suffix))==suffix +end + +function Send2Ebook:downloadFileAndRemove(connection_url, remote_path, local_download_path) + local url = connection_url .. remote_path + local response = FtpApi:downloadFile(url) + + if response ~= nil then + local_download_path = util.fixUtf8(local_download_path, "_") + local file = io.open(local_download_path, "w") + file:write(response) + file:close() + FtpApi:delete(url) + return 1 + else + logger.err("Send2Ebook: Error. Invalid connection data? ") + return 0 + end +end + +-- TODO: implement as NetworkMgr:afterWifiAction with configuration options +function Send2Ebook:afterWifiAction() + if not wifi_enabled_before_action then + NetworkMgr:promptWifiOff() + end +end + +function Send2Ebook:init() + self.ui.menu:registerToMainMenu(self) +end + +function Send2Ebook:addToMainMenu(menu_items) + self:lazyInitialization() + menu_items.send2ebook = { + text = _("Send2Ebook (Receiver)"), + sub_item_table = { + { + text = _("Download and remove from server"), + callback = function() + if not NetworkMgr:isOnline() then + wifi_enabled_before_action = false + NetworkMgr:beforeWifiAction(self.process) + else + self:process() + end + end, + }, + { + text = _("Go to download folder"), + callback = function() + local FileManager = require("apps/filemanager/filemanager") + if FileManager.instance then + FileManager.instance:reinit(download_dir_path) + else + FileManager:showFiles(download_dir_path) + end + end, + }, + { + text = _("Remove read (opened) articles"), + callback = self.removeReadActicles, + }, + { + text = _("Set custom download directory"), + callback = self.setCustomDownloadDirectory, + }, + { + text = _("Settings"), + callback = self.editFtpConnection, + }, + { + text = _("Help"), + callback = function() + UIManager:show(InfoMessage:new{ + text = T(_('Send2Ebook lets you send articles found on PC/Android phone to your Ebook reader (using ftp server).\n\nMore details: https://github.com/mwoz123/send2ebook\n\nDownloads to local folder: %1'), download_dir_path) + }) + end, + }, + }, + } +end + +function Send2Ebook:lazyInitialization() + if not initialized then + logger.dbg("Send2Ebook: obtaining download folder") + send2ebook_settings = LuaSettings:open(("%s/%s"):format(DataStorage:getSettingsDir(), send2ebook_config_file)) + if send2ebook_settings:has(config_key_custom_dl_dir) then + download_dir_path = send2ebook_settings:readSetting(config_key_custom_dl_dir) + else + download_dir_path = ("%s/%s/"):format(DataStorage:getFullDataDir(), default_download_dir_name) + end + + if not lfs.attributes(download_dir_path, "mode") then + logger.dbg("Send2Ebook: Creating initial directory") + lfs.mkdir(download_dir_path) + end + end +end + +function Send2Ebook:process() + local info = InfoMessage:new{ text = _("Connecting …") } + UIManager:show(info) + logger.dbg("Send2Ebook: force repaint due to upcoming blocking calls") + UIManager:forceRePaint() + UIManager:close(info) + + local count = 1 + local ftp_config = send2ebook_settings:readSetting("ftp_config") or {address="Please setup ftp in settings", username="", password="", folder=""} + + local connection_url = FtpApi:generateUrl(ftp_config.address, ftp_config.username, ftp_config.password) + + local ftp_files_table = FtpApi:listFolder(connection_url .. ftp_config.folder, ftp_config.folder) --args looks strange but otherwise resonse with invalid paths + + if not ftp_files_table then + info = InfoMessage:new{ text = T(_("Could not get file list for server: %1, user: %2, folder: %3"), ftp_config.address, ftp_config.username, ftp_config.folder) } + else + local total_entries = table.getn(ftp_files_table) + logger.dbg("Send2Ebook: total_entries ", total_entries) + if total_entries > 1 then total_entries = total_entries -2 end --remove result "../" (upper folder) and "./" (current folder) + for idx, ftp_file in ipairs(ftp_files_table) do + logger.dbg("Send2Ebook: processing ftp_file:", ftp_file) + if ftp_file["type"] == "file" and stringEnds(ftp_file["text"], supported_ftp_file_type) then + + info = InfoMessage:new{ text = T(_("Processing %1/%2"), count, total_entries) } + UIManager:show(info) + UIManager:forceRePaint() + UIManager:close(info) + + local remote_file_path = ftp_file["url"] + logger.dbg("Send2Ebook: remote_file_path", remote_file_path) + local local_file_path = download_dir_path .. ftp_file["text"] + count = count + Send2Ebook:downloadFileAndRemove(connection_url, remote_file_path, local_file_path) + end + info = InfoMessage:new{ text = T(_("Processing finished. Success: %1"), count -1) } + end + end + UIManager:show(info) + Send2Ebook:afterWifiAction() +end + +function Send2Ebook:removeReadActicles() + logger.dbg("Send2Ebook: Removing read articles from :", download_dir_path) + for entry in lfs.dir(download_dir_path) do + if entry ~= "." and entry ~= ".." then + local entry_path = download_dir_path .. entry + if DocSettings:hasSidecarFile(entry_path) then + local entry_mode = lfs.attributes(entry_path, "mode") + if entry_mode == "file" then + os.remove(entry_path) + local sdr_dir = DocSettings:getSidecarDir(entry_path) + logger.dbg("Send2Ebook: sdr dir to be removed:", sdr_dir) + FFIUtil.purgeDir(sdr_dir) + end + end + end + end + UIManager:show(InfoMessage:new{ + text = _("All read articles removed.") + }) +end + +function Send2Ebook:setCustomDownloadDirectory() + UIManager:show(InfoMessage:new{ + text = _("To select a folder press down and hold it for 1 second.") + }) + require("ui/downloadmgr"):new{ + title = _("Choose download directory"), + onConfirm = function(path) + logger.dbg("Send2Ebook: set download directory to: ", path) + send2ebook_settings:saveSetting(config_key_custom_dl_dir, ("%s/"):format(path)) + send2ebook_settings:flush() + + initialized = false + self:lazyInitialization() + end, + }:chooseDir() +end + +function Send2Ebook:editFtpConnection() + local item = send2ebook_settings:readSetting("ftp_config") or {text="ignore this field, it's not used here;) fill rest", address="ftp://", username="",password="" , folder="/"} + local callbackEdit = function(updated_config, fields) + local data = {text=fields[1], address=fields[2], username=fields[3],password=fields[4] , folder=fields[5]} + send2ebook_settings:saveSetting("ftp_config", data) + send2ebook_settings:flush() + initialized = false + Send2Ebook:lazyInitialization() + end + Ftp:config(item, callbackEdit) +end + +function Send2Ebook:onCloseDocument() + local document_full_path = self.ui.document.file + if document_full_path and download_dir_path == string.sub(document_full_path, 1, string.len(download_dir_path)) then + logger.dbg("Send2Ebook: document_full_path:", document_full_path) + logger.dbg("Send2Ebook: download_dir_path:", download_dir_path) + logger.dbg("Send2Ebook: removing Send2Ebook file from history.") + ReadHistory:removeItemByPath(document_full_path) + end +end + +return Send2Ebook