diff --git a/frontend/apps/cloudstorage/cloudstorage.lua b/frontend/apps/cloudstorage/cloudstorage.lua
index 334598f30..1eb24d16e 100644
--- a/frontend/apps/cloudstorage/cloudstorage.lua
+++ b/frontend/apps/cloudstorage/cloudstorage.lua
@@ -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
diff --git a/frontend/apps/cloudstorage/ftp.lua b/frontend/apps/cloudstorage/ftp.lua
index bc1e9e5f3..04a2195ed 100644
--- a/frontend/apps/cloudstorage/ftp.lua
+++ b/frontend/apps/cloudstorage/ftp.lua
@@ -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")
diff --git a/frontend/apps/cloudstorage/webdav.lua b/frontend/apps/cloudstorage/webdav.lua
new file mode 100644
index 000000000..8388ed1b2
--- /dev/null
+++ b/frontend/apps/cloudstorage/webdav.lua
@@ -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
diff --git a/frontend/apps/cloudstorage/webdavapi.lua b/frontend/apps/cloudstorage/webdavapi.lua
new file mode 100644
index 000000000..41c45f041
--- /dev/null
+++ b/frontend/apps/cloudstorage/webdavapi.lua
@@ -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 = [[]]
+ 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 tags, each containing an entry
+ for item in res_data:gmatch("(.-)") do
+ --logger.dbg("WebDav catalog item=", item)
+ -- is the path and filename of the entry.
+ local item_fullpath = item:match("(.*)")
+ 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("") 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("") 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