diff --git a/app/models/media_asset.rb b/app/models/media_asset.rb index 74b0cc824..457c9c7e0 100644 --- a/app/models/media_asset.rb +++ b/app/models/media_asset.rb @@ -3,6 +3,7 @@ class MediaAsset < ApplicationRecord class Error < StandardError; end + FILE_KEY_LENGTH = 9 VARIANTS = %i[preview 180x180 360x360 720x720 sample original] MAX_VIDEO_DURATION = Danbooru.config.max_video_duration.to_i MAX_IMAGE_RESOLUTION = Danbooru.config.max_image_resolution @@ -22,6 +23,9 @@ class MediaAsset < ApplicationRecord delegate :metadata, to: :media_metadata delegate :is_non_repeating_animation?, :is_greyscale?, :is_rotated?, to: :metadata + scope :public_only, -> { where(is_public: true) } + scope :private_only, -> { where(is_public: false) } + # Processing: The asset's files are currently being resized and distributed to the backend servers. # Active: The asset has been successfully uploaded and is ready to use. # Deleted: The asset's files have been deleted by moving them to a trash folder. They can be undeleted by moving them out of the trash folder. @@ -39,9 +43,12 @@ class MediaAsset < ApplicationRecord validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } } validates :file_ext, inclusion: { in: %w[jpg png gif mp4 webm swf zip], message: "File is not an image or video" } validates :file_size, numericality: { less_than_or_equal_to: Danbooru.config.max_file_size, message: ->(asset, _) { "too large (size: #{asset.file_size.to_formatted_s(:human_size)}; max size: #{Danbooru.config.max_file_size.to_formatted_s(:human_size)})" } } + validates :file_key, length: { is: FILE_KEY_LENGTH }, uniqueness: true, if: :file_key_changed? validates :duration, numericality: { less_than_or_equal_to: MAX_VIDEO_DURATION, message: "must be less than #{MAX_VIDEO_DURATION} seconds", allow_nil: true }, on: :create # XXX should allow admins to bypass validate :validate_resolution, on: :create + before_create :initialize_file_key + class Variant extend Memoist @@ -186,7 +193,7 @@ class MediaAsset < ApplicationRecord concerning :SearchMethods do class_methods do def search(params) - q = search_attributes(params, :id, :created_at, :updated_at, :md5, :file_ext, :file_size, :image_width, :image_height) + q = search_attributes(params, :id, :created_at, :updated_at, :md5, :file_ext, :file_size, :image_width, :image_height, :file_key, :is_public) if params[:metadata].present? q = q.joins(:media_metadata).merge(MediaMetadata.search(metadata: params[:metadata])) @@ -368,4 +375,15 @@ class MediaAsset < ApplicationRecord end end end + + def self.generate_file_key + loop do + key = SecureRandom.send(:choose, [*"0".."9", *"A".."Z", *"a".."z"], FILE_KEY_LENGTH) # base62 + return key unless MediaAsset.exists?(file_key: key) + end + end + + def initialize_file_key + self.file_key = MediaAsset.generate_file_key + end end diff --git a/app/policies/media_asset_policy.rb b/app/policies/media_asset_policy.rb index d48cf5b73..9d6f45887 100644 --- a/app/policies/media_asset_policy.rb +++ b/app/policies/media_asset_policy.rb @@ -13,7 +13,7 @@ class MediaAssetPolicy < ApplicationPolicy if can_see_image? super else - super.excluding(:md5) + super.excluding(:md5, :file_key) end end end diff --git a/db/migrate/20220504235329_add_file_key_to_media_assets.rb b/db/migrate/20220504235329_add_file_key_to_media_assets.rb new file mode 100644 index 000000000..1095df8e1 --- /dev/null +++ b/db/migrate/20220504235329_add_file_key_to_media_assets.rb @@ -0,0 +1,9 @@ +class AddFileKeyToMediaAssets < ActiveRecord::Migration[7.0] + def change + add_column :media_assets, :file_key, :string, null: true + add_column :media_assets, :is_public, :boolean, default: true, null: false + + add_index :media_assets, :file_key, unique: true + add_index :media_assets, :is_public, where: "is_public = FALSE" + end +end diff --git a/db/structure.sql b/db/structure.sql index 688dfb05d..23d257796 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1102,7 +1102,9 @@ CREATE TABLE public.media_assets ( image_width integer NOT NULL, image_height integer NOT NULL, duration double precision, - status integer DEFAULT 200 NOT NULL + status integer DEFAULT 200 NOT NULL, + file_key character varying, + is_public boolean DEFAULT true NOT NULL ); @@ -3805,6 +3807,13 @@ CREATE INDEX index_media_assets_on_duration ON public.media_assets USING btree ( CREATE INDEX index_media_assets_on_file_ext ON public.media_assets USING btree (file_ext); +-- +-- Name: index_media_assets_on_file_key; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_media_assets_on_file_key ON public.media_assets USING btree (file_key); + + -- -- Name: index_media_assets_on_file_size; Type: INDEX; Schema: public; Owner: - -- @@ -3826,6 +3835,13 @@ CREATE INDEX index_media_assets_on_image_height ON public.media_assets USING btr CREATE INDEX index_media_assets_on_image_width ON public.media_assets USING btree (image_width); +-- +-- Name: index_media_assets_on_is_public; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_media_assets_on_is_public ON public.media_assets USING btree (is_public) WHERE (is_public = false); + + -- -- Name: index_media_assets_on_md5; Type: INDEX; Schema: public; Owner: - -- @@ -5806,6 +5822,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220403042706'), ('20220403220558'), ('20220407203236'), -('20220410050628'); +('20220410050628'), +('20220504235329'); diff --git a/script/fixes/109_generate_media_asset_file_keys.rb b/script/fixes/109_generate_media_asset_file_keys.rb new file mode 100755 index 000000000..ab1b93b52 --- /dev/null +++ b/script/fixes/109_generate_media_asset_file_keys.rb @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby + +require_relative "base" + +MediaAsset.where(file_key: nil).parallel_each do |asset| + asset.update_columns(file_key: MediaAsset.generate_file_key) + puts "id=#{asset.id} file_key=#{asset.file_key}" +end