base = File.dirname(__FILE__) require File.join(base, 'libxml_loader') require File.join(base, 's3_acl') module S33r # Object representation of the content of a bucket. class BucketListing attr_reader :delimiter, :prefix, :marker, :max_keys, :is_truncated, :common_prefixes # Name of the bucket this listing is for. attr_reader :name # Hash of objects in this bucket, keyed by their S3 keys. attr_reader :contents # A NamedBucket instance associated with this listing. attr_accessor :named_bucket # The last key listed in this BucketListing. attr_reader :last_key # Set to true to show raw parsing errors, instead of the catch all error message # (useful for debugging). attr_accessor :raw # Create a new object representing a ListBucketResult. # # +bucket_listing_xml+ is a ListBucketResult document, as returned from a GET on a bucket # (see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/). # # +named_bucket+ can be set to an existing NamedBucket instance, so that any objects # inside this listing can be associated with that instance. This enables objects to be easily deleted # without having to create a new Client instance. # # If +raw+ is set to true, you get ugly parser errors. def initialize(bucket_listing_xml, named_bucket=nil, raw=false) @contents = {} @common_prefixes = {} # the NamedBucket instance associated with this listing (if any) @named_bucket = named_bucket @raw = raw set_listing_xml(bucket_listing_xml) end # Convert a ListBucketResult XML document into an object representation. def set_listing_xml(bucket_listing_xml) # proc to remove the namespace and parse the listing work = lambda do |bucket_listing_xml| # remove the namespace declaration: libxml doesn't like it bucket_listing_xml = S33r.remove_namespace(bucket_listing_xml) parse_listing(bucket_listing_xml) end if @raw work.call(bucket_listing_xml) else begin work.call(bucket_listing_xml) rescue message = "Cannot create bucket listing from supplied XML" message += " (was nil)" if bucket_listing_xml.nil? raise S33rException::InvalidBucketListing, message end end end # Parse raw XML ListBucketResponse from S3 into object instances. # The S3Objects are skeletons, and are not automatically populated # from S3 (their @value attributes are nil). To load the data into # an object, grab it from the listing and call its load method to # pull the data down from S3. def parse_listing(bucket_listing_xml) doc = XML.get_xml_doc(bucket_listing_xml) prop_setter = lambda do |prop, path| node = doc.find("//ListBucketResult/#{path}").to_a.first self.send("#{prop}=", node.content) if node end # metadata prop_setter.call(:name, 'Name') prop_setter.call(:delimiter, 'Delimiter') prop_setter.call(:prefix, 'Prefix') prop_setter.call(:marker, 'Marker') prop_setter.call(:max_keys, 'MaxKeys') prop_setter.call(:is_truncated, 'IsTruncated') # contents doc.find('//Contents').to_a.each do |node| obj = S3Object.from_xml_node(node) # Add to the content listing for the bucket @contents[obj.key] = obj end end # Get the last key in the contents hash. def last_key @contents.keys.last end # Return an object in this bucket by key. def [](key) @contents[key] end # Pretty listing of keys in alphabetical order. def pretty @contents.keys.sort.each { |k| puts k } end # Setters which perform some type casts and normalisation. private def name=(val); @name = string_prop_normalise(val); end def prefix=(val); @prefix = string_prop_normalise(val); end def delimiter=(val); @delimiter = string_prop_normalise(val); end def marker=(val); @marker = string_prop_normalise(val); end def max_keys=(val); @max_keys = val.to_i; end def is_truncated=(val); @is_truncated = ('true' == val || true == val || 'True' == val); end # normalise string properties: # if value for XML element is nil, set property to empty string def string_prop_normalise(val) val = '' if val.nil? val end end end