mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-02 10:55:30 -04:00
Add RAR/CBR support
This commit is contained in:
parent
8665616c2e
commit
6b43ee7fe5
@ -4,6 +4,10 @@ shards:
|
|||||||
github: crystal-ameba/ameba
|
github: crystal-ameba/ameba
|
||||||
version: 0.12.0
|
version: 0.12.0
|
||||||
|
|
||||||
|
archive:
|
||||||
|
github: hkalexling/archive.cr
|
||||||
|
commit: 64223b64d79afafcec67a127b92f21f6625ce5ce
|
||||||
|
|
||||||
baked_file_system:
|
baked_file_system:
|
||||||
github: schovi/baked_file_system
|
github: schovi/baked_file_system
|
||||||
version: 0.9.8
|
version: 0.9.8
|
||||||
|
@ -19,6 +19,8 @@ dependencies:
|
|||||||
github: crystal-lang/crystal-sqlite3
|
github: crystal-lang/crystal-sqlite3
|
||||||
baked_file_system:
|
baked_file_system:
|
||||||
github: schovi/baked_file_system
|
github: schovi/baked_file_system
|
||||||
|
archive:
|
||||||
|
github: hkalexling/archive.cr
|
||||||
|
|
||||||
development_dependencies:
|
development_dependencies:
|
||||||
ameba:
|
ameba:
|
||||||
|
53
src/archive.cr
Normal file
53
src/archive.cr
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
require "zip"
|
||||||
|
require "archive"
|
||||||
|
|
||||||
|
# A unified class to handle all supported archive formats. It uses the ::Zip
|
||||||
|
# module in crystal standard library if the target file is a zip archive.
|
||||||
|
# Otherwise it uses `archive.cr`.
|
||||||
|
class ArchiveFile
|
||||||
|
def initialize(@filename : String)
|
||||||
|
if [".cbz", ".zip"].includes? File.extname filename
|
||||||
|
@archive_file = Zip::File.new filename
|
||||||
|
else
|
||||||
|
@archive_file = Archive::File.new filename
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.open(filename : String, &)
|
||||||
|
s = self.new filename
|
||||||
|
yield s
|
||||||
|
s.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
if @archive_file.is_a? Zip::File
|
||||||
|
@archive_file.as(Zip::File).close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Lists all file entries
|
||||||
|
def entries
|
||||||
|
ary = [] of Zip::File::Entry | Archive::Entry
|
||||||
|
@archive_file.entries.map do |e|
|
||||||
|
if (e.is_a? Zip::File::Entry && e.file?) ||
|
||||||
|
(e.is_a? Archive::Entry && e.info.file?)
|
||||||
|
ary.push e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ary
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_entry(e : Zip::File::Entry | Archive::Entry) : Bytes?
|
||||||
|
if e.is_a? Zip::File::Entry
|
||||||
|
data = nil
|
||||||
|
e.open do |io|
|
||||||
|
slice = Bytes.new e.uncompressed_size
|
||||||
|
bytes_read = io.read_fully? slice
|
||||||
|
data = slice if bytes_read
|
||||||
|
end
|
||||||
|
data
|
||||||
|
else
|
||||||
|
e.read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,8 +1,8 @@
|
|||||||
require "zip"
|
|
||||||
require "mime"
|
require "mime"
|
||||||
require "json"
|
require "json"
|
||||||
require "uri"
|
require "uri"
|
||||||
require "./util"
|
require "./util"
|
||||||
|
require "./archive"
|
||||||
|
|
||||||
struct Image
|
struct Image
|
||||||
property data : Bytes
|
property data : Bytes
|
||||||
@ -25,7 +25,7 @@ class Entry
|
|||||||
@title = File.basename path, File.extname path
|
@title = File.basename path, File.extname path
|
||||||
@encoded_title = URI.encode @title
|
@encoded_title = URI.encode @title
|
||||||
@size = (File.size path).humanize_bytes
|
@size = (File.size path).humanize_bytes
|
||||||
file = Zip::File.new path
|
file = ArchiveFile.new path
|
||||||
@pages = file.entries.count do |e|
|
@pages = file.entries.count do |e|
|
||||||
["image/jpeg", "image/png"].includes? \
|
["image/jpeg", "image/png"].includes? \
|
||||||
MIME.from_filename? e.filename
|
MIME.from_filename? e.filename
|
||||||
@ -68,7 +68,8 @@ class Entry
|
|||||||
end
|
end
|
||||||
|
|
||||||
def read_page(page_num)
|
def read_page(page_num)
|
||||||
Zip::File.open @zip_path do |file|
|
img = nil
|
||||||
|
ArchiveFile.open @zip_path do |file|
|
||||||
page = file.entries
|
page = file.entries
|
||||||
.select { |e|
|
.select { |e|
|
||||||
["image/jpeg", "image/png"].includes? \
|
["image/jpeg", "image/png"].includes? \
|
||||||
@ -78,16 +79,13 @@ class Entry
|
|||||||
compare_alphanumerically a.filename, b.filename
|
compare_alphanumerically a.filename, b.filename
|
||||||
}
|
}
|
||||||
.[page_num - 1]
|
.[page_num - 1]
|
||||||
page.open do |io|
|
data = file.read_entry page
|
||||||
slice = Bytes.new page.uncompressed_size
|
if data
|
||||||
bytes_read = io.read_fully? slice
|
img = Image.new data, MIME.from_filename(page.filename), page.filename,
|
||||||
unless bytes_read
|
data.size
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return Image.new slice, MIME.from_filename(page.filename),
|
|
||||||
page.filename, bytes_read
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
img
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -115,12 +113,12 @@ class Title
|
|||||||
@title_ids << title.id
|
@title_ids << title.id
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
if [".zip", ".cbz"].includes? File.extname path
|
if [".zip", ".cbz", ".rar", ".cbr"].includes? File.extname path
|
||||||
zip_exception = validate_zip path
|
archive_exception = validate_archive path
|
||||||
unless zip_exception.nil?
|
unless archive_exception.nil?
|
||||||
Logger.warn "File #{path} is corrupted or is not a valid zip " \
|
Logger.warn "File #{path} is corrupted or is not a valid archive. " \
|
||||||
"archive. Ignoring it."
|
"Ignoring it."
|
||||||
Logger.debug "Zip error: #{zip_exception}"
|
Logger.debug "Archive error: #{archive_exception}"
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
entry = Entry.new path, self, @id, storage
|
entry = Entry.new path, self, @id, storage
|
||||||
|
@ -371,7 +371,7 @@ module MangaDex
|
|||||||
writer.close
|
writer.close
|
||||||
Logger.debug "cbz File created at #{zip_path}"
|
Logger.debug "cbz File created at #{zip_path}"
|
||||||
|
|
||||||
zip_exception = validate_zip zip_path
|
zip_exception = validate_archive zip_path
|
||||||
if !zip_exception.nil?
|
if !zip_exception.nil?
|
||||||
@queue.add_message "The downloaded archive is corrupted. " \
|
@queue.add_message "The downloaded archive is corrupted. " \
|
||||||
"Error: #{zip_exception}", job
|
"Error: #{zip_exception}", job
|
||||||
|
@ -85,12 +85,8 @@ def compare_alphanumerically(a : String, b : String)
|
|||||||
compare_alphanumerically split_by_alphanumeric(a), split_by_alphanumeric(b)
|
compare_alphanumerically split_by_alphanumeric(a), split_by_alphanumeric(b)
|
||||||
end
|
end
|
||||||
|
|
||||||
# When downloading from MangaDex, the zip/cbz file would not be valid
|
def validate_archive(path : String) : Exception?
|
||||||
# before the download is completed. If we scan the zip file,
|
file = ArchiveFile.new path
|
||||||
# Entry.new would throw, so we use this method to check before
|
|
||||||
# constructing Entry
|
|
||||||
def validate_zip(path : String) : Exception?
|
|
||||||
file = Zip::File.new path
|
|
||||||
file.close
|
file.close
|
||||||
return
|
return
|
||||||
rescue e
|
rescue e
|
||||||
|
Loading…
x
Reference in New Issue
Block a user