WebDav CloudStorage (#4272)

Addition of WebDav to the CloudStorage. The functionality is the same as with Dropbox and FTP, it is possible to browse the files on the server and download a copy.

Tested with:
NextCloud
HubZilla
pull/4274/head
Yann Muller 6 years ago committed by Frans de Jonge
parent 53e7a0b6c9
commit a3a17dbbeb

@ -8,6 +8,7 @@ local InfoMessage = require("ui/widget/infomessage")
local LuaSettings = require("luasettings")
local Menu = require("ui/widget/menu")
local UIManager = require("ui/uimanager")
local WebDav = require("apps/cloudstorage/webdav")
local lfs = require("libs/libkoreader-lfs")
local _ = require("gettext")
local Screen = require("device").screen
@ -88,6 +89,15 @@ function CloudStorage:selectCloudType()
end,
},
},
{
{
text = _("WebDAV"),
callback = function()
UIManager:close(self.cloud_dialog)
self:configCloud("webdav")
end,
},
},
}
self.cloud_dialog = ButtonDialogTitle:new{
title = _("Choose cloud storage type"),
@ -114,6 +124,12 @@ function CloudStorage:openCloudServer(url)
return
end
tbl = Ftp:run(self.address, self.username, self.password, url)
elseif self.type == "webdav" then
if not NetworkMgr:isConnected() then
NetworkMgr:promptWifiOn()
return
end
tbl = WebDav:run(self.address, self.username, self.password, url)
end
if tbl and #tbl > 0 then
self:switchItemTable(url, tbl)
@ -171,6 +187,7 @@ end
function CloudStorage:cloudFile(item, path)
local path_dir = path
local download_text = _("Downloading. This might take a moment.")
local buttons = {
{
{
@ -185,7 +202,7 @@ function CloudStorage:cloudFile(item, path)
end)
UIManager:close(self.download_dialog)
UIManager:show(InfoMessage:new{
text = _("Downloading may take several minutes…"),
text = download_text,
timeout = 1,
})
elseif self.type == "ftp" then
@ -197,7 +214,19 @@ function CloudStorage:cloudFile(item, path)
end)
UIManager:close(self.download_dialog)
UIManager:show(InfoMessage:new{
text = _("Downloading may take several minutes…"),
text = download_text,
timeout = 1,
})
elseif self.type == "webdav" then
local callback_close = function()
self:onClose()
end
UIManager:scheduleIn(1, function()
WebDav:downloadFile(item, self.address, self.username, self.password, path_dir, callback_close)
end)
UIManager:close(self.download_dialog)
UIManager:show(InfoMessage:new{
text = download_text,
timeout = 1,
})
end
@ -286,6 +315,16 @@ function CloudStorage:configCloud(type)
type = "ftp",
url = "/"
})
elseif type == "webdav" then
table.insert(cs_servers,{
name = fields[1],
address = fields[2],
username = fields[3],
password = fields[4],
url = fields[5],
type = "webdav",
--url = "/"
})
end
cs_settings:saveSetting("cs_servers", cs_servers)
cs_settings:flush()
@ -297,6 +336,9 @@ function CloudStorage:configCloud(type)
if type == "ftp" then
Ftp:config(nil, callbackAdd)
end
if type == "webdav" then
WebDav:config(nil, callbackAdd)
end
end
function CloudStorage:editCloudServer(item)
@ -324,6 +366,18 @@ function CloudStorage:editCloudServer(item)
break
end
end
elseif item.type == "webdav" 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]
server.url = fields[5]
cs_servers[i] = server
break
end
end
end
cs_settings:saveSetting("cs_servers", cs_servers)
cs_settings:flush()
@ -333,6 +387,8 @@ function CloudStorage:editCloudServer(item)
DropBox:config(item, callbackEdit)
elseif item.type == "ftp" then
Ftp:config(item, callbackEdit)
elseif item.type == "webdav" then
WebDav:config(item, callbackEdit)
end
end
@ -355,6 +411,8 @@ function CloudStorage:infoServer(item)
DropBox:info(item.password)
elseif item.type == "ftp" then
Ftp:info(item)
elseif item.type == "webdav" then
WebDav:info(item)
end
end

@ -43,7 +43,7 @@ function Ftp:downloadFile(item, address, user, pass, path, close)
end
function Ftp:config(item, callback)
local text_info = "FTP address must be in the format ftp://example.domian.com\n"..
local text_info = "FTP address must be in the format ftp://example.domain.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")

@ -0,0 +1,146 @@
local ConfirmBox = require("ui/widget/confirmbox")
local InfoMessage = require("ui/widget/infomessage")
local MultiInputDialog = require("ui/widget/multiinputdialog")
local UIManager = require("ui/uimanager")
local ReaderUI = require("apps/reader/readerui")
local WebDavApi = require("apps/cloudstorage/webdavapi")
local _ = require("gettext")
local Screen = require("device").screen
local T = require("ffi/util").template
local WebDav = {}
function WebDav:run(address, user, pass, path)
return WebDavApi:listFolder(address, user, pass, path)
end
function WebDav:downloadFile(item, address, username, password, local_path, close)
local code_response = WebDavApi:downloadFile(address .. WebDavApi:urlEncode( item.url ), username, password, local_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?"),
local_path),
ok_callback = function()
close()
ReaderUI:showReader(local_path)
end
})
else
UIManager:show(InfoMessage:new{
text = T(_("Could not save file to:\n%1"), local_path),
timeout = 3,
})
end
end
function WebDav:config(item, callback)
local text_info = _([[Server address must be of the form http(s)://domain.name/path
This can point to a sub-directory of the WebDAV server.
The start folder is appended to the server path.]])
local hint_name = _("Server display name")
local text_name = ""
local hint_address = _("WebDAV address eg https://example.com/dav")
local text_address = ""
local hint_username = _("Username")
local text_username = ""
local hint_password = _("Password")
local text_password = ""
local hint_folder = _("Start folder")
local text_folder = ""
local title
local text_button_ok = _("Add")
if item then
title = _("Edit WebDAV account")
text_button_ok = _("Apply")
text_name = item.text
text_address = item.address
text_username = item.username
text_password = item.password
text_folder = item.url
else
title = _("Add WebDAV 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",
text_type = "password",
hint = hint_password,
},
{
text = text_folder,
input_type = "string",
hint = hint_folder,
},
},
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_ok,
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",
}
UIManager:show(self.settings_dialog)
self.settings_dialog:onShowKeyboard()
end
function WebDav:info(item)
local info_text = T(_"Type: %1\nName: %2\nAddress: %3", "WebDAV", item.text, item.address)
UIManager:show(InfoMessage:new{text = info_text})
end
return WebDav

@ -0,0 +1,162 @@
local DocumentRegistry = require("document/documentregistry")
local FFIUtil = require("ffi/util")
local http = require('socket.http')
local https = require('ssl.https')
local ltn12 = require('ltn12')
local mime = require('mime')
local socket = require('socket')
local url = require('socket.url')
local util = require("util")
local _ = require("gettext")
local WebDavApi = {
}
function WebDavApi:isCurrentDirectory( current_item, address, path )
local is_home, is_parent
local home_path
-- find first occurence of / after http(s)://
local start = string.find( address, "/", 9 )
if not start then
home_path = "/"
else
home_path = string.sub( address, start )
end
local item
if string.sub( current_item, -1 ) == "/" then
item = string.sub( current_item, 1, -2 )
else
item = current_item
end
if item == home_path then
is_home = true
else
local temp_path = string.sub( item, string.len(home_path) + 1 )
if temp_path == path then
is_parent = true
end
end
return is_home or is_parent
end
-- version of urlEncode that doesn't encode the /
function WebDavApi:urlEncode(url_data)
local char_to_hex = function(c)
return string.format("%%%02X", string.byte(c))
end
if url_data == nil then
return
end
url_data = url_data:gsub("([^%w%/%-%.%_%~%!%*%'%(%)])", char_to_hex)
return url_data
end
function WebDavApi:listFolder(address, user, pass, folder_path)
local path = self:urlEncode( folder_path )
local webdav_list = {}
local webdav_file = {}
local has_trailing_slash = false
local has_leading_slash = false
if string.sub( address, -1 ) ~= "/" then has_trailing_slash = true end
if path == nil or path == "/" then
path = ""
elseif string.sub( path, 1, 2 ) == "/" then
if has_trailing_slash then
-- too many slashes, remove one
path = string.sub( path, 1 )
end
has_leading_slash = true
end
if not has_trailing_slash and not has_leading_slash then
address = address .. "/"
end
local webdav_url = address .. path
local request, sink = {}, {}
local parsed = url.parse(webdav_url)
local data = [[<?xml version="1.0"?><a:propfind xmlns:a="DAV:"><a:prop><a:resourcetype/></a:prop></a:propfind>]]
local auth = string.format("%s:%s", user, pass)
local headers = { ["Authorization"] = "Basic " .. mime.b64( auth ),
["Content-Type"] = "application/xml",
["Depth"] = "1",
["Content-Length"] = #data}
request["url"] = webdav_url
request["method"] = "PROPFIND"
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 res_data = table.concat(sink)
if res_data ~= "" then
-- iterate through the <d:response> tags, each containing an entry
for item in res_data:gmatch("<d:response>(.-)</d:response>") do
--logger.dbg("WebDav catalog item=", item)
-- <d:href> is the path and filename of the entry.
local item_fullpath = item:match("<d:href>(.*)</d:href>")
local is_current_dir = self:isCurrentDirectory( item_fullpath, address, path )
local item_name = util.urlDecode( FFIUtil.basename( item_fullpath ) )
local item_path = path .. "/" .. item_name
if item:find("<d:collection/>") then
item_name = item_name .. "/"
if not is_current_dir then
table.insert(webdav_list, {
text = item_name,
url = util.urlDecode( item_path ),
type = "folder",
})
end
elseif item:find("<d:resourcetype/>") and DocumentRegistry:hasProvider(item_name) then
table.insert(webdav_file, {
text = item_name,
url = util.urlDecode( item_path ),
type = "file",
})
end
end
else
return nil
end
--sort
table.sort(webdav_list, function(v1,v2)
return v1.text < v2.text
end)
table.sort(webdav_file, function(v1,v2)
return v1.text < v2.text
end)
for _, files in ipairs(webdav_file) do
table.insert(webdav_list, {
text = files.text,
url = files.url,
type = files.type,
})
end
return webdav_list
end
function WebDavApi:downloadFile(file_url, user, pass, local_path)
local parsed = url.parse(file_url)
local auth = string.format("%s:%s", user, pass)
local headers = { ["Authorization"] = "Basic " .. mime.b64( auth ) }
http.TIMEOUT = 5
https.TIMEOUT = 5
local httpRequest = parsed.scheme == "http" and http.request or https.request
local _, code_return, _ = httpRequest{
url = file_url,
method = "GET",
headers = headers,
sink = ltn12.sink.file(io.open(local_path, "w"))
}
return code_return
end
return WebDavApi
Loading…
Cancel
Save