@ -19,12 +19,28 @@ local DOCSETTINGS_DIR = DataStorage:getDocSettingsDir()
local DOCSETTINGS_HASH_DIR = DataStorage : getDocSettingsHashDir ( )
local custom_metadata_filename = " custom_metadata.lua "
local is_hash_location_enabled
function DocSettings . getSidecarStorage ( location )
if location == " dir " then
return DOCSETTINGS_DIR
elseif location == " hash " then
return DOCSETTINGS_HASH_DIR
end
end
local function isDir ( dir )
return lfs.attributes ( dir , " mode " ) == " directory "
end
local function isFile ( file )
return lfs.attributes ( file , " mode " ) == " file "
end
local doc_hash_cache = { }
local is_hash_location_enabled
function DocSettings . isHashLocationEnabled ( )
if is_hash_location_enabled == nil then
is_hash_location_enabled = lfs.attributes ( DOCSETTINGS_HASH_DIR , " mode " ) == " directory "
is_hash_location_enabled = isDir( DOCSETTINGS_HASH_DIR )
end
return is_hash_location_enabled
end
@ -33,14 +49,13 @@ function DocSettings.setIsHashLocationEnabled(value)
is_hash_location_enabled = value
end
local function buildCandidates ( list )
local candidates = { }
local previous_entry_exists = false
for i , file_path in ipairs ( list ) do
-- Ignore missing files.
if file_path ~= " " and lfs.attributes( file_path , " mode " ) == " file " then
if file_path ~= " " and isFile( file_path ) then
local mtime = lfs.attributes ( file_path , " modification " )
-- NOTE: Extra trickery: if we're inserting a "backup" file, and its primary buddy exists,
-- make sure it will *never* sort ahead of it by using the same mtime.
@ -81,6 +96,18 @@ local function buildCandidates(list)
return candidates
end
local function getOrderedLocationCandidates ( )
local preferred_location = G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
if preferred_location == " hash " then
return { " hash " , " doc " , " dir " }
end
local candidates = preferred_location == " doc " and { " doc " , " dir " } or { " dir " , " doc " }
if DocSettings.isHashLocationEnabled ( ) then
table.insert ( candidates , " hash " )
end
return candidates
end
--- Returns path to sidecar directory (`filename.sdr`).
-- Sidecar directory is the file without _last_ suffix.
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
@ -111,50 +138,42 @@ function DocSettings:getSidecarDir(doc_path, force_location)
return path .. " .sdr "
end
--- Returns path to `metadata.lua` file.
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
-- @treturn string path to `/foo/bar.sdr/metadata.lua` file
function DocSettings : getSidecarFile ( doc_path , force_location )
if doc_path == nil or doc_path == " " then return " " end
-- If the file does not have a suffix or we are working on a directory, we
-- should ignore the suffix part in metadata file path.
local suffix = doc_path : match ( " .*%.(.+) " ) or " "
return self : getSidecarDir ( doc_path , force_location ) .. " /metadata. " .. suffix .. " .lua "
function DocSettings . getSidecarFilename ( doc_path )
local suffix = doc_path : match ( " .*%.(.+) " ) or " _ "
return " metadata. " .. suffix .. " .lua "
end
--- Returns `true` if there is a `metadata.lua` file.
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
-- @treturn bool
function DocSettings : hasSidecarFile ( doc_path )
return self : getDoc SidecarFile( doc_path ) and true or false
return self : find SidecarFile( doc_path ) and true or false
end
--- Returns path of `metadata.lua` file if it exists, or nil.
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
-- @bool no_legacy set to true to skip check of the legacy history file
-- @treturn string
function DocSettings : getDocSidecarFile ( doc_path , no_legacy )
local sidecar_file = self : getSidecarFile ( doc_path , " doc " )
if lfs.attributes ( sidecar_file , " mode " ) == " file " then
return sidecar_file
end
sidecar_file = self : getSidecarFile ( doc_path , " dir " )
if lfs.attributes ( sidecar_file , " mode " ) == " file " then
return sidecar_file
end
-- Calculate partial hash and check for hash-based files only if there are files to check
if DocSettings.isHashLocationEnabled ( ) then
sidecar_file = self : getSidecarFile ( doc_path , " hash " )
if lfs.attributes ( sidecar_file , " mode " ) == " file " then
return sidecar_file
function DocSettings : findSidecarFile ( doc_path , no_legacy )
local sidecar_filename = DocSettings.getSidecarFilename ( doc_path )
local sidecar_file
for _ , location in ipairs ( getOrderedLocationCandidates ( ) ) do
sidecar_file = self : getSidecarDir ( doc_path , location ) .. " / " .. sidecar_filename
if isFile ( sidecar_file ) then
return sidecar_file , location
end
end
if not no_legacy then
sidecar_file = self : getHistoryPath ( doc_path )
if lfs.attributes ( sidecar_file , " mode " ) == " file " then
return sidecar_file
if isFile ( sidecar_file ) then
return sidecar_file , " hist " -- for isSidecarFileNotInPreferredLocation() used in moveBookMetadata
end
end
end
function DocSettings . isSidecarFileNotInPreferredLocation ( doc_path )
local _ , location = DocSettings : findSidecarFile ( doc_path )
return location and location ~= G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
end
function DocSettings : getHistoryPath ( doc_path )
@ -193,20 +212,6 @@ function DocSettings:getFileFromHistory(hist_name)
end
end
--- Returns the directory and full filepath of a hash-ID-based sidecar metadata store
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
function DocSettings : getSidecarHashDirAndFilepath ( doc_path )
-- Getting PDF ID from trailer via mupdf has not been implemented - everything uses partial MD5
local path = self : getSidecarDir ( doc_path , " hash " )
local filetype = doc_path : match ( " .+%.(%w+)$ " )
if not filetype or filetype == " " then
return " " , " "
end
local hash_file = " metadata. " .. filetype .. " .lua "
local hash_filepath = path .. " / " .. hash_file
return path , hash_filepath
end
--- Opens a document's individual settings (font, margin, dictionary, etc.)
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
-- @treturn DocSettings object
@ -214,28 +219,25 @@ function DocSettings:open(doc_path)
-- NOTE: Beware, our new instance is new, but self is still DocSettings!
local new = DocSettings : extend { }
new.sidecar_filename = DocSettings.getSidecarFilename ( doc_path )
new.doc_sidecar_dir = new : getSidecarDir ( doc_path , " doc " )
new.doc_sidecar_file = new : getSidecarFile ( doc_path , " doc " )
local doc_sidecar_file , legacy_sidecar_file
if lfs.attributes( new.doc_sidecar_dir , " mode " ) == " directory " then
doc_sidecar_file = new.doc_sidecar_ file
if isDir( new.doc_sidecar_dir ) then
doc_sidecar_file = new.doc_sidecar_ dir .. " / " .. new.sidecar_ filenam e
legacy_sidecar_file = new.doc_sidecar_dir .. " / " .. ffiutil.basename ( doc_path ) .. " .lua "
end
new.dir_sidecar_dir = new : getSidecarDir ( doc_path , " dir " )
new.dir_sidecar_file = new : getSidecarFile ( doc_path , " dir " )
local dir_sidecar_file
if lfs.attributes( new.dir_sidecar_dir , " mode " ) == " directory " then
dir_sidecar_file = new.dir_sidecar_ file
if isDir( new.dir_sidecar_dir ) then
dir_sidecar_file = new.dir_sidecar_ dir .. " / " .. new.sidecar_ filenam e
end
local history_file = new : getHistoryPath ( doc_path )
local hash_sidecar_dir , hash_sidecar_file
local hash_sidecar_file
if DocSettings.isHashLocationEnabled ( ) then
hash_sidecar_dir , hash_sidecar_file =
new : getSidecarHashDirAndFilepath ( doc_path )
new.hash_sidecar_dir = hash_sidecar_dir
new.hash_sidecar_file = hash_sidecar_file
new.hash_sidecar_dir = new : getSidecarDir ( doc_path , " hash " )
hash_sidecar_file = new.hash_sidecar_dir .. " / " .. new.sidecar_filename
end
local history_file = new : getHistoryPath ( doc_path )
-- Candidates list, in order of priority:
local candidates_list = {
@ -249,10 +251,10 @@ function DocSettings:open(doc_path)
dir_sidecar_file or " " ,
-- Backup file of new sidecar file in docsettings folder
dir_sidecar_file and ( dir_sidecar_file .. " .old " ) or " " ,
-- Hash or PDF fingerprint-based sidecar file lookup
-- New sidecar file in hashdocsettings folder
hash_sidecar_file or " " ,
-- Backup file of hash or PDF fingerprint-based sidecar file lookup
hash_sidecar_file and ( new. hash_sidecar_file .. " .old " ) or " " ,
-- Backup file of new sidecar file in hashdocsettings folder
hash_sidecar_file and ( hash_sidecar_file .. " .old " ) or " " ,
-- Legacy history folder
history_file ,
-- Backup file in legacy history folder
@ -290,52 +292,69 @@ function DocSettings:open(doc_path)
return new
end
--- Light version of open(). Opens a sidecar file or a custom metadata file.
-- Returned object cannot be used to save changes to the sidecar file (flush()).
-- Must be used to save changes to the custom metadata file (flushCustomMetadata()).
function DocSettings . openSettingsFile ( sidecar_file )
local new = DocSettings : extend { }
local ok , stored
if sidecar_file then
ok , stored = pcall ( dofile , sidecar_file )
end
if ok and next ( stored ) ~= nil then
new.data = stored
else
new.data = { }
end
new.sidecar_file = sidecar_file
return new
end
--- Serializes settings and writes them to `metadata.lua`.
function DocSettings : flush ( data , no_custom_metadata )
-- Depending on the settings, doc_settings are saved to the book folder or
-- to koreader/docsettings folder. The latter is also a fallback for read-only book storage.
local serials
local preferred_metdata_storage = G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
if preferred_metdata_storage == " doc " then
serials = { { self.doc_sidecar_dir , self.doc_sidecar_file } ,
{ self.dir_sidecar_dir , self.dir_sidecar_file } , }
elseif preferred_metdata_storage == " dir " then
serials = { { self.dir_sidecar_dir , self.dir_sidecar_file } , }
elseif preferred_metdata_storage == " hash " then
if self.hash_sidecar_dir == nil or self.hash_sidecar_file == nil then
self.hash_sidecar_dir , self.hash_sidecar_file =
self : getSidecarHashDirAndFilepath ( self.data . doc_path )
end
serials = { { self.hash_sidecar_dir , self.hash_sidecar_file } }
end
local s_out = dump ( data or self.data , nil , true )
for _ , s in ipairs ( serials ) do
local sidecar_dir , sidecar_file = unpack ( s )
data = data or self.data
local sidecar_dirs
local preferred_location = G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
if preferred_location == " doc " then
sidecar_dirs = { self.doc_sidecar_dir , self.dir_sidecar_dir } -- fallback for read-only book storage
elseif preferred_location == " dir " then
sidecar_dirs = { self.dir_sidecar_dir }
elseif preferred_location == " hash " then
if self.hash_sidecar_dir == nil then
self.hash_sidecar_dir = self : getSidecarDir ( data.doc_path , " hash " )
end
sidecar_dirs = { self.hash_sidecar_dir }
end
local ser_data = dump ( data , nil , true )
for _ , sidecar_dir in ipairs ( sidecar_dirs ) do
local sidecar_dir_slash = sidecar_dir .. " / "
local sidecar_file = sidecar_dir_slash .. self.sidecar_filename
util.makePath ( sidecar_dir )
logger.dbg ( " DocSettings: Writing to " , sidecar_file )
local directory_updated = LuaSettings : backup ( sidecar_file )
if util.writeToFile ( s _out , sidecar_file , true , true , directory_updated ) then
local directory_updated = LuaSettings : backup ( sidecar_file ) -- "*.old"
if util.writeToFile ( s er_data , sidecar_file , true , true , directory_updated ) then
-- move custom cover file and custom metadata file to the metadata file location
if not no_custom_metadata then
local metadata_file , filepath , filename
-- custom cover
metadata_file = self : getC overFile( )
metadata_file = self : getC ustomC overFile( )
if metadata_file then
filepath , filename = util.splitFilePathName ( metadata_file )
if filepath ~= sidecar_dir .. " / " then
ffiutil.copyFile ( metadata_file , sidecar_dir .. " / " .. filename )
if filepath ~= sidecar_dir _slash then
ffiutil.copyFile ( metadata_file , sidecar_dir _slash .. filename )
os.remove ( metadata_file )
self : getC overFile( true ) -- reset cache
self : getC ustomC overFile( true ) -- reset cache
end
end
-- custom metadata
metadata_file = self : getCustomMetadataFile ( )
if metadata_file then
filepath , filename = util.splitFilePathName ( metadata_file )
if filepath ~= sidecar_dir .. " / " then
ffiutil.copyFile ( metadata_file , sidecar_dir .. " / " .. filename )
if filepath ~= sidecar_dir _slash then
ffiutil.copyFile ( metadata_file , sidecar_dir _slash .. filename )
os.remove ( metadata_file )
self : getCustomMetadataFile ( true ) -- reset cache
end
end
end
@ -351,10 +370,10 @@ end
function DocSettings : purge ( sidecar_to_keep , data_to_purge )
local custom_cover_file , custom_metadata_file
if sidecar_to_keep == nil then
custom_cover_file = self : getC overFile( )
custom_cover_file = self : getC ustomC overFile( )
custom_metadata_file = self : getCustomMetadataFile ( )
end
if data_to_purge == nil then
if data_to_purge == nil then -- purge all
data_to_purge = {
doc_settings = true ,
custom_cover_file = custom_cover_file ,
@ -366,7 +385,7 @@ function DocSettings:purge(sidecar_to_keep, data_to_purge)
if data_to_purge.doc_settings and self.candidates then
for _ , t in ipairs ( self.candidates ) do
local candidate_path = t.path
if lfs.attributes( candidate_path , " mode " ) == " file " then
if isFile( candidate_path ) then
if ( not sidecar_to_keep )
or ( candidate_path ~= sidecar_to_keep and candidate_path ~= sidecar_to_keep .. " .old " ) then
os.remove ( candidate_path )
@ -376,105 +395,111 @@ function DocSettings:purge(sidecar_to_keep, data_to_purge)
end
end
-- Remove custom
if data_to_purge.custom_cover_file then
os.remove ( data_to_purge.custom_cover_file )
self : getC overFile( true ) -- reset cache
self : getC ustomC overFile( true ) -- reset cache
end
if data_to_purge.custom_metadata_file then
os.remove ( data_to_purge.custom_metadata_file )
self : getCustomMetadataFile ( true ) -- reset cache
end
-- Remove empty sidecar dirs
if data_to_purge.doc_settings or data_to_purge.custom_cover_file or data_to_purge.custom_metadata_file then
-- remove sidecar dirs iff empty
if lfs.attributes ( self.doc_sidecar_dir , " mode " ) == " directory " then
os.remove ( self.doc_sidecar_dir ) -- keep parent folders
end
if lfs.attributes ( self.dir_sidecar_dir , " mode " ) == " directory " then
util.removePath ( self.dir_sidecar_dir ) -- remove empty parent folders
end
if self.hash_sidecar_dir and lfs.attributes ( self.hash_sidecar_dir , " mode " ) == " directory " then
util.removePath ( self.hash_sidecar_dir ) -- remove empty parent folders
for _ , dir in ipairs ( { self.doc_sidecar_dir , self.dir_sidecar_dir , self.hash_sidecar_dir } ) do
DocSettings.removeSidecarDir ( dir )
end
end
DocSettings.setIsHashLocationEnabled ( nil ) -- reset this in case last hash book is purged
end
--- Removes empty sidecar dir.
function DocSettings : removeSidecarDir ( doc_path , sidecar_dir )
if sidecar_dir == self : getSidecarDir ( doc_path , " doc " ) then
os.remove ( sidecar_dir )
--- Removes sidecar dir iff empty.
function DocSettings . removeSidecarDir ( dir )
if dir and isDir ( dir ) then
if dir : match ( " ^ " .. DOCSETTINGS_DIR ) or dir : match ( " ^ " .. DOCSETTINGS_HASH_DIR ) then
util.removePath ( dir ) -- remove empty parent folders
else
util.removePath ( sidecar_dir )
os.remove ( dir ) -- keep parent folders
end
end
end
--- Updates sdr location for file rename/copy/move/delete operations.
function DocSettings : updateLocation ( doc_path , new_doc_path , copy )
local doc_settings , new_sidecar_dir , cover_file
if G_reader_settings : readSetting ( " document_metadata_folder " ) == " hash " then
-- none of these operations (except delete) changes the hash -> no location change
if not new_doc_path then
doc_settings = DocSettings : open ( doc_path )
local cache_file_path = doc_settings : readSetting ( " cache_file_path " )
if cache_file_path then os.remove ( cache_file_path ) end
cover_file = doc_settings : getCoverFile ( )
doc_settings : purge ( )
end
else
-- update metadata
if DocSettings : hasSidecarFile ( doc_path ) then
doc_settings = DocSettings : open ( doc_path )
if new_doc_path then
local new_doc_settings = DocSettings : open ( new_doc_path )
-- save doc settings to the new location, no custom metadata yet
new_sidecar_dir = new_doc_settings : flush ( doc_settings.data , true )
else
local cache_file_path = doc_settings : readSetting ( " cache_file_path " )
if cache_file_path then
os.remove ( cache_file_path )
end
end
end
function DocSettings . updateLocation ( doc_path , new_doc_path , copy )
local has_sidecar_file = DocSettings : hasSidecarFile ( doc_path )
local custom_cover_file = DocSettings : findCustomCoverFile ( doc_path )
local custom_metadata_file = DocSettings : findCustomMetadataFile ( doc_path )
if not ( has_sidecar_file or custom_cover_file or custom_metadata_file ) then return end
local doc_settings = DocSettings : open ( doc_path )
local do_purge
-- update custom metadata
if not doc_settings then
doc_settings = DocSettings : open ( doc_path )
if new_doc_path then -- copy/rename/move
if G_reader_settings : readSetting ( " document_metadata_folder " ) ~= " hash " then -- keep hash location unchanged
local new_sidecar_dir
if has_sidecar_file then
local new_doc_settings = DocSettings : open ( new_doc_path )
doc_settings.data . doc_path = new_doc_path
new_sidecar_dir = new_doc_settings : flush ( doc_settings.data , true ) -- without custom
end
cover_file = doc_settings : getCoverFile ( )
if new_doc_path then
-- custom cover
if cover_file then
if not new_sidecar_dir then
new_sidecar_dir = DocSettings : getSidecarDir ( new_doc_path )
util.makePath ( new_sidecar_dir )
end
local _ , filename = util.splitFilePathName ( cover_file )
ffiutil.copyFile ( cover_file , new_sidecar_dir .. " / " .. filename )
if custom_cover_file then
local _ , filename = util.splitFilePathName ( custom_cover_file )
ffiutil.copyFile ( custom_cover_file , new_sidecar_dir .. " / " .. filename )
end
-- custom metadata
local metadata_file = self : getCustomMetadataFile ( doc_path )
if metadata_file then
if not new_sidecar_dir then
new_sidecar_dir = DocSettings : getSidecarDir ( new_doc_path )
util.makePath ( new_sidecar_dir )
if custom_metadata_file then
ffiutil.copyFile ( custom_metadata_file , new_sidecar_dir .. " / " .. custom_metadata_filename )
end
do_purge = not copy
end
else -- delete
if has_sidecar_file then
local cache_file_path = doc_settings : readSetting ( " cache_file_path " )
if cache_file_path then
os.remove ( cache_file_path )
end
ffiutil.copyFile ( metadata_file , new_sidecar_dir .. " / " .. custom_metadata_filename )
end
do_purge = true
end
if not copy then
if do_purge then
doc_settings.custom_cover_file = custom_cover_file -- cache
doc_settings.custom_metadata_file = custom_metadata_file -- cache
doc_settings : purge ( )
end
end
if cover_file then -- after purge because purge uses cover file cache
doc_settings : getCoverFile ( true ) -- reset cache
-- custom section
function DocSettings : getCustomLocationCandidates ( doc_path )
local sidecar_dir
local sidecar_file = self : findSidecarFile ( doc_path , true ) -- new locations only
if sidecar_file then -- book was opened, write custom metadata to its sidecar dir
sidecar_dir = util.splitFilePathName ( sidecar_file ) : sub ( 1 , - 2 )
return { sidecar_dir }
end
-- new book, create sidecar dir in accordance with sdr location setting
local preferred_location = G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
if preferred_location ~= " hash " then
sidecar_dir = self : getSidecarDir ( doc_path , " dir " )
if preferred_location == " doc " then
local doc_sidecar_dir = self : getSidecarDir ( doc_path , " doc " )
return { doc_sidecar_dir , sidecar_dir } -- fallback for read-only book storage
end
else -- "hash"
sidecar_dir = self : getSidecarDir ( doc_path , " hash " )
end
return { sidecar_dir }
end
-- custom cover
local function findCoverFileInDir ( dir )
local function findC ustomC overFileInDir( dir )
local ok , iter , dir_obj = pcall ( lfs.dir , dir )
if ok then
for f in iter , dir_obj do
@ -486,57 +511,30 @@ local function findCoverFileInDir(dir)
end
--- Returns path to book custom cover file if it exists, or nil.
function DocSettings : findC overFile( doc_path )
function DocSettings : findC ustomC overFile( doc_path )
doc_path = doc_path or self.data . doc_path
local location = G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
for _ , location in ipairs ( getOrderedLocationCandidates ( ) ) do
local sidecar_dir = self : getSidecarDir ( doc_path , location )
local cover_file = findCoverFileInDir ( sidecar_dir )
if cover_file then return cover_file end
local candidates = { " doc " , " dir " }
if DocSettings.isHashLocationEnabled ( ) then
table.insert ( candidates , " hash " )
end
for _ , mode in ipairs ( candidates ) do
if mode ~= location then
sidecar_dir = self : getSidecarDir ( doc_path , mode )
cover_file = findCoverFileInDir ( sidecar_dir )
if cover_file then return cover_file end
local custom_cover_file = findCustomCoverFileInDir ( sidecar_dir )
if custom_cover_file then
return custom_cover_file
end
end
end
function DocSettings : getC overFile( reset_cache )
function DocSettings : getCustomCoverFile ( reset_cache )
if reset_cache then
self.c over_file = nil
self.custom_cover_file = nil
else
if self.c over_file == nil then -- fill empty cache
self.c over_file = self : find CoverFile( ) or false
if self.c ustom_c over_file == nil then -- fill empty cache
self.c ustom_c over_file = self : find Custom CoverFile( ) or false
end
return self.c over_file
return self.c ustom_c over_file
end
end
function DocSettings : getCustomCandidateSidecarDirs ( doc_path )
local sidecar_file = self : getDocSidecarFile ( doc_path , true ) -- new locations only
if sidecar_file then -- book was opened, write custom metadata to its sidecar dir
local sidecar_dir = util.splitFilePathName ( sidecar_file ) : sub ( 1 , - 2 )
return { sidecar_dir }
end
-- new book, create sidecar dir in accordance with sdr location setting
local dir_sidecar_dir = self : getSidecarDir ( doc_path , " dir " )
local preferred_metadata_storage = G_reader_settings : readSetting ( " document_metadata_folder " , " doc " )
if preferred_metadata_storage == " doc " then
local doc_sidecar_dir = self : getSidecarDir ( doc_path , " doc " )
return { doc_sidecar_dir , dir_sidecar_dir } -- fallback in case of readonly book storage
elseif preferred_metadata_storage == " hash " then
local hash_sidecar_dir = self : getSidecarDir ( doc_path , " hash " )
return { hash_sidecar_dir }
end
return { dir_sidecar_dir }
end
function DocSettings : flushCustomCover ( doc_path , image_file )
local sidecar_dirs = self : getCustom CandidateSidecarDir s( doc_path )
local sidecar_dirs = self : getCustomLocationCandidates ( doc_path )
local new_cover_filename = " /cover. " .. util.getFileNameSuffix ( image_file ) : lower ( )
for _ , sidecar_dir in ipairs ( sidecar_dirs ) do
util.makePath ( sidecar_dir )
@ -550,130 +548,57 @@ end
-- custom metadata
--- Returns path to book custom metadata file if it exists, or nil.
function DocSettings : get CustomMetadataFile( doc_path )
function DocSettings : find CustomMetadataFile( doc_path )
doc_path = doc_path or self.data . doc_path
local candidates = { " doc " , " dir " }
if DocSettings.isHashLocationEnabled ( ) then
table.insert ( candidates , " hash " )
end
for _ , mode in ipairs ( candidates ) do
local file = self : getSidecarDir ( doc_path , mode ) .. " / " .. custom_metadata_filename
if lfs.attributes ( file , " mode " ) == " file " then
return file
for _ , location in ipairs ( getOrderedLocationCandidates ( ) ) do
local sidecar_dir = self : getSidecarDir ( doc_path , location )
local custom_metadata_file = sidecar_dir .. " / " .. custom_metadata_filename
if isFile ( custom_metadata_file ) then
return custom_metadata_file
end
end
end
function DocSettings : openCustomMetadata ( custom_metadata_file )
local new = DocSettings : extend { }
local ok , stored
if custom_metadata_file then
ok , stored = pcall ( dofile , custom_metadata_file )
end
if ok and next ( stored ) ~= nil then
new.data = stored
function DocSettings : getCustomMetadataFile ( reset_cache )
if reset_cache then
self.custom_metadata_file = nil
else
new.data = { }
if self.custom_metadata_file == nil then -- fill empty cache
self.custom_metadata_file = self : findCustomMetadataFile ( ) or false
end
return self.custom_metadata_file
end
new.custom_metadata_file = custom_metadata_file
return new
end
function DocSettings : flushCustomMetadata ( doc_path )
local sidecar_dirs = self : getCustomCandidateSidecarDirs ( doc_path )
local new_sidecar_dir
local sidecar_dirs = self : getCustomLocationCandidates ( doc_path )
local s_out = dump ( self.data , nil , true )
for _ , sidecar_dir in ipairs ( sidecar_dirs ) do
util.makePath ( sidecar_dir )
if util.writeToFile ( s_out , sidecar_dir .. " / " .. custom_metadata_filename , true , true ) then
new_sidecar_dir = sidecar_dir .. " / "
break
end
end
-- remove old custom metadata file if it was in alternative location
if self.custom_metadata_file then
local old_sidecar_dir = util.splitFilePathName ( self.custom_metadata_file )
if old_sidecar_dir ~= new_sidecar_dir then
os.remove ( self.custom_metadata_file )
self : removeSidecarDir ( doc_path , old_sidecar_dir )
local new_metadata_file = sidecar_dir .. " / " .. custom_metadata_filename
if util.writeToFile ( s_out , new_metadata_file , true , true ) then
return true
end
end
end
-- hash-based SDR storage
local function getSdrsInDir ( path )
-- Get all the metadata.filetype.lua files under directory path.
-- Derived from readerdictionary.getIfosInDir()
local sdrs = { }
local ok , iter , dir_obj = pcall ( lfs.dir , path )
if ok then
for name in iter , dir_obj do
if name ~= " . " and name ~= " .. " then
local fullpath = path .. " / " .. name
local attributes = lfs.attributes ( fullpath )
if attributes ~= nil then
if attributes.mode == " directory " then
local dirifos = getSdrsInDir ( fullpath ) -- recurse
for _ , ifo in pairs ( dirifos ) do
table.insert ( sdrs , ifo )
end
elseif name : match ( " metadata%..+%.lua$ " ) then
table.insert ( sdrs , fullpath )
end
end
end
end
end
return sdrs
end
-- "hash" section
function DocSettings . getHashDirSdrInfos ( )
local sdrs = getSdrsInDir ( DOCSETTINGS_HASH_DIR )
local title_author_strs = { }
for _ , sdr in ipairs ( sdrs ) do
-- Ignore empty files
if lfs.attributes ( sdr , " size " ) > 0 then
local ok , stored
ok , stored = pcall ( dofile , sdr )
-- Ignore empty tables
if ok and next ( stored ) ~= nil then
local info_str , custom_authors
local sdr_path = sdr : sub ( 1 , sdr : match ( " .*/() " ) - 1 ) -- SDR path
local custom_metadata_file = sdr_path .. custom_metadata_filename
if custom_metadata_file then
local custom = DocSettings : openCustomMetadata ( custom_metadata_file )
local custom_props = custom : readSetting ( " custom_props " )
if custom_props then
if custom_props.title then info_str = custom_props.title end
if custom_props.authors then custom_authors = custom_props.authors end
-- Returns the list of pairs {sidecar_file, custom_metadata_file}.
function DocSettings . findSidecarFilesInHashLocation ( )
local res = { }
local callback = function ( fullpath , name )
if name : match ( " metadata%..+%.lua$ " ) then
local sdr = { fullpath }
local custom_metadata_file = fullpath : gsub ( name , custom_metadata_filename )
if isFile ( custom_metadata_file ) then
table.insert ( sdr , custom_metadata_file )
end
end
if not info_str then info_str = stored.doc_props . title end
if not info_str then info_str = " untitled document " end
if custom_authors then
info_str = info_str .. " , author: " .. custom_authors
elseif stored.doc_props . authors then
info_str = info_str .. " , author: " .. stored.doc_props . authors
end
if stored.stats then
if stored.stats . highlights > 0 then
info_str = info_str .. " , highlights: " .. stored.stats . highlights
end
if stored.stats . notes > 0 then
info_str = info_str .. " , notes: " .. stored.stats . notes
end
end
info_str = info_str .. " , path: " .. sdr : sub ( sdr : find ( " / " , 3 ) + 1 )
table.insert ( title_author_strs , info_str )
else
table.insert ( title_author_strs , " error " .. sdr )
end
else
table.insert ( title_author_strs , " zero-size file " .. sdr )
table.insert ( res , sdr )
end
end
return title_author_strs
util.findFiles ( DOCSETTINGS_HASH_DIR , callback )
return res
end
return DocSettings