require "zip" require "mime" require "json" class Image property data : Bytes property mime : String property filename : String property size : Int32 def initialize(@data, @mime, @filename, @size) end end class Entry property zip_path : String property book_title : String property title : String property size : String property pages : Int32 property cover_url : String JSON.mapping zip_path: String, book_title: String, title: String, \ size: String, pages: Int32, cover_url: String def initialize(path, @book_title) @zip_path = path @title = File.basename path, ".zip" @size = (File.size path).humanize_bytes @pages = Zip::File.new(path).entries .select { |e| ["image/jpeg", "image/png"].includes? \ MIME.from_filename? e.filename } .size @cover_url = "/api/page/#{@book_title}/#{title}/0" end def read_page(page_num) Zip::File.open @zip_path do |file| page = file.entries .select { |e| ["image/jpeg", "image/png"].includes? \ MIME.from_filename? e.filename } .sort { |a, b| a.filename <=> b.filename } .[page_num] page.open do |io| slice = Bytes.new page.uncompressed_size bytes_read = io.read_fully? slice unless bytes_read return nil end return Image.new slice, MIME.from_filename(page.filename),\ page.filename, bytes_read end end end end class Title JSON.mapping dir: String, entries: Array(Entry), title: String def initialize(dir : String) @dir = dir @title = File.basename dir @entries = (Dir.entries dir) .select { |path| (File.extname path) == ".zip" } .map { |path| Entry.new File.join(dir, path), @title } .select { |e| e.pages > 0 } .sort { |a, b| a.title <=> b.title } end def get_entry(name) @entries.find { |e| e.title == name } end def save_progress(username, entry, page) info = TitleInfo.new @dir if info.progress[username]?.nil? info.progress[username] = {entry => page} info.save @dir return end info.progress[username][entry] = page info.save @dir end def load_progress(username, entry : String) info = TitleInfo.new @dir if info.progress[username]?.nil? return 0 end if info.progress[username][entry]?.nil? return 0 end info.progress[username][entry] end def load_percetage(username, entry : String) info = TitleInfo.new @dir page = load_progress username, entry entry_obj = get_entry entry return 0 if entry_obj.nil? page / entry_obj.pages end def load_percetage(username) read_pages = 0 total_pages = 0 @entries.each do |e| read_pages += load_progress username, e.title total_pages += e.pages end read_pages / total_pages end end class TitleInfo # { user1: { entry1: 10, entry2: 0 } } include JSON::Serializable @[JSON::Field(key: "comment")] property comment = "Generated by Mango. DO NOT EDIT!" @[JSON::Field(key: "progress")] property progress : Hash(String, Hash(String, Int32)) def initialize(title_dir) info = nil json_path = File.join title_dir, "info.json" if File.exists? json_path info = TitleInfo.from_json File.read json_path else info = TitleInfo.from_json "{\"progress\": {}}" end @progress = info.progress.clone end def save(title_dir) json_path = File.join title_dir, "info.json" File.write json_path, self.to_pretty_json end end class Library JSON.mapping dir: String, titles: Array(Title) def initialize(dir : String) @dir = dir unless Dir.exists? dir Dir.mkdir_p dir end @titles = (Dir.entries dir) .select { |path| File.directory? File.join dir, path } .map { |path| Title.new File.join dir, path } .select { |title| !title.entries.empty? } end def get_title(name) @titles.find { |t| t.title == name } end end