fic_tracker/lib/fic_tracker/models/story.rb

187 lines
4.8 KiB
Ruby

# frozen_string_literal: true
require_relative 'light/tag'
module FicTracker::Models
class Story < Sequel::Model
# 1/week
METADATA_REFRESH_INTERVAL = 7 * 24 * 60 * 60
# 3/day
CONTENT_REFRESH_INTERVAL = 12 * 60 * 60
many_to_one :author
one_to_many :chapters, order: :index
many_to_many :collection, join_table: :collection_stories
plugin :serialization, [Light::Tag.method(:store), Light::Tag.method(:load)], :tags
plugin :serialization, :json, :data
# Defer creation of author/chapters until story requiring them is to be saved
def before_create
if @author
@author.save unless @author.id
self.author_id = @author.id
end
super
end
def after_create
return if [@author, @chapters].all?(&:nil?)
self.author = @author if @author
@author = nil
if @chapters
latest_chapter_at = self.updated_at || self.published_at || Time.at(0)
@chapters&.each do |chapter|
latest_chapter_at = [chapter.published_at || Time.at(0), latest_chapter_at].max
add_chapter chapter
end
update(updated_at: latest_chapter_at) if latest_chapter_at > Time.at(0) && (updated_at.nil? || latest_chapter_at >= updated_at)
end
@chapters = nil
end
def completed?
completed
end
# Support attaching author to a not-yet-saved story
def author
@author || super
end
def author=(author_name)
author = author_name if author_name.is_a?(FicTracker::Models::Author)
author ||= Author.find(backend_name: backend.name, slug: author_name)
author ||= backend.load_author(author_name) if id
author ||= Author.new(backend: backend, slug: author_name)
if id
@author = nil
author.save unless author.id
super(author)
else
@author = author
end
end
# Support attaching chapters to a not-yet-saved story
def chapters
@chapters || super
end
def chapters=(entries)
latest_chapter_at = self.published_at || Time.at(0)
to_add = []
to_remove = self.chapters.map(&:id)
entries.each do |entry|
chapter = self.chapters.find { |c| c.slug == entry[:slug] }
if chapter
latest_chapter_at = [chapter.published_at || Time.at(0), latest_chapter_at].max
chapter.set(**entry)
else
chapter = FicTracker::Models::Chapter.new(**entry)
to_add << chapter
end
to_remove.delete chapter.id
end
if id
@chapters = nil
to_add.each do |entry|
logger.debug "Adding new chapter #{entry.inspect} to story #{self}"
add_chapter entry
end
if to_remove.any?
logger.debug "Removing chapter(s) #{to_remove.inspect} from story #{self}"
chapter_dataset.where(id: to_remove).delete
end
update(updated_at: latest_chapter_at) if latest_chapter_at > Time.at(0) && (updated_at.nil? || latest_chapter_at >= updated_at)
else
@chapters = (@chapters || []) + to_add - to_remove
end
end
def ensure_fully_loaded
ensure_chapters
refresh_content
refresh_metadata
end
def ensure_chapters
# FIXME: Should check for a reasonable set of parameters - full load unless XX% (75%?) of chapters have content
return if chapters && chapters.any? && chapters.all? { |c| c.content? && c.content_type? }
backend.load_full_story(self)
end
def backend
return unless backend_name
@backend ||= FicTracker::Backends.get(backend_name)
end
def backend=(backend)
@backend = backend
self.backend_name = backend.name
end
def etag
chapters.select { |c| c.etag }.last
end
def cache_key
backend.cache_key + [:s, slug]
end
def safe_name
[backend_name, slug, name].join('_').downcase.gsub(/[^a-z0-9\-_]/, '_').gsub(/__+/, '_')
end
def refresh_metadata
refresh_metadata! if backend && needs_metadata_refresh?
end
def refresh_metadata!
backend.load_story(self)
end
def refresh_content
backend.find_chapters(self) if backend && needs_content_refresh?
chapters.each(&:refresh_content)
end
def refresh_content!
backend.find_chapters(self)
chapters.each(&:refresh_content!)
end
def needs_metadata_refresh?
Time.now - (last_metadata_refresh || Time.at(0)) >= METADATA_REFRESH_INTERVAL
end
def needs_content_refresh?
Time.now - (last_content_refresh || Time.at(0)) >= (completed? ? METADATA_REFRESH_INTERVAL : CONTENT_REFRESH_INTERVAL)
end
def to_s
"#{name}, by #{author.nil? ? '<Unknown>' : author.to_s}"
end
def uid
Digest::SHA1.hexdigest("#{backend}/#{slug}")
end
private
def logger
Logging.logger[self]
end
end
end