class ActiveStorage::Service::CloudinaryService
Attributes
Public Class Methods
# File lib/active_storage/service/cloudinary_service.rb, line 33 def initialize(**options) @options = options end
Public Instance Methods
# File lib/active_storage/service/cloudinary_service.rb, line 101 def delete(key) instrument :delete, key: key do options = { resource_type: resource_type(nil, key), type: @options[:type] }.compact Cloudinary::Uploader.destroy public_id(key), **options end end
# File lib/active_storage/service/cloudinary_service.rb, line 112 def delete_prefixed(prefix) # This method is used by ActiveStorage to delete derived resources after the main resource was deleted. # In Cloudinary, the derived resources are deleted automatically when the main resource is deleted. end
# File lib/active_storage/service/cloudinary_service.rb, line 133 def download(key, &block) uri = URI(url(key)) if block_given? instrument :streaming_download, key: key do Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| request = Net::HTTP::Get.new uri http.request request do |response| response.read_body &block end end end else instrument :download, key: key do res = Net::HTTP::get_response(uri) res.body end end end
Return the partial content in the byte range
of the file at the key
.
# File lib/active_storage/service/cloudinary_service.rb, line 153 def download_chunk(key, range) url = Cloudinary::Utils.cloudinary_url(public_id(key), resource_type: resource_type(nil, key)) uri = URI(url) instrument :download, key: key do req = Net::HTTP::Get.new(uri) range_end = case when range.end.nil? then '' when range.exclude_end? then range.end - 1 else range.end end req['range'] = "bytes=#{[range.begin, range_end].join('-')}" res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| http.request(req) end res.body.force_encoding(Encoding::BINARY) end end
# File lib/active_storage/service/cloudinary_service.rb, line 117 def exist?(key) instrument :exist, key: key do |payload| begin options = { resource_type: resource_type(nil, key), type: @options[:type] }.compact Cloudinary::Api.resource public_id(key), **options true rescue Cloudinary::Api::NotFound => e false end end end
# File lib/active_storage/service/cloudinary_service.rb, line 94 def headers_for_direct_upload(key, content_type:, checksum:, **) { Headers::CONTENT_TYPE => content_type, Headers::CONTENT_MD5 => checksum, } end
Returns the public id of the asset.
Public id includes both folder(defined globally in the configuration) and the active storage key. For raw files it also includes the file extension, since that’s how Cloudinary
stores raw files.
@param [ActiveStorage::BlobKey] key The blob key with attributes. @param [ActiveStorage::Filename] filename The original filename. @param [string] content_type The content type of the file.
@return [string] The public id of the asset.
# File lib/active_storage/service/cloudinary_service.rb, line 182 def public_id(key, filename = nil, content_type = '') public_id = key if resource_type(nil, key) == 'raw' public_id = [key, ext_for_file(key, filename, content_type)].reject(&:blank?).join('.') end full_public_id_internal(public_id) end
# File lib/active_storage/service/cloudinary_service.rb, line 37 def upload(key, io, filename: nil, checksum: nil, **options) instrument :upload, key: key, checksum: checksum do begin extra_headers = checksum.nil? ? {} : {Headers::CONTENT_MD5 => checksum} options = @options.merge(options) resource_type = resource_type(io, key) options[:format] = ext_for_file(key) if resource_type == "raw" Cloudinary::Uploader.upload_large( io, public_id: public_id_internal(key), resource_type: resource_type, context: {active_storage_key: key, checksum: checksum}, extra_headers: extra_headers, **options ) rescue CloudinaryException => e raise ActiveStorage::IntegrityError, e.message, e.backtrace end end end
# File lib/active_storage/service/cloudinary_service.rb, line 58 def url(key, filename: nil, content_type: '', **options) instrument :url, key: key do |payload| url = Cloudinary::Utils.cloudinary_url( full_public_id_internal(key), resource_type: resource_type(nil, key, content_type), format: ext_for_file(key, filename, content_type), **@options.merge(options.symbolize_keys) ) payload[:url] = url url end end
# File lib/active_storage/service/cloudinary_service.rb, line 73 def url_for_direct_upload(key, **options) instrument :url, key: key do |payload| options = @options.merge(options.symbolize_keys) options[:resource_type] ||= resource_type(nil, key, options[:content_type]) options[:public_id] = public_id_internal(key) # Provide file format for raw files, since js client does not include original file name. # # When the file is uploaded from the server, the request includes original filename. That allows Cloudinary # to identify file extension and append it to the public id of the file (raw files include file extension # in their public id, opposed to transformable assets (images/video) that use only basename). When uploading # through direct upload (client side js), filename is missing, and that leads to inconsistent/broken URLs. # To avoid that, we explicitly pass file format in options. options[:format] = ext_for_file(key) if options[:resource_type] == "raw" context = options.delete(:context) options[:context] = {active_storage_key: key} options[:context].reverse_merge!(context) if context.respond_to?(:merge) options.delete(:file) payload[:url] = api_uri("upload", options) end end
Private Instance Methods
# File lib/active_storage/service/cloudinary_service.rb, line 193 def api_uri(action, options) base_url = Cloudinary::Utils.cloudinary_api_url(action, options) upload_params = Cloudinary::Uploader.build_upload_params(options) upload_params.reject! { |k, v| Cloudinary::Utils.safe_blank?(v) } unless options[:unsigned] upload_params = Cloudinary::Utils.sign_request(upload_params, options) end "#{base_url}?#{upload_params.to_query}" end
# File lib/active_storage/service/cloudinary_service.rb, line 247 def content_type_to_resource_type(content_type) return 'image' if content_type.nil? type, subtype = content_type.split('/') case type when 'video', 'audio' 'video' when 'text' 'raw' when 'application' case subtype when 'pdf', 'postscript' 'image' when 'vnd.apple.mpegurl', 'x-mpegurl', 'mpegurl' # m3u8 'video' else 'raw' end else 'image' end end
Helper method for getting the filename extension.
It does the best effort when original filename does not include extension, but we know the mime-type.
@param [ActiveStorage::BlobKey] key The blob key with attributes. @param [ActiveStorage::Filename] filename The original filename. @param [string] content_type The content type of the file.
@return [string] The extension of the filename.
# File lib/active_storage/service/cloudinary_service.rb, line 213 def ext_for_file(key, filename = nil, content_type = nil) if filename.blank? options = key.respond_to?(:attributes) ? key.attributes : {} filename = ActiveStorage::Filename.new(options[:filename]) if options.has_key?(:filename) end ext = filename.respond_to?(:extension_without_delimiter) ? filename.extension_without_delimiter : nil return ext unless ext.blank? # Raw files are not convertible, no extension guessing for them return nil if content_type_to_resource_type(content_type).eql?('raw') # Fallback when there is no extension. @formats ||= Hash.new do |h, key| ext = Rack::Mime::MIME_TYPES.invert[key] h[key] = ext.slice(1..-1) unless ext.nil? end @formats[content_type] end
Returns the full public id including folder.
# File lib/active_storage/service/cloudinary_service.rb, line 234 def full_public_id_internal(key) public_id = public_id_internal(key) return public_id unless @options[:folder] File.join(@options.fetch(:folder), public_id) end
# File lib/active_storage/service/cloudinary_service.rb, line 242 def public_id_internal(key) # TODO: Allow custom manipulation of key to obscure how we store in Cloudinary key end
# File lib/active_storage/service/cloudinary_service.rb, line 270 def resource_type(io, key = "", content_type = "") if content_type.blank? options = key.respond_to?(:attributes) ? key.attributes : {} content_type = options[:content_type] || (io.nil? ? '' : Marcel::MimeType.for(io)) end content_type_to_resource_type(content_type) end