mirror of https://github.com/koreader/koreader
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 HubZillapull/4274/head
parent
53e7a0b6c9
commit
a3a17dbbeb
@ -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…
Reference in New Issue