diff --git a/src/library/entry.cr b/src/library/entry.cr index 9fb1ef9..b43dd77 100644 --- a/src/library/entry.cr +++ b/src/library/entry.cr @@ -337,3 +337,145 @@ class ZippedEntry < Entry File.exists? @zip_path end end + +class DirectoryEntry < Entry + include YAML::Serializable + + getter dir_path : String + + @[YAML::Field(ignore: true)] + @sorted_files : Array(String)? + + @signature : String + + def initialize(@dir_path, @book) + storage = Storage.default + @encoded_path = URI.encode @dir_path + @title = File.basename @dir_path + @encoded_title = URI.encode @title + + unless File.readable? @dir_path + @err_msg = "Directory #{@dir_path} is not readable." + Logger.warn "#{@err_msg} Please make sure the " \ + "file permission is configured correctly." + return + end + + unless DirectoryEntry.validate_directory_entry @dir_path + @err_msg = "Directory #{@dir_path} is not valid directory entry." + Logger.warn "#{@err_msg} Please make sure the " \ + "directory has valid images." + return + end + + size_sum = 0 + sorted_files.each do |file_path| + size_sum += File.size file_path + end + @size = size_sum.humanize_bytes + + @signature = Dir.directory_entry_signature @dir_path + id = storage.get_entry_id @dir_path, @signature + if id.nil? + id = random_str + storage.insert_entry_id({ + path: @dir_path, + id: id, + signature: @signature, + }) + end + @id = id + + mtimes = sorted_files.map { |file_path| File.info(file_path).modification_time } + @mtime = mtimes.max + + @pages = sorted_files.size + end + + def path : String + @dir_path + end + + def createtime : Time + ctime @dir_path + end + + def read_page(page_num) + img = nil + begin + files = sorted_files + file_path = files[page_num - 1] + data = File.read(file_path).to_slice + if data + img = Image.new data, MIME.from_filename(file_path), + File.basename(file_path), data.size + end + rescue e + Logger.warn "Unable to read page #{page_num} of #{@dir_path}. Error: #{e}" + end + img + end + + def page_dimensions + sizes = [] of Hash(String, Int32) + sorted_files.each_with_index do |path, i| + data = File.read(path).to_slice + begin + data.not_nil! + size = ImageSize.get data + sizes << { + "width" => size.width, + "height" => size.height, + } + rescue e + Logger.warn "Failed to read page #{i} of entry #{@dir_path}. #{e}" + sizes << {"width" => 1000_i32, "height" => 1000_i32} + end + end + sizes + end + + def exists? : Bool + existence = File.exists? @dir_path + return false unless existence + files = DirectoryEntry.get_valid_files @dir_path + signature = Dir.directory_entry_signature @dir_path + existence = files.size > 0 && @signature == signature + @sorted_files = nil unless existence + + # For more efficient, + # Fix a directory instance with new property + # and return true + existence + end + + def sorted_files + cached_sorted_files = @sorted_files + return cached_sorted_files if cached_sorted_files + @sorted_files = DirectoryEntry.get_valid_files_sorted @dir_path + @sorted_files.not_nil! + end + + def self.validate_directory_entry(dir_path) + files = DirectoryEntry.get_valid_files dir_path + files.size > 0 + end + + def self.get_valid_files(dir_path) + files = [] of String + Dir.entries(dir_path).each do |fn| + next if fn.starts_with? "." + path = File.join dir_path, fn + next unless is_supported_image_file path + next if File.directory? path + next unless File.readable? path + files << path + end + files + end + + def self.get_valid_files_sorted(dir_path) + files = DirectoryEntry.get_valid_files dir_path + files.sort! { |a, b| compare_numerically a, b } + end +end diff --git a/src/util/signature.cr b/src/util/signature.cr index 5ca3e14..63d31ce 100644 --- a/src/util/signature.cr +++ b/src/util/signature.cr @@ -19,7 +19,7 @@ class File # information as long as the above changes do not happen together with # a file/folder rename, with no library scan in between. def self.signature(filename) : UInt64 - if is_supported_file filename + if is_supported_file(filename) || is_supported_image_file(filename) File.info(filename).inode else 0u64 @@ -64,6 +64,9 @@ class Dir path = File.join dirname, fn if File.directory? path signatures << Dir.contents_signature path, cache + if DirectoryEntry.validate_directory_entry path + signatures << Dir.directory_entry_signature path, cache + end else # Only add its signature value to `signatures` when it is a # supported file @@ -76,4 +79,19 @@ class Dir cache[dirname] = hash hash end + + def self.directory_entry_signature(dirname, cache = {} of String => String) + return cache[dirname + "?entry"] if cache[dirname + "?entry"]? + Fiber.yield + signatures = [] of String + image_files = DirectoryEntry.get_valid_files_sorted dirname + if image_files.size > 0 + image_files.each do |path| + signatures << File.signature(path).to_s + end + end + hash = Digest::SHA1.hexdigest(signatures.join) + cache[dirname + "?entry"] = hash + hash + end end