Skip to content
This repository was archived by the owner on Mar 23, 2025. It is now read-only.

Commit 2d97faa

Browse files
authored
Merge pull request #305 from Leeingnyo/feature/unzipped-entry
Support unzipped entry
2 parents 883e01b + 9ce8e91 commit 2d97faa

File tree

13 files changed

+413
-161
lines changed

13 files changed

+413
-161
lines changed

src/library/archive_entry.cr

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
require "yaml"
2+
3+
require "./entry"
4+
5+
class ArchiveEntry < Entry
6+
include YAML::Serializable
7+
8+
getter zip_path : String
9+
10+
def initialize(@zip_path, @book)
11+
storage = Storage.default
12+
@path = @zip_path
13+
@encoded_path = URI.encode @zip_path
14+
@title = File.basename @zip_path, File.extname @zip_path
15+
@encoded_title = URI.encode @title
16+
@size = (File.size @zip_path).humanize_bytes
17+
id = storage.get_entry_id @zip_path, File.signature(@zip_path)
18+
if id.nil?
19+
id = random_str
20+
storage.insert_entry_id({
21+
path: @zip_path,
22+
id: id,
23+
signature: File.signature(@zip_path).to_s,
24+
})
25+
end
26+
@id = id
27+
@mtime = File.info(@zip_path).modification_time
28+
29+
unless File.readable? @zip_path
30+
@err_msg = "File #{@zip_path} is not readable."
31+
Logger.warn "#{@err_msg} Please make sure the " \
32+
"file permission is configured correctly."
33+
return
34+
end
35+
36+
archive_exception = validate_archive @zip_path
37+
unless archive_exception.nil?
38+
@err_msg = "Archive error: #{archive_exception}"
39+
Logger.warn "Unable to extract archive #{@zip_path}. " \
40+
"Ignoring it. #{@err_msg}"
41+
return
42+
end
43+
44+
file = ArchiveFile.new @zip_path
45+
@pages = file.entries.count do |e|
46+
SUPPORTED_IMG_TYPES.includes? \
47+
MIME.from_filename? e.filename
48+
end
49+
file.close
50+
end
51+
52+
private def sorted_archive_entries
53+
ArchiveFile.open @zip_path do |file|
54+
entries = file.entries
55+
.select { |e|
56+
SUPPORTED_IMG_TYPES.includes? \
57+
MIME.from_filename? e.filename
58+
}
59+
.sort! { |a, b|
60+
compare_numerically a.filename, b.filename
61+
}
62+
yield file, entries
63+
end
64+
end
65+
66+
def read_page(page_num)
67+
raise "Unreadble archive. #{@err_msg}" if @err_msg
68+
img = nil
69+
begin
70+
sorted_archive_entries do |file, entries|
71+
page = entries[page_num - 1]
72+
data = file.read_entry page
73+
if data
74+
img = Image.new data, MIME.from_filename(page.filename),
75+
page.filename, data.size
76+
end
77+
end
78+
rescue e
79+
Logger.warn "Unable to read page #{page_num} of #{@zip_path}. Error: #{e}"
80+
end
81+
img
82+
end
83+
84+
def page_dimensions
85+
sizes = [] of Hash(String, Int32)
86+
sorted_archive_entries do |file, entries|
87+
entries.each_with_index do |e, i|
88+
begin
89+
data = file.read_entry(e).not_nil!
90+
size = ImageSize.get data
91+
sizes << {
92+
"width" => size.width,
93+
"height" => size.height,
94+
}
95+
rescue e
96+
Logger.warn "Failed to read page #{i} of entry #{zip_path}. #{e}"
97+
sizes << {"width" => 1000_i32, "height" => 1000_i32}
98+
end
99+
end
100+
end
101+
sizes
102+
end
103+
104+
def examine : Bool
105+
File.exists? @zip_path
106+
end
107+
108+
def self.is_valid?(path : String) : Bool
109+
is_supported_file path
110+
end
111+
end

src/library/cache.cr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ class SortedEntriesCacheEntry < CacheEntry(Array(String), Array(Entry))
7676
entries : Array(Entry), opt : SortOptions?)
7777
entries_sig = Digest::SHA1.hexdigest (entries.map &.id).to_s
7878
user_context = opt && opt.method == SortMethod::Progress ? username : ""
79-
sig = Digest::SHA1.hexdigest (book_id + entries_sig + user_context +
80-
(opt ? opt.to_tuple.to_s : "nil"))
79+
sig = Digest::SHA1.hexdigest(book_id + entries_sig + user_context +
80+
(opt ? opt.to_tuple.to_s : "nil"))
8181
"#{sig}:sorted_entries"
8282
end
8383
end
@@ -101,8 +101,8 @@ class SortedTitlesCacheEntry < CacheEntry(Array(String), Array(Title))
101101
def self.gen_key(username : String, titles : Array(Title), opt : SortOptions?)
102102
titles_sig = Digest::SHA1.hexdigest (titles.map &.id).to_s
103103
user_context = opt && opt.method == SortMethod::Progress ? username : ""
104-
sig = Digest::SHA1.hexdigest (titles_sig + user_context +
105-
(opt ? opt.to_tuple.to_s : "nil"))
104+
sig = Digest::SHA1.hexdigest(titles_sig + user_context +
105+
(opt ? opt.to_tuple.to_s : "nil"))
106106
"#{sig}:sorted_titles"
107107
end
108108
end

src/library/dir_entry.cr

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
require "yaml"
2+
3+
require "./entry"
4+
5+
class DirEntry < Entry
6+
include YAML::Serializable
7+
8+
getter dir_path : String
9+
10+
@[YAML::Field(ignore: true)]
11+
@sorted_files : Array(String)?
12+
13+
@signature : String
14+
15+
def initialize(@dir_path, @book)
16+
storage = Storage.default
17+
@path = @dir_path
18+
@encoded_path = URI.encode @dir_path
19+
@title = File.basename @dir_path
20+
@encoded_title = URI.encode @title
21+
22+
unless File.readable? @dir_path
23+
@err_msg = "Directory #{@dir_path} is not readable."
24+
Logger.warn "#{@err_msg} Please make sure the " \
25+
"file permission is configured correctly."
26+
return
27+
end
28+
29+
unless DirEntry.is_valid? @dir_path
30+
@err_msg = "Directory #{@dir_path} is not valid directory entry."
31+
Logger.warn "#{@err_msg} Please make sure the " \
32+
"directory has valid images."
33+
return
34+
end
35+
36+
size_sum = 0
37+
sorted_files.each do |file_path|
38+
size_sum += File.size file_path
39+
end
40+
@size = size_sum.humanize_bytes
41+
42+
@signature = Dir.directory_entry_signature @dir_path
43+
id = storage.get_entry_id @dir_path, @signature
44+
if id.nil?
45+
id = random_str
46+
storage.insert_entry_id({
47+
path: @dir_path,
48+
id: id,
49+
signature: @signature,
50+
})
51+
end
52+
@id = id
53+
54+
@mtime = sorted_files.map do |file_path|
55+
File.info(file_path).modification_time
56+
end.max
57+
@pages = sorted_files.size
58+
end
59+
60+
def read_page(page_num)
61+
img = nil
62+
begin
63+
files = sorted_files
64+
file_path = files[page_num - 1]
65+
data = File.read(file_path).to_slice
66+
if data
67+
img = Image.new data, MIME.from_filename(file_path),
68+
File.basename(file_path), data.size
69+
end
70+
rescue e
71+
Logger.warn "Unable to read page #{page_num} of #{@dir_path}. Error: #{e}"
72+
end
73+
img
74+
end
75+
76+
def page_dimensions
77+
sizes = [] of Hash(String, Int32)
78+
sorted_files.each_with_index do |path, i|
79+
data = File.read(path).to_slice
80+
begin
81+
data.not_nil!
82+
size = ImageSize.get data
83+
sizes << {
84+
"width" => size.width,
85+
"height" => size.height,
86+
}
87+
rescue e
88+
Logger.warn "Failed to read page #{i} of entry #{@dir_path}. #{e}"
89+
sizes << {"width" => 1000_i32, "height" => 1000_i32}
90+
end
91+
end
92+
sizes
93+
end
94+
95+
def examine : Bool
96+
existence = File.exists? @dir_path
97+
return false unless existence
98+
files = DirEntry.image_files @dir_path
99+
signature = Dir.directory_entry_signature @dir_path
100+
existence = files.size > 0 && @signature == signature
101+
@sorted_files = nil unless existence
102+
103+
# For more efficient, update a directory entry with new property
104+
# and return true like Title.examine
105+
existence
106+
end
107+
108+
def sorted_files
109+
cached_sorted_files = @sorted_files
110+
return cached_sorted_files if cached_sorted_files
111+
@sorted_files = DirEntry.sorted_image_files @dir_path
112+
@sorted_files.not_nil!
113+
end
114+
115+
def self.image_files(dir_path)
116+
Dir.entries(dir_path)
117+
.reject(&.starts_with? ".")
118+
.map { |fn| File.join dir_path, fn }
119+
.select { |fn| is_supported_image_file fn }
120+
.reject { |fn| File.directory? fn }
121+
.select { |fn| File.readable? fn }
122+
end
123+
124+
def self.sorted_image_files(dir_path)
125+
self.image_files(dir_path)
126+
.sort { |a, b| compare_numerically a, b }
127+
end
128+
129+
def self.is_valid?(path : String) : Bool
130+
image_files(path).size > 0
131+
end
132+
end

0 commit comments

Comments
 (0)