module Paperclip::Storage::Azure

Azure's container file hosting service is a scalable, easy place to store files for distribution. You can find out more about it at azure.microsoft.com/en-us/services/storage/

There are a few Azure-specific options for has_attached_file:

Public Class Methods

extended(base) click to toggle source
# File lib/paperclip/storage/azure.rb, line 56
def self.extended base
  begin
    require 'azure'
  rescue LoadError => e
    e.message << " (You may need to install the azure SDK gem)"
    raise e
  end unless defined?(::Azure::Core)

  base.instance_eval do
    @azure_options     = @options[:azure_options]     || {}

    unless @options[:url].to_s.match(/\A:azure.*url\z/) || @options[:url] == ":asset_host".freeze
      @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
      @options[:url]  = ":azure_path_url".freeze
    end
    @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)

    @http_proxy = @options[:http_proxy] || nil
  end

  Paperclip.interpolates(:azure_path_url) do |attachment, style|
    attachment.azure_uri(style)
  end unless Paperclip::Interpolations.respond_to? :azure_path_url
  Paperclip.interpolates(:asset_host) do |attachment, style|
    "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
  end unless Paperclip::Interpolations.respond_to? :asset_host
end

Public Instance Methods

auto_connect_duration() click to toggle source
# File lib/paperclip/storage/azure.rb, line 100
def auto_connect_duration
  @auto_connect_duration ||= @options[:auto_connect_duration] || azure_credentials[:auto_connect_duration] || 10
  @auto_connect_duration
end
azure_account_name() click to toggle source
# File lib/paperclip/storage/azure.rb, line 109
def azure_account_name
  account_name = @options[:azure_storage_account_name] || azure_credentials[:storage_account_name]
  account_name = account_name.call(self) if account_name.is_a?(Proc)

  account_name
end
azure_base_url() click to toggle source
# File lib/paperclip/storage/azure.rb, line 158
def azure_base_url
  Environment.url_for azure_account_name, azure_credentials[:region]
end
azure_container() click to toggle source
# File lib/paperclip/storage/azure.rb, line 162
def azure_container
  @azure_container ||= azure_interface.get_container_properties container_name
end
azure_credentials() click to toggle source
# File lib/paperclip/storage/azure.rb, line 105
def azure_credentials
  @azure_credentials ||= parse_credentials(@options[:azure_credentials])
end
azure_interface() click to toggle source
# File lib/paperclip/storage/azure.rb, line 122
def azure_interface
  @azure_interface ||= begin
    config = {}

    [:storage_account_name, :storage_access_key, :container].each do |opt|
      config[opt] = azure_credentials[opt] if azure_credentials[opt]
    end

    obtain_azure_instance_for(config.merge(@azure_options))
  end
end
azure_object(style_name = default_style) click to toggle source
# File lib/paperclip/storage/azure.rb, line 166
def azure_object(style_name = default_style)
  azure_interface.get_blob_properties container_name, path(style_name).sub(%r{\A/},'')
end
azure_storage_client() click to toggle source
# File lib/paperclip/storage/azure.rb, line 134
def azure_storage_client
  config = {}

  [:storage_account_name, :storage_access_key].each do |opt|
    config[opt] = azure_credentials[opt] if azure_credentials[opt]
  end

  @azure_storage_client ||= ::Azure::Storage::Client.create config
end
azure_uri(style_name = default_style) click to toggle source
# File lib/paperclip/storage/azure.rb, line 154
def azure_uri(style_name = default_style)
  "https://#{azure_base_url}/#{container_name}/#{path(style_name).gsub(%r{\A/}, '')}"
end
container_name() click to toggle source
# File lib/paperclip/storage/azure.rb, line 116
def container_name
  @container ||= @options[:container] || azure_credentials[:container]
  @container = @container.call(self) if @container.respond_to?(:call)
  @container or raise ArgumentError, "missing required :container option"
end
copy_to_local_file(style, local_dest_path) click to toggle source
# File lib/paperclip/storage/azure.rb, line 254
def copy_to_local_file(style, local_dest_path)
  log("copying #{path(style)} to local file #{local_dest_path}")

  blob, content = azure_interface.get_blob(container_name, path(style).sub(%r{\A/},''))

  ::File.open(local_dest_path, 'wb') do |local_file|
    local_file.write(content)
  end
rescue ::Azure::Core::Http::HTTPError => e
  raise unless e.status_code == 404

  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
  false
end
create_container() click to toggle source
# File lib/paperclip/storage/azure.rb, line 189
def create_container
  azure_interface.create_container container_name
end
exists?(style = default_style) click to toggle source
# File lib/paperclip/storage/azure.rb, line 177
def exists?(style = default_style)
  if original_filename
    !azure_object(style).nil?
  else
    false
  end
rescue ::Azure::Core::Http::HTTPError => e
  raise unless e.status_code == 404

  false
end
expiring_url(time = 3600, style_name = default_style) click to toggle source
# File lib/paperclip/storage/azure.rb, line 84
def expiring_url(time = 3600, style_name = default_style)
  if path(style_name)
    uri = URI azure_uri(style_name)
    generator = ::Azure::Storage::Core::Auth::SharedAccessSignature.new azure_account_name,
                                                                        azure_credentials[:storage_access_key]

    generator.signed_uri uri, false, service:      'b',
                                     resource:     'b',
                                     permissions:  'r',
                                     start:        (Time.now - (5 * 60)).utc.iso8601,
                                     expiry:       (Time.now + time).utc.iso8601
  else
    url(style_name)
  end
end
obtain_azure_instance_for(options) click to toggle source
# File lib/paperclip/storage/azure.rb, line 144
def obtain_azure_instance_for(options)
  instances = (Thread.current[:paperclip_azure_instances] ||= {})
  return instances[options] if instance[options]

  service = ::Azure::Storage::Blob::BlobService.new(client: azure_storage_client)
  service.with_filter ::Azure::Storage::Core::Filter::ExponentialRetryPolicyFilter.new

  instances[options] = service
end
parse_credentials(creds) click to toggle source
# File lib/paperclip/storage/azure.rb, line 170
def parse_credentials(creds)
  creds = creds.respond_to?('call') ? creds.call(self) : creds
  creds = find_credentials(creds).stringify_keys
  env = Object.const_defined?(:Rails) ? Rails.env : nil
  (creds[env] || creds).symbolize_keys
end
save_blob(container_name, storage_path, file, write_options) click to toggle source
# File lib/paperclip/storage/azure.rb, line 223
def save_blob(container_name, storage_path, file, write_options)

  if file.size < 64.megabytes
    azure_interface.create_block_blob container_name, storage_path, file.read, write_options
  else
    blocks = []; count = 0
    while data = file.read(4.megabytes)
      block_id = "block_#{(count += 1).to_s.rjust(5, '0')}"

      azure_interface.create_blob_block container_name, storage_path, block_id, data

      blocks << [block_id]
    end

    azure_interface.commit_blob_blocks container_name, storage_path, blocks
  end
end

Private Instance Methods

find_credentials(creds) click to toggle source
# File lib/paperclip/storage/azure.rb, line 271
def find_credentials creds
  case creds
  when File
    YAML::load(ERB.new(File.read(creds.path)).result)
  when String, Pathname
    YAML::load(ERB.new(File.read(creds)).result)
  when Hash
    creds
  when NilClass
    {}
  else
    raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
  end
end