Class: FileUploader
- Inherits:
-
GitlabUploader
- Object
- CarrierWave::Uploader::Base
- GitlabUploader
- FileUploader
- Defined in:
- app/uploaders/file_uploader.rb
Overview
This class breaks the actual CarrierWave concept. Every uploader should use a base_dir that is model agnostic so we can build back URLs from base_dir-relative paths saved in the `Upload` model.
As the `.base_dir` is model dependent and *not* saved in the upload model (see #upload_path) there is no way to build back the correct file path without the model, which defies CarrierWave way of storing files.
Direct Known Subclasses
Constant Summary collapse
- MARKDOWN_PATTERN =
%r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
- DYNAMIC_PATH_PATTERN =
%r{.*(?<secret>\b(\h{10}|\h{32}))\/(?<identifier>.*)}.freeze
- VALID_SECRET_PATTERN =
%r{\A\h{10,32}\z}.freeze
- InvalidSecret =
Class.new(StandardError)
Constants included from Gitlab::FileTypeDetection
Gitlab::FileTypeDetection::DANGEROUS_AUDIO_EXT, Gitlab::FileTypeDetection::DANGEROUS_IMAGE_EXT, Gitlab::FileTypeDetection::DANGEROUS_VIDEO_EXT, Gitlab::FileTypeDetection::PDF_EXT, Gitlab::FileTypeDetection::SAFE_AUDIO_EXT, Gitlab::FileTypeDetection::SAFE_IMAGE_EXT, Gitlab::FileTypeDetection::SAFE_IMAGE_FOR_SCALING_EXT, Gitlab::FileTypeDetection::SAFE_VIDEO_EXT
Instance Attribute Summary collapse
-
#model ⇒ Object
Returns the value of attribute model.
Attributes included from RecordsUploads::Concern
Class Method Summary collapse
-
.absolute_base_dir(model) ⇒ Object
used in migrations and import/exports.
- .absolute_path(upload) ⇒ Object
- .base_dir(model, store = Store::LOCAL) ⇒ Object
-
.copy_to(uploader, to_project) ⇒ Object
return a new uploader with a file copy on another project.
- .extract_dynamic_path(path) ⇒ Object
- .generate_secret ⇒ Object
-
.model_path_segment(model) ⇒ Object
Returns the part of `store_dir` that can change based on the model's current path.
- .root ⇒ Object
Instance Method Summary collapse
-
#absolute_path ⇒ Object
we don't need to know the actual path, an uploader instance should be able to yield the file content on demand, so we should build the digest.
-
#base_dir(store = nil) ⇒ Object
enforce the usage of Hashed storage when storing to remote store as the FileMover doesn't support OS.
- #copy_file(file) ⇒ Object
-
#initialize(model, mounted_as = nil, **uploader_context) ⇒ FileUploader
constructor
A new instance of FileUploader.
- #initialize_copy(from) ⇒ Object
- #local_storage_path(file_identifier) ⇒ Object
- #remote_storage_path(file_identifier) ⇒ Object
- #secret ⇒ Object
- #store_dirs ⇒ Object
- #to_h ⇒ Object
- #upload=(value) ⇒ Object
- #upload_path ⇒ Object
- #upload_paths(identifier) ⇒ Object
Methods included from ObjectStorage::Concern
#cache!, #delete_migrated_file, #exclusive_lease_key, #exists?, #file_cache_storage?, #file_storage?, #filename, #filename=, #fog_attributes, #fog_credentials, #fog_directory, #fog_public, #migrate!, #object_store, #object_store=, #persist_object_store!, #persist_object_store?, #schedule_background_upload, #store!, #store_dir, #use_file
Methods included from RecordsUploads::Concern
#filename, #readd_upload, #record_upload
Methods included from Gitlab::FileMarkdownLinkBuilder
#markdown_link, #markdown_name
Methods included from Gitlab::FileTypeDetection
#audio?, #dangerous_audio?, #dangerous_embeddable?, #dangerous_image?, #dangerous_video?, #embeddable?, extension_match?, #image?, #image_safe_for_scaling?, #pdf?, #video?
Methods inherited from GitlabUploader
#cache_dir, #cached_size, #exists?, #file_cache_storage?, file_storage?, #filename, #local_url, #model_valid?, #move_to_cache, #move_to_store, #open, #relative_path, #replace_file_without_saving!, storage_options, #work_dir
Constructor Details
#initialize(model, mounted_as = nil, **uploader_context) ⇒ FileUploader
Returns a new instance of FileUploader.
86 87 88 89 90 91 |
# File 'app/uploaders/file_uploader.rb', line 86 def initialize(model, mounted_as = nil, **uploader_context) super(model, nil, **uploader_context) @model = model apply_context!(uploader_context) end |
Instance Attribute Details
#model ⇒ Object
Returns the value of attribute model
84 85 86 |
# File 'app/uploaders/file_uploader.rb', line 84 def model @model end |
Class Method Details
.absolute_base_dir(model) ⇒ Object
used in migrations and import/exports
48 49 50 |
# File 'app/uploaders/file_uploader.rb', line 48 def self.absolute_base_dir(model) File.join(root, base_dir(model)) end |
.absolute_path(upload) ⇒ Object
33 34 35 36 37 38 |
# File 'app/uploaders/file_uploader.rb', line 33 def self.absolute_path(upload) File.join( absolute_base_dir(upload.model), upload.path # already contain the dynamic_segment, see #upload_path ) end |
.base_dir(model, store = Store::LOCAL) ⇒ Object
40 41 42 43 44 45 |
# File 'app/uploaders/file_uploader.rb', line 40 def self.base_dir(model, store = Store::LOCAL) decorated_model = model decorated_model = Storage::Hashed.new(model) if store == Store::REMOTE model_path_segment(decorated_model) end |
.copy_to(uploader, to_project) ⇒ Object
return a new uploader with a file copy on another project
166 167 168 169 170 171 172 173 |
# File 'app/uploaders/file_uploader.rb', line 166 def self.copy_to(uploader, to_project) moved = self.new(to_project) moved.object_store = uploader.object_store moved.filename = uploader.filename moved.copy_file(uploader.file) moved end |
.extract_dynamic_path(path) ⇒ Object
73 74 75 |
# File 'app/uploaders/file_uploader.rb', line 73 def self.extract_dynamic_path(path) DYNAMIC_PATH_PATTERN.match(path) end |
.generate_secret ⇒ Object
69 70 71 |
# File 'app/uploaders/file_uploader.rb', line 69 def self.generate_secret SecureRandom.hex end |
.model_path_segment(model) ⇒ Object
Returns the part of `store_dir` that can change based on the model's current path
This is used to build Upload paths dynamically based on the model's current namespace and path, allowing us to ignore renames or transfers.
model - Object that responds to `full_path` and `disk_path`
Returns a String without a trailing slash
61 62 63 64 65 66 67 |
# File 'app/uploaders/file_uploader.rb', line 61 def self.model_path_segment(model) case model when Storage::Hashed then model.disk_path else model.hashed_storage?(:attachments) ? model.disk_path : model.full_path end end |
.root ⇒ Object
29 30 31 |
# File 'app/uploaders/file_uploader.rb', line 29 def self.root File.join(.storage_path, 'uploads') end |
Instance Method Details
#absolute_path ⇒ Object
we don't need to know the actual path, an uploader instance should be able to yield the file content on demand, so we should build the digest
108 109 110 |
# File 'app/uploaders/file_uploader.rb', line 108 def absolute_path self.class.absolute_path(@upload) end |
#base_dir(store = nil) ⇒ Object
enforce the usage of Hashed storage when storing to remote store as the FileMover doesn't support OS
102 103 104 |
# File 'app/uploaders/file_uploader.rb', line 102 def base_dir(store = nil) self.class.base_dir(@model, store || object_store) end |
#copy_file(file) ⇒ Object
175 176 177 178 179 180 181 182 183 184 |
# File 'app/uploaders/file_uploader.rb', line 175 def copy_file(file) to_path = if file_storage? File.join(self.class.root, store_path) else store_path end self.file = file.copy_to(to_path) record_upload # after_store is not triggered end |
#initialize_copy(from) ⇒ Object
93 94 95 96 97 98 |
# File 'app/uploaders/file_uploader.rb', line 93 def initialize_copy(from) super @secret = self.class.generate_secret @upload = nil # calling record_upload would delete the old upload if set end |
#local_storage_path(file_identifier) ⇒ Object
121 122 123 |
# File 'app/uploaders/file_uploader.rb', line 121 def local_storage_path(file_identifier) File.join(dynamic_segment, file_identifier) end |
#remote_storage_path(file_identifier) ⇒ Object
125 126 127 |
# File 'app/uploaders/file_uploader.rb', line 125 def remote_storage_path(file_identifier) File.join(store_dir, file_identifier) end |
#secret ⇒ Object
157 158 159 160 161 162 163 |
# File 'app/uploaders/file_uploader.rb', line 157 def secret @secret ||= self.class.generate_secret raise InvalidSecret unless @secret =~ VALID_SECRET_PATTERN @secret end |
#store_dirs ⇒ Object
129 130 131 132 133 134 |
# File 'app/uploaders/file_uploader.rb', line 129 def store_dirs { Store::LOCAL => File.join(base_dir, dynamic_segment), Store::REMOTE => File.join(base_dir(ObjectStorage::Store::REMOTE), dynamic_segment) } end |
#to_h ⇒ Object
136 137 138 139 140 141 142 |
# File 'app/uploaders/file_uploader.rb', line 136 def to_h { alt: markdown_name, url: secure_url, markdown: markdown_link } end |
#upload=(value) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'app/uploaders/file_uploader.rb', line 144 def upload=(value) super return unless value return if apply_context!(value.uploader_context) # fallback to the regex based extraction if matches = self.class.extract_dynamic_path(value.path) @secret = matches[:secret] @identifier = matches[:identifier] end end |
#upload_path ⇒ Object
112 113 114 115 116 117 118 119 |
# File 'app/uploaders/file_uploader.rb', line 112 def upload_path if file_storage? # Legacy path relative to project.full_path local_storage_path(identifier) else remote_storage_path(identifier) end end |
#upload_paths(identifier) ⇒ Object
77 78 79 80 81 82 |
# File 'app/uploaders/file_uploader.rb', line 77 def upload_paths(identifier) [ File.join(secret, identifier), File.join(base_dir(Store::REMOTE), secret, identifier) ] end |