mirror of
https://github.com/manyfold3d/manyfold.git
synced 2026-10-22 03:32:18 -05:00
212 lines
5.4 KiB
Ruby
212 lines
5.4 KiB
Ruby
class Library < ApplicationRecord
|
|
extend Memoist
|
|
include PublicIDable
|
|
include Problematic
|
|
|
|
STORAGE_SERVICES = [
|
|
"filesystem",
|
|
"s3"
|
|
]
|
|
|
|
has_many :models, dependent: :destroy
|
|
has_many :model_files, through: :models
|
|
serialize :tag_regex, type: Array, coder: YAML
|
|
after_initialize :init
|
|
before_validation :ensure_path_case_is_correct
|
|
after_destroy :reset_default_library
|
|
after_commit :register_storage, on: :create
|
|
|
|
normalizes :path, with: ->(path) do
|
|
Pathname.new(path.strip).realpath.to_s
|
|
rescue Errno::ENOENT, Errno::EACCES # carry on, we validate these later
|
|
path.strip
|
|
end
|
|
|
|
validates :storage_service, presence: true, inclusion: STORAGE_SERVICES
|
|
validates :path,
|
|
presence: true,
|
|
uniqueness: true,
|
|
safe_path: true,
|
|
read_write: true,
|
|
disjoint_library_folder: true,
|
|
if: -> { storage_service == "filesystem" }
|
|
|
|
validates :s3_bucket, presence: true, if: -> { storage_service == "s3" }
|
|
validates :s3_region, presence: true, if: -> { storage_service == "s3" }
|
|
validates :s3_access_key_id, presence: true, if: -> { storage_service == "s3" }
|
|
validates :s3_secret_access_key, presence: true, if: -> { storage_service == "s3" }
|
|
|
|
validates :path_template, presence: true
|
|
|
|
default_scope { order(:name) }
|
|
|
|
def name
|
|
self[:name] || (path ? File.basename(path) : "")
|
|
end
|
|
|
|
def init
|
|
self.name = nil if name == ""
|
|
end
|
|
|
|
# Backwards compatibility for use during migrations
|
|
def storage_service
|
|
attributes["storage_service"] || "filesystem"
|
|
end
|
|
|
|
def storage_exists?
|
|
case storage_service
|
|
when "filesystem"
|
|
Dir.exist?(path)
|
|
when "s3"
|
|
storage.bucket.exists?
|
|
else
|
|
raise "Invalid storage service: #{storage_service}"
|
|
end
|
|
end
|
|
|
|
def free_space
|
|
case storage_service
|
|
when "filesystem"
|
|
stat = Sys::Filesystem.stat(path)
|
|
stat.bytes_available
|
|
when "s3"
|
|
nil
|
|
else
|
|
raise "Invalid storage service: #{storage_service}"
|
|
end
|
|
end
|
|
|
|
def storage_key
|
|
:"library_#{id}"
|
|
end
|
|
|
|
def storage_origin
|
|
case storage_service
|
|
when "s3"
|
|
URI.parse(storage.presign(nil)[:url]).origin
|
|
end
|
|
end
|
|
memoize :storage_origin
|
|
|
|
def storage
|
|
case storage_service
|
|
when "filesystem"
|
|
Shrine::Storage::FileSystem.new(path)
|
|
when "s3"
|
|
Shrine::Storage::S3.new(
|
|
endpoint: s3_endpoint,
|
|
bucket: s3_bucket,
|
|
region: s3_region,
|
|
access_key_id: s3_access_key_id,
|
|
secret_access_key: s3_secret_access_key,
|
|
force_path_style: s3_endpoint.present? && s3_path_style,
|
|
use_accelerate_endpoint: s3_endpoint.blank?
|
|
)
|
|
else
|
|
raise "Invalid storage service: #{storage_service}"
|
|
end
|
|
end
|
|
memoize :storage
|
|
|
|
def register_storage
|
|
ModelFileUploader.storages[storage_key] = storage
|
|
end
|
|
|
|
def self.register_all_storage
|
|
find_each(&:register_storage)
|
|
rescue ActiveRecord::StatementInvalid, NameError
|
|
nil # migrations probably haven't run yet to create library table
|
|
end
|
|
|
|
def list_files(pattern, flags = 0)
|
|
files = case storage_service
|
|
when "filesystem"
|
|
Dir.glob(pattern, flags, base: path).filter { |it| File.file?(File.join(path, it)) }
|
|
when "s3"
|
|
keys = []
|
|
pattern_array = [pattern].flatten
|
|
storage.bucket.objects.each do |object|
|
|
keys << object.key if pattern_array.any? { |p| File.fnmatch?(p, object.key) }
|
|
end
|
|
keys
|
|
else
|
|
raise "Invalid storage service: #{storage_service}"
|
|
end
|
|
# Filter out files that should be ignored
|
|
files.uniq.reject { |str| SiteSettings.ignored_file?(str) }
|
|
end
|
|
|
|
def has_file?(path)
|
|
storage.exists?(path)
|
|
end
|
|
|
|
def has_folder?(path)
|
|
case storage_service
|
|
when "s3"
|
|
storage.bucket.objects(prefix: path).count > 0
|
|
else
|
|
storage.exists?(path)
|
|
end
|
|
end
|
|
|
|
def file_last_modified(file)
|
|
case storage_service
|
|
when "filesystem"
|
|
File.mtime(File.join(path, file))
|
|
when "s3"
|
|
storage.bucket.object(file).last_modified
|
|
else
|
|
raise "Invalid storage service: #{storage_service}"
|
|
end
|
|
end
|
|
|
|
def create_path_if_not_on_disk=(val)
|
|
if val == "1" && storage_service == "filesystem" && path.presence
|
|
begin
|
|
FileUtils.makedirs(path)
|
|
rescue Errno::EROFS, Errno::EACCES
|
|
errors.add(:path, :non_writable)
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.default
|
|
Library.find_by(id: SiteSettings.default_library) || Library.first
|
|
end
|
|
|
|
def default?
|
|
SiteSettings.default_library == id
|
|
end
|
|
|
|
def make_default
|
|
SiteSettings.default_library = id
|
|
end
|
|
|
|
def reset_default_library
|
|
new_default = Library.where.not(id: id).first # rubocop:disable Pundit/UsePolicyScope
|
|
new_default ? new_default.make_default : SiteSettings.default_library = nil
|
|
end
|
|
|
|
def detect_filesystem_changes_later(delay: 0.seconds)
|
|
Scan::Library::DetectFilesystemChangesJob.set(wait: delay).perform_later(id)
|
|
end
|
|
|
|
def create_model_from_path_later(path, delay: 0.seconds)
|
|
Scan::Library::CreateModelFromPathJob.set(wait: delay).perform_later(id, path)
|
|
end
|
|
|
|
private
|
|
|
|
def ensure_path_case_is_correct
|
|
# On case-preserving-and-insensitive filesystems (i.e. macOS)
|
|
# if you get the case wrong, the library can be created, but then
|
|
# models will get the wrong paths. This method makes sure that the
|
|
# case is stored in the canonical form that the OS will give us back
|
|
# in globs
|
|
if storage_service == "filesystem" && path
|
|
normalised = Dir.glob(path).first
|
|
self.path = normalised if normalised
|
|
end
|
|
end
|
|
end
|