mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 11:25:29 -04:00
Split Entry and ZippedEntry, Fix to work anyway
make Entry an abstract class
This commit is contained in:
parent
883e01bbdd
commit
ea6cbbd9ce
@ -1,62 +1,27 @@
|
|||||||
require "image_size"
|
require "image_size"
|
||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
class Entry
|
abstract class Entry
|
||||||
include YAML::Serializable
|
getter id : String, book : Title, title : String,
|
||||||
|
size : String, pages : Int32, mtime : Time,
|
||||||
|
encoded_path : String, encoded_title : String, err_msg : String?
|
||||||
|
|
||||||
getter zip_path : String, book : Title, title : String,
|
def initialize(
|
||||||
size : String, pages : Int32, id : String, encoded_path : String,
|
@id, @title, @book,
|
||||||
encoded_title : String, mtime : Time, err_msg : String?
|
@size, @pages, @mtime,
|
||||||
|
@encoded_path, @encoded_title, @err_msg)
|
||||||
@[YAML::Field(ignore: true)]
|
|
||||||
@sort_title : String?
|
|
||||||
|
|
||||||
def initialize(@zip_path, @book)
|
|
||||||
storage = Storage.default
|
|
||||||
@encoded_path = URI.encode @zip_path
|
|
||||||
@title = File.basename @zip_path, File.extname @zip_path
|
|
||||||
@encoded_title = URI.encode @title
|
|
||||||
@size = (File.size @zip_path).humanize_bytes
|
|
||||||
id = storage.get_entry_id @zip_path, File.signature(@zip_path)
|
|
||||||
if id.nil?
|
|
||||||
id = random_str
|
|
||||||
storage.insert_entry_id({
|
|
||||||
path: @zip_path,
|
|
||||||
id: id,
|
|
||||||
signature: File.signature(@zip_path).to_s,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
@id = id
|
|
||||||
@mtime = File.info(@zip_path).modification_time
|
|
||||||
|
|
||||||
unless File.readable? @zip_path
|
|
||||||
@err_msg = "File #{@zip_path} is not readable."
|
|
||||||
Logger.warn "#{@err_msg} Please make sure the " \
|
|
||||||
"file permission is configured correctly."
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
archive_exception = validate_archive @zip_path
|
def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
|
||||||
unless archive_exception.nil?
|
# TODO: check node? and select proper subclass
|
||||||
@err_msg = "Archive error: #{archive_exception}"
|
ZippedEntry.new ctx, node
|
||||||
Logger.warn "Unable to extract archive #{@zip_path}. " \
|
|
||||||
"Ignoring it. #{@err_msg}"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
file = ArchiveFile.new @zip_path
|
|
||||||
@pages = file.entries.count do |e|
|
|
||||||
SUPPORTED_IMG_TYPES.includes? \
|
|
||||||
MIME.from_filename? e.filename
|
|
||||||
end
|
|
||||||
file.close
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(*, slim = false)
|
def build_json(*, slim = false)
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
{% for str in %w(zip_path title size id) %}
|
{% for str in %w(path title size id) %}
|
||||||
json.field {{str}}, @{{str.id}}
|
json.field {{str}}, {{str.id}}
|
||||||
{% end %}
|
{% end %}
|
||||||
if err_msg
|
if err_msg
|
||||||
json.field "err_msg", err_msg
|
json.field "err_msg", err_msg
|
||||||
@ -74,6 +39,9 @@ class Entry
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@[YAML::Field(ignore: true)]
|
||||||
|
@sort_title : String?
|
||||||
|
|
||||||
def sort_title
|
def sort_title
|
||||||
sort_title_cached = @sort_title
|
sort_title_cached = @sort_title
|
||||||
return sort_title_cached if sort_title_cached
|
return sort_title_cached if sort_title_cached
|
||||||
@ -131,58 +99,6 @@ class Entry
|
|||||||
url
|
url
|
||||||
end
|
end
|
||||||
|
|
||||||
private def sorted_archive_entries
|
|
||||||
ArchiveFile.open @zip_path do |file|
|
|
||||||
entries = file.entries
|
|
||||||
.select { |e|
|
|
||||||
SUPPORTED_IMG_TYPES.includes? \
|
|
||||||
MIME.from_filename? e.filename
|
|
||||||
}
|
|
||||||
.sort! { |a, b|
|
|
||||||
compare_numerically a.filename, b.filename
|
|
||||||
}
|
|
||||||
yield file, entries
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_page(page_num)
|
|
||||||
raise "Unreadble archive. #{@err_msg}" if @err_msg
|
|
||||||
img = nil
|
|
||||||
begin
|
|
||||||
sorted_archive_entries do |file, entries|
|
|
||||||
page = entries[page_num - 1]
|
|
||||||
data = file.read_entry page
|
|
||||||
if data
|
|
||||||
img = Image.new data, MIME.from_filename(page.filename),
|
|
||||||
page.filename, data.size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue e
|
|
||||||
Logger.warn "Unable to read page #{page_num} of #{@zip_path}. Error: #{e}"
|
|
||||||
end
|
|
||||||
img
|
|
||||||
end
|
|
||||||
|
|
||||||
def page_dimensions
|
|
||||||
sizes = [] of Hash(String, Int32)
|
|
||||||
sorted_archive_entries do |file, entries|
|
|
||||||
entries.each_with_index do |e, i|
|
|
||||||
begin
|
|
||||||
data = file.read_entry(e).not_nil!
|
|
||||||
size = ImageSize.get data
|
|
||||||
sizes << {
|
|
||||||
"width" => size.width,
|
|
||||||
"height" => size.height,
|
|
||||||
}
|
|
||||||
rescue e
|
|
||||||
Logger.warn "Failed to read page #{i} of entry #{zip_path}. #{e}"
|
|
||||||
sizes << {"width" => 1000_i32, "height" => 1000_i32}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
sizes
|
|
||||||
end
|
|
||||||
|
|
||||||
def next_entry(username)
|
def next_entry(username)
|
||||||
entries = @book.sorted_entries username
|
entries = @book.sorted_entries username
|
||||||
idx = entries.index self
|
idx = entries.index self
|
||||||
@ -197,20 +113,6 @@ class Entry
|
|||||||
entries[idx - 1]
|
entries[idx - 1]
|
||||||
end
|
end
|
||||||
|
|
||||||
def date_added
|
|
||||||
date_added = nil
|
|
||||||
TitleInfo.new @book.dir do |info|
|
|
||||||
info_da = info.date_added[@title]?
|
|
||||||
if info_da.nil?
|
|
||||||
date_added = info.date_added[@title] = ctime @zip_path
|
|
||||||
info.save
|
|
||||||
else
|
|
||||||
date_added = info_da
|
|
||||||
end
|
|
||||||
end
|
|
||||||
date_added.not_nil! # is it ok to set not_nil! here?
|
|
||||||
end
|
|
||||||
|
|
||||||
# For backward backward compatibility with v0.1.0, we save entry titles
|
# For backward backward compatibility with v0.1.0, we save entry titles
|
||||||
# instead of IDs in info.json
|
# instead of IDs in info.json
|
||||||
def save_progress(username, page)
|
def save_progress(username, page)
|
||||||
@ -290,7 +192,7 @@ class Entry
|
|||||||
end
|
end
|
||||||
Storage.default.save_thumbnail @id, img
|
Storage.default.save_thumbnail @id, img
|
||||||
rescue e
|
rescue e
|
||||||
Logger.warn "Failed to generate thumbnail for file #{@zip_path}. #{e}"
|
Logger.warn "Failed to generate thumbnail for file #{path}. #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
img
|
img
|
||||||
@ -299,4 +201,139 @@ class Entry
|
|||||||
def get_thumbnail : Image?
|
def get_thumbnail : Image?
|
||||||
Storage.default.get_thumbnail @id
|
Storage.default.get_thumbnail @id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def date_added : Time
|
||||||
|
date_added = nil
|
||||||
|
TitleInfo.new @book.dir do |info|
|
||||||
|
info_da = info.date_added[@title]?
|
||||||
|
if info_da.nil?
|
||||||
|
date_added = info.date_added[@title] = createtime
|
||||||
|
info.save
|
||||||
|
else
|
||||||
|
date_added = info_da
|
||||||
|
end
|
||||||
|
end
|
||||||
|
date_added.not_nil! # is it ok to set not_nil! here?
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract def path : String
|
||||||
|
|
||||||
|
abstract def createtime : Time
|
||||||
|
|
||||||
|
abstract def read_page(page_num)
|
||||||
|
|
||||||
|
abstract def page_dimensions
|
||||||
|
|
||||||
|
abstract def exists? : Bool?
|
||||||
|
end
|
||||||
|
|
||||||
|
class ZippedEntry < Entry
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
getter zip_path : String
|
||||||
|
|
||||||
|
def initialize(@zip_path, @book)
|
||||||
|
storage = Storage.default
|
||||||
|
@encoded_path = URI.encode @zip_path
|
||||||
|
@title = File.basename @zip_path, File.extname @zip_path
|
||||||
|
@encoded_title = URI.encode @title
|
||||||
|
@size = (File.size @zip_path).humanize_bytes
|
||||||
|
id = storage.get_entry_id @zip_path, File.signature(@zip_path)
|
||||||
|
if id.nil?
|
||||||
|
id = random_str
|
||||||
|
storage.insert_entry_id({
|
||||||
|
path: @zip_path,
|
||||||
|
id: id,
|
||||||
|
signature: File.signature(@zip_path).to_s,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
@id = id
|
||||||
|
@mtime = File.info(@zip_path).modification_time
|
||||||
|
|
||||||
|
unless File.readable? @zip_path
|
||||||
|
@err_msg = "File #{@zip_path} is not readable."
|
||||||
|
Logger.warn "#{@err_msg} Please make sure the " \
|
||||||
|
"file permission is configured correctly."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
archive_exception = validate_archive @zip_path
|
||||||
|
unless archive_exception.nil?
|
||||||
|
@err_msg = "Archive error: #{archive_exception}"
|
||||||
|
Logger.warn "Unable to extract archive #{@zip_path}. " \
|
||||||
|
"Ignoring it. #{@err_msg}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
file = ArchiveFile.new @zip_path
|
||||||
|
@pages = file.entries.count do |e|
|
||||||
|
SUPPORTED_IMG_TYPES.includes? \
|
||||||
|
MIME.from_filename? e.filename
|
||||||
|
end
|
||||||
|
file.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def path : String
|
||||||
|
@zip_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def createtime : Time
|
||||||
|
ctime @zip_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private def sorted_archive_entries
|
||||||
|
ArchiveFile.open @zip_path do |file|
|
||||||
|
entries = file.entries
|
||||||
|
.select { |e|
|
||||||
|
SUPPORTED_IMG_TYPES.includes? \
|
||||||
|
MIME.from_filename? e.filename
|
||||||
|
}
|
||||||
|
.sort! { |a, b|
|
||||||
|
compare_numerically a.filename, b.filename
|
||||||
|
}
|
||||||
|
yield file, entries
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_page(page_num)
|
||||||
|
raise "Unreadble archive. #{@err_msg}" if @err_msg
|
||||||
|
img = nil
|
||||||
|
begin
|
||||||
|
sorted_archive_entries do |file, entries|
|
||||||
|
page = entries[page_num - 1]
|
||||||
|
data = file.read_entry page
|
||||||
|
if data
|
||||||
|
img = Image.new data, MIME.from_filename(page.filename),
|
||||||
|
page.filename, data.size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue e
|
||||||
|
Logger.warn "Unable to read page #{page_num} of #{@zip_path}. Error: #{e}"
|
||||||
|
end
|
||||||
|
img
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_dimensions
|
||||||
|
sizes = [] of Hash(String, Int32)
|
||||||
|
sorted_archive_entries do |file, entries|
|
||||||
|
entries.each_with_index do |e, i|
|
||||||
|
begin
|
||||||
|
data = file.read_entry(e).not_nil!
|
||||||
|
size = ImageSize.get data
|
||||||
|
sizes << {
|
||||||
|
"width" => size.width,
|
||||||
|
"height" => size.height,
|
||||||
|
}
|
||||||
|
rescue e
|
||||||
|
Logger.warn "Failed to read page #{i} of entry #{zip_path}. #{e}"
|
||||||
|
sizes << {"width" => 1000_i32, "height" => 1000_i32}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sizes
|
||||||
|
end
|
||||||
|
|
||||||
|
def exists? : Bool
|
||||||
|
File.exists? @zip_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -55,7 +55,7 @@ class Title
|
|||||||
next
|
next
|
||||||
end
|
end
|
||||||
if is_supported_file path
|
if is_supported_file path
|
||||||
entry = Entry.new path, self
|
entry = ZippedEntry.new path, self
|
||||||
@entries << entry if entry.pages > 0 || entry.err_msg
|
@entries << entry if entry.pages > 0 || entry.err_msg
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -127,12 +127,12 @@ class Title
|
|||||||
|
|
||||||
previous_entries_size = @entries.size
|
previous_entries_size = @entries.size
|
||||||
@entries.select! do |entry|
|
@entries.select! do |entry|
|
||||||
existence = File.exists? entry.zip_path
|
existence = entry.exists?
|
||||||
Fiber.yield
|
Fiber.yield
|
||||||
context["deleted_entry_ids"] << entry.id unless existence
|
context["deleted_entry_ids"] << entry.id unless existence
|
||||||
existence
|
existence
|
||||||
end
|
end
|
||||||
remained_entry_zip_paths = @entries.map &.zip_path
|
remained_entry_zip_paths = @entries.map &.path
|
||||||
|
|
||||||
is_titles_added = false
|
is_titles_added = false
|
||||||
is_entries_added = false
|
is_entries_added = false
|
||||||
@ -162,7 +162,7 @@ class Title
|
|||||||
end
|
end
|
||||||
if is_supported_file path
|
if is_supported_file path
|
||||||
next if remained_entry_zip_paths.includes? path
|
next if remained_entry_zip_paths.includes? path
|
||||||
entry = Entry.new path, self
|
entry = ZippedEntry.new path, self
|
||||||
if entry.pages > 0 || entry.err_msg
|
if entry.pages > 0 || entry.err_msg
|
||||||
@entries << entry
|
@entries << entry
|
||||||
is_entries_added = true
|
is_entries_added = true
|
||||||
@ -627,7 +627,7 @@ class Title
|
|||||||
|
|
||||||
@entries.each do |e|
|
@entries.each do |e|
|
||||||
next if da.has_key? e.title
|
next if da.has_key? e.title
|
||||||
da[e.title] = ctime e.zip_path
|
da[e.title] = ctime e.path
|
||||||
end
|
end
|
||||||
|
|
||||||
TitleInfo.new @dir do |info|
|
TitleInfo.new @dir do |info|
|
||||||
|
@ -40,7 +40,7 @@ struct APIRouter
|
|||||||
Koa.schema "entry", {
|
Koa.schema "entry", {
|
||||||
"pages" => Int32,
|
"pages" => Int32,
|
||||||
"mtime" => Int64,
|
"mtime" => Int64,
|
||||||
}.merge(s %w(zip_path title size id title_id display_name cover_url)),
|
}.merge(s %w(path title size id title_id display_name cover_url)),
|
||||||
desc: "An entry in a book"
|
desc: "An entry in a book"
|
||||||
|
|
||||||
Koa.schema "title", {
|
Koa.schema "title", {
|
||||||
@ -1138,7 +1138,7 @@ struct APIRouter
|
|||||||
entry = title.get_entry eid
|
entry = title.get_entry eid
|
||||||
raise "Entry ID `#{eid}` of `#{title.title}` not found" if entry.nil?
|
raise "Entry ID `#{eid}` of `#{title.title}` not found" if entry.nil?
|
||||||
|
|
||||||
file_hash = Digest::SHA1.hexdigest (entry.zip_path + entry.mtime.to_s)
|
file_hash = Digest::SHA1.hexdigest (entry.path + entry.mtime.to_s)
|
||||||
e_tag = "W/#{file_hash}"
|
e_tag = "W/#{file_hash}"
|
||||||
if e_tag == prev_e_tag
|
if e_tag == prev_e_tag
|
||||||
env.response.status_code = 304
|
env.response.status_code = 304
|
||||||
@ -1172,7 +1172,7 @@ struct APIRouter
|
|||||||
title = (Library.default.get_title env.params.url["tid"]).not_nil!
|
title = (Library.default.get_title env.params.url["tid"]).not_nil!
|
||||||
entry = (title.get_entry env.params.url["eid"]).not_nil!
|
entry = (title.get_entry env.params.url["eid"]).not_nil!
|
||||||
|
|
||||||
send_attachment env, entry.zip_path
|
send_attachment env, entry.path
|
||||||
rescue e
|
rescue e
|
||||||
Logger.error e
|
Logger.error e
|
||||||
env.response.status_code = 404
|
env.response.status_code = 404
|
||||||
|
@ -53,6 +53,7 @@ struct ReaderRouter
|
|||||||
render "src/views/reader.html.ecr"
|
render "src/views/reader.html.ecr"
|
||||||
rescue e
|
rescue e
|
||||||
Logger.error e
|
Logger.error e
|
||||||
|
puts e.backtrace?
|
||||||
env.response.status_code = 404
|
env.response.status_code = 404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
<link rel="http://opds-spec.org/image" href="<%= e.cover_url %>" />
|
<link rel="http://opds-spec.org/image" href="<%= e.cover_url %>" />
|
||||||
<link rel="http://opds-spec.org/image/thumbnail" href="<%= e.cover_url %>" />
|
<link rel="http://opds-spec.org/image/thumbnail" href="<%= e.cover_url %>" />
|
||||||
|
|
||||||
<link rel="http://opds-spec.org/acquisition" href="<%= base_url %>api/download/<%= e.book.id %>/<%= e.id %>" title="Read" type="<%= MIME.from_filename e.zip_path %>" />
|
<link rel="http://opds-spec.org/acquisition" href="<%= base_url %>api/download/<%= e.book.id %>/<%= e.id %>" title="Read" type="<%= MIME.from_filename e.path %>" />
|
||||||
|
|
||||||
<link type="text/html" rel="alternate" title="Read in Mango" href="<%= base_url %>reader/<%= e.book.id %>/<%= e.id %>" />
|
<link type="text/html" rel="alternate" title="Read in Mango" href="<%= base_url %>reader/<%= e.book.id %>/<%= e.id %>" />
|
||||||
<link type="text/html" rel="alternate" title="Open in Mango" href="<%= base_url %>book/<%= e.book.id %>" />
|
<link type="text/html" rel="alternate" title="Open in Mango" href="<%= base_url %>book/<%= e.book.id %>" />
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h3 class="uk-modal-title uk-margin-remove-top">Error</h3>
|
<h3 class="uk-modal-title uk-margin-remove-top">Error</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="uk-text-meta uk-margin-remove-bottom"><%= entry.zip_path %></p>
|
<p class="uk-text-meta uk-margin-remove-bottom"><%= entry.path %></p>
|
||||||
<p class="uk-text-meta uk-margin-remove-top"><%= entry.err_msg %></p>
|
<p class="uk-text-meta uk-margin-remove-top"><%= entry.err_msg %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-modal-body">
|
<div class="uk-modal-body">
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
<button class="uk-modal-close-default" type="button" uk-close></button>
|
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||||
<div class="uk-modal-header">
|
<div class="uk-modal-header">
|
||||||
<h3 class="uk-modal-title break-word"><%= entry.display_name %></h3>
|
<h3 class="uk-modal-title break-word"><%= entry.display_name %></h3>
|
||||||
<p class="uk-text-meta uk-margin-remove-bottom break-word"><%= entry.zip_path %></p>
|
<p class="uk-text-meta uk-margin-remove-bottom break-word"><%= entry.path %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-modal-body">
|
<div class="uk-modal-body">
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user