Always store original files in `public/data/original` instead of directly in
`public/data`. Previously this was optional and defaulted to off.
Downstream boorus will need to either move all images in the
`public/data` directory to `public/data/original`, or symlink the
`public/data/original` directory to the toplevel `public/data` directory:
ln -s . /path/to/danbooru/public/data/original
This to simplify file layout. This option existed because in the past we
stored original files in different locations on different servers (for
no particular reason).
115 lines
3.1 KiB
Ruby
115 lines
3.1 KiB
Ruby
class StorageManager
|
|
class Error < StandardError; end
|
|
|
|
attr_reader :base_url, :base_dir, :hierarchical, :tagged_filenames
|
|
|
|
def initialize(base_url:, base_dir:, hierarchical: false, tagged_filenames: Danbooru.config.enable_seo_post_urls)
|
|
@base_url = base_url.chomp("/")
|
|
@base_dir = base_dir
|
|
@hierarchical = hierarchical
|
|
@tagged_filenames = tagged_filenames
|
|
end
|
|
|
|
# Store the given file at the given path. If a file already exists at that
|
|
# location it should be overwritten atomically. Either the file is fully
|
|
# written, or an error is raised and the original file is left unchanged. The
|
|
# file should never be in a partially written state.
|
|
def store(io, path)
|
|
raise NotImplementedError, "store not implemented"
|
|
end
|
|
|
|
# Delete the file at the given path. If the file doesn't exist, no error
|
|
# should be raised.
|
|
def delete(path)
|
|
raise NotImplementedError, "delete not implemented"
|
|
end
|
|
|
|
# Return a readonly copy of the file located at the given path.
|
|
def open(path)
|
|
raise NotImplementedError, "open not implemented"
|
|
end
|
|
|
|
def store_file(io, post, type)
|
|
store(io, file_path(post.md5, post.file_ext, type))
|
|
end
|
|
|
|
def delete_file(post_id, md5, file_ext, type)
|
|
delete(file_path(md5, file_ext, type))
|
|
end
|
|
|
|
def open_file(post, type)
|
|
self.open(file_path(post.md5, post.file_ext, type))
|
|
end
|
|
|
|
def file_url(post, type, tagged_filenames: false)
|
|
subdir = subdir_for(post.md5)
|
|
file = file_name(post.md5, post.file_ext, type)
|
|
seo_tags = seo_tags(post) if tagged_filenames
|
|
|
|
if type == :preview && !post.has_preview?
|
|
"#{root_url}/images/download-preview.png"
|
|
elsif type == :preview
|
|
"#{base_url}/preview/#{subdir}#{file}"
|
|
elsif type == :crop
|
|
"#{base_url}/crop/#{subdir}#{file}"
|
|
elsif type == :large && post.has_large?
|
|
"#{base_url}/sample/#{subdir}#{seo_tags}#{file}"
|
|
else
|
|
"#{base_url}/original/#{subdir}#{seo_tags}#{post.md5}.#{post.file_ext}"
|
|
end
|
|
end
|
|
|
|
def root_url
|
|
origin = Addressable::URI.parse(base_url).origin
|
|
origin = "" if origin == "null" # base_url was relative
|
|
origin
|
|
end
|
|
|
|
def file_path(post_or_md5, file_ext, type)
|
|
md5 = post_or_md5.is_a?(String) ? post_or_md5 : post_or_md5.md5
|
|
subdir = subdir_for(md5)
|
|
file = file_name(md5, file_ext, type)
|
|
|
|
case type
|
|
when :preview
|
|
"#{base_dir}/preview/#{subdir}#{file}"
|
|
when :crop
|
|
"#{base_dir}/crop/#{subdir}#{file}"
|
|
when :large
|
|
"#{base_dir}/sample/#{subdir}#{file}"
|
|
when :original
|
|
"#{base_dir}/original/#{subdir}#{file}"
|
|
end
|
|
end
|
|
|
|
def file_name(md5, file_ext, type)
|
|
large_file_ext = (file_ext == "zip") ? "webm" : "jpg"
|
|
|
|
case type
|
|
when :preview
|
|
"#{md5}.jpg"
|
|
when :crop
|
|
"#{md5}.jpg"
|
|
when :large
|
|
"sample-#{md5}.#{large_file_ext}"
|
|
when :original
|
|
"#{md5}.#{file_ext}"
|
|
end
|
|
end
|
|
|
|
def subdir_for(md5)
|
|
if hierarchical
|
|
"#{md5[0..1]}/#{md5[2..3]}/"
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
def seo_tags(post)
|
|
return "" if !tagged_filenames
|
|
|
|
tags = post.presenter.humanized_essential_tag_string.gsub(/[^a-z0-9]+/, "_").gsub(/(?:^_+)|(?:_+$)/, "").gsub(/_{2,}/, "_")
|
|
"__#{tags}__"
|
|
end
|
|
end
|