require "fastandand"
module Defog
# Create a Defog::Handle proxy instance via Defog::Proxy#file, such as
#
# defog = Defog::Proxy.new(:provider => :AWS, :aws_access_key_id => access_key, ...)
#
# handle = defog.file("key/to/my/file")
#
# or
#
# defog.file("key/to/my/file") do |handle|
# # ... access the proxy handle ...
# end
#
# The #proxy_path attribute method returns a Pathname
# giving the local proxy file location. Querying the attribute does
# not upload, download, synchronize, or otherwise interact with
# the cloud or local proxy file in any way -- it just returns a constructed
# Pathname. The proxy_path
is a deterministic function of the
# cloud key and Defog::Proxy#proxy_root, so you can rely on it not
# changing between independent accesses to a cloud file.
#
class Handle
attr_reader :key
attr_reader :proxy #:nodoc:
# Pathname where proxy file is, was, or will be located.
attr_reader :proxy_path
def initialize(proxy, key) #:nodoc:
@proxy = proxy
@key = key
@proxy_path = Pathname.new("#{@proxy.proxy_root}/#{@proxy.prefix}#{@key}").expand_path
end
def to_s
"<#{self.class}: key=#{key}>"
end
# Returns true if the remote cloud file exists
def exist?
!!fog_model
end
# Deletes the remote cloud file if it exists
def delete
fog_model and @proxy.fog_wrapper.fog_delete(@key)
end
# Returns the size of the remote cloud file, or nil if it doesn't exist
def size
fog_model.andand.content_length
end
# Returns the modification date of the remote cloud file, or nil if it
# doesn't exist
def last_modified
fog_model.andand.last_modified
end
# Returns the MD5 hash digest of the remote cloud file, or nil if it
# doesn't exist
#
def md5_hash
return @proxy.fog_wrapper.get_md5(@key) if exist?
end
# Returns a URL to access the remote cloud file. The options are
# storage-specific.
#
# For :AWS files, the option
# :expiry => time
# is required and specifies the expiration of time-limited URLS when
# using :AWS. The default is Time.now + 10.minutes
.
# The option
# :query => { ... }
# is optional and is passed directly to fog. Example usage might be
# :query => {'response-content-disposition' => 'attachment'}
#
# For :local cloud files, all options are ignored. If Rails is defined
# and the file is in Rails app's public directory, returns a path
# relative to the public directory. Otherwise returns a
# "file://"
URL
def url(opts={})
opts = opts.keyword_args(:expiry => Time.now + 10*60, :query => :optional)
@proxy.fog_wrapper.url(@key, opts)
end
# Returns the underlying Fog::Model, should you need it for something.
# Returns nil if the model doesn't exist.
#
# If Defog::Proxy.new was passed a :prefix, the Fog::Model key and
# Defog::Handle key are related by:
# handle.fog_model.key == defog.prefix + handle.key
def fog_model
@proxy.fog_wrapper.fog_head(@key)
end
# Returns a Defog::File object, which is a specialization of ::File.
#
# mode
can be the usual "r", "r+", "w", "w+", "a", or "a+" with the
# usual semantics. When opened in a readable mode ("r", "r+", "w+",
# "a+"), first caches the cloud file in the local proxy. When opened
# in a writeable mode ("r+", "w", "w+", "a", "a+"), arranges to upload
# the changes back to the cloud file at close time. The mode can be
# suffixed with 'b' or with ':' and encoding specifiers as usual.
#
# Like ::File.open, if called with a block yields the file object to
# the block and ensures the file will be closed when leaving the block.
#
# Normally the proxy file gets deleted upon close (after synchronized
# as needed) rather than persisted, although the default behavior can
# be controlled by Defog::Proxy.new. To specify persistence behavior
# on a per-file basis, use
# :persist => true-or-false
# See File#close for more details.
#
# If you are managing your cache size, when opening a proxy for writing
# you may want to provide a hint as to the expected size of the data:
# :size_hint => 500.kilobytes
# See README for more details.
#
# Normally upon close of a writeable proxy file, the synchronization
# happens synchronously and the close will wait, althrough the behavior
# can be controlled by Defog::Proxy.new. To specify synchronization
# behavior on a per-file basis, use
# :synchronize => true-or-false-or-async
# See File#close for more details.
#
def open(mode, opts={}, &block)
opts = opts.keyword_args(:persist => @proxy.persist, :synchronize => @proxy.synchronize, :size_hint => :optional)
File.open(opts.merge(:handle => self, :mode => mode), &block)
end
end
end