|
|
|
@ -4,7 +4,6 @@ local Cache = require("cache")
|
|
|
|
|
local CacheItem = require("cacheitem")
|
|
|
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
|
local LoginDialog = require("ui/widget/logindialog")
|
|
|
|
|
local Menu = require("ui/widget/menu")
|
|
|
|
|
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
|
|
|
|
local NetworkMgr = require("ui/network/manager")
|
|
|
|
@ -88,11 +87,13 @@ function OPDSBrowser:init()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:addServerFromInput(fields)
|
|
|
|
|
logger.dbg("input catalog", fields)
|
|
|
|
|
logger.info("input catalog", fields)
|
|
|
|
|
local servers = G_reader_settings:readSetting("opds_servers") or {}
|
|
|
|
|
table.insert(servers, {
|
|
|
|
|
title = fields[1],
|
|
|
|
|
url = (fields[2]:match("^%a+://") and fields[2] or "http://" .. fields[2]),
|
|
|
|
|
username = fields[3],
|
|
|
|
|
password = fields[4],
|
|
|
|
|
})
|
|
|
|
|
G_reader_settings:saveSetting("opds_servers", servers)
|
|
|
|
|
self:init()
|
|
|
|
@ -107,6 +108,12 @@ function OPDSBrowser:editCalibreFromInput(fields)
|
|
|
|
|
if tonumber(fields[2]) then
|
|
|
|
|
calibre.port = fields[2]
|
|
|
|
|
end
|
|
|
|
|
if fields[3] then
|
|
|
|
|
calibre.username = fields[3]
|
|
|
|
|
end
|
|
|
|
|
if fields[4] then
|
|
|
|
|
calibre.password = fields[4]
|
|
|
|
|
end
|
|
|
|
|
G_reader_settings:saveSetting("calibre_opds", calibre)
|
|
|
|
|
self:init()
|
|
|
|
|
end
|
|
|
|
@ -123,6 +130,15 @@ function OPDSBrowser:addNewCatalog()
|
|
|
|
|
text = "",
|
|
|
|
|
hint = _("Catalog URL"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = "",
|
|
|
|
|
hint = _("Username (optional)"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = "",
|
|
|
|
|
hint = _("Password (optional)"),
|
|
|
|
|
text_type = "password",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
buttons = {
|
|
|
|
|
{
|
|
|
|
@ -164,6 +180,15 @@ function OPDSBrowser:editCalibreServer()
|
|
|
|
|
text = calibre.port and tostring(calibre.port) or "8080",
|
|
|
|
|
hint = _("calibre port"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = calibre.username or "",
|
|
|
|
|
hint = _("Username (optional)"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = calibre.password or "",
|
|
|
|
|
hint = _("Password (optional)"),
|
|
|
|
|
text_type = "password",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
buttons = {
|
|
|
|
|
{
|
|
|
|
@ -199,6 +224,8 @@ function OPDSBrowser:genItemTableFromRoot()
|
|
|
|
|
text = server.title,
|
|
|
|
|
content = server.subtitle,
|
|
|
|
|
url = server.url,
|
|
|
|
|
username = server.username,
|
|
|
|
|
password = server.password,
|
|
|
|
|
deletable = true,
|
|
|
|
|
editable = true,
|
|
|
|
|
})
|
|
|
|
@ -217,6 +244,8 @@ function OPDSBrowser:genItemTableFromRoot()
|
|
|
|
|
text = self.calibre_name,
|
|
|
|
|
url = string.format("http://%s:%d/opds",
|
|
|
|
|
calibre_opds.host, calibre_opds.port),
|
|
|
|
|
username = calibre_opds.username,
|
|
|
|
|
password = calibre_opds.password,
|
|
|
|
|
editable = true,
|
|
|
|
|
deletable = false,
|
|
|
|
|
})
|
|
|
|
@ -230,120 +259,43 @@ function OPDSBrowser:genItemTableFromRoot()
|
|
|
|
|
return item_table
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:getBasicAuthentication(host)
|
|
|
|
|
local authentications = G_reader_settings:readSetting("www-auth") or {}
|
|
|
|
|
return authentications[host]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:setBasicAuthentication(host, username, password)
|
|
|
|
|
local authentications = G_reader_settings:readSetting("www-auth") or {}
|
|
|
|
|
authentications[host] = {
|
|
|
|
|
username = username,
|
|
|
|
|
password = password,
|
|
|
|
|
}
|
|
|
|
|
G_reader_settings:saveSetting("www-auth", authentications)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:getAuthorizationHeader(host)
|
|
|
|
|
local auth = self:getBasicAuthentication(host)
|
|
|
|
|
if auth then
|
|
|
|
|
local authorization = auth.username .. ':' .. auth.password
|
|
|
|
|
return {
|
|
|
|
|
Authorization = "Basic " .. mime.b64(authorization),
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:fetchFeed(feed_url)
|
|
|
|
|
function OPDSBrowser:fetchFeed(item_url, username, password)
|
|
|
|
|
local request, sink = {}, {}
|
|
|
|
|
local parsed = url.parse(feed_url)
|
|
|
|
|
request['url'] = feed_url
|
|
|
|
|
local parsed = url.parse(item_url)
|
|
|
|
|
local auth = string.format("%s:%s", username, password)
|
|
|
|
|
request['url'] = item_url
|
|
|
|
|
request['method'] = 'GET'
|
|
|
|
|
request['sink'] = ltn12.sink.table(sink)
|
|
|
|
|
request['headers'] = self:getAuthorizationHeader(parsed.host)
|
|
|
|
|
logger.dbg("request", request)
|
|
|
|
|
request['headers'] = { Authorization = "Basic " .. mime.b64(auth), }
|
|
|
|
|
logger.logger("request", request)
|
|
|
|
|
http.TIMEOUT, https.TIMEOUT = 10, 10
|
|
|
|
|
local httpRequest = parsed.scheme == 'http' and http.request or https.request
|
|
|
|
|
local code, headers, status = socket.skip(1, httpRequest(request))
|
|
|
|
|
|
|
|
|
|
local code, headers = socket.skip(1, httpRequest(request))
|
|
|
|
|
-- raise error message when network is unavailable
|
|
|
|
|
if headers == nil then
|
|
|
|
|
error(code)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if code == 401 and status and status:find("Unauthorized") then
|
|
|
|
|
self._coroutine = coroutine.running() or self._coroutine
|
|
|
|
|
self:fetchWithLogin(parsed.host, function()
|
|
|
|
|
return self:fetchFeed(feed_url)
|
|
|
|
|
end)
|
|
|
|
|
if coroutine.running() then
|
|
|
|
|
local result = coroutine.yield()
|
|
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
if code == 200 then
|
|
|
|
|
local xml = table.concat(sink)
|
|
|
|
|
if xml ~= "" then
|
|
|
|
|
return xml
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
|
text = T(_("Cannot get catalog. Server code response %1"), code),
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:fetchWithLogin(host, callback)
|
|
|
|
|
self.login_dialog = LoginDialog:new{
|
|
|
|
|
title = _("Login to OPDS server"),
|
|
|
|
|
username = "",
|
|
|
|
|
buttons = {
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
text = _("Cancel"),
|
|
|
|
|
enabled = true,
|
|
|
|
|
callback = function()
|
|
|
|
|
self:closeDialog()
|
|
|
|
|
end,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = _("Login"),
|
|
|
|
|
enabled = true,
|
|
|
|
|
callback = function()
|
|
|
|
|
local username, password = self:getCredential()
|
|
|
|
|
self:setBasicAuthentication(host, username, password)
|
|
|
|
|
self:closeDialog()
|
|
|
|
|
UIManager:scheduleIn(0.5, function()
|
|
|
|
|
local res = callback()
|
|
|
|
|
if res then
|
|
|
|
|
coroutine.resume(self._coroutine, res)
|
|
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
end,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
width = Screen:getWidth() * 0.8,
|
|
|
|
|
height = Screen:getHeight() * 0.4,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIManager:show(self.login_dialog)
|
|
|
|
|
self.login_dialog:onShowKeyboard()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:closeDialog()
|
|
|
|
|
self.login_dialog:onClose()
|
|
|
|
|
UIManager:close(self.login_dialog)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:getCredential()
|
|
|
|
|
return self.login_dialog:getCredential()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:parseFeed(feed_url)
|
|
|
|
|
function OPDSBrowser:parseFeed(item_url, username, password)
|
|
|
|
|
local feed
|
|
|
|
|
local hash = "opds|catalog|" .. feed_url
|
|
|
|
|
local hash = "opds|catalog|" .. item_url
|
|
|
|
|
local cache = CatalogCache:check(hash)
|
|
|
|
|
if cache then
|
|
|
|
|
feed = cache.feed
|
|
|
|
|
else
|
|
|
|
|
logger.dbg("cache", hash)
|
|
|
|
|
feed = self:fetchFeed(feed_url)
|
|
|
|
|
feed = self:fetchFeed(item_url, username, password)
|
|
|
|
|
if feed then
|
|
|
|
|
CatalogCache:insert(hash, CatalogCacheItem:new{ feed = feed })
|
|
|
|
|
end
|
|
|
|
@ -353,31 +305,30 @@ function OPDSBrowser:parseFeed(feed_url)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:getCatalog(feed_url)
|
|
|
|
|
local ok, catalog = pcall(self.parseFeed, self, feed_url)
|
|
|
|
|
function OPDSBrowser:getCatalog(item_url, username, password)
|
|
|
|
|
local ok, catalog = pcall(self.parseFeed, self, item_url, username, password)
|
|
|
|
|
if not ok and catalog and not NetworkMgr:isOnline() then
|
|
|
|
|
NetworkMgr:promptWifiOn()
|
|
|
|
|
return
|
|
|
|
|
elseif not ok and catalog then
|
|
|
|
|
logger.warn("cannot get catalog info from", feed_url, catalog)
|
|
|
|
|
logger.info("cannot get catalog info from", item_url, catalog)
|
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
|
text = T(_("Cannot get catalog info from %1"), (feed_url or "")),
|
|
|
|
|
text = T(_("Cannot get catalog info from %1"), (item_url or "")),
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if ok and catalog then
|
|
|
|
|
logger.dbg("catalog", catalog)
|
|
|
|
|
return catalog
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:genItemTableFromURL(item_url)
|
|
|
|
|
local catalog = self:getCatalog(item_url)
|
|
|
|
|
return self:genItemTableFromCatalog(catalog, item_url)
|
|
|
|
|
function OPDSBrowser:genItemTableFromURL(item_url, username, password)
|
|
|
|
|
local catalog = self:getCatalog(item_url, username, password)
|
|
|
|
|
return self:genItemTableFromCatalog(catalog, item_url, username, password)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
|
|
|
|
function OPDSBrowser:genItemTableFromCatalog(catalog, item_url, username, password)
|
|
|
|
|
local item_table = {}
|
|
|
|
|
if not catalog then
|
|
|
|
|
return item_table
|
|
|
|
@ -403,6 +354,12 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
item_table.hrefs = hrefs
|
|
|
|
|
if username then
|
|
|
|
|
item_table.username = username
|
|
|
|
|
end
|
|
|
|
|
if password then
|
|
|
|
|
item_table.password = password
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if not feed.entry then
|
|
|
|
|
return item_table
|
|
|
|
@ -443,7 +400,7 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if title == "Unknown" then
|
|
|
|
|
logger.warn("Cannot handle title", entry.title)
|
|
|
|
|
logger.info("Cannot handle title", entry.title)
|
|
|
|
|
end
|
|
|
|
|
local author = "Unknown Author"
|
|
|
|
|
if type(entry.author) == "table" and entry.author.name then
|
|
|
|
@ -455,13 +412,19 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
|
|
|
|
item.id = entry.id
|
|
|
|
|
item.content = entry.content
|
|
|
|
|
item.updated = entry.updated
|
|
|
|
|
if username then
|
|
|
|
|
item.username = username
|
|
|
|
|
end
|
|
|
|
|
if password then
|
|
|
|
|
item.password = password
|
|
|
|
|
end
|
|
|
|
|
table.insert(item_table, item)
|
|
|
|
|
end
|
|
|
|
|
return item_table
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:updateCatalog(item_table_url)
|
|
|
|
|
local menu_table = self:genItemTableFromURL(item_table_url)
|
|
|
|
|
function OPDSBrowser:updateCatalog(item_url, username, password)
|
|
|
|
|
local menu_table = self:genItemTableFromURL(item_url, username, password)
|
|
|
|
|
if #menu_table > 0 then
|
|
|
|
|
self:switchItemTable(nil, menu_table)
|
|
|
|
|
if self.page_num <= 1 then
|
|
|
|
@ -471,8 +434,8 @@ function OPDSBrowser:updateCatalog(item_table_url)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:appendCatalog(item_table_url)
|
|
|
|
|
local new_table = self:genItemTableFromURL(item_table_url)
|
|
|
|
|
function OPDSBrowser:appendCatalog(item_url, username, password)
|
|
|
|
|
local new_table = self:genItemTableFromURL(item_url, username, password)
|
|
|
|
|
if #new_table == 0 then return false end
|
|
|
|
|
|
|
|
|
|
for _, item in ipairs(new_table) do
|
|
|
|
@ -508,9 +471,10 @@ function OPDSBrowser:downloadFile(item, format, remote_url)
|
|
|
|
|
local parsed = url.parse(remote_url)
|
|
|
|
|
http.TIMEOUT, https.TIMEOUT = 20, 20
|
|
|
|
|
local httpRequest = parsed.scheme == 'http' and http.request or https.request
|
|
|
|
|
local _, c, _ = httpRequest {
|
|
|
|
|
local auth = string.format("%s:%s", item.username, item.password)
|
|
|
|
|
local __, c = httpRequest {
|
|
|
|
|
url = remote_url,
|
|
|
|
|
headers = self:getAuthorizationHeader(parsed.host),
|
|
|
|
|
headers = { Authorization = "Basic " .. mime.b64(auth), },
|
|
|
|
|
sink = ltn12.sink.file(io.open(local_path, "w")),
|
|
|
|
|
}
|
|
|
|
|
if c == 200 then
|
|
|
|
@ -588,7 +552,7 @@ function OPDSBrowser:showDownloads(item)
|
|
|
|
|
callback = function()
|
|
|
|
|
require("ui/downloadmgr"):new{
|
|
|
|
|
onConfirm = function(path)
|
|
|
|
|
logger.dbg("set download directory to", path)
|
|
|
|
|
logger.info("set download directory to", path)
|
|
|
|
|
G_reader_settings:saveSetting("download_dir", path)
|
|
|
|
|
UIManager:nextTick(function()
|
|
|
|
|
UIManager:close(self.download_dialog)
|
|
|
|
@ -617,8 +581,10 @@ function OPDSBrowser:onMenuSelect(item)
|
|
|
|
|
else
|
|
|
|
|
table.insert(self.paths, {
|
|
|
|
|
url = item.url,
|
|
|
|
|
username = item.username,
|
|
|
|
|
password = item.password,
|
|
|
|
|
})
|
|
|
|
|
if not self:updateCatalog(item.url) then
|
|
|
|
|
if not self:updateCatalog(item.url, item.username, item.password) then
|
|
|
|
|
table.remove(self.paths)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
@ -626,12 +592,14 @@ function OPDSBrowser:onMenuSelect(item)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:editServerFromInput(item, fields)
|
|
|
|
|
logger.dbg("input catalog", fields)
|
|
|
|
|
logger.info("input catalog", fields)
|
|
|
|
|
local servers = {}
|
|
|
|
|
for _, server in ipairs(G_reader_settings:readSetting("opds_servers") or {}) do
|
|
|
|
|
if server.title == item.text or server.url == item.url then
|
|
|
|
|
server.title = fields[1]
|
|
|
|
|
server.url = (fields[2]:match("^%a+://") and fields[2] or "http://" .. fields[2])
|
|
|
|
|
server.username = fields[3]
|
|
|
|
|
server.password = fields[4]
|
|
|
|
|
end
|
|
|
|
|
table.insert(servers, server)
|
|
|
|
|
end
|
|
|
|
@ -640,7 +608,7 @@ function OPDSBrowser:editServerFromInput(item, fields)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:editOPDSServer(item)
|
|
|
|
|
logger.dbg("edit", item)
|
|
|
|
|
logger.info("edit", item)
|
|
|
|
|
self.edit_server_dialog = MultiInputDialog:new{
|
|
|
|
|
title = _("Edit OPDS catalog"),
|
|
|
|
|
fields = {
|
|
|
|
@ -652,6 +620,15 @@ function OPDSBrowser:editOPDSServer(item)
|
|
|
|
|
text = item.url or "",
|
|
|
|
|
hint = _("Catalog URL"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = item.username or "",
|
|
|
|
|
hint = _("Username (optional)"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text = item.password or "",
|
|
|
|
|
hint = _("Password (optional)"),
|
|
|
|
|
text_type = "password",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
buttons = {
|
|
|
|
|
{
|
|
|
|
@ -680,7 +657,7 @@ function OPDSBrowser:editOPDSServer(item)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:deleteOPDSServer(item)
|
|
|
|
|
logger.dbg("delete", item)
|
|
|
|
|
logger.info("delete", item)
|
|
|
|
|
local servers = {}
|
|
|
|
|
for _, server in ipairs(G_reader_settings:readSetting("opds_servers") or {}) do
|
|
|
|
|
if server.title ~= item.text or server.url ~= item.url then
|
|
|
|
@ -725,13 +702,12 @@ function OPDSBrowser:onMenuHold(item)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:onReturn()
|
|
|
|
|
logger.dbg("return to last page catalog")
|
|
|
|
|
if #self.paths > 0 then
|
|
|
|
|
table.remove(self.paths)
|
|
|
|
|
local path = self.paths[#self.paths]
|
|
|
|
|
if path then
|
|
|
|
|
-- return to last path
|
|
|
|
|
self:updateCatalog(path.url)
|
|
|
|
|
self:updateCatalog(path.url, path.username, path.password)
|
|
|
|
|
else
|
|
|
|
|
-- return to root path, we simply reinit opdsbrowser
|
|
|
|
|
self:init()
|
|
|
|
@ -741,14 +717,13 @@ function OPDSBrowser:onReturn()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function OPDSBrowser:onNext()
|
|
|
|
|
logger.dbg("fetch next page catalog")
|
|
|
|
|
-- self.page_num comes from menu.lua
|
|
|
|
|
local page_num = self.page_num
|
|
|
|
|
-- fetch more entries until we fill out one page or reach the end
|
|
|
|
|
while page_num == self.page_num do
|
|
|
|
|
local hrefs = self.item_table.hrefs
|
|
|
|
|
if hrefs and hrefs.next then
|
|
|
|
|
if not self:appendCatalog(hrefs.next) then
|
|
|
|
|
if not self:appendCatalog(hrefs.next, self.item_table.username, self.item_table.password) then
|
|
|
|
|
break -- reach end of paging
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|