diff --git a/src/library/cache.cr b/src/library/cache.cr new file mode 100644 index 0000000..00c65ba --- /dev/null +++ b/src/library/cache.cr @@ -0,0 +1,159 @@ +require "digest" + +class InfoCache + alias ProgressCache = Tuple(String, Int32) + + def self.clear + clear_cover_url + clear_progress_cache + clear_sort_opt + end + + def self.clean + clean_cover_url + clean_progress_cache + clean_sort_opt + end + + # item id => cover_url + @@cached_cover_url = {} of String => String + @@cached_cover_url_previous = {} of String => String # item id => cover_url + + def self.set_cover_url(id : String, cover_url : String) + @@cached_cover_url[id] = cover_url + end + + def self.get_cover_url(id : String) + @@cached_cover_url[id]? + end + + def self.invalidate_cover_url(id : String) + @@cached_cover_url.delete id + end + + def self.move_cover_url(id : String) + if @@cached_cover_url_previous[id]? + @@cached_cover_url[id] = @@cached_cover_url_previous[id] + end + end + + private def self.clear_cover_url + @@cached_cover_url_previous = @@cached_cover_url + @@cached_cover_url = {} of String => String + end + + private def self.clean_cover_url + @@cached_cover_url_previous = {} of String => String + end + + # book.id:username => {signature, sum} + @@progress_cache = {} of String => ProgressCache + # book.id => username => {signature, sum} + @@progress_cache_previous = {} of String => Hash(String, ProgressCache) + + def self.set_progress_cache(book_id : String, username : String, + entries : Array(Entry), sum : Int32) + progress_cache_id = "#{book_id}:#{username}" + progress_cache_sig = Digest::SHA1.hexdigest (entries.map &.id).to_s + @@progress_cache[progress_cache_id] = {progress_cache_sig, sum} + Logger.debug "Progress Cached #{progress_cache_id}" + end + + def self.get_progress_cache(book_id : String, username : String, + entries : Array(Entry)) + progress_cache_id = "#{book_id}:#{username}" + progress_cache_sig = Digest::SHA1.hexdigest (entries.map &.id).to_s + cached = @@progress_cache[progress_cache_id]? + if cached && cached[0] == progress_cache_sig + Logger.debug "Progress Cache Hit! #{progress_cache_id}" + return cached[1] + end + end + + def self.invalidate_progress_cache(book_id : String, username : String) + progress_cache_id = "#{book_id}:#{username}" + if @@progress_cache[progress_cache_id]? + @@progress_cache.delete progress_cache_id + Logger.debug "Progress Invalidate Cache #{progress_cache_id}" + end + end + + def self.move_progress_cache(book_id : String) + if @@progress_cache_previous[book_id]? + @@progress_cache_previous[book_id].each do |username, cached| + id = "#{book_id}:#{username}" + unless @@progress_cache[id]? + # It would be invalidated when entries changed + @@progress_cache[id] = cached + end + end + end + end + + private def self.clear_progress_cache + @@progress_cache_previous = {} of String => Hash(String, ProgressCache) + @@progress_cache.each do |id, cached| + splitted = id.split(':', 2) + book_id = splitted[0] + username = splitted[1] + unless @@progress_cache_previous[book_id]? + @@progress_cache_previous[book_id] = {} of String => ProgressCache + end + + @@progress_cache_previous[book_id][username] = cached + end + @@progress_cache = {} of String => ProgressCache + end + + private def self.clean_progress_cache + @@progress_cache_previous = {} of String => Hash(String, ProgressCache) + end + + # book.dir:username => SortOptions + @@cached_sort_opt = {} of String => SortOptions + @@cached_sort_opt_previous = {} of String => Hash(String, SortOptions) + + def self.set_sort_opt(dir : String, username : String, sort_opt : SortOptions) + id = "#{dir}:#{username}" + @@cached_sort_opt[id] = sort_opt + end + + def self.get_sort_opt(dir : String, username : String) + id = "#{dir}:#{username}" + @@cached_sort_opt[id]? + end + + def self.invalidate_sort_opt(dir : String, username : String) + id = "#{dir}:#{username}" + @@cached_sort_opt.delete id + end + + def self.move_sort_opt(dir : String) + if @@cached_sort_opt_previous[dir]? + @@cached_sort_opt_previous[dir].each do |username, cached| + id = "#{dir}:#{username}" + unless @@cached_sort_opt[id]? + @@cached_sort_opt[id] = cached + end + end + end + end + + private def self.clear_sort_opt + @@cached_sort_opt_previous = {} of String => Hash(String, SortOptions) + @@cached_sort_opt.each do |id, cached| + splitted = id.split(':', 2) + book_dir = splitted[0] + username = splitted[1] + unless @@cached_sort_opt_previous[book_dir]? + @@cached_sort_opt_previous[book_dir] = {} of String => SortOptions + end + @@cached_sort_opt_previous[book_dir][username] = cached + end + @@cached_sort_opt = {} of String => SortOptions + end + + private def self.clean_sort_opt + @@cached_sort_opt_previous = {} of String => Hash(String, SortOptions) + end +end diff --git a/src/library/entry.cr b/src/library/entry.cr index b5e582f..cbebf7f 100644 --- a/src/library/entry.cr +++ b/src/library/entry.cr @@ -44,6 +44,8 @@ class Entry MIME.from_filename? e.filename end file.close + + InfoCache.move_cover_url @id end def to_slim_json : String @@ -81,6 +83,8 @@ class Entry def cover_url return "#{Config.current.base_url}img/icon.png" if @err_msg + cached_cover_url = InfoCache.get_cover_url @id + return cached_cover_url if cached_cover_url unless @book.entry_cover_url_cache TitleInfo.new @book.dir do |info| @@ -96,6 +100,7 @@ class Entry url = File.join Config.current.base_url, info_url end end + InfoCache.set_cover_url @id, url url end @@ -178,6 +183,11 @@ class Entry # For backward backward compatibility with v0.1.0, we save entry titles # instead of IDs in info.json def save_progress(username, page) + InfoCache.invalidate_progress_cache @book.id, username + @book.parents.each do |parent| + InfoCache.invalidate_progress_cache parent.id, username + end + TitleInfo.new @book.dir do |info| if info.progress[username]?.nil? info.progress[username] = {@title => page} diff --git a/src/library/library.cr b/src/library/library.cr index 30e93b2..2638ffd 100644 --- a/src/library/library.cr +++ b/src/library/library.cr @@ -102,6 +102,8 @@ class Library storage = Storage.new auto_close: false + InfoCache.clear + (Dir.entries @dir) .select { |fn| !fn.starts_with? "." } .map { |fn| File.join @dir, fn } @@ -115,6 +117,8 @@ class Library @title_ids << title.id end + InfoCache.clean + storage.bulk_insert_ids storage.close diff --git a/src/library/title.cr b/src/library/title.cr index aa4a479..14b5754 100644 --- a/src/library/title.cr +++ b/src/library/title.cr @@ -60,6 +60,10 @@ class Title @entries.sort! do |a, b| sorter.compare a.title, b.title end + + InfoCache.move_cover_url @id + InfoCache.move_progress_cache @id + InfoCache.move_sort_opt @dir end def to_slim_json : String @@ -230,6 +234,9 @@ class Title end def cover_url + cached_cover_url = InfoCache.get_cover_url @id + return cached_cover_url if cached_cover_url + url = "#{Config.current.base_url}img/icon.png" readable_entries = @entries.select &.err_msg.nil? if readable_entries.size > 0 @@ -241,10 +248,12 @@ class Title url = File.join Config.current.base_url, info_url end end + InfoCache.set_cover_url @id, url url end def set_cover_url(url : String) + InfoCache.invalidate_cover_url @id TitleInfo.new @dir do |info| info.cover_url = url info.save @@ -252,6 +261,8 @@ class Title end def set_cover_url(entry_name : String, url : String) + selected_entry = @entries.find { |entry| entry.display_name == entry_name } + InfoCache.invalidate_cover_url selected_entry.id if selected_entry TitleInfo.new @dir do |info| info.entry_cover_url[entry_name] = url info.save @@ -273,8 +284,13 @@ class Title end def deep_read_page_count(username) : Int32 - load_progress_for_all_entries(username).sum + - titles.flat_map(&.deep_read_page_count username).sum + # CACHE HERE + cached_sum = InfoCache.get_progress_cache @id, username, @entries + return cached_sum unless cached_sum.nil? + sum = load_progress_for_all_entries(username).sum + + titles.flat_map(&.deep_read_page_count username).sum + InfoCache.set_progress_cache @id, username, @entries, sum + sum end def deep_total_page_count : Int32 @@ -422,6 +438,11 @@ class Title end def bulk_progress(action, ids : Array(String), username) + InfoCache.invalidate_progress_cache @id, username + parents.each do |parent| + InfoCache.invalidate_progress_cache parent.id, username + end + selected_entries = ids .map { |id| @entries.find &.id.==(id) diff --git a/src/library/types.cr b/src/library/types.cr index 4e83135..094cb64 100644 --- a/src/library/types.cr +++ b/src/library/types.cr @@ -35,12 +35,15 @@ class SortOptions end def self.from_info_json(dir, username) + cached_opt = InfoCache.get_sort_opt dir, username + return cached_opt if cached_opt opt = SortOptions.new TitleInfo.new dir do |info| if info.sort_by.has_key? username opt = SortOptions.from_tuple info.sort_by[username] end end + InfoCache.set_sort_opt dir, username, opt opt end diff --git a/src/util/web.cr b/src/util/web.cr index 5704ea8..5e873ca 100644 --- a/src/util/web.cr +++ b/src/util/web.cr @@ -120,6 +120,7 @@ macro get_and_save_sort_opt(dir) sort_opt = SortOptions.new sort_method, is_ascending + InfoCache.set_sort_opt {{dir}}, username, sort_opt TitleInfo.new {{dir}} do |info| info.sort_by[username] = sort_opt.to_tuple info.save