Module: AttachmentSaver::DataStores::FileSystem
- Defined in:
- lib/datastores/file_system.rb
Constant Summary collapse
- RETRIES =
max attempts at finding a unique storage key. very rare to have to retry at all, so if it fails after 100 attempts, something’s seriously wrong.
100
Class Method Summary collapse
Instance Method Summary collapse
- #in_storage? ⇒ Boolean
- #public_path ⇒ Object
- #reprocess! ⇒ Object
- #save_attachment ⇒ Object
- #storage_filename ⇒ Object
Class Method Details
.included(base) ⇒ Object
12 13 14 15 16 17 |
# File 'lib/datastores/file_system.rb', line 12 def self.included(base) base.[:storage_directory] ||= File.join(Rails.root, 'public') # this is the part of the full filename that _doesn't_ form part of the HTTP path to the files base.[:storage_path_base] ||= Rails.env == 'production' ? base.table_name : File.join(Rails.env, base.table_name) # and this is the part that does. base.[:filter_filenames] = Regexp.new(base.[:filter_filenames]) if base.[:filter_filenames].is_a?(String) # may be nil, in which case the normal randomised-filename scheme is used instead of the filtered-original-filename scheme base.[:file_permissions] = 0664 unless base..has_key?(:file_permissions) # we don't use || as nil is a meaningful value for this option - it means to not explicitly set the file permissions end |
Instance Method Details
#in_storage? ⇒ Boolean
85 86 87 |
# File 'lib/datastores/file_system.rb', line 85 def in_storage? File.exist?(storage_filename) end |
#public_path ⇒ Object
89 90 91 |
# File 'lib/datastores/file_system.rb', line 89 def public_path "/#{storage_key.tr('\\', '/')}" # the tr is just for windows' benefit end |
#reprocess! ⇒ Object
93 94 95 96 97 |
# File 'lib/datastores/file_system.rb', line 93 def reprocess! raise "this attachment already has a file open to process" unless uploaded_file.nil? (storage_filename) if save! end |
#save_attachment ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/datastores/file_system.rb', line 19 def return unless @save_upload # this method is called every time the model is saved, not just when a new file has been uploaded old_storage_key = storage_key @old_filenames ||= [] @old_filenames << storage_filename unless storage_key.blank? self.storage_key = nil define_finalizer # choose a storage key (ie. path/filename) and try it; note that we assign a new # storage key for every new upload, not just every new AR model, so that the URL # changes each time, which allows long/infinite cache TTLs & CDN support. begin if derive_storage_key? begin # for thumbnail/other derived images, we base the filename on the original # (parent) image + the derived format name self.storage_key = derive_storage_key_from(original) (storage_filename) rescue Errno::EEXIST # if clobbering pre-existing files (only possible if using filtered_filenames, and even then only if creating new derived images explicitly at some time other than during processing the parent), we still don't want to write into them, we want to use a new file & an atomic rename retries = 0 begin self.storage_key = derive_storage_key_from(original, retries + 2) # +2 is arbitrary, I just think it's more human-friendly to go from xyz_thumb.jpg to xyz_thumb2.jpg rather than xyz_thumb0.jpg (storage_filename) rescue Errno::EEXIST raise if (retries += 1) >= RETRIES # in fact it would be very unusual to ever need to retry at all, let alone multiple times; if you hit this, your operating system is actually broken (or someone's messed with storage_filename) retry # pick a new random name and try again end end else retries = 0 begin if self.class.[:filter_filenames] && respond_to?(:original_filename) && !original_filename.blank? # replace all the original_filename characters not included in the keep_filenames character list with underscores, leave the rest; store in randomized directories to avoid naming clashes basename = AttachmentSaver::split_filename(original_filename).first.gsub(self.class.[:filter_filenames], '_') self.storage_key = File.join(self.class.[:storage_path_base], random_segment(3), random_segment(3), "#{basename}.#{file_extension}") else # for new files under this option, we pick a random name (split into 3 parts - 2 directories and a file - to help keep the directories at manageable sizes), and never overwrite # this is the default setting, and IMHO the most best choice for most apps; the original filenames are typically pretty meaningless self.storage_key = File.join(self.class.[:storage_path_base], random_segment(2), random_segment(2), "#{random_segment(6)}.#{file_extension}") # in fact just two random characters in the last part would be ample, since 36^(2+2+2) = billions, but we sacrifice 4 more characters of URL shortness for the benefit of ppl saving the assets to disk without renaming them end (storage_filename) rescue Errno::EEXIST raise if (retries += 1) >= RETRIES # in fact it would be very unusual to ever need to retry at all, let alone multiple times; if you hit this, your operating system is actually broken (or someone's messed with storage_filename) retry # pick a new random name and try again end end # successfully written to file; process the attachment (storage_filename) if # if there's exceptions later (ie. during save itself) that prevent the record from being saved, the finalizer will clean up the file @save_upload = nil rescue Exception => ex FileUtils.rm_f(storage_filename) unless storage_key.blank? || ex.is_a?(Errno::EEXIST) self.storage_key = old_storage_key @old_filenames.pop unless old_storage_key.blank? raise if ex.is_a?(AttachmentSaverError) raise FileSystemAttachmentDataStoreError, "#{ex.class}: #{ex.message}", ex.backtrace end end |
#storage_filename ⇒ Object
81 82 83 |
# File 'lib/datastores/file_system.rb', line 81 def storage_filename File.join(self.class.[:storage_directory], storage_key) end |