mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 11:25:29 -04:00
Merge branch 'nested' into v0.2.1
This commit is contained in:
commit
e902e1dff0
152
src/library.cr
152
src/library.cr
@ -15,10 +15,10 @@ struct Image
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Entry
|
class Entry
|
||||||
JSON.mapping zip_path: String, book_title: String, title: String,
|
property zip_path : String, book_title : String, title : String,
|
||||||
size: String, pages: Int32, cover_url: String, id: String,
|
size : String, pages : Int32, cover_url : String, id : String,
|
||||||
title_id: String, encoded_path: String, encoded_title: String,
|
title_id : String, encoded_path : String, encoded_title : String,
|
||||||
mtime: Time
|
mtime : Time
|
||||||
|
|
||||||
def initialize(path, @book_title, @title_id, storage)
|
def initialize(path, @book_title, @title_id, storage)
|
||||||
@zip_path = path
|
@zip_path = path
|
||||||
@ -38,6 +38,19 @@ class Entry
|
|||||||
@cover_url = "/api/page/#{@title_id}/#{@id}/1"
|
@cover_url = "/api/page/#{@title_id}/#{@id}/1"
|
||||||
@mtime = File.info(@zip_path).modification_time
|
@mtime = File.info(@zip_path).modification_time
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_json(json : JSON::Builder)
|
||||||
|
json.object do
|
||||||
|
{% for str in ["zip_path", "book_title", "title", "size",
|
||||||
|
"cover_url", "id", "title_id", "encoded_path",
|
||||||
|
"encoded_title"] %}
|
||||||
|
json.field {{str}}, @{{str.id}}
|
||||||
|
{% end %}
|
||||||
|
json.field "pages" {json.number @pages}
|
||||||
|
json.field "mtime" {json.number @mtime.to_unix}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def read_page(page_num)
|
def read_page(page_num)
|
||||||
Zip::File.open @zip_path do |file|
|
Zip::File.open @zip_path do |file|
|
||||||
page = file.entries
|
page = file.entries
|
||||||
@ -63,27 +76,91 @@ class Entry
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Title
|
class Title
|
||||||
JSON.mapping dir: String, entries: Array(Entry), title: String,
|
property dir : String, parent_id : String, title_ids : Array(String),
|
||||||
id: String, encoded_title: String, mtime: Time, logger: MLogger
|
entries : Array(Entry), title : String, id : String,
|
||||||
|
encoded_title : String, mtime : Time
|
||||||
|
|
||||||
def initialize(dir : String, storage, @logger : MLogger)
|
def initialize(dir : String, @parent_id, storage,
|
||||||
|
@logger : MLogger, @library : Library)
|
||||||
@dir = dir
|
@dir = dir
|
||||||
@id = storage.get_id @dir, true
|
@id = storage.get_id @dir, true
|
||||||
@title = File.basename dir
|
@title = File.basename dir
|
||||||
@encoded_title = URI.encode @title
|
@encoded_title = URI.encode @title
|
||||||
@entries = (Dir.entries dir)
|
@title_ids = [] of String
|
||||||
.select { |path| [".zip", ".cbz"].includes? File.extname path }
|
@entries = [] of Entry
|
||||||
.map { |path| File.join dir, path }
|
|
||||||
.select { |path| valid_zip path }
|
Dir.entries(dir).each do |fn|
|
||||||
.map { |path|
|
next if fn.starts_with? "."
|
||||||
Entry.new path, @title, @id, storage
|
path = File.join dir, fn
|
||||||
}
|
if File.directory? path
|
||||||
.select { |e| e.pages > 0 }
|
title = Title.new path, @id, storage, @logger, library
|
||||||
.sort { |a, b| a.title <=> b.title }
|
next if title.entries.size == 0 && title.titles.size == 0
|
||||||
|
@library.title_hash[title.id] = title
|
||||||
|
@title_ids << title.id
|
||||||
|
next
|
||||||
|
end
|
||||||
|
if [".zip", ".cbz"].includes? File.extname path
|
||||||
|
next if !valid_zip path
|
||||||
|
entry = Entry.new path, @title, @id, storage
|
||||||
|
@entries << entry if entry.pages > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@title_ids.sort! { |a,b|
|
||||||
|
@library.title_hash[a].title <=> @library.title_hash[b].title
|
||||||
|
}
|
||||||
|
@entries.sort! { |a,b| a.title <=> b.title }
|
||||||
|
|
||||||
mtimes = [File.info(dir).modification_time]
|
mtimes = [File.info(dir).modification_time]
|
||||||
|
mtimes += @title_ids.map{|e| @library.title_hash[e].mtime}
|
||||||
mtimes += @entries.map{|e| e.mtime}
|
mtimes += @entries.map{|e| e.mtime}
|
||||||
@mtime = mtimes.max
|
@mtime = mtimes.max
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_json(json : JSON::Builder)
|
||||||
|
json.object do
|
||||||
|
{% for str in ["dir", "title", "id", "encoded_title"] %}
|
||||||
|
json.field {{str}}, @{{str.id}}
|
||||||
|
{% end %}
|
||||||
|
json.field "mtime" {json.number @mtime.to_unix}
|
||||||
|
json.field "titles" do
|
||||||
|
json.raw self.titles.to_json
|
||||||
|
end
|
||||||
|
json.field "entries" do
|
||||||
|
json.raw @entries.to_json
|
||||||
|
end
|
||||||
|
json.field "parents" do
|
||||||
|
json.array do
|
||||||
|
self.parents.each do |title|
|
||||||
|
json.object do
|
||||||
|
json.field "title", title.title
|
||||||
|
json.field "id", title.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def titles
|
||||||
|
@title_ids.map {|tid| @library.get_title! tid}
|
||||||
|
end
|
||||||
|
|
||||||
|
def parents
|
||||||
|
ary = [] of Title
|
||||||
|
tid = @parent_id
|
||||||
|
while !tid.empty?
|
||||||
|
title = @library.get_title! tid
|
||||||
|
ary << title
|
||||||
|
tid = title.parent_id
|
||||||
|
end
|
||||||
|
ary
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@entries.size + @title_ids.size
|
||||||
|
end
|
||||||
|
|
||||||
# When downloading from MangaDex, the zip/cbz file would not be valid
|
# When downloading from MangaDex, the zip/cbz file would not be valid
|
||||||
# before the download is completed. If we scan the zip file,
|
# before the download is completed. If we scan the zip file,
|
||||||
# Entry.new would throw, so we use this method to check before
|
# Entry.new would throw, so we use this method to check before
|
||||||
@ -150,10 +227,7 @@ class TitleInfo
|
|||||||
# { user1: { entry1: 10, entry2: 0 } }
|
# { user1: { entry1: 10, entry2: 0 } }
|
||||||
include JSON::Serializable
|
include JSON::Serializable
|
||||||
|
|
||||||
@[JSON::Field(key: "comment")]
|
|
||||||
property comment = "Generated by Mango. DO NOT EDIT!"
|
property comment = "Generated by Mango. DO NOT EDIT!"
|
||||||
|
|
||||||
@[JSON::Field(key: "progress")]
|
|
||||||
property progress : Hash(String, Hash(String, Int32))
|
property progress : Hash(String, Hash(String, Int32))
|
||||||
|
|
||||||
def initialize(title_dir)
|
def initialize(title_dir)
|
||||||
@ -175,13 +249,14 @@ class TitleInfo
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Library
|
class Library
|
||||||
JSON.mapping dir: String, titles: Array(Title), scan_interval: Int32,
|
property dir : String, title_ids : Array(String), scan_interval : Int32,
|
||||||
logger: MLogger, storage: Storage
|
logger : MLogger, storage : Storage, title_hash : Hash(String, Title)
|
||||||
|
|
||||||
def initialize(@dir, @scan_interval, @logger, @storage)
|
def initialize(@dir, @scan_interval, @logger, @storage)
|
||||||
# explicitly initialize @titles to bypass the compiler check. it will
|
# explicitly initialize @titles to bypass the compiler check. it will
|
||||||
# be filled with actual Titles in the `scan` call below
|
# be filled with actual Titles in the `scan` call below
|
||||||
@titles = [] of Title
|
@title_ids = [] of String
|
||||||
|
@title_hash = {} of String => Title
|
||||||
|
|
||||||
return scan if @scan_interval < 1
|
return scan if @scan_interval < 1
|
||||||
spawn do
|
spawn do
|
||||||
@ -189,13 +264,27 @@ class Library
|
|||||||
start = Time.local
|
start = Time.local
|
||||||
scan
|
scan
|
||||||
ms = (Time.local - start).total_milliseconds
|
ms = (Time.local - start).total_milliseconds
|
||||||
@logger.info "Scanned #{@titles.size} titles in #{ms}ms"
|
@logger.info "Scanned #{@title_ids.size} titles in #{ms}ms"
|
||||||
sleep @scan_interval * 60
|
sleep @scan_interval * 60
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
def titles
|
||||||
|
@title_ids.map {|tid| self.get_title!(tid) }
|
||||||
|
end
|
||||||
|
def to_json(json : JSON::Builder)
|
||||||
|
json.object do
|
||||||
|
json.field "dir", @dir
|
||||||
|
json.field "titles" do
|
||||||
|
json.raw self.titles.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
def get_title(tid)
|
def get_title(tid)
|
||||||
@titles.find { |t| t.id == tid }
|
@title_hash[tid]?
|
||||||
|
end
|
||||||
|
def get_title!(tid)
|
||||||
|
@title_hash[tid]
|
||||||
end
|
end
|
||||||
def scan
|
def scan
|
||||||
unless Dir.exists? @dir
|
unless Dir.exists? @dir
|
||||||
@ -203,11 +292,18 @@ class Library
|
|||||||
"Attempting to create it"
|
"Attempting to create it"
|
||||||
Dir.mkdir_p @dir
|
Dir.mkdir_p @dir
|
||||||
end
|
end
|
||||||
@titles = (Dir.entries @dir)
|
@title_ids.clear
|
||||||
.select { |path| File.directory? File.join @dir, path }
|
(Dir.entries @dir)
|
||||||
.map { |path| Title.new File.join(@dir, path), @storage, @logger }
|
.select { |fn| !fn.starts_with? "." }
|
||||||
.select { |title| !title.entries.empty? }
|
.map { |fn| File.join @dir, fn }
|
||||||
|
.select { |path| File.directory? path }
|
||||||
|
.map { |path| Title.new path, "", @storage, @logger, self }
|
||||||
|
.select { |title| !(title.entries.empty? && title.titles.empty?) }
|
||||||
.sort { |a, b| a.title <=> b.title }
|
.sort { |a, b| a.title <=> b.title }
|
||||||
|
.each do |title|
|
||||||
|
@title_hash[title.id] = title
|
||||||
|
@title_ids << title.id
|
||||||
|
end
|
||||||
@logger.debug "Scan completed"
|
@logger.debug "Scan completed"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -26,7 +26,7 @@ class APIRouter < Router
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/book/:title" do |env|
|
get "/api/book/:tid" do |env|
|
||||||
begin
|
begin
|
||||||
tid = env.params.url["tid"]
|
tid = env.params.url["tid"]
|
||||||
title = @context.library.get_title tid
|
title = @context.library.get_title tid
|
||||||
|
@ -47,6 +47,8 @@ class MainRouter < Router
|
|||||||
username = get_username env
|
username = get_username env
|
||||||
percentage = title.entries.map { |e|
|
percentage = title.entries.map { |e|
|
||||||
title.load_percetage username, e.title }
|
title.load_percetage username, e.title }
|
||||||
|
titles_percentage = title.titles.map { |t|
|
||||||
|
title.load_percetage username, t.title }
|
||||||
layout "title"
|
layout "title"
|
||||||
rescue e
|
rescue e
|
||||||
@context.error e
|
@context.error e
|
||||||
|
@ -26,12 +26,18 @@
|
|||||||
<a class="acard" href="/book/<%= t.id %>">
|
<a class="acard" href="/book/<%= t.id %>">
|
||||||
<div class="uk-card uk-card-default">
|
<div class="uk-card uk-card-default">
|
||||||
<div class="uk-card-media-top">
|
<div class="uk-card-media-top">
|
||||||
|
<%- if t.entries.size > 0 -%>
|
||||||
<img src="<%= t.entries[0].cover_url %>" alt="">
|
<img src="<%= t.entries[0].cover_url %>" alt="">
|
||||||
|
<%- else -%>
|
||||||
|
<img src="/img/icon.png" alt="">
|
||||||
|
<%- end -%>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-card-body">
|
<div class="uk-card-body">
|
||||||
|
<%- if t.entries.size > 0 -%>
|
||||||
<div class="uk-card-badge uk-label"><%= (percentage[i] * 100).round(1) %>%</div>
|
<div class="uk-card-badge uk-label"><%= (percentage[i] * 100).round(1) %>%</div>
|
||||||
|
<%- end -%>
|
||||||
<h3 class="uk-card-title"><%= t.title %></h3>
|
<h3 class="uk-card-title"><%= t.title %></h3>
|
||||||
<p><%= t.entries.size %> entries</p>
|
<p><%= t.size %> entries</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
<h2 class=uk-title><%= title.title %></h2>
|
<h2 class=uk-title><%= title.title %></h2>
|
||||||
<p class="uk-text-meta"><%= title.entries.size %> entries found</p>
|
<ul class="uk-breadcrumb">
|
||||||
|
<li><a href="/">Library</a></li>
|
||||||
|
<%- title.parents.each do |t| -%>
|
||||||
|
<li><a href="/book/<%= t.id %>"><%= t.title %></a></li>
|
||||||
|
<%- end -%>
|
||||||
|
<li class="uk-disabled"><a><%= title.title %></a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="uk-text-meta"><%= title.size %> entries found</p>
|
||||||
<div class="uk-grid-small" uk-grid>
|
<div class="uk-grid-small" uk-grid>
|
||||||
<div class="uk-margin-bottom uk-width-3-4@s">
|
<div class="uk-margin-bottom uk-width-3-4@s">
|
||||||
<form class="uk-search uk-search-default">
|
<form class="uk-search uk-search-default">
|
||||||
@ -23,6 +30,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
||||||
|
<%- title.titles.each_with_index do |t, i| -%>
|
||||||
|
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="<%= titles_percentage[i] %>">
|
||||||
|
<a class="acard" href="/book/<%= t.id %>">
|
||||||
|
<div class="uk-card uk-card-default">
|
||||||
|
<div class="uk-card-media-top">
|
||||||
|
<%- if t.entries.size > 0 -%>
|
||||||
|
<img src="<%= t.entries[0].cover_url %>" alt="">
|
||||||
|
<%- else -%>
|
||||||
|
<img src="/img/icon.png" alt="">
|
||||||
|
<%- end -%>
|
||||||
|
</div>
|
||||||
|
<div class="uk-card-body">
|
||||||
|
<%- if t.entries.size > 0 -%>
|
||||||
|
<div class="uk-card-badge uk-label"><%= (titles_percentage[i] * 100).round(1) %>%</div>
|
||||||
|
<%- end -%>
|
||||||
|
<h3 class="uk-card-title"><%= t.title %></h3>
|
||||||
|
<p><%= t.size %> entries</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<%- end -%>
|
||||||
<%- title.entries.each_with_index do |e, i| -%>
|
<%- title.entries.each_with_index do |e, i| -%>
|
||||||
<div class="item" data-mtime="<%= e.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
<div class="item" data-mtime="<%= e.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
||||||
<a class="acard">
|
<a class="acard">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user