lib/cloudfiles/container.rb in cloudfiles-1.4.10 vs lib/cloudfiles/container.rb in cloudfiles-1.4.11
- old
+ new
@@ -1,255 +1,279 @@
module CloudFiles
class Container
# See COPYING for license information.
- # Copyright (c) 2009, Rackspace US, Inc.
+ # Copyright (c) 2011, Rackspace US, Inc.
# Name of the container which corresponds to the instantiated container class
attr_reader :name
- # Size of the container (in bytes)
- attr_reader :bytes
-
- # Number of objects in the container
- attr_reader :count
-
- # True if container is public, false if container is private
- attr_reader :cdn_enabled
-
- # CDN container TTL (if container is public)
- attr_reader :cdn_ttl
-
- # CDN container URL (if container if public)
- attr_reader :cdn_url
-
# The parent CloudFiles::Connection object for this container
attr_reader :connection
-
- # The container ACL on the User Agent
- attr_reader :user_agent_acl
-
- # The container ACL on the site Referrer
- attr_reader :referrer_acl
# Retrieves an existing CloudFiles::Container object tied to the current CloudFiles::Connection. If the requested
- # container does not exist, it will raise a NoSuchContainerException.
- #
+ # container does not exist, it will raise a CloudFiles::Exception::NoSuchContainer Exception.
+ #
# Will likely not be called directly, instead use connection.container('container_name') to retrieve the object.
- def initialize(connection,name)
+ def initialize(connection, name)
@connection = connection
@name = name
@storagehost = self.connection.storagehost
- @storagepath = self.connection.storagepath + "/" + URI.encode(@name).gsub(/&/,'%26')
+ @storagepath = self.connection.storagepath + "/" + CloudFiles.escape(@name)
@storageport = self.connection.storageport
@storagescheme = self.connection.storagescheme
@cdnmgmthost = self.connection.cdnmgmthost
- @cdnmgmtpath = self.connection.cdnmgmtpath + "/" + URI.encode(@name).gsub(/&/,'%26')
+ @cdnmgmtpath = self.connection.cdnmgmtpath + "/" + CloudFiles.escape(@name) if self.connection.cdnmgmtpath
@cdnmgmtport = self.connection.cdnmgmtport
@cdnmgmtscheme = self.connection.cdnmgmtscheme
- populate
+ # Load the metadata now, so we'll get a CloudFiles::Exception::NoSuchContainer exception should the container
+ # not exist.
+ self.metadata
end
- # Retrieves data about the container and populates class variables. It is automatically called
- # when the Container class is instantiated. If you need to refresh the variables, such as
- # size, count, cdn_enabled, cdn_ttl, and cdn_url, this method can be called again.
+ # Refreshes data about the container and populates class variables. Items are otherwise
+ # loaded in a lazy loaded fashion.
#
# container.count
# => 2
# [Upload new file to the container]
# container.count
# => 2
# container.populate
# container.count
# => 3
- def populate
- # Get the size and object count
- response = self.connection.cfreq("HEAD",@storagehost,@storagepath+"/",@storageport,@storagescheme)
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code =~ /^20/)
- @bytes = response["x-container-bytes-used"].to_i
- @count = response["x-container-object-count"].to_i
+ def refresh
+ @metadata = @cdn_metadata = nil
+ true
+ end
+ alias :populate :refresh
- # Get the CDN-related details
- response = self.connection.cfreq("HEAD",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme)
- @cdn_enabled = ((response["x-cdn-enabled"] || "").downcase == "true") ? true : false
- @cdn_ttl = @cdn_enabled ? response["x-ttl"].to_i : false
- @cdn_url = @cdn_enabled ? response["x-cdn-uri"] : false
- @user_agent_acl = response["x-user-agent-acl"]
- @referrer_acl = response["x-referrer-acl"]
- if @cdn_enabled
- @cdn_log = response["x-log-retention"] == "False" ? false : true
- else
- @cdn_log = false
- end
+ # Retrieves Metadata for the container
+ def metadata
+ @metadata ||= (
+ response = self.connection.cfreq("HEAD", @storagehost, @storagepath + "/", @storageport, @storagescheme)
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code =~ /^20/)
+ {:bytes => response["x-container-bytes-used"].to_i, :count => response["x-container-object-count"].to_i}
+ )
+ end
- true
+ # Retrieves CDN-Enabled Meta Data
+ def cdn_metadata
+ @cdn_metadata ||= (
+ response = self.connection.cfreq("HEAD", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme)
+ cdn_enabled = ((response["x-cdn-enabled"] || "").downcase == "true") ? true : false
+ {
+ :cdn_enabled => cdn_enabled,
+ :cdn_ttl => cdn_enabled ? response["x-ttl"].to_i : nil,
+ :cdn_url => cdn_enabled ? response["x-cdn-uri"] : nil,
+ :user_agent_acl => response["x-user-agent-acl"],
+ :referrer_acl => response["x-referrer-acl"],
+ :cdn_log => (cdn_enabled and response["x-log-retention"] == "True") ? true : false
+ }
+ )
end
- alias :refresh :populate
-
+
+ # Size of the container (in bytes)
+ def bytes
+ self.metadata[:bytes]
+ end
+
+ # Number of objects in the container
+ def count
+ self.metadata[:count]
+ end
+
+ # Returns true if the container is public and CDN-enabled. Returns false otherwise.
+ #
+ # Aliased as container.public?
+ #
+ # public_container.cdn_enabled?
+ # => true
+ #
+ # private_container.public?
+ # => false
+ def cdn_enabled
+ self.cdn_metadata[:cdn_enabled]
+ end
+ alias :cdn_enabled? :cdn_enabled
+ alias :public? :cdn_enabled
+
+ # CDN container TTL (if container is public)
+ def cdn_ttl
+ self.cdn_metadata[:cdn_ttl]
+ end
+
+ # CDN container URL (if container if public)
+ def cdn_url
+ self.cdn_metadata[:cdn_url]
+ end
+
+ # The container ACL on the User Agent
+ def user_agent_acl
+ self.cdn_metadata[:user_agent_acl]
+ end
+
+ # The container ACL on the site Referrer
+ def referrer_acl
+ self.cdn_metadata[:referrer_acl]
+ end
+
# Returns true if log retention is enabled on this container, false otherwise
- def log_retention?
- @cdn_log
+ def cdn_log
+ self.cdn_metadata[:cdn_log]
end
-
+ alias :log_retention? :cdn_log
+ alias :cdn_log? :cdn_log
+
# Change the log retention status for this container. Values are true or false.
#
- # These logs will be periodically (at unpredictable intervals) compressed and uploaded
+ # These logs will be periodically (at unpredictable intervals) compressed and uploaded
# to a “.CDN_ACCESS_LOGS” container in the form of “container_name.YYYYMMDDHH-XXXX.gz”.
def log_retention=(value)
- response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,{"x-log-retention" => value.to_s.capitalize})
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "201" or response.code == "202")
- return true
+ response = self.connection.cfreq("POST", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme, {"x-log-retention" => value.to_s.capitalize})
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "201" or response.code == "202")
+ return true
end
-
+
# Returns the CloudFiles::StorageObject for the named object. Refer to the CloudFiles::StorageObject class for available
# methods. If the object exists, it will be returned. If the object does not exist, a NoSuchObjectException will be thrown.
- #
+ #
# object = container.object('test.txt')
# object.data
# => "This is test data"
#
# object = container.object('newfile.txt')
# => NoSuchObjectException: Object newfile.txt does not exist
def object(objectname)
- o = CloudFiles::StorageObject.new(self,objectname,true)
+ o = CloudFiles::StorageObject.new(self, objectname, true)
return o
end
alias :get_object :object
-
- # Gathers a list of all available objects in the current container and returns an array of object names.
+
+ # Gathers a list of all available objects in the current container and returns an array of object names.
# container = cf.container("My Container")
# container.objects #=> [ "cat", "dog", "donkey", "monkeydir", "monkeydir/capuchin"]
# Pass a limit argument to limit the list to a number of objects:
# container.objects(:limit => 1) #=> [ "cat" ]
# Pass an marker with or without a limit to start the list at a certain object:
# container.objects(:limit => 1, :marker => 'dog') #=> [ "donkey" ]
# Pass a prefix to search for objects that start with a certain string:
# container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
# Only search within a certain pseudo-filesystem path:
# container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
+ # Only grab "virtual directories", based on a single-character delimiter (no "directory" objects required):
+ # container.objects(:delimiter => '/') #=> ["monkeydir"]
# All arguments to this method are optional.
- #
+ #
# Returns an empty array if no object exist in the container. Throws an InvalidResponseException
# if the request fails.
def objects(params = {})
- params[:marker] ||= params[:offset]
- paramarr = []
- paramarr << ["limit=#{URI.encode(params[:limit].to_s).gsub(/&/,'%26')}"] if params[:limit]
- paramarr << ["marker=#{URI.encode(params[:marker].to_s).gsub(/&/,'%26')}"] if params[:marker]
- paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
- paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
- paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
- response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
+ params[:marker] ||= params[:offset] unless params[:offset].nil?
+ query = []
+ params.each do |param, value|
+ if [:limit, :marker, :prefix, :path, :delimiter].include? param
+ query << "#{param}=#{CloudFiles.escape(value.to_s)}"
+ end
+ end
+ response = self.connection.cfreq("GET", @storagehost, "#{@storagepath}?#{query.join '&'}", @storageport, @storagescheme)
return [] if (response.code == "204")
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
return CloudFiles.lines(response.body)
end
alias :list_objects :objects
# Retrieves a list of all objects in the current container along with their size in bytes, hash, and content_type.
# If no objects exist, an empty hash is returned. Throws an InvalidResponseException if the request fails. Takes a
# parameter hash as an argument, in the same form as the objects method.
- #
+ #
# Returns a hash in the same format as the containers_detail from the CloudFiles class.
#
# container.objects_detail
- # => {"test.txt"=>{:content_type=>"application/octet-stream",
- # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
- # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
- # :bytes=>"16"},
- # "new.txt"=>{:content_type=>"application/octet-stream",
- # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
- # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
+ # => {"test.txt"=>{:content_type=>"application/octet-stream",
+ # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
+ # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
+ # :bytes=>"16"},
+ # "new.txt"=>{:content_type=>"application/octet-stream",
+ # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
+ # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
# :bytes=>"22"}
# }
def objects_detail(params = {})
- params[:marker] ||= params[:offset]
- paramarr = []
- paramarr << ["format=xml"]
- paramarr << ["limit=#{URI.encode(params[:limit].to_s).gsub(/&/,'%26')}"] if params[:limit]
- paramarr << ["marker=#{URI.encode(params[:marker].to_s).gsub(/&/,'%26')}"] if params[:marker]
- paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
- paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
- paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
- response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
+ params[:marker] ||= params[:offset] unless params[:offset].nil?
+ query = ["format=xml"]
+ params.each do |param, value|
+ if [:limit, :marker, :prefix, :path, :delimiter].include? param
+ query << "#{param}=#{CloudFiles.escape(value.to_s)}"
+ end
+ end
+ response = self.connection.cfreq("GET", @storagehost, "#{@storagepath}?#{query.join '&'}", @storageport, @storagescheme)
return {} if (response.code == "204")
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
doc = REXML::Document.new(response.body)
detailhash = {}
doc.elements.each("container/object") { |o|
detailhash[o.elements["name"].text] = { :bytes => o.elements["bytes"].text, :hash => o.elements["hash"].text, :content_type => o.elements["content_type"].text, :last_modified => DateTime.parse(o.elements["last_modified"].text) }
}
doc = nil
return detailhash
end
alias :list_objects_info :objects_detail
- # Returns true if the container is public and CDN-enabled. Returns false otherwise.
- #
- # public_container.public?
- # => true
- #
- # private_container.public?
- # => false
- def public?
- return @cdn_enabled
- end
-
# Returns true if a container is empty and returns false otherwise.
#
# new_container.empty?
# => true
#
# full_container.empty?
# => false
def empty?
- return (@count.to_i == 0)? true : false
+ return (metadata[:count].to_i == 0)? true : false
end
# Returns true if object exists and returns false otherwise.
#
# container.object_exists?('goodfile.txt')
# => true
#
# container.object_exists?('badfile.txt')
# => false
def object_exists?(objectname)
- response = self.connection.cfreq("HEAD",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}",@storageport,@storagescheme)
+ response = self.connection.cfreq("HEAD", @storagehost, "#{@storagepath}/#{CloudFiles.escape objectname}", @storageport, @storagescheme)
return (response.code =~ /^20/)? true : false
end
- # Creates a new CloudFiles::StorageObject in the current container.
+ # Creates a new CloudFiles::StorageObject in the current container.
#
# If an object with the specified name exists in the current container, that object will be returned. Otherwise,
# an empty new object will be returned.
#
# Passing in the optional make_path argument as true will create zero-byte objects to simulate a filesystem path
- # to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
+ # to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
# be used in the Container.objects method.
- def create_object(objectname,make_path = false)
- CloudFiles::StorageObject.new(self,objectname,false,make_path)
+ def create_object(objectname, make_path = false)
+ CloudFiles::StorageObject.new(self, objectname, false, make_path)
end
-
- # Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
+
+ # Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
# NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request fails.
#
# container.delete_object('new.txt')
# => true
#
# container.delete_object('nonexistent_file.txt')
# => NoSuchObjectException: Object nonexistent_file.txt does not exist
def delete_object(objectname)
- response = self.connection.cfreq("DELETE",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}",@storageport,@storagescheme)
- raise NoSuchObjectException, "Object #{objectname} does not exist" if (response.code == "404")
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
+ response = self.connection.cfreq("DELETE", @storagehost, "#{@storagepath}/#{CloudFiles.escape objectname}", @storageport, @storagescheme)
+ raise CloudFiles::Exception::NoSuchObject, "Object #{objectname} does not exist" if (response.code == "404")
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
true
end
# Makes a container publicly available via the Cloud Files CDN and returns true upon success. Throws NoSuchContainerException
# if the container doesn't exist or if the request fails.
- #
+ #
# Takes an optional hash of options, including:
#
# :ttl, which is the CDN cache TTL in seconds (default 86400 seconds or 1 day, minimum 3600 or 1 hour, maximum 259200 or 3 days)
#
# :user_agent_acl, a Perl-compatible regular expression limiting access to this container to user agents matching the given regular expression
@@ -262,20 +286,20 @@
if options.is_a?(Fixnum)
print "DEPRECATED: make_public takes a hash of options now, instead of a TTL number"
ttl = options
options = {:ttl => ttl}
end
-
- response = self.connection.cfreq("PUT",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme)
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
+ response = self.connection.cfreq("PUT", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme)
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
+
headers = { "X-TTL" => options[:ttl].to_s , "X-CDN-Enabled" => "True" }
headers["X-User-Agent-ACL"] = options[:user_agent_acl] if options[:user_agent_acl]
headers["X-Referrer-ACL"] = options[:referrer_acl] if options[:referrer_acl]
- response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,headers)
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
- populate
+ response = self.connection.cfreq("POST", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme, headers)
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
+ refresh
true
end
# Makes a container private and returns true upon success. Throws NoSuchContainerException
# if the container doesn't exist or if the request fails.
@@ -284,15 +308,15 @@
#
# container.make_private
# => true
def make_private
headers = { "X-CDN-Enabled" => "False" }
- response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,headers)
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
- populate
+ response = self.connection.cfreq("POST", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme, headers)
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
+ refresh
true
end
-
+
def to_s # :nodoc:
@name
end
end