credocument: deal with loadDocument() failures (#3570)

cre:loadDocument() may fail in recognizing the document format, and
koreader would previously keep calling other methods on it, which would
make crengine segfaults. We now check loadDocument success at the
various places it is called, and try to deal the best way we can when it fails.
pull/3576/head
poire-z 6 years ago committed by GitHub
parent 6f41cecab2
commit 7797c2369e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit b7342e6aa475876b5c95440ca24278aacba6eb85 Subproject commit 81e789e724c7417c1691652532c3ea0a574cba2a

@ -98,23 +98,36 @@ function BookInfo:show(file, book_props)
-- If still no book_props (book never opened or empty 'stats'), open the -- If still no book_props (book never opened or empty 'stats'), open the
-- document to get them -- document to get them
if not book_props then if not book_props then
local pages
local document = DocumentRegistry:openDocument(file) local document = DocumentRegistry:openDocument(file)
if document.loadDocument then -- needed for crengine if document then
document:loadDocument() local loaded = true
-- document:render() local pages
-- It would be needed to get nb of pages, but the nb obtained if document.loadDocument then -- CreDocument
-- by simply calling here document:getPageCount() is wrong, if not document:loadDocument() then
-- often 2 to 3 times the nb of pages we see when opening -- failed loading, calling other methods would segfault
-- the document (may be some other cre settings should be applied loaded = false
-- before calling render() ?) end
else -- For CreDocument, we would need to call document:render()
-- for all others than crengine, we seem to get an accurate nb of pages -- to get nb of pages, but the nb obtained by simply calling
pages = document:getPageCount() -- here document:getPageCount() is wrong, often 2 to 3 times
-- the nb of pages we see when opening the document (may be
-- some other cre settings should be applied before calling
-- render() ?)
else
-- for all others than crengine, we seem to get an accurate nb of pages
pages = document:getPageCount()
end
if loaded then
book_props = document:getProps()
book_props.pages = pages
end
DocumentRegistry:closeDocument(file)
end end
book_props = document:getProps() end
book_props.pages = pages
DocumentRegistry:closeDocument(file) -- If still no book_props, fall back to empty ones
if not book_props then
book_props = {}
end end
local title = book_props.title local title = book_props.title

@ -263,7 +263,9 @@ function ReaderUI:init()
else else
-- make sure we render document first before calling any callback -- make sure we render document first before calling any callback
self:registerPostInitCallback(function() self:registerPostInitCallback(function()
self.document:loadDocument() if not self.document:loadDocument() then
self:dealWithLoadDocumentFailure()
end
-- used to read additional settings after the document has been -- used to read additional settings after the document has been
-- loaded (but not rendered yet) -- loaded (but not rendered yet)
@ -561,4 +563,34 @@ function ReaderUI:onClose()
end end
end end
function ReaderUI:dealWithLoadDocumentFailure()
-- Sadly, we had to delay loadDocument() to about now, so we only
-- know now this document is not valid or recognized.
-- We can't do much more than crash properly here (still better than
-- going on and segfaulting when calling other methods on unitiliazed
-- _document)
-- We must still remove it from lastfile and history (as it has
-- already been added there) so that koreader don't crash again
-- at next launch...
local readhistory = require("readhistory")
readhistory:removeItemByPath(self.document.file)
if G_reader_settings:readSetting("lastfile") == self.document.file then
G_reader_settings:saveSetting("lastfile", #readhistory.hist > 0 and readhistory.hist[1].file or nil)
end
-- As we are in a coroutine, we can pause and show an InfoMessage before exiting
local _coroutine = coroutine.running()
if coroutine then
logger.warn("crengine failed recognizing or parsing this file: unsupported or invalid document")
UIManager:show(InfoMessage:new{
text = _("Failed recognizing or parsing this file: unsupported or invalid document.\nKOReader will exit now."),
dismiss_callback = function()
coroutine.resume(_coroutine, false)
end,
})
coroutine.yield() -- pause till InfoMessage is dismissed
end
-- We have to error and exit the coroutine anyway to avoid any segfault
error("crengine failed recognizing or parsing this file: unsupported or invalid document")
end
return ReaderUI return ReaderUI

@ -109,6 +109,11 @@ function CreDocument:init()
-- set fallback font face -- set fallback font face
self._document:setStringProperty("crengine.font.fallback.face", self.fallback_font) self._document:setStringProperty("crengine.font.fallback.face", self.fallback_font)
-- We would have liked to call self._document:loadDocument(self.file)
-- here, to detect early if file is a supported document, but we
-- need to delay it till after some crengine settings are set for a
-- consistent behaviour.
self.is_open = true self.is_open = true
self.info.has_pages = false self.info.has_pages = false
self:_readMetadata() self:_readMetadata()
@ -117,9 +122,11 @@ end
function CreDocument:loadDocument() function CreDocument:loadDocument()
if not self._loaded then if not self._loaded then
self._document:loadDocument(self.file) if self._document:loadDocument(self.file) then
self._loaded = true self._loaded = true
end
end end
return self._loaded
end end
function CreDocument:render() function CreDocument:render()
@ -160,7 +167,9 @@ end
function CreDocument:getCoverPageImage() function CreDocument:getCoverPageImage()
-- don't need to render document in order to get cover image -- don't need to render document in order to get cover image
self:loadDocument() if not self:loadDocument() then
return nil -- not recognized by crengine
end
local data, size = self._document:getCoverPageImageData() local data, size = self._document:getCoverPageImageData()
if data and size then if data and size then
local Mupdf = require("ffi/mupdf") local Mupdf = require("ffi/mupdf")

@ -363,78 +363,85 @@ function BookInfoManager:extractBookInfo(filepath, cover_specs)
-- Proceed with extracting info -- Proceed with extracting info
local document = DocumentRegistry:openDocument(filepath) local document = DocumentRegistry:openDocument(filepath)
local loaded = true
if document then if document then
local pages
if document.loadDocument then -- needed for crengine if document.loadDocument then -- needed for crengine
-- Setting a default font before loading document -- Setting a default font before loading document
-- actually do prevent some crashes -- actually do prevent some crashes
document:setFontFace(document.default_font) document:setFontFace(document.default_font)
document:loadDocument() if not document:loadDocument() then
-- Not needed for getting props: -- failed loading, calling other methods would segfault
-- document:render() loaded = false
-- It would be needed to get nb of pages, but the nb obtained end
-- by simply calling here document:getPageCount() is wrong, -- For CreDocument, we would need to call document:render()
-- often 2 to 3 times the nb of pages we see when opening -- to get nb of pages, but the nb obtained by simply calling
-- the document (may be some other cre settings should be applied -- here document:getPageCount() is wrong, often 2 to 3 times
-- before calling render() ?) -- the nb of pages we see when opening the document (may be
-- some other cre settings should be applied before calling
-- render() ?)
else else
-- for all others than crengine, we seem to get an accurate nb of pages -- for all others than crengine, we seem to get an accurate nb of pages
local pages = document:getPageCount() pages = document:getPageCount()
dbrow.pages = pages
end
local props = document:getProps()
if next(props) then -- there's at least one item
dbrow.has_meta = 'Y'
end end
if props.title and props.title ~= "" then dbrow.title = props.title end if loaded then
if props.authors and props.authors ~= "" then dbrow.authors = props.authors end dbrow.pages = pages
if props.series and props.series ~= "" then dbrow.series = props.series end local props = document:getProps()
if props.language and props.language ~= "" then dbrow.language = props.language end if next(props) then -- there's at least one item
if props.keywords and props.keywords ~= "" then dbrow.keywords = props.keywords end dbrow.has_meta = 'Y'
if props.description and props.description ~= "" then dbrow.description = props.description end end
if cover_specs then if props.title and props.title ~= "" then dbrow.title = props.title end
local spec_sizetag = cover_specs.sizetag if props.authors and props.authors ~= "" then dbrow.authors = props.authors end
local spec_max_cover_w = cover_specs.max_cover_w if props.series and props.series ~= "" then dbrow.series = props.series end
local spec_max_cover_h = cover_specs.max_cover_h if props.language and props.language ~= "" then dbrow.language = props.language end
if props.keywords and props.keywords ~= "" then dbrow.keywords = props.keywords end
dbrow.cover_fetched = 'Y' -- we had a try at getting a cover if props.description and props.description ~= "" then dbrow.description = props.description end
-- XXX make picdocument return a blitbuffer of the image if cover_specs then
local cover_bb = document:getCoverPageImage() local spec_sizetag = cover_specs.sizetag
if cover_bb then local spec_max_cover_w = cover_specs.max_cover_w
dbrow.has_cover = 'Y' local spec_max_cover_h = cover_specs.max_cover_h
dbrow.cover_sizetag = spec_sizetag
-- we should scale down the cover to our max size dbrow.cover_fetched = 'Y' -- we had a try at getting a cover
local cbb_w, cbb_h = cover_bb:getWidth(), cover_bb:getHeight() -- XXX make picdocument return a blitbuffer of the image
local scale_factor = 1 local cover_bb = document:getCoverPageImage()
if cbb_w > spec_max_cover_w or cbb_h > spec_max_cover_h then if cover_bb then
-- scale down if bigger than what we will display dbrow.has_cover = 'Y'
scale_factor = math.min(spec_max_cover_w / cbb_w, spec_max_cover_h / cbb_h) dbrow.cover_sizetag = spec_sizetag
cbb_w = math.min(math.floor(cbb_w * scale_factor)+1, spec_max_cover_w) -- we should scale down the cover to our max size
cbb_h = math.min(math.floor(cbb_h * scale_factor)+1, spec_max_cover_h) local cbb_w, cbb_h = cover_bb:getWidth(), cover_bb:getHeight()
local new_bb local scale_factor = 1
if self.use_legacy_image_scaling then if cbb_w > spec_max_cover_w or cbb_h > spec_max_cover_h then
new_bb = cover_bb:scale(cbb_w, cbb_h) -- scale down if bigger than what we will display
else scale_factor = math.min(spec_max_cover_w / cbb_w, spec_max_cover_h / cbb_h)
new_bb = Mupdf.scaleBlitBuffer(cover_bb, cbb_w, cbb_h) cbb_w = math.min(math.floor(cbb_w * scale_factor)+1, spec_max_cover_w)
cbb_h = math.min(math.floor(cbb_h * scale_factor)+1, spec_max_cover_h)
local new_bb
if self.use_legacy_image_scaling then
new_bb = cover_bb:scale(cbb_w, cbb_h)
else
new_bb = Mupdf.scaleBlitBuffer(cover_bb, cbb_w, cbb_h)
end
cover_bb:free()
cover_bb = new_bb
end end
cover_bb:free() dbrow.cover_w = cbb_w
cover_bb = new_bb dbrow.cover_h = cbb_h
dbrow.cover_btype = cover_bb:getType()
dbrow.cover_bpitch = cover_bb.pitch
local cover_data = Blitbuffer.tostring(cover_bb)
cover_bb:free() -- free bb before compressing to save memory
dbrow.cover_datalen = cover_data:len()
local cover_dataz = xutil.zlib_compress(cover_data)
-- release memory used by uncompressed data:
cover_data = nil -- luacheck: no unused
dbrow.cover_dataz = SQ3.blob(cover_dataz) -- cast to blob for sqlite
logger.dbg("cover for", filename, "scaled by", scale_factor, "=>", cbb_w, "x", cbb_h, "(compressed from ", dbrow.cover_datalen, " to ", cover_dataz:len())
end end
dbrow.cover_w = cbb_w
dbrow.cover_h = cbb_h
dbrow.cover_btype = cover_bb:getType()
dbrow.cover_bpitch = cover_bb.pitch
local cover_data = Blitbuffer.tostring(cover_bb)
cover_bb:free() -- free bb before compressing to save memory
dbrow.cover_datalen = cover_data:len()
local cover_dataz = xutil.zlib_compress(cover_data)
-- release memory used by uncompressed data:
cover_data = nil -- luacheck: no unused
dbrow.cover_dataz = SQ3.blob(cover_dataz) -- cast to blob for sqlite
logger.dbg("cover for", filename, "scaled by", scale_factor, "=>", cbb_w, "x", cbb_h, "(compressed from ", dbrow.cover_datalen, " to ", cover_dataz:len())
end end
end end
DocumentRegistry:closeDocument(filepath) DocumentRegistry:closeDocument(filepath)
else end
if not loaded then
dbrow.unsupported = _("not readable by engine") dbrow.unsupported = _("not readable by engine")
dbrow.cover_fetched = 'Y' -- so we don't try again if we're called later if cover_specs dbrow.cover_fetched = 'Y' -- so we don't try again if we're called later if cover_specs
end end

Loading…
Cancel
Save