2014-09-05 13:02:13 +00:00
|
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
2017-02-25 17:01:37 +00:00
|
|
|
local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
|
2017-04-02 14:17:49 +00:00
|
|
|
local Cache = require("cache")
|
|
|
|
local CacheItem = require("cacheitem")
|
2014-09-05 13:02:13 +00:00
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
2015-09-14 16:59:00 +00:00
|
|
|
local LoginDialog = require("ui/widget/logindialog")
|
2014-09-05 13:02:13 +00:00
|
|
|
local Menu = require("ui/widget/menu")
|
2017-04-02 14:17:49 +00:00
|
|
|
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
|
|
|
local NetworkMgr = require("ui/network/manager")
|
|
|
|
local OPDSParser = require("ui/opdsparser")
|
2014-10-30 18:42:18 +00:00
|
|
|
local Screen = require("device").screen
|
2017-04-02 14:17:49 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2017-03-15 07:59:42 +00:00
|
|
|
local gettext = require("gettext")
|
2014-09-05 13:02:13 +00:00
|
|
|
local http = require('socket.http')
|
|
|
|
local https = require('ssl.https')
|
2017-04-02 14:17:49 +00:00
|
|
|
local logger = require("logger")
|
2014-09-05 13:02:13 +00:00
|
|
|
local ltn12 = require('ltn12')
|
2015-09-14 16:59:00 +00:00
|
|
|
local mime = require('mime')
|
2017-04-02 14:17:49 +00:00
|
|
|
local socket = require('socket')
|
|
|
|
local url = require('socket.url')
|
|
|
|
local util = require("util")
|
|
|
|
local T = require("ffi/util").template
|
2014-09-05 13:02:13 +00:00
|
|
|
|
|
|
|
local CatalogCacheItem = CacheItem:new{
|
|
|
|
size = 1024, -- fixed size for catalog item
|
|
|
|
}
|
|
|
|
|
|
|
|
-- cache catalog parsed from feed xml
|
|
|
|
local CatalogCache = Cache:new{
|
|
|
|
max_memsize = 20*1024, -- keep only 20 cache items
|
|
|
|
current_memsize = 0,
|
|
|
|
cache = {},
|
|
|
|
cache_order = {},
|
|
|
|
}
|
|
|
|
|
|
|
|
local OPDSBrowser = Menu:extend{
|
|
|
|
opds_servers = {},
|
2017-03-15 07:59:42 +00:00
|
|
|
calibre_name = gettext("Local calibre catalog"),
|
2014-09-10 05:26:50 +00:00
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
catalog_type = "application/atom%+xml",
|
|
|
|
search_type = "application/opensearchdescription%+xml",
|
2014-10-03 14:22:37 +00:00
|
|
|
acquisition_rel = "^http://opds%-spec%.org/acquisition",
|
2014-11-30 20:44:47 +00:00
|
|
|
image_rel = "http://opds-spec.org/image",
|
2014-09-05 13:02:13 +00:00
|
|
|
thumbnail_rel = "http://opds-spec.org/image/thumbnail",
|
|
|
|
|
|
|
|
formats = {
|
|
|
|
["application/epub+zip"] = "EPUB",
|
2014-09-19 10:43:58 +00:00
|
|
|
["application/fb2+zip"] = "FB2",
|
2014-09-05 13:02:13 +00:00
|
|
|
["application/pdf"] = "PDF",
|
|
|
|
["text/plain"] = "TXT",
|
|
|
|
["application/x-mobipocket-ebook"] = "MOBI",
|
|
|
|
["application/x-mobi8-ebook"] = "AZW3",
|
|
|
|
},
|
|
|
|
|
|
|
|
width = Screen:getWidth(),
|
|
|
|
height = Screen:getHeight(),
|
|
|
|
no_title = false,
|
|
|
|
parent = nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
function OPDSBrowser:init()
|
2017-01-22 01:00:02 +00:00
|
|
|
local servers = G_reader_settings:readSetting("opds_servers")
|
|
|
|
if not servers then -- If there are no saved servers, add some defaults
|
|
|
|
servers = {
|
|
|
|
{
|
|
|
|
title = "Project Gutenberg",
|
|
|
|
url = "http://m.gutenberg.org/ebooks.opds/?format=opds",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title = "Feedbooks",
|
|
|
|
url = "http://www.feedbooks.com/publicdomain/catalog.atom",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title = "ManyBooks",
|
|
|
|
url = "http://manybooks.net/opds/index.php",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title = "Internet Archive",
|
|
|
|
url = "http://bookserver.archive.org/catalog/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
G_reader_settings:saveSetting("opds_servers", servers)
|
|
|
|
end
|
|
|
|
self.item_table = self:genItemTableFromRoot()
|
2014-09-05 13:02:13 +00:00
|
|
|
Menu.init(self) -- call parent's init()
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:addServerFromInput(fields)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("input catalog", fields)
|
2014-09-05 13:02:13 +00:00
|
|
|
local servers = G_reader_settings:readSetting("opds_servers") or {}
|
|
|
|
table.insert(servers, {
|
|
|
|
title = fields[1],
|
2014-11-28 20:42:33 +00:00
|
|
|
url = (fields[2]:match("^%a+://") and fields[2] or "http://" .. fields[2]),
|
2014-09-05 13:02:13 +00:00
|
|
|
})
|
|
|
|
G_reader_settings:saveSetting("opds_servers", servers)
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
|
2014-09-10 05:26:50 +00:00
|
|
|
function OPDSBrowser:editCalibreFromInput(fields)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("input calibre server", fields)
|
2014-09-10 05:26:50 +00:00
|
|
|
local calibre = G_reader_settings:readSetting("calibre_opds") or {}
|
|
|
|
if fields[1] then
|
|
|
|
calibre.host = fields[1]
|
|
|
|
end
|
|
|
|
if tonumber(fields[2]) then
|
|
|
|
calibre.port = fields[2]
|
|
|
|
end
|
|
|
|
G_reader_settings:saveSetting("calibre_opds", calibre)
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
function OPDSBrowser:addNewCatalog()
|
|
|
|
self.add_server_dialog = MultiInputDialog:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
title = gettext("Add OPDS catalog"),
|
2014-09-05 13:02:13 +00:00
|
|
|
fields = {
|
|
|
|
{
|
|
|
|
text = "",
|
2017-03-15 07:59:42 +00:00
|
|
|
hint = gettext("Catalog name"),
|
2014-09-05 13:02:13 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
text = "",
|
2017-03-15 07:59:42 +00:00
|
|
|
hint = gettext("Catalog URL"),
|
2014-09-05 13:02:13 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Cancel"),
|
2014-09-05 13:02:13 +00:00
|
|
|
callback = function()
|
|
|
|
self.add_server_dialog:onClose()
|
|
|
|
UIManager:close(self.add_server_dialog)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Add"),
|
2014-09-05 13:02:13 +00:00
|
|
|
callback = function()
|
|
|
|
self.add_server_dialog:onClose()
|
|
|
|
UIManager:close(self.add_server_dialog)
|
|
|
|
self:addServerFromInput(MultiInputDialog:getFields())
|
|
|
|
end
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
width = Screen:getWidth() * 0.95,
|
|
|
|
height = Screen:getHeight() * 0.2,
|
|
|
|
}
|
|
|
|
self.add_server_dialog:onShowKeyboard()
|
|
|
|
UIManager:show(self.add_server_dialog)
|
|
|
|
end
|
|
|
|
|
2014-09-10 05:26:50 +00:00
|
|
|
function OPDSBrowser:editCalibreServer()
|
|
|
|
local calibre = G_reader_settings:readSetting("calibre_opds") or {}
|
|
|
|
self.add_server_dialog = MultiInputDialog:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
title = gettext("Edit local calibre host and port"),
|
2014-09-10 05:26:50 +00:00
|
|
|
fields = {
|
|
|
|
{
|
|
|
|
-- TODO: get IP address of current device
|
|
|
|
text = calibre.host or "192.168.1.1",
|
2017-03-15 07:59:42 +00:00
|
|
|
hint = gettext("calibre host"),
|
2014-09-10 05:26:50 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
text = calibre.port and tostring(calibre.port) or "8080",
|
2017-03-15 07:59:42 +00:00
|
|
|
hint = gettext("calibre port"),
|
2014-09-10 05:26:50 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Cancel"),
|
2014-09-10 05:26:50 +00:00
|
|
|
callback = function()
|
|
|
|
self.add_server_dialog:onClose()
|
|
|
|
UIManager:close(self.add_server_dialog)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Apply"),
|
2014-09-10 05:26:50 +00:00
|
|
|
callback = function()
|
|
|
|
self.add_server_dialog:onClose()
|
|
|
|
UIManager:close(self.add_server_dialog)
|
|
|
|
self:editCalibreFromInput(MultiInputDialog:getFields())
|
|
|
|
end
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
width = Screen:getWidth() * 0.95,
|
|
|
|
height = Screen:getHeight() * 0.2,
|
|
|
|
}
|
|
|
|
self.add_server_dialog:onShowKeyboard()
|
|
|
|
UIManager:show(self.add_server_dialog)
|
|
|
|
end
|
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
function OPDSBrowser:genItemTableFromRoot()
|
|
|
|
local item_table = {}
|
|
|
|
local added_servers = G_reader_settings:readSetting("opds_servers") or {}
|
2016-02-10 18:30:05 +00:00
|
|
|
for _, server in ipairs(added_servers) do
|
2014-09-05 13:02:13 +00:00
|
|
|
table.insert(item_table, {
|
|
|
|
text = server.title,
|
|
|
|
content = server.subtitle,
|
|
|
|
url = server.url,
|
2014-09-10 04:25:08 +00:00
|
|
|
deletable = true,
|
|
|
|
editable = true,
|
2014-09-05 13:02:13 +00:00
|
|
|
})
|
|
|
|
end
|
2014-09-10 05:26:50 +00:00
|
|
|
local calibre_opds = G_reader_settings:readSetting("calibre_opds") or {}
|
|
|
|
if not calibre_opds.host or not calibre_opds.port then
|
|
|
|
table.insert(item_table, {
|
|
|
|
text = self.calibre_name,
|
|
|
|
callback = function()
|
|
|
|
self:editCalibreServer()
|
|
|
|
end,
|
|
|
|
deletable = false,
|
|
|
|
})
|
|
|
|
else
|
|
|
|
table.insert(item_table, {
|
|
|
|
text = self.calibre_name,
|
|
|
|
url = string.format("http://%s:%d/opds",
|
|
|
|
calibre_opds.host, calibre_opds.port),
|
|
|
|
editable = true,
|
|
|
|
deletable = false,
|
|
|
|
})
|
|
|
|
end
|
2014-09-05 13:02:13 +00:00
|
|
|
table.insert(item_table, {
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Add new OPDS catalog"),
|
2014-09-05 13:02:13 +00:00
|
|
|
callback = function()
|
|
|
|
self:addNewCatalog()
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
return item_table
|
|
|
|
end
|
|
|
|
|
2015-09-14 16:59:00 +00:00
|
|
|
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
|
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
function OPDSBrowser:fetchFeed(feed_url)
|
2016-01-03 07:44:23 +00:00
|
|
|
local request, sink = {}, {}
|
2014-09-05 13:02:13 +00:00
|
|
|
local parsed = url.parse(feed_url)
|
|
|
|
request['url'] = feed_url
|
|
|
|
request['method'] = 'GET'
|
|
|
|
request['sink'] = ltn12.sink.table(sink)
|
2015-09-14 16:59:00 +00:00
|
|
|
request['headers'] = self:getAuthorizationHeader(parsed.host)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("request", request)
|
2014-09-05 13:02:13 +00:00
|
|
|
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))
|
|
|
|
|
|
|
|
-- raise error message when network is unavailable
|
|
|
|
if headers == nil then
|
2014-09-10 04:25:08 +00:00
|
|
|
error(code)
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
|
2015-09-14 16:59:00 +00:00
|
|
|
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
|
|
|
|
local xml = table.concat(sink)
|
|
|
|
if xml ~= "" then
|
|
|
|
return xml
|
|
|
|
end
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-14 16:59:00 +00:00
|
|
|
function OPDSBrowser:fetchWithLogin(host, callback)
|
|
|
|
self.login_dialog = LoginDialog:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
title = gettext("Login to OPDS server"),
|
2015-09-14 16:59:00 +00:00
|
|
|
username = "",
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Cancel"),
|
2015-09-14 16:59:00 +00:00
|
|
|
enabled = true,
|
|
|
|
callback = function()
|
|
|
|
self:closeDialog()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Login"),
|
2015-09-14 16:59:00 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
self.login_dialog:onShowKeyboard()
|
|
|
|
UIManager:show(self.login_dialog)
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:closeDialog()
|
|
|
|
self.login_dialog:onClose()
|
|
|
|
UIManager:close(self.login_dialog)
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:getCredential()
|
|
|
|
return self.login_dialog:getCredential()
|
|
|
|
end
|
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
function OPDSBrowser:parseFeed(feed_url)
|
2016-01-03 07:44:23 +00:00
|
|
|
local feed
|
2014-09-05 13:02:13 +00:00
|
|
|
local hash = "opds|catalog|" .. feed_url
|
|
|
|
local cache = CatalogCache:check(hash)
|
|
|
|
if cache then
|
|
|
|
feed = cache.feed
|
|
|
|
else
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("cache", hash)
|
2014-09-05 13:02:13 +00:00
|
|
|
feed = self:fetchFeed(feed_url)
|
|
|
|
if feed then
|
2016-01-03 07:44:23 +00:00
|
|
|
CatalogCache:insert(hash, CatalogCacheItem:new{ feed = feed })
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
if feed then
|
|
|
|
return OPDSParser:parse(feed)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:getCatalog(feed_url)
|
|
|
|
local ok, catalog = pcall(self.parseFeed, self, feed_url)
|
2017-03-18 12:49:28 +00:00
|
|
|
if not ok and catalog and not NetworkMgr:isOnline() then
|
2014-09-05 13:02:13 +00:00
|
|
|
NetworkMgr:promptWifiOn()
|
|
|
|
return
|
|
|
|
elseif not ok and catalog then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.warn("cannot get catalog info from", feed_url, catalog)
|
2014-09-10 04:25:08 +00:00
|
|
|
UIManager:show(InfoMessage:new{
|
2017-02-25 17:01:37 +00:00
|
|
|
text = T(
|
2017-03-15 07:59:42 +00:00
|
|
|
gettext("Cannot get catalog info from %1"),
|
2014-11-28 13:10:37 +00:00
|
|
|
(feed_url or "")
|
|
|
|
),
|
2014-09-10 04:25:08 +00:00
|
|
|
})
|
2014-09-05 13:02:13 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if ok and catalog then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("catalog", catalog)
|
2014-09-05 13:02:13 +00:00
|
|
|
return catalog
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-13 14:07:14 +00:00
|
|
|
function OPDSBrowser:genItemTableFromURL(item_url)
|
|
|
|
local catalog = self:getCatalog(item_url)
|
|
|
|
return self:genItemTableFromCatalog(catalog, item_url)
|
2014-11-30 18:06:27 +00:00
|
|
|
end
|
|
|
|
|
2015-09-13 14:07:14 +00:00
|
|
|
function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
2014-11-30 18:06:27 +00:00
|
|
|
local item_table = {}
|
2016-12-27 10:00:13 +00:00
|
|
|
if not catalog then
|
|
|
|
return item_table
|
|
|
|
end
|
|
|
|
|
|
|
|
local feed = catalog.feed or catalog
|
|
|
|
|
|
|
|
local function build_href(href)
|
|
|
|
return url.absolute(item_url, href)
|
|
|
|
end
|
|
|
|
|
|
|
|
local hrefs = {}
|
|
|
|
if feed.link then
|
|
|
|
for _, link in ipairs(feed.link) do
|
|
|
|
if link.type ~= nil then
|
|
|
|
if link.type:find(self.catalog_type) or
|
|
|
|
link.type:find(self.search_type) then
|
|
|
|
if link.rel and link.href then
|
|
|
|
hrefs[link.rel] = build_href(link.href)
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-12-27 10:00:13 +00:00
|
|
|
end
|
|
|
|
item_table.hrefs = hrefs
|
|
|
|
|
|
|
|
if not feed.entry then
|
|
|
|
return item_table
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, entry in ipairs(feed.entry) do
|
|
|
|
local item = {}
|
|
|
|
item.acquisitions = {}
|
|
|
|
if entry.link then
|
|
|
|
for _, link in ipairs(entry.link) do
|
|
|
|
if link.type:find(self.catalog_type)
|
|
|
|
and (not link.rel
|
|
|
|
or link.rel == "subsection"
|
|
|
|
or link.rel == "http://opds-spec.org/sort/popular"
|
|
|
|
or link.rel == "http://opds-spec.org/sort/new") then
|
|
|
|
item.url = build_href(link.href)
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
2016-12-27 10:00:13 +00:00
|
|
|
if link.rel then
|
|
|
|
if link.rel:match(self.acquisition_rel) then
|
|
|
|
table.insert(item.acquisitions, {
|
|
|
|
type = link.type,
|
|
|
|
href = build_href(link.href),
|
|
|
|
})
|
|
|
|
elseif link.rel == self.thumbnail_rel then
|
|
|
|
item.thumbnail = build_href(link.href)
|
|
|
|
elseif link.rel == self.image_rel then
|
|
|
|
item.image = build_href(link.href)
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-12-27 10:00:13 +00:00
|
|
|
local title = "Unknown"
|
|
|
|
if type(entry.title) == "string" then
|
|
|
|
title = entry.title
|
|
|
|
elseif type(entry.title) == "table" then
|
|
|
|
if type(entry.title.type) == "string" and entry.title.div ~= "" then
|
|
|
|
title = entry.title.div
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if title == "Unknown" then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.warn("Cannot handle title", entry.title)
|
2016-12-27 10:00:13 +00:00
|
|
|
end
|
|
|
|
local author = "Unknown Author"
|
|
|
|
if type(entry.author) == "table" and entry.author.name then
|
|
|
|
author = entry.author.name
|
|
|
|
end
|
|
|
|
item.text = title
|
|
|
|
item.title = title
|
|
|
|
item.author = author
|
|
|
|
item.id = entry.id
|
|
|
|
item.content = entry.content
|
|
|
|
item.updated = entry.updated
|
|
|
|
table.insert(item_table, item)
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
return item_table
|
|
|
|
end
|
|
|
|
|
2016-01-03 07:44:23 +00:00
|
|
|
function OPDSBrowser:updateCatalog(item_table_url)
|
|
|
|
local menu_table = self:genItemTableFromURL(item_table_url)
|
2014-09-05 13:02:13 +00:00
|
|
|
if #menu_table > 0 then
|
2017-02-01 14:24:21 +00:00
|
|
|
self:switchItemTable(nil, menu_table)
|
2014-11-07 21:08:03 +00:00
|
|
|
if self.page_num <= 1 then
|
|
|
|
self:onNext()
|
|
|
|
end
|
2014-09-05 13:02:13 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-03 07:44:23 +00:00
|
|
|
function OPDSBrowser:appendCatalog(item_table_url)
|
|
|
|
local new_table = self:genItemTableFromURL(item_table_url)
|
2017-03-18 22:03:30 +00:00
|
|
|
if #new_table == 0 then return false end
|
|
|
|
|
2016-02-10 18:30:05 +00:00
|
|
|
for _, item in ipairs(new_table) do
|
2014-09-05 13:02:13 +00:00
|
|
|
table.insert(self.item_table, item)
|
|
|
|
end
|
|
|
|
self.item_table.hrefs = new_table.hrefs
|
2017-02-01 14:24:21 +00:00
|
|
|
self:switchItemTable(nil, self.item_table, -1)
|
2014-09-05 13:02:13 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2017-02-25 17:01:37 +00:00
|
|
|
function OPDSBrowser.getCurrentDownloadDir()
|
|
|
|
local lastdir = G_reader_settings:readSetting("lastdir")
|
|
|
|
return G_reader_settings:readSetting("download_dir") or lastdir
|
|
|
|
end
|
|
|
|
|
2015-10-18 17:54:54 +00:00
|
|
|
function OPDSBrowser:downloadFile(item, format, remote_url)
|
2014-09-10 04:25:08 +00:00
|
|
|
-- download to user selected directory or last opened dir
|
2017-02-25 17:01:37 +00:00
|
|
|
local download_dir = self.getCurrentDownloadDir()
|
2017-04-14 18:12:21 +00:00
|
|
|
local file_system = util.getFilesystemType(download_dir)
|
2017-01-10 00:05:15 +00:00
|
|
|
if file_system == "vfat" or file_system == "fuse.fsp" then
|
2017-04-14 18:12:21 +00:00
|
|
|
item.author = util.replaceInvalidChars(item.author)
|
|
|
|
item.title = util.replaceInvalidChars(item.title)
|
2017-01-10 00:05:15 +00:00
|
|
|
else
|
2017-04-14 18:12:21 +00:00
|
|
|
item.author = util.replaceSlashChar(item.author)
|
|
|
|
item.title = util.replaceSlashChar(item.title)
|
2017-01-10 00:05:15 +00:00
|
|
|
end
|
2015-10-18 17:54:54 +00:00
|
|
|
local local_path = download_dir .. "/" .. item.author .. ' - ' .. item.title .. "." .. string.lower(format)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("downloading file", local_path, "from", remote_url)
|
2014-09-05 13:02:13 +00:00
|
|
|
|
2017-04-02 14:17:49 +00:00
|
|
|
local_path = util.fixUtf8(local_path, "_")
|
2014-09-05 13:02:13 +00:00
|
|
|
local parsed = url.parse(remote_url)
|
2017-03-15 07:59:42 +00:00
|
|
|
http.TIMEOUT, https.TIMEOUT = 20, 20
|
2014-09-05 13:02:13 +00:00
|
|
|
local httpRequest = parsed.scheme == 'http' and http.request or https.request
|
2016-12-29 08:10:38 +00:00
|
|
|
local _, c, _ = httpRequest{
|
2014-09-05 13:02:13 +00:00
|
|
|
url = remote_url,
|
2015-09-14 16:59:00 +00:00
|
|
|
headers = self:getAuthorizationHeader(parsed.host),
|
2014-09-05 13:02:13 +00:00
|
|
|
sink = ltn12.sink.file(io.open(local_path, "w")),
|
|
|
|
}
|
|
|
|
|
|
|
|
if c == 200 then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("file downloaded to", local_path)
|
2016-11-27 15:06:24 +00:00
|
|
|
if self.file_downloaded_callback then
|
|
|
|
self.file_downloaded_callback(local_path)
|
|
|
|
end
|
2014-09-05 13:02:13 +00:00
|
|
|
else
|
2014-09-10 04:25:08 +00:00
|
|
|
UIManager:show(InfoMessage:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Could not save file to:\n") .. local_path,
|
2014-09-10 04:25:08 +00:00
|
|
|
timeout = 3,
|
|
|
|
})
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-25 17:01:37 +00:00
|
|
|
function OPDSBrowser:createNewDownloadDialog(path, buttons)
|
|
|
|
self.download_dialog = ButtonDialogTitle:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
title = T(gettext("Download directory:\n%1\n\nDownload file type:"), path),
|
2017-02-25 17:01:37 +00:00
|
|
|
buttons = buttons
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
function OPDSBrowser:showDownloads(item)
|
|
|
|
local acquisitions = item.acquisitions
|
|
|
|
local downloadsperline = 2
|
|
|
|
local lines = math.ceil(#acquisitions/downloadsperline)
|
|
|
|
local buttons = {}
|
|
|
|
for i = 1, lines do
|
|
|
|
local line = {}
|
|
|
|
for j = 1, downloadsperline do
|
|
|
|
local button = {}
|
|
|
|
local index = (i-1)*downloadsperline + j
|
|
|
|
local acquisition = acquisitions[index]
|
|
|
|
if acquisition then
|
|
|
|
local format = self.formats[acquisition.type]
|
|
|
|
if format then
|
2017-02-25 17:01:37 +00:00
|
|
|
-- append DOWNWARDS BLACK ARROW ⬇ U+2B07 to format
|
|
|
|
button.text = format .. "\xE2\xAC\x87"
|
2014-09-05 13:02:13 +00:00
|
|
|
button.callback = function()
|
|
|
|
UIManager:scheduleIn(1, function()
|
2015-10-18 17:54:54 +00:00
|
|
|
self:downloadFile(item, format, acquisition.href)
|
2014-09-05 13:02:13 +00:00
|
|
|
end)
|
|
|
|
UIManager:close(self.download_dialog)
|
|
|
|
UIManager:show(InfoMessage:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Downloading may take several minutes…"),
|
2016-11-27 15:06:24 +00:00
|
|
|
timeout = 1,
|
2014-09-05 13:02:13 +00:00
|
|
|
})
|
|
|
|
end
|
|
|
|
table.insert(line, button)
|
|
|
|
end
|
2017-02-25 17:01:37 +00:00
|
|
|
elseif #acquisitions > downloadsperline then
|
|
|
|
table.insert(line, {text=""})
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
table.insert(buttons, line)
|
|
|
|
end
|
2017-02-25 17:01:37 +00:00
|
|
|
table.insert(buttons, {})
|
2014-09-10 04:25:08 +00:00
|
|
|
-- set download directory button
|
|
|
|
table.insert(buttons, {
|
|
|
|
{
|
2017-05-16 04:48:18 +00:00
|
|
|
text = gettext("Choose download directory by long-pressing"),
|
2014-09-10 04:25:08 +00:00
|
|
|
callback = function()
|
2014-10-28 09:14:06 +00:00
|
|
|
require("ui/downloadmgr"):new{
|
2017-03-15 07:59:42 +00:00
|
|
|
title = gettext("Choose download directory"),
|
2014-09-10 04:25:08 +00:00
|
|
|
onConfirm = function(path)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("set download directory to", path)
|
2014-09-10 04:25:08 +00:00
|
|
|
G_reader_settings:saveSetting("download_dir", path)
|
2017-02-25 17:01:37 +00:00
|
|
|
UIManager:nextTick(function()
|
|
|
|
UIManager:close(self.download_dialog)
|
|
|
|
self:createNewDownloadDialog(path, buttons)
|
|
|
|
UIManager:show(self.download_dialog)
|
|
|
|
end)
|
2014-09-10 04:25:08 +00:00
|
|
|
end,
|
2014-10-28 09:14:06 +00:00
|
|
|
}:chooseDir()
|
2014-09-10 04:25:08 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
})
|
2014-09-05 13:02:13 +00:00
|
|
|
|
2017-02-25 17:01:37 +00:00
|
|
|
self:createNewDownloadDialog(self.getCurrentDownloadDir(), buttons)
|
2014-09-05 13:02:13 +00:00
|
|
|
UIManager:show(self.download_dialog)
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:onMenuSelect(item)
|
|
|
|
-- add catalog
|
|
|
|
if item.callback then
|
|
|
|
item.callback()
|
|
|
|
-- acquisition
|
|
|
|
elseif item.acquisitions and #item.acquisitions > 0 then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("downloads available", item)
|
2014-09-05 13:02:13 +00:00
|
|
|
self:showDownloads(item)
|
|
|
|
-- navigation
|
|
|
|
else
|
|
|
|
table.insert(self.paths, {
|
|
|
|
url = item.url,
|
|
|
|
})
|
2015-09-13 14:07:14 +00:00
|
|
|
if not self:updateCatalog(item.url) then
|
2014-09-05 13:02:13 +00:00
|
|
|
table.remove(self.paths)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2014-09-10 04:25:08 +00:00
|
|
|
function OPDSBrowser:editServerFromInput(item, fields)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("input catalog", fields)
|
2014-09-10 04:25:08 +00:00
|
|
|
local servers = {}
|
2016-02-10 18:30:05 +00:00
|
|
|
for _, server in ipairs(G_reader_settings:readSetting("opds_servers") or {}) do
|
2014-09-10 04:25:08 +00:00
|
|
|
if server.title == item.text or server.url == item.url then
|
|
|
|
server.title = fields[1]
|
2014-11-28 20:42:33 +00:00
|
|
|
server.url = (fields[2]:match("^%a+://") and fields[2] or "http://" .. fields[2])
|
2014-09-10 04:25:08 +00:00
|
|
|
end
|
|
|
|
table.insert(servers, server)
|
|
|
|
end
|
|
|
|
G_reader_settings:saveSetting("opds_servers", servers)
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:editOPDSServer(item)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("edit", item)
|
2014-09-10 04:25:08 +00:00
|
|
|
self.edit_server_dialog = MultiInputDialog:new{
|
2017-03-15 07:59:42 +00:00
|
|
|
title = gettext("Edit OPDS catalog"),
|
2014-09-10 04:25:08 +00:00
|
|
|
fields = {
|
|
|
|
{
|
|
|
|
text = item.text or "",
|
2017-03-15 07:59:42 +00:00
|
|
|
hint = gettext("Catalog Name"),
|
2014-09-10 04:25:08 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
text = item.url or "",
|
2017-03-15 07:59:42 +00:00
|
|
|
hint = gettext("Catalog URL"),
|
2014-09-10 04:25:08 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Cancel"),
|
2014-09-10 04:25:08 +00:00
|
|
|
callback = function()
|
|
|
|
self.edit_server_dialog:onClose()
|
|
|
|
UIManager:close(self.edit_server_dialog)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Apply"),
|
2014-09-10 04:25:08 +00:00
|
|
|
callback = function()
|
|
|
|
self.edit_server_dialog:onClose()
|
|
|
|
UIManager:close(self.edit_server_dialog)
|
|
|
|
self:editServerFromInput(item, MultiInputDialog:getFields())
|
|
|
|
end
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
width = Screen:getWidth() * 0.95,
|
|
|
|
height = Screen:getHeight() * 0.2,
|
|
|
|
}
|
|
|
|
self.edit_server_dialog:onShowKeyboard()
|
|
|
|
UIManager:show(self.edit_server_dialog)
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:deleteOPDSServer(item)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("delete", item)
|
2014-09-10 04:25:08 +00:00
|
|
|
local servers = {}
|
2016-02-10 18:30:05 +00:00
|
|
|
for _, server in ipairs(G_reader_settings:readSetting("opds_servers") or {}) do
|
2014-09-10 04:25:08 +00:00
|
|
|
if server.title ~= item.text or server.url ~= item.url then
|
|
|
|
table.insert(servers, server)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
G_reader_settings:saveSetting("opds_servers", servers)
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:onMenuHold(item)
|
|
|
|
if item.deletable or item.editable then
|
|
|
|
self.opds_server_dialog = ButtonDialog:new{
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Edit"),
|
2014-09-10 04:25:08 +00:00
|
|
|
enabled = item.editable,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.opds_server_dialog)
|
2014-09-10 05:26:50 +00:00
|
|
|
if item.text ~= self.calibre_name then
|
|
|
|
self:editOPDSServer(item)
|
|
|
|
else
|
|
|
|
self:editCalibreServer(item)
|
|
|
|
end
|
2014-09-10 04:25:08 +00:00
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2017-03-15 07:59:42 +00:00
|
|
|
text = gettext("Delete"),
|
2014-09-10 04:25:08 +00:00
|
|
|
enabled = item.deletable,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.opds_server_dialog)
|
|
|
|
self:deleteOPDSServer(item)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UIManager:show(self.opds_server_dialog)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
function OPDSBrowser:onReturn()
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("return to last page catalog")
|
2014-09-05 13:02:13 +00:00
|
|
|
if #self.paths > 0 then
|
|
|
|
table.remove(self.paths)
|
|
|
|
local path = self.paths[#self.paths]
|
|
|
|
if path then
|
|
|
|
-- return to last path
|
2015-09-13 14:07:14 +00:00
|
|
|
self:updateCatalog(path.url)
|
2014-09-05 13:02:13 +00:00
|
|
|
else
|
|
|
|
-- return to root path, we simply reinit opdsbrowser
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function OPDSBrowser:onNext()
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("fetch next page catalog")
|
2017-03-18 22:03:30 +00:00
|
|
|
-- self.page_num comes from menu.lua
|
2014-11-07 21:05:16 +00:00
|
|
|
local page_num = self.page_num
|
2017-03-18 22:03:30 +00:00
|
|
|
-- 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
|
|
|
|
break -- reach end of paging
|
|
|
|
end
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
2014-09-05 13:02:13 +00:00
|
|
|
end
|
2017-03-18 22:03:30 +00:00
|
|
|
|
2014-09-05 13:02:13 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
return OPDSBrowser
|