Cloud storage - dropbox and ftp integration (#2424)

pull/2443/head
Robert 7 years ago committed by Qingping Hou
parent c662ca4cc8
commit 187598deb2

@ -0,0 +1,370 @@
local UIManager = require("ui/uimanager")
local Screen = require("device").screen
local _ = require("gettext")
local Menu = require("ui/widget/menu")
local InfoMessage = require("ui/widget/infomessage")
local ButtonDialog = require("ui/widget/buttondialog")
local DropBox = require("frontend/apps/cloudstorage/dropbox")
local LuaSettings = require("luasettings")
local DataStorage = require("datastorage")
local Ftp = require("frontend/apps/cloudstorage/ftp")
local ConfirmBox = require("ui/widget/confirmbox")
local lfs = require("libs/libkoreader-lfs")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local CloudStorage = Menu:extend{
cloud_servers = {
{
text = "Add new cloud storage",
title = "Choose type of cloud",
url = "add",
editable = false,
},
},
width = Screen:getWidth(),
height = Screen:getHeight(),
no_title = false,
show_parent = nil,
is_popout = false,
is_borderless = true,
}
function CloudStorage:init()
self.cs_settings = self:readSettings()
self.menu_select = nil
self.title = "Cloud Storage"
self.show_parent = self
self.item_table = self:genItemTableFromRoot()
Menu.init(self)
end
function CloudStorage:genItemTableFromRoot()
local item_table = {}
table.insert(item_table, {
text = _("Add new cloud storage"),
callback = function()
self:selectCloudType()
end,
})
local added_servers = self.cs_settings:readSetting("cs_servers") or {}
for _, server in ipairs(added_servers) do
table.insert(item_table, {
text = server.name,
address = server.address,
username = server.username,
password = server.password,
type = server.type,
editable = true,
url = server.url,
callback = function()
self.type = server.type
self.password = server.password
self.address = server.address
self.username = server.username
self:openCloudServer(server.url)
end,
})
end
return item_table
end
function CloudStorage:selectCloudType()
local buttons = {
{
{
text = _("Dropbox"),
callback = function()
UIManager:close(self.cloud_dialog)
self:configCloud("dropbox")
end,
},
},
{
{
text = _("FTP"),
callback = function()
UIManager:close(self.cloud_dialog)
self:configCloud("ftp")
end,
},
},
}
self.cloud_dialog = ButtonDialogTitle:new{
title = _("Choose cloud storage type"),
title_align = "center",
buttons = buttons,
}
UIManager:show(self.cloud_dialog)
return true
end
function CloudStorage:openCloudServer(url)
local tbl
if self.type == "dropbox" then
tbl = DropBox:run(url, self.password)
elseif self.type == "ftp" then
tbl = Ftp:run(self.address, self.username, self.password, url)
end
if tbl and #tbl > 0 then
self:swithItemTable(url, tbl)
return true
elseif not tbl then
UIManager:show(InfoMessage:new{
text = _("Cannot fetch list folder!\nCheck configuration or network connection."),
timeout = 3,
})
table.remove(self.paths)
return false
else
UIManager:show(InfoMessage:new{text = _("Empty folder") })
return false
end
end
function CloudStorage:onMenuSelect(item)
if item.callback then
if item.url ~= nil then
table.insert(self.paths, {
url = item.url,
})
end
item.callback()
elseif item.type == "file" then
self:downloadFile(item)
else
table.insert(self.paths, {
url = item.url,
})
if not self:openCloudServer(item.url) then
table.remove(self.paths)
end
end
return true
end
function CloudStorage:downloadFile(item)
local lastdir = G_reader_settings:readSetting("lastdir")
local cs_settings = self:readSettings()
local download_dir = cs_settings:readSetting("download_dir") or lastdir
local path = download_dir .. '/' .. item.text
if lfs.attributes(path) then
UIManager:show(ConfirmBox:new{
text = _("File exist! Would you like to override it?"),
ok_callback = function()
self:cloudFile(item, path)
end
})
else
self:cloudFile(item, path)
end
end
function CloudStorage:cloudFile(item, path)
local path_dir = path
local buttons = {
{
{
text = _("Download file"),
callback = function()
if self.type == "dropbox" then
local callback_close = function()
self:onClose()
end
UIManager:scheduleIn(1, function()
DropBox:downloadFile(item, self.password, path_dir, callback_close)
end)
UIManager:close(self.download_dialog)
UIManager:show(InfoMessage:new{
text = _("Downloading may take several minutes..."),
timeout = 1,
})
elseif self.type == "ftp" then
local callback_close = function()
self:onClose()
end
UIManager:scheduleIn(1, function()
Ftp:downloadFile(item, self.address, self.username, self.password, path_dir, callback_close)
end)
UIManager:close(self.download_dialog)
UIManager:show(InfoMessage:new{
text = _("Downloading may take several minutes..."),
timeout = 1,
})
end
end,
},
},
{
{
text = _("Set download directory"),
callback = function()
require("ui/downloadmgr"):new{
title = _("Choose download directory"),
onConfirm = function(path_download)
self.cs_settings:saveSetting("download_dir", path_download)
self.cs_settings:flush()
path_dir = path_download .. '/' .. item.text
end,
}:chooseDir()
end,
},
},
}
self.download_dialog = ButtonDialog:new{
buttons = buttons
}
UIManager:show(self.download_dialog)
end
function CloudStorage:onMenuHold(item)
if item.editable then
local cs_server_dialog
cs_server_dialog = ButtonDialog:new{
buttons = {
{
{
text = _("Info"),
enabled = true,
callback = function()
UIManager:close(cs_server_dialog)
self:infoServer(item)
end
},
{
text = _("Edit"),
enabled = true,
callback = function()
UIManager:close(cs_server_dialog)
self:editCloudServer(item)
end
},
{
text = _("Delete"),
enabled = true,
callback = function()
UIManager:close(cs_server_dialog)
self:deleteCloudServer(item)
end
},
},
}
}
UIManager:show(cs_server_dialog)
return true
end
end
function CloudStorage:configCloud(type)
local callbackAdd = function(fields)
local cs_settings = self:readSettings()
local cs_servers = cs_settings:readSetting("cs_servers") or {}
if type == "dropbox" then
table.insert(cs_servers,{
name = fields[1],
password = fields[2],
type = "dropbox",
url = "/"
})
elseif type == "ftp" then
table.insert(cs_servers,{
name = fields[1],
address = fields[2],
username = fields[3],
password = fields[4],
type = "ftp",
url = "/"
})
end
cs_settings:saveSetting("cs_servers", cs_servers)
cs_settings:flush()
self:init()
end
if type == "dropbox" then
DropBox:config(nil, callbackAdd)
end
if type == "ftp" then
Ftp:config(nil, callbackAdd)
end
end
function CloudStorage:editCloudServer(item)
local callbackEdit = function(updated_config, fields)
local cs_settings = self:readSettings()
local cs_servers = cs_settings:readSetting("cs_servers") or {}
if item.type == "dropbox" then
for i, server in ipairs(cs_servers) do
if server.name == updated_config.text and server.password == updated_config.password then
server.name = fields[1]
server.password = fields[2]
cs_servers[i] = server
break
end
end
elseif item.type == "ftp" then
for i, server in ipairs(cs_servers) do
if server.name == updated_config.text and server.address == updated_config.address then
server.name = fields[1]
server.address = fields[2]
server.username = fields[3]
server.password = fields[4]
cs_servers[i] = server
break
end
end
end
cs_settings:saveSetting("cs_servers", cs_servers)
cs_settings:flush()
self:init()
end
if item.type == "dropbox" then
DropBox:config(item, callbackEdit)
elseif item.type == "ftp" then
Ftp:config(item, callbackEdit)
end
end
function CloudStorage:deleteCloudServer(item)
local cs_settings = self:readSettings()
local cs_servers = cs_settings:readSetting("cs_servers") or {}
for i, server in ipairs(cs_servers) do
if server.name == item.text and server.password == item.password and server.type == item.type then
table.remove(cs_servers, i)
break
end
end
cs_settings:saveSetting("cs_servers", cs_servers)
cs_settings:flush()
self:init()
end
function CloudStorage:infoServer(item)
if item.type == "dropbox" then
DropBox:info(item.password)
elseif item.type == "ftp" then
Ftp:info(item)
end
end
function CloudStorage:readSettings()
self.cs_settings = LuaSettings:open(DataStorage:getSettingsDir().."/cloudstorage.lua")
return self.cs_settings
end
function CloudStorage:onReturn()
if #self.paths > 0 then
table.remove(self.paths)
local path = self.paths[#self.paths]
if path then
-- return to last path
self:openCloudServer(path.url)
else
-- return to root path
self:init()
end
end
return true
end
return CloudStorage

@ -0,0 +1,129 @@
local DropBoxApi = require("frontend/apps/cloudstorage/dropboxapi")
local ConfirmBox = require("ui/widget/confirmbox")
local InfoMessage = require("ui/widget/infomessage")
local MultiInputDialog = require("ui/widget/multiinputdialog")
local UIManager = require("ui/uimanager")
local _ = require("gettext")
local T = require("ffi/util").template
local ReaderUI = require("apps/reader/readerui")
local Screen = require("device").screen
local DropBox = {
}
function DropBox:run(url, password)
return DropBoxApi:listFolder(url, password)
end
function DropBox:downloadFile(item, password, path, close)
local code_response = DropBoxApi:downloadFile(item.url, password, path)
if code_response == 200 then
UIManager:show(ConfirmBox:new{
text = T(_("File saved to:\n %1\nWould you like to read the downloaded book now?"),
path),
ok_callback = function()
close()
ReaderUI:showReader(path)
end
})
else
UIManager:show(InfoMessage:new{
text = _("Could not save file to:\n") .. path,
timeout = 3,
})
end
end
function DropBox:config(item, callback)
local text_info = "How to generate Access Token:\n"..
"1. Open the following URL in your Browser, and log in using your account: https://www.dropbox.com/developers/apps.\n"..
"2. Click on >>Create App<<, then select >>Dropbox API app<<.\n"..
"3. Now go on with the configuration, choosing the app permissions and access restrictions to your DropBox folder.\n"..
"4. Enter the >>App Name<< that you prefer (e.g. KOReader).\n"..
"5. Now, click on the >>Create App<< button.\n" ..
"6. When your new App is successfully created, please click on the Generate button.\n"..
"7. Under the 'Generated access token' section, then enter code in Dropbox token field."
local hint_top = _("Your Dropbox name")
local text_top = ""
local hint_bottom = _("Dropbox token\n\n\n\n ")
local text_bottom = ""
local title
local text_button_right = _("Add")
if item then
title = _("Edit Dropbox account")
text_button_right = _("Apply")
text_top = item.text
text_bottom = item.password
else
title = _("Add Dropbox account")
end
self.settings_dialog = MultiInputDialog:new {
title = title,
fields = {
{
text = text_top,
hint = hint_top ,
},
{
text = text_bottom,
hint = hint_bottom,
scroll = false,
},
},
buttons = {
{
{
text = _("Cancel"),
callback = function()
self.settings_dialog:onClose()
UIManager:close(self.settings_dialog)
end
},
{
text = _("Info"),
callback = function()
UIManager:show(InfoMessage:new{text = text_info })
end
},
{
text = text_button_right,
callback = function()
local fields = MultiInputDialog:getFields()
if fields[1] ~= "" and fields[2] ~= "" then
if item then
--edit
callback(item, fields)
else
-- add new
callback(fields)
end
self.settings_dialog:onClose()
UIManager:close(self.settings_dialog)
else
UIManager:show(InfoMessage:new{text = "Please fill in all fields." })
end
end
},
},
},
width = Screen:getWidth() * 0.95,
height = Screen:getHeight() * 0.2,
input_type = "text",
}
self.settings_dialog:onShowKeyboard()
UIManager:show(self.settings_dialog)
end
function DropBox:info(token)
local info = DropBoxApi:fetchInfo(token)
local info_text
if info and info.name then
info_text = T(_"Type: %1\nName: %2\nEmail: %3\nCounty: %4",
"Dropbox",info.name.display_name, info.email, info.country)
else
info_text = _("No information available")
end
UIManager:show(InfoMessage:new{text = info_text})
end
return DropBox

@ -0,0 +1,135 @@
local url = require('socket.url')
local socket = require('socket')
local http = require('socket.http')
local https = require('ssl.https')
local ltn12 = require('ltn12')
local _ = require("gettext")
local JSON = require("json")
local DocumentRegistry = require("document/documentregistry")
local DropBoxApi = {
}
local API_URL_INFO = "https://api.dropboxapi.com/2/users/get_current_account"
local API_LIST_FOLDER = "https://api.dropboxapi.com/2/files/list_folder"
local API_DOWNLOAD_FILE = "https://content.dropboxapi.com/2/files/download"
function DropBoxApi:fetchInfo(token)
local request, sink = {}, {}
local parsed = url.parse(API_URL_INFO)
request['url'] = API_URL_INFO
request['method'] = 'POST'
local headers = { ["Authorization"] = "Bearer ".. token }
request['headers'] = headers
request['sink'] = ltn12.sink.table(sink)
http.TIMEOUT = 5
https.TIMEOUT = 5
local httpRequest = parsed.scheme == 'http' and http.request or https.request
local headers_request = socket.skip(1, httpRequest(request))
local result_response = table.concat(sink)
if headers_request == nil then
return nil
end
if result_response ~= "" then
local _, result = pcall(JSON.decode, result_response)
return result
else
return nil
end
end
function DropBoxApi:fetchListFolders(path, token)
local request, sink = {}, {}
if path == nil or path == "/" then path = "" end
local parsed = url.parse(API_LIST_FOLDER)
request['url'] = API_LIST_FOLDER
request['method'] = 'POST'
local data = "{\"path\": \"" .. path .. "\",\"recursive\": false,\"include_media_info\": false,"..
"\"include_deleted\": false,\"include_has_explicit_shared_members\": false}"
local headers = { ["Authorization"] = "Bearer ".. token,
["Content-Type"] = "application/json" ,
["Content-Length"] = #data}
request['headers'] = headers
request['source'] = ltn12.source.string(data)
request['sink'] = ltn12.sink.table(sink)
http.TIMEOUT = 5
https.TIMEOUT = 5
local httpRequest = parsed.scheme == 'http' and http.request or https.request
local headers_request = socket.skip(1, httpRequest(request))
if headers_request == nil then
return nil
end
local result_response = table.concat(sink)
if result_response ~= "" then
local ret, result = pcall(JSON.decode, result_response)
if ret then
return result
else
return nil
end
else
return nil
end
end
function DropBoxApi:downloadFile(path, token, local_path)
local parsed = url.parse(API_DOWNLOAD_FILE)
local url_api = API_DOWNLOAD_FILE
local data1 = "{\"path\": \"" .. path .. "\"}"
local headers = { ["Authorization"] = "Bearer ".. token,
["Dropbox-API-Arg"] = data1}
http.TIMEOUT = 5
https.TIMEOUT = 5
local httpRequest = parsed.scheme == 'http' and http.request or https.request
local _, code_return, _ = httpRequest{
url = url_api,
method = 'GET',
headers = headers,
sink = ltn12.sink.file(io.open(local_path, "w"))
}
return code_return
end
function DropBoxApi:listFolder(path, token)
local dropbox_list = {}
local dropbox_file = {}
local tag, text
local ls_dropbox = self:fetchListFolders(path, token)
if ls_dropbox == nil then return false end
for _, files in ipairs(ls_dropbox.entries) do
text = files.name
tag = files[".tag"]
if tag == "folder" then
text = text .. "/"
table.insert(dropbox_list, {
text = text,
url = files.path_display,
type = tag
})
--show only file with supported formats
elseif tag == "file" and DocumentRegistry:getProvider(text) then
table.insert(dropbox_file, {
text = text,
url = files.path_display,
type = tag
})
end
end
--sort
table.sort(dropbox_list, function(v1,v2)
return v1.text < v2.text
end)
table.sort(dropbox_file, function(v1,v2)
return v1.text < v2.text
end)
for _, files in ipairs(dropbox_file) do
table.insert(dropbox_list, {
text = files.text,
url = files.url,
type = files.type
})
end
return dropbox_list
end
return DropBoxApi

@ -0,0 +1,152 @@
local FtpApi = require("frontend/apps/cloudstorage/ftpapi")
local ConfirmBox = require("ui/widget/confirmbox")
local InfoMessage = require("ui/widget/infomessage")
local MultiInputDialog = require("ui/widget/multiinputdialog")
local UIManager = require("ui/uimanager")
local _ = require("gettext")
local T = require("ffi/util").template
local ReaderUI = require("apps/reader/readerui")
local Screen = require("device").screen
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
function Ftp:run(address, user, pass, path)
local url = generateUrl(address, user, pass) .. path
return FtpApi:listFolder(url)
end
function Ftp:downloadFile(item, address, user, pass, path, close)
local url = generateUrl(address, user, pass) .. item.url
local response = FtpApi:downloadFile(url)
if response ~= nil then
local file = io.open(path, "w")
file:write(response)
file:close()
UIManager:show(ConfirmBox:new{
text = T(_("File saved to:\n %1\nWould you like to read the downloaded book now?"),
path),
ok_callback = function()
close()
ReaderUI:showReader(path)
end
})
else
UIManager:show(InfoMessage:new{
text = _("Could not save file to:\n") .. path,
timeout = 3,
})
end
end
function Ftp:config(item, callback)
local text_info = "FTP address must be in the format ftp://example.domian.com\n"..
"Also supported is format with IP e.g: ftp://10.10.10.1\n"..
"Username and password are optional."
local hint_name = _("Your FTP name")
local text_name = ""
local hint_address = _("FTP address eg ftp://example.com")
local text_address = ""
local hint_username = _("FTP username")
local text_username = ""
local hint_password = _("FTP password")
local text_password = ""
local title
local text_button_right = _("Add")
if item then
title = _("Edit FTP account")
text_button_right = _("Apply")
text_name = item.text
text_address = item.address
text_username = item.username
text_password = item.password
else
title = _("Add FTP account")
end
self.settings_dialog = MultiInputDialog:new {
title = title,
fields = {
{
text = text_name,
input_type = "string",
hint = hint_name ,
},
{
text = text_address,
input_type = "string",
hint = hint_address ,
},
{
text = text_username,
input_type = "string",
hint = hint_username,
},
{
text = text_password,
input_type = "string",
hint = hint_password,
},
},
buttons = {
{
{
text = _("Cancel"),
callback = function()
self.settings_dialog:onClose()
UIManager:close(self.settings_dialog)
end
},
{
text = _("Info"),
callback = function()
UIManager:show(InfoMessage:new{text = text_info })
end
},
{
text = text_button_right,
callback = function()
local fields = MultiInputDialog:getFields()
if fields[1] ~= "" and fields[2] ~= "" then
if item then
-- edit
callback(item, fields)
else
-- add new
callback(fields)
end
self.settings_dialog:onClose()
UIManager:close(self.settings_dialog)
else
UIManager:show(InfoMessage:new{text = "Please fill in all fields." })
end
end
},
},
},
width = Screen:getWidth() * 0.95,
height = Screen:getHeight() * 0.2,
input_type = "text",
}
self.settings_dialog:onShowKeyboard()
UIManager:show(self.settings_dialog)
end
function Ftp:info(item)
local info_text = T(_"Type: %1\nName: %2\nAddress: %3", "FTP", item.text, item.address)
UIManager:show(InfoMessage:new{text = info_text})
end
return Ftp

@ -0,0 +1,70 @@
local ftp = require("socket.ftp")
local ltn12 = require("ltn12")
local url = require("socket.url")
local DocumentRegistry = require("document/documentregistry")
local FtpApi = {
}
function FtpApi:nlst(u)
local t = {}
local p = url.parse(u)
p.command = "nlst"
p.sink = ltn12.sink.table(t)
local r, e = ftp.get(p)
return r and table.concat(t), e
end
function FtpApi:listFolder(address_path)
local ftp_list = {}
local ftp_file = {}
local type
local extension
local file_name
local ls_ftp = self:nlst(address_path)
if ls_ftp == nil then return false end
for item in (ls_ftp..'\n'):gmatch'(.-)\r?\n' do
if item ~= '' then
file_name = item:match("([^/]+)$")
extension = item:match("^.+(%..+)$")
item = "/" .. item
if not extension then
type = "folder"
table.insert(ftp_list, {
text = file_name .. "/",
url = item,
type = type,
})
--show only file with supported formats
elseif extension and DocumentRegistry:getProvider(item) then
type = "file"
table.insert(ftp_file, {
text = file_name,
url = item,
type = type,
})
end
end
end
--sort
table.sort(ftp_list, function(v1,v2)
return v1.text < v2.text
end)
table.sort(ftp_file, function(v1,v2)
return v1.text < v2.text
end)
for _, files in ipairs(ftp_file) do
table.insert(ftp_list, {
text = files.text,
url = files.url,
type = files.type
})
end
return ftp_list
end
function FtpApi:downloadFile(file_path)
return ftp.get(file_path ..";type=i")
end
return FtpApi

@ -13,6 +13,7 @@ local _ = require("gettext")
local FileSearcher = require("apps/filemanager/filemanagerfilesearcher")
local Search = require("apps/filemanager/filemanagersearch")
local SetDefaults = require("apps/filemanager/filemanagersetdefaults")
local CloudStorage = require("apps/cloudstorage/cloudstorage")
local FileManagerMenu = InputContainer:extend{
tab_item_table = nil,
@ -230,6 +231,18 @@ function FileManagerMenu:setUpdateItemTable()
},
}
})
table.insert(self.tab_item_table.tools, {
text = _("Cloud storage"),
callback = function()
local cloud_storage = CloudStorage:new{}
UIManager:show(cloud_storage)
local filemanagerRefresh = function() self.ui:onRefresh() end
function cloud_storage:onClose()
filemanagerRefresh()
UIManager:close(cloud_storage)
end
end,
})
-- search tab
table.insert(self.tab_item_table.search, {

Loading…
Cancel
Save