diff --git a/.gitignore b/.gitignore
index cf4e1e4..387eb72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.rake_tasks
site/public/index.html
pkg/
+.idea/*
diff --git a/bin/s3sh b/bin/s3sh
old mode 100644
new mode 100755
diff --git a/bin/setup.rb b/bin/setup.rb
old mode 100644
new mode 100755
diff --git a/lib/aws/bucket.rb b/lib/aws/bucket.rb
new file mode 100644
index 0000000..08ab91e
--- /dev/null
+++ b/lib/aws/bucket.rb
@@ -0,0 +1,11 @@
+require_relative 'writer'
+
+class Bucket
+ def initialize(bucket_name)
+ end
+
+ def objects(name = nil, options = {})
+ Writer.new
+ end
+
+end
\ No newline at end of file
diff --git a/lib/aws/s3.rb b/lib/aws/s3.rb
index e6c23f5..2a7178d 100644
--- a/lib/aws/s3.rb
+++ b/lib/aws/s3.rb
@@ -1,79 +1,14 @@
-require 'cgi'
-require 'uri'
-require 'openssl'
-require 'digest/sha1'
-require 'net/https'
-require 'time'
-require 'date'
-require 'open-uri'
-
-$:.unshift(File.dirname(__FILE__))
-require 's3/extensions'
-require_library_or_gem 'builder' unless defined? Builder
-require_library_or_gem 'mime/types', 'mime-types' unless defined? MIME::Types
-
-require 's3/base'
-require 's3/version'
-require 's3/parsing'
-require 's3/acl'
-require 's3/logging'
-require 's3/bittorrent'
-require 's3/service'
-require 's3/owner'
-require 's3/bucket'
-require 's3/object'
-require 's3/error'
-require 's3/exceptions'
-require 's3/connection'
-require 's3/authentication'
-require 's3/response'
+require_relative 'bucket'
module AWS
- module S3
- UNSAFE_URI = /[^-_.!~*'()a-zA-Z\d;\/?:@&=$,\[\]]/n
-
- def self.escape_uri(path)
- URI.escape(path.to_s, UNSAFE_URI)
- end
-
- def self.escape_uri_component(path)
- escaped = escape_uri(path)
- escaped.gsub!(/=/, '%3D')
- escaped.gsub!(/&/, '%26')
- escaped.gsub!(/;/, '%3B')
- escaped
+ class S3
+ def initialize(options)
end
- Base.class_eval do
- include AWS::S3::Connection::Management
+ def buckets
+ { 'exportspdf' => Bucket.new('exportspdf') }
end
- Bucket.class_eval do
- include AWS::S3::Logging::Management
- include AWS::S3::ACL::Bucket
- end
-
- S3Object.class_eval do
- include AWS::S3::ACL::S3Object
- include AWS::S3::BitTorrent
- end
end
end
-
-require_library_or_gem 'xmlsimple', 'xml-simple' unless defined? XmlSimple
-# If libxml is installed, we use the FasterXmlSimple library, that provides most of the functionality of XmlSimple
-# except it uses the xml/libxml library for xml parsing (rather than REXML). If libxml isn't installed, we just fall back on
-# XmlSimple.
-AWS::S3::Parsing.parser =
- begin
- require_library_or_gem 'xml/libxml'
- # Older version of libxml aren't stable (bus error when requesting attributes that don't exist) so we
- # have to use a version greater than '0.3.8.2'.
- raise LoadError unless XML::Parser::VERSION > '0.3.8.2'
- $:.push(File.join(File.dirname(__FILE__), '..', '..', 'support', 'faster-xml-simple', 'lib'))
- require_library_or_gem 'faster_xml_simple'
- FasterXmlSimple
- rescue LoadError
- XmlSimple
- end
diff --git a/lib/aws/s3/acl.rb b/lib/aws/s3/acl.rb
deleted file mode 100644
index b780aaf..0000000
--- a/lib/aws/s3/acl.rb
+++ /dev/null
@@ -1,636 +0,0 @@
-module AWS
- module S3
- # By default buckets are private. This means that only the owner has access rights to the bucket and its objects.
- # Objects in that bucket inherit the permission of the bucket unless otherwise specified. When an object is private, the owner can
- # generate a signed url that exposes the object to anyone who has that url. Alternatively, buckets and objects can be given other
- # access levels. Several canned access levels are defined:
- #
- # * :private - Owner gets FULL_CONTROL. No one else has any access rights. This is the default.
- # * :public_read - Owner gets FULL_CONTROL and the anonymous principal is granted READ access. If this policy is used on an object, it can be read from a browser with no authentication.
- # * :public_read_write - Owner gets FULL_CONTROL, the anonymous principal is granted READ and WRITE access. This is a useful policy to apply to a bucket, if you intend for any anonymous user to PUT objects into the bucket.
- # * :authenticated_read - Owner gets FULL_CONTROL, and any principal authenticated as a registered Amazon S3 user is granted READ access.
- #
- # You can set a canned access level when you create a bucket or an object by using the :access option:
- #
- # S3Object.store(
- # 'kiss.jpg',
- # data,
- # 'marcel',
- # :access => :public_read
- # )
- #
- # Since the image we created is publicly readable, we can access it directly from a browser by going to the corresponding bucket name
- # and specifying the object's key without a special authenticated url:
- #
- # http://s3.amazonaws.com/marcel/kiss.jpg
- #
- # ==== Building custum access policies
- #
- # For both buckets and objects, you can use the acl method to see its access control policy:
- #
- # policy = S3Object.acl('kiss.jpg', 'marcel')
- # pp policy.grants
- # [#,
- # #]
- #
- # Policies are made up of one or more grants which grant a specific permission to some grantee. Here we see the default FULL_CONTROL grant
- # to the owner of this object. There is also READ permission granted to the Allusers Group, which means anyone has read access for the object.
- #
- # Say we wanted to grant access to anyone to read the access policy of this object. The current READ permission only grants them permission to read
- # the object itself (for example, from a browser) but it does not allow them to read the access policy. For that we will need to grant the AllUsers group the READ_ACP permission.
- #
- # First we'll create a new grant object:
- #
- # grant = ACL::Grant.new
- # # => #
- # grant.permission = 'READ_ACP'
- #
- # Now we need to indicate who this grant is for. In other words, who the grantee is:
- #
- # grantee = ACL::Grantee.new
- # # => #
- #
- # There are three ways to specify a grantee: 1) by their internal amazon id, such as the one returned with an object's Owner,
- # 2) by their Amazon account email address or 3) by specifying a group. As of this writing you can not create custom groups, but
- # Amazon does provide three already: AllUsers, Authenticated and LogDelivery. In this case we want to provide the grant to all users.
- # This effectively means "anyone".
- #
- # grantee.group = 'AllUsers'
- #
- # Now that our grantee is setup, we'll associate it with the grant:
- #
- # grant.grantee = grantee
- # grant
- # # => #
- #
- # Are grant has all the information we need. Now that it's ready, we'll add it on to the object's access control policy's list of grants:
- #
- # policy.grants << grant
- # pp policy.grants
- # [#,
- # #,
- # #]
- #
- # Now that the policy has the new grant, we reuse the acl method to persist the policy change:
- #
- # S3Object.acl('kiss.jpg', 'marcel', policy)
- #
- # If we fetch the object's policy again, we see that the grant has been added:
- #
- # pp S3Object.acl('kiss.jpg', 'marcel').grants
- # [#,
- # #,
- # #]
- #
- # If we were to access this object's acl url from a browser:
- #
- # http://s3.amazonaws.com/marcel/kiss.jpg?acl
- #
- # we would be shown its access control policy.
- #
- # ==== Pre-prepared grants
- #
- # Alternatively, the ACL::Grant class defines a set of stock grant policies that you can fetch by name. In most cases, you can
- # just use one of these pre-prepared grants rather than building grants by hand. Two of these stock policies are :public_read
- # and :public_read_acp, which happen to be the two grants that we built by hand above. In this case we could have simply written:
- #
- # policy.grants << ACL::Grant.grant(:public_read)
- # policy.grants << ACL::Grant.grant(:public_read_acp)
- # S3Object.acl('kiss.jpg', 'marcel', policy)
- #
- # The full details can be found in ACL::Policy, ACL::Grant and ACL::Grantee.
- module ACL
- # The ACL::Policy class lets you inspect and modify access controls for buckets and objects.
- # A policy is made up of one or more Grants which specify a permission and a Grantee to whom that permission is granted.
- #
- # Buckets and objects are given a default access policy which contains one grant permitting the owner of the bucket or object
- # FULL_CONTROL over its contents. This means they can read the object, write to the object, as well as read and write its
- # policy.
- #
- # The acl method for both buckets and objects returns the policy object for that entity:
- #
- # policy = Bucket.acl('some-bucket')
- #
- # The grants method of a policy exposes its grants. You can treat this collection as an array and push new grants onto it:
- #
- # policy.grants << grant
- #
- # Check the documentation for Grant and Grantee for more details on how to create new grants.
- class Policy
- include SelectiveAttributeProxy #:nodoc:
- attr_accessor :owner, :grants
-
- def initialize(attributes = {})
- @attributes = attributes
- @grants = [].extend(GrantListExtensions)
- extract_owner! if owner?
- extract_grants! if grants?
- end
-
- # The xml representation of the policy.
- def to_xml
- Builder.new(owner, grants).to_s
- end
-
- private
-
- def owner?
- attributes.has_key?('owner') || !owner.nil?
- end
-
- def grants?
- (attributes.has_key?('access_control_list') && attributes['access_control_list']['grant']) || !grants.empty?
- end
-
- def extract_owner!
- @owner = Owner.new(attributes.delete('owner'))
- end
-
- def extract_grants!
- attributes['access_control_list']['grant'].each do |grant|
- grants << Grant.new(grant)
- end
- end
-
- module GrantListExtensions #:nodoc:
- def include?(grant)
- case grant
- when Symbol
- super(ACL::Grant.grant(grant))
- else
- super
- end
- end
-
- def delete(grant)
- case grant
- when Symbol
- super(ACL::Grant.grant(grant))
- else
- super
- end
- end
-
- # Two grant lists are equal if they have identical grants both in terms of permission and grantee.
- def ==(grants)
- size == grants.size && all? {|grant| grants.include?(grant)}
- end
- end
-
- class Builder < XmlGenerator #:nodoc:
- attr_reader :owner, :grants
- def initialize(owner, grants)
- @owner = owner
- @grants = grants.uniq # There could be some duplicate grants
- super()
- end
-
- def build
- xml.tag!('AccessControlPolicy', 'xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do
- xml.Owner do
- xml.ID owner.id
- xml.DisplayName owner.display_name
- end
-
- xml.AccessControlList do
- xml << grants.map {|grant| grant.to_xml}.join("\n")
- end
- end
- end
- end
- end
-
- # A Policy is made up of one or more Grant objects. A grant sets a specific permission and grants it to the associated grantee.
- #
- # When creating a new grant to add to a policy, you need only set its permission and then associate with a Grantee.
- #
- # grant = ACL::Grant.new
- # => #
- #
- # Here we see that neither the permission nor the grantee have been set. Let's make this grant provide the READ permission.
- #
- # grant.permission = 'READ'
- # grant
- # => #
- #
- # Now let's assume we have a grantee to the AllUsers group already set up. Just associate that grantee with our grant.
- #
- # grant.grantee = all_users_group_grantee
- # grant
- # => #
- #
- # And now are grant is complete. It provides READ permission to the AllUsers group, effectively making this object publicly readable
- # without any authorization.
- #
- # Assuming we have some object's policy available in a local variable called policy, we can now add this grant onto its
- # collection of grants.
- #
- # policy.grants << grant
- #
- # And then we send the updated policy to the S3 servers.
- #
- # some_s3object.acl(policy)
- class Grant
- include SelectiveAttributeProxy #:nodoc:
- constant :VALID_PERMISSIONS, %w(READ WRITE READ_ACP WRITE_ACP FULL_CONTROL)
- attr_accessor :grantee
-
- class << self
- # Returns stock grants with name type.
- #
- # public_read_grant = ACL::Grant.grant :public_read
- # => #
- #
- # Valid stock grant types are:
- #
- # * :authenticated_read
- # * :authenticated_read_acp
- # * :authenticated_write
- # * :authenticated_write_acp
- # * :logging_read
- # * :logging_read_acp
- # * :logging_write
- # * :logging_write_acp
- # * :public_read
- # * :public_read_acp
- # * :public_write
- # * :public_write_acp
- def grant(type)
- case type
- when *stock_grant_map.keys
- build_stock_grant_for type
- else
- raise ArgumentError, "Unknown grant type `#{type}'"
- end
- end
-
- private
- def stock_grant_map
- grant = lambda {|permission, group| {:permission => permission, :group => group}}
- groups = {:public => 'AllUsers', :authenticated => 'Authenticated', :logging => 'LogDelivery'}
- permissions = %w(READ WRITE READ_ACP WRITE_ACP)
- stock_grants = {}
- groups.each do |grant_group_name, group_name|
- permissions.each do |permission|
- stock_grants["#{grant_group_name}_#{permission.downcase}".to_sym] = grant[permission, group_name]
- end
- end
- stock_grants
- end
- memoized :stock_grant_map
-
- def build_stock_grant_for(type)
- stock_grant = stock_grant_map[type]
- grant = new do |g|
- g.permission = stock_grant[:permission]
- end
- grant.grantee = Grantee.new do |gr|
- gr.group = stock_grant[:group]
- end
- grant
- end
- end
-
- def initialize(attributes = {})
- attributes = {'permission' => nil}.merge(attributes)
- @attributes = attributes
- extract_grantee!
- yield self if block_given?
- end
-
- # Set the permission for this grant.
- #
- # grant.permission = 'READ'
- # grant
- # => #
- #
- # If the specified permisison level is not valid, an InvalidAccessControlLevel exception will be raised.
- def permission=(permission_level)
- unless self.class.valid_permissions.include?(permission_level)
- raise InvalidAccessControlLevel.new(self.class.valid_permissions, permission_level)
- end
- attributes['permission'] = permission_level
- end
-
- # The xml representation of this grant.
- def to_xml
- Builder.new(permission, grantee).to_s
- end
-
- def inspect #:nodoc:
- "#<%s:0x%s %s>" % [self.class, object_id, self]
- end
-
- def to_s #:nodoc:
- [permission || '(permission)', 'to', grantee ? grantee.type_representation : '(grantee)'].join ' '
- end
-
- def eql?(grant) #:nodoc:
- # This won't work for an unposted AmazonCustomerByEmail because of the normalization
- # to CanonicalUser but it will work for groups.
- to_s == grant.to_s
- end
- alias_method :==, :eql?
-
- def hash #:nodoc:
- to_s.hash
- end
-
- private
-
- def extract_grantee!
- @grantee = Grantee.new(attributes['grantee']) if attributes['grantee']
- end
-
- class Builder < XmlGenerator #:nodoc:
- attr_reader :grantee, :permission
-
- def initialize(permission, grantee)
- @permission = permission
- @grantee = grantee
- super()
- end
-
- def build
- xml.Grant do
- xml << grantee.to_xml
- xml.Permission permission
- end
- end
- end
- end
-
- # Grants bestow a access permission to grantees. Each grant of some access control list Policy is associated with a grantee.
- # There are three ways of specifying a grantee at the time of this writing.
- #
- # * By canonical user - This format uses the id of a given Amazon account. The id value for a given account is available in the
- # Owner object of a bucket, object or policy.
- #
- # grantee.id = 'bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1'
- #
- # Often the id will just be fetched from some owner object.
- #
- # grantee.id = some_object.owner.id
- #
- # * By amazon email address - You can specify an email address for any Amazon account. The Amazon account need not be signed up with the S3 service.
- # though it must be unique across the entire Amazon system. This email address is normalized into a canonical user representation once the grant
- # has been sent back up to the S3 servers.
- #
- # grantee.email_address = 'joe@example.org'
- #
- # * By group - As of this writing you can not create custom groups, but Amazon provides three group that you can use. See the documentation for the
- # Grantee.group= method for details.
- #
- # grantee.group = 'Authenticated'
- class Grantee
- include SelectiveAttributeProxy #:nodoc:
-
- undef_method :id if method_defined?(:id) # Get rid of Object#id
-
- def initialize(attributes = {})
- # Set default values for attributes that may not be passed in but we still want the object
- # to respond to
- attributes = {'id' => nil, 'display_name' => nil, 'email_address' => nil, 'uri' => nil}.merge(attributes)
- @attributes = attributes
- extract_type!
- yield self if block_given?
- end
-
- # The xml representation of the current grantee object.
- def to_xml
- Builder.new(self).to_s
- end
-
- # Returns the type of grantee. Will be one of CanonicalUser, AmazonCustomerByEmail or Group.
- def type
- return attributes['type'] if attributes['type']
-
- # Lookups are in order of preference so if, for example, you set the uri but display_name and id are also
- # set, we'd rather go with the canonical representation.
- if display_name && id
- 'CanonicalUser'
- elsif email_address
- 'AmazonCustomerByEmail'
- elsif uri
- 'Group'
- end
- end
-
- # Sets the grantee's group by name.
- #
- # grantee.group = 'AllUsers'
- #
- # Currently, valid groups defined by S3 are:
- #
- # * AllUsers: This group represents anyone. In other words, an anonymous request.
- # * Authenticated: Any authenticated account on the S3 service.
- # * LogDelivery: The entity that delivers bucket access logs.
- def group=(group_name)
- section = %w(AllUsers Authenticated).include?(group_name) ? 'global' : 's3'
- self.uri = "http://acs.amazonaws.com/groups/#{section}/#{group_name}"
- end
-
- # Returns the grantee's group. If the grantee is not a group, nil is returned.
- def group
- return unless uri
- uri[%r([^/]+$)]
- end
-
- def type_representation #:nodoc:
- case type
- when 'CanonicalUser' then display_name || id
- when 'AmazonCustomerByEmail' then email_address
- when 'Group' then "#{group} Group"
- end
- end
-
- def inspect #:nodoc:
- "#<%s:0x%s %s>" % [self.class, object_id, type_representation || '(type not set yet)']
- end
-
- private
- def extract_type!
- attributes['type'] = attributes.delete('xsi:type')
- end
-
- class Builder < XmlGenerator #:nodoc:
-
- def initialize(grantee)
- @grantee = grantee
- super()
- end
-
- def build
- xml.tag!('Grantee', attributes) do
- representation
- end
- end
-
- private
- attr_reader :grantee
-
- def representation
- case grantee.type
- when 'CanonicalUser'
- xml.ID grantee.id
- xml.DisplayName grantee.display_name
- when 'AmazonCustomerByEmail'
- xml.EmailAddress grantee.email_address
- when 'Group'
- xml.URI grantee.uri
- end
- end
-
- def attributes
- {'xsi:type' => grantee.type, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'}
- end
- end
- end
-
- module Bucket
- def self.included(klass) #:nodoc:
- klass.extend(ClassMethods)
- end
-
- module ClassMethods
- # The acl method is the single point of entry for reading and writing access control list policies for a given bucket.
- #
- # # Fetch the acl for the 'marcel' bucket
- # policy = Bucket.acl 'marcel'
- #
- # # Modify the policy ...
- # # ...
- #
- # # Send updated policy back to the S3 servers
- # Bucket.acl 'marcel', policy
- def acl(name = nil, policy = nil)
- if name.is_a?(ACL::Policy)
- policy = name
- name = nil
- end
-
- path = path(name) << '?acl'
- respond_with ACL::Policy::Response do
- policy ? put(path, {}, policy.to_xml) : ACL::Policy.new(get(path(name) << '?acl').policy)
- end
- end
- end
-
- # The acl method returns and updates the acl for a given bucket.
- #
- # # Fetch a bucket
- # bucket = Bucket.find 'marcel'
- #
- # # Add a grant to the bucket's policy
- # bucket.acl.grants << some_grant
- #
- # # Write the changes to the policy
- # bucket.acl(bucket.acl)
- def acl(reload = false)
- policy = reload.is_a?(ACL::Policy) ? reload : nil
- expirable_memoize(reload) do
- self.class.acl(name, policy) if policy
- self.class.acl(name)
- end
- end
- end
-
- module S3Object
- def self.included(klass) #:nodoc:
- klass.extend(ClassMethods)
- end
-
- module ClassMethods
- # The acl method is the single point of entry for reading and writing access control list policies for a given object.
- #
- # # Fetch the acl for the 'kiss.jpg' object in the 'marcel' bucket
- # policy = S3Object.acl 'kiss.jpg', 'marcel'
- #
- # # Modify the policy ...
- # # ...
- #
- # # Send updated policy back to the S3 servers
- # S3Object.acl 'kiss.jpg', 'marcel', policy
- def acl(name, bucket = nil, policy = nil)
- # We're using the second argument as the ACL::Policy
- if bucket.is_a?(ACL::Policy)
- policy = bucket
- bucket = nil
- end
-
- bucket = bucket_name(bucket)
- path = path!(bucket, name) << '?acl'
-
- respond_with ACL::Policy::Response do
- policy ? put(path, {}, policy.to_xml) : ACL::Policy.new(get(path).policy)
- end
- end
- end
-
- # The acl method returns and updates the acl for a given s3 object.
- #
- # # Fetch a the object
- # object = S3Object.find 'kiss.jpg', 'marcel'
- #
- # # Add a grant to the object's
- # object.acl.grants << some_grant
- #
- # # Write the changes to the policy
- # object.acl(object.acl)
- def acl(reload = false)
- policy = reload.is_a?(ACL::Policy) ? reload : nil
- expirable_memoize(reload) do
- self.class.acl(key, bucket.name, policy) if policy
- self.class.acl(key, bucket.name)
- end
- end
- end
-
- class OptionProcessor #:nodoc:
- attr_reader :options
- class << self
- def process!(options)
- new(options).process!
- end
- end
-
- def initialize(options)
- options.to_normalized_options!
- @options = options
- @access_level = extract_access_level
- end
-
- def process!
- return unless access_level_specified?
- validate!
- options['x-amz-acl'] = access_level
- end
-
- private
- def extract_access_level
- options.delete('access') || options.delete('x-amz-acl')
- end
-
- def validate!
- raise InvalidAccessControlLevel.new(valid_levels, access_level) unless valid?
- end
-
- def valid?
- valid_levels.include?(access_level)
- end
-
- def access_level_specified?
- !@access_level.nil?
- end
-
- def valid_levels
- %w(private public-read public-read-write authenticated-read)
- end
-
- def access_level
- @normalized_access_level ||= @access_level.to_header
- end
- end
- end
- end
-end
diff --git a/lib/aws/s3/authentication.rb b/lib/aws/s3/authentication.rb
deleted file mode 100644
index 0307c80..0000000
--- a/lib/aws/s3/authentication.rb
+++ /dev/null
@@ -1,260 +0,0 @@
-module AWS
- module S3
- # All authentication is taken care of for you by the AWS::S3 library. None the less, some details of the two types
- # of authentication and when they are used may be of interest to some.
- #
- # === Header based authentication
- #
- # Header based authentication is achieved by setting a special Authorization header whose value
- # is formatted like so:
- #
- # "AWS #{access_key_id}:#{encoded_canonical}"
- #
- # The access_key_id is the public key that is assigned by Amazon for a given account which you use when
- # establishing your initial connection. The encoded_canonical is computed according to rules layed out
- # by Amazon which we will describe presently.
- #
- # ==== Generating the encoded canonical string
- #
- # The "canonical string", generated by the CanonicalString class, is computed by collecting the current request method,
- # a set of significant headers of the current request, and the current request path into a string.
- # That canonical string is then encrypted with the secret_access_key assigned by Amazon. The resulting encrypted canonical
- # string is then base 64 encoded.
- #
- # === Query string based authentication
- #
- # When accessing a restricted object from the browser, you can authenticate via the query string, by setting the following parameters:
- #
- # "AWSAccessKeyId=#{access_key_id}&Expires=#{expires}&Signature=#{encoded_canonical}"
- #
- # The QueryString class is responsible for generating the appropriate parameters for authentication via the
- # query string.
- #
- # The access_key_id and encoded_canonical are the same as described in the Header based authentication section.
- # The expires value dictates for how long the current url is valid (by default, it will expire in 5 minutes). Expiration can be specified
- # either by an absolute time (expressed in seconds since the epoch), or in relative time (in number of seconds from now).
- # Details of how to customize the expiration of the url are provided in the documentation for the QueryString class.
- #
- # All requests made by this library use header authentication. When a query string authenticated url is needed,
- # the S3Object#url method will include the appropriate query string parameters.
- #
- # === Full authentication specification
- #
- # The full specification of the authentication protocol can be found at
- # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
- class Authentication
- constant :AMAZON_HEADER_PREFIX, 'x-amz-'
-
- # Signature is the abstract super class for the Header and QueryString authentication methods. It does the job
- # of computing the canonical_string using the CanonicalString class as well as encoding the canonical string. The subclasses
- # parameterize these computations and arrange them in a string form appropriate to how they are used, in one case a http request
- # header value, and in the other case key/value query string parameter pairs.
- class Signature < String #:nodoc:
- attr_reader :request, :access_key_id, :secret_access_key, :options
-
- def initialize(request, access_key_id, secret_access_key, options = {})
- super()
- @request, @access_key_id, @secret_access_key = request, access_key_id, secret_access_key
- @options = options
- end
-
- private
-
- def canonical_string
- options = {}
- options[:expires] = expires if expires?
- CanonicalString.new(request, options)
- end
- memoized :canonical_string
-
- def encoded_canonical
- klass = OpenSSL::Digest.respond_to?(:new) ? OpenSSL::Digest : OpenSSL::Digest::Digest
- digest = klass.new('sha1')
- b64_hmac = [OpenSSL::HMAC.digest(digest, secret_access_key, canonical_string)].pack("m").strip
- url_encode? ? CGI.escape(b64_hmac) : b64_hmac
- end
-
- def url_encode?
- !@options[:url_encode].nil?
- end
-
- def expires?
- is_a? QueryString
- end
-
- def date
- request['date'].to_s.strip.empty? ? Time.now : Time.parse(request['date'])
- end
- end
-
- # Provides header authentication by computing the value of the Authorization header. More details about the
- # various authentication schemes can be found in the docs for its containing module, Authentication.
- class Header < Signature #:nodoc:
- def initialize(*args)
- super
- self << "AWS #{access_key_id}:#{encoded_canonical}"
- end
- end
-
- # Provides query string authentication by computing the three authorization parameters: AWSAccessKeyId, Expires and Signature.
- # More details about the various authentication schemes can be found in the docs for its containing module, Authentication.
- class QueryString < Signature #:nodoc:
- constant :DEFAULT_EXPIRY, 300 # 5 minutes
- def initialize(*args)
- super
- options[:url_encode] = true
- self << build
- end
-
- private
-
- # Will return one of three values, in the following order of precedence:
- #
- # 1) Seconds since the epoch explicitly passed in the +:expires+ option
- # 2) The current time in seconds since the epoch plus the number of seconds passed in
- # the +:expires_in+ option
- # 3) The current time in seconds since the epoch plus the default number of seconds (60 seconds)
- def expires
- return options[:expires] if options[:expires]
- date.to_i + expires_in
- end
-
- def expires_in
- options.has_key?(:expires_in) ? Integer(options[:expires_in]) : DEFAULT_EXPIRY
- end
-
- def build
- "AWSAccessKeyId=#{access_key_id}&Expires=#{expires}&Signature=#{encoded_canonical}"
- end
- end
-
- # The CanonicalString is used to generate an encrypted signature, signed with your secrect access key. It is composed of
- # data related to the given request for which it provides authentication. This data includes the request method, request headers,
- # and the request path. Both Header and QueryString use it to generate their signature.
- class CanonicalString < String #:nodoc:
- class << self
- def default_headers
- %w(content-type content-md5)
- end
-
- def interesting_headers
- ['content-md5', 'content-type', 'date', amazon_header_prefix]
- end
-
- def amazon_header_prefix
- /^#{AMAZON_HEADER_PREFIX}/io
- end
-
- def query_parameters
- %w(acl location logging notification partNumber policy
- requestPayment torrent uploadId uploads versionId
- versioning versions delete lifecycle tagging cors
- response-content-type response-content-language
- response-expires response-cache-control
- response-content-disposition response-content-encoding)
- end
-
- def query_parameters_for_signature(params)
- params.select {|k, v| query_parameters.include?(k)}
- end
-
- def resource_parameters
- Set.new %w(acl logging torrent)
- end
-
- memoized :default_headers
- memoized :interesting_headers
- memoized :query_parameters
- memoized :resource_parameters
- end
-
- attr_reader :request, :headers
-
- def initialize(request, options = {})
- super()
- @request = request
- @headers = {}
- @options = options
- # "For non-authenticated or anonymous requests. A NotImplemented error result code will be returned if
- # an authenticated (signed) request specifies a Host: header other than 's3.amazonaws.com'"
- # (from http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html)
- request['Host'] = DEFAULT_HOST
- build
- end
-
- private
- def build
- self << "#{request.method}\n"
- ensure_date_is_valid
-
- initialize_headers
- set_expiry!
-
- headers.sort_by {|k, _| k}.each do |key, value|
- value = value.to_s.strip
- self << (key =~ self.class.amazon_header_prefix ? "#{key}:#{value}" : value)
- self << "\n"
- end
- self << path
- end
-
- def initialize_headers
- identify_interesting_headers
- set_default_headers
- end
-
- def set_expiry!
- self.headers['date'] = @options[:expires] if @options[:expires]
- end
-
- def ensure_date_is_valid
- request['Date'] ||= Time.now.httpdate
- end
-
- def identify_interesting_headers
- request.each do |key, value|
- key = key.downcase # Can't modify frozen string so no bang
- if self.class.interesting_headers.any? {|header| header === key}
- self.headers[key] = value.to_s.strip
- end
- end
- end
-
- def set_default_headers
- self.class.default_headers.each do |header|
- self.headers[header] ||= ''
- end
- end
-
- def path
- [only_path, extract_significant_parameter].compact.join('?')
- end
-
- def extract_significant_parameter
- query = URI.parse(request.path).query
- return nil if query.nil?
- params = CGI.parse(query) #this automatically unescapes query params
- params = self.class.query_parameters_for_signature(params).to_a
- return nil if params.empty?
- params.sort! { |(x_key, _), (y_key, _)| x_key <=> y_key }
- params.map! do |(key, value)|
- if value.nil? || resource_parameter?(key)
- key
- else
- value = value.join if value.respond_to?(:join)
- "#{key}=#{value}"
- end
- end.join("&")
- end
-
- def resource_parameter?(key)
- self.class.resource_parameters.include? key
- end
-
- def only_path
- request.path[/^[^?]*/]
- end
- end
- end
- end
-end
diff --git a/lib/aws/s3/base.rb b/lib/aws/s3/base.rb
deleted file mode 100644
index 63abafa..0000000
--- a/lib/aws/s3/base.rb
+++ /dev/null
@@ -1,240 +0,0 @@
-module AWS #:nodoc:
- # AWS::S3 is a Ruby library for Amazon's Simple Storage Service's REST API (http://aws.amazon.com/s3).
- # Full documentation of the currently supported API can be found at http://docs.amazonwebservices.com/AmazonS3/2006-03-01.
- #
- # == Getting started
- #
- # To get started you need to require 'aws/s3':
- #
- # % irb -rubygems
- # irb(main):001:0> require 'aws/s3'
- # # => true
- #
- # The AWS::S3 library ships with an interactive shell called s3sh. From within it, you have access to all the operations the library exposes from the command line.
- #
- # % s3sh
- # >> Version
- #
- # Before you can do anything, you must establish a connection using Base.establish_connection!. A basic connection would look something like this:
- #
- # AWS::S3::Base.establish_connection!(
- # :access_key_id => 'abc',
- # :secret_access_key => '123'
- # )
- #
- # The minimum connection options that you must specify are your access key id and your secret access key.
- #
- # (If you don't already have your access keys, all you need to sign up for the S3 service is an account at Amazon. You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.)
- #
- # For convenience, if you set two special environment variables with the value of your access keys, the console will automatically create a default connection for you. For example:
- #
- # % cat .amazon_keys
- # export AMAZON_ACCESS_KEY_ID='abcdefghijklmnop'
- # export AMAZON_SECRET_ACCESS_KEY='1234567891012345'
- #
- # Then load it in your shell's rc file.
- #
- # % cat .zshrc
- # if [[ -f "$HOME/.amazon_keys" ]]; then
- # source "$HOME/.amazon_keys";
- # fi
- #
- # See more connection details at AWS::S3::Connection::Management::ClassMethods.
- module S3
- constant :DEFAULT_HOST, 's3.amazonaws.com'
-
- # AWS::S3::Base is the abstract super class of all classes who make requests against S3, such as the built in
- # Service, Bucket and S3Object classes. It provides methods for making requests, inferring or setting response classes,
- # processing request options, and accessing attributes from S3's response data.
- #
- # Establishing a connection with the Base class is the entry point to using the library:
- #
- # AWS::S3::Base.establish_connection!(:access_key_id => '...', :secret_access_key => '...')
- #
- # The :access_key_id and :secret_access_key are the two required connection options. More
- # details can be found in the docs for Connection::Management::ClassMethods.
- #
- # Extensive examples can be found in the README[link:files/README.html].
- class Base
- class << self
- # Wraps the current connection's request method and picks the appropriate response class to wrap the response in.
- # If the response is an error, it will raise that error as an exception. All such exceptions can be caught by rescuing
- # their superclass, the ResponseError exception class.
- #
- # It is unlikely that you would call this method directly. Subclasses of Base have convenience methods for each http request verb
- # that wrap calls to request.
- def request(verb, path, options = {}, body = nil, attempts = 0, &block)
- Service.response = nil
- process_options!(options, verb)
- response = response_class.new(connection.request(verb, path, options, body, attempts, &block))
- Service.response = response
-
- Error::Response.new(response.response).error.raise if response.error?
- response
- # Once in a while, a request to S3 returns an internal error. A glitch in the matrix I presume. Since these
- # errors are few and far between the request method will rescue InternalErrors the first three times they encouter them
- # and will retry the request again. Most of the time the second attempt will work.
- rescue InternalError, RequestTimeout
- if attempts == 3
- raise
- else
- attempts += 1
- retry
- end
- end
-
- [:get, :post, :put, :delete, :head].each do |verb|
- class_eval(<<-EVAL, __FILE__, __LINE__)
- def #{verb}(path, headers = {}, body = nil, &block)
- request(:#{verb}, path, headers, body, &block)
- end
- EVAL
- end
-
- # Called when a method which requires a bucket name is called without that bucket name specified. It will try to
- # infer the current bucket by looking for it as the subdomain of the current connection's address. If no subdomain
- # is found, CurrentBucketNotSpecified will be raised.
- #
- # MusicBucket.establish_connection! :server => 'jukeboxzero.s3.amazonaws.com'
- # MusicBucket.connection.server
- # => 'jukeboxzero.s3.amazonaws.com'
- # MusicBucket.current_bucket
- # => 'jukeboxzero'
- #
- # Rather than infering the current bucket from the subdomain, the current class' bucket can be explicitly set with
- # set_current_bucket_to.
- def current_bucket
- connection.subdomain or raise CurrentBucketNotSpecified.new(connection.http.address)
- end
-
- # If you plan on always using a specific bucket for certain files, you can skip always having to specify the bucket by creating
- # a subclass of Bucket or S3Object and telling it what bucket to use:
- #
- # class JukeBoxSong < AWS::S3::S3Object
- # set_current_bucket_to 'jukebox'
- # end
- #
- # For all methods that take a bucket name as an argument, the current bucket will be used if the bucket name argument is omitted.
- #
- # other_song = 'baby-please-come-home.mp3'
- # JukeBoxSong.store(other_song, open(other_song))
- #
- # This time we didn't have to explicitly pass in the bucket name, as the JukeBoxSong class knows that it will
- # always use the 'jukebox' bucket.
- #
- # "Astute readers", as they say, may have noticed that we used the third parameter to pass in the content type,
- # rather than the fourth parameter as we had the last time we created an object. If the bucket can be inferred, or
- # is explicitly set, as we've done in the JukeBoxSong class, then the third argument can be used to pass in
- # options.
- #
- # Now all operations that would have required a bucket name no longer do.
- #
- # other_song = JukeBoxSong.find('baby-please-come-home.mp3')
- def set_current_bucket_to(name)
- raise ArgumentError, "`#{__method__}' must be called on a subclass of #{self.name}" if self == AWS::S3::Base
- instance_eval(<<-EVAL)
- def current_bucket
- '#{name}'
- end
- EVAL
- end
- alias_method :current_bucket=, :set_current_bucket_to
-
- private
-
- def response_class
- FindResponseClass.for(self)
- end
-
- def process_options!(options, verb)
- options.replace(RequestOptions.process(options, verb))
- end
-
- # Using the conventions layed out in the response_class works for more than 80% of the time.
- # There are a few edge cases though where we want a given class to wrap its responses in different
- # response classes depending on which method is being called.
- def respond_with(klass)
- eval(<<-EVAL, binding, __FILE__, __LINE__)
- def new_response_class
- #{klass}
- end
-
- class << self
- alias_method :old_response_class, :response_class
- alias_method :response_class, :new_response_class
- end
- EVAL
-
- yield
- ensure
- # Restore the original version
- eval(<<-EVAL, binding, __FILE__, __LINE__)
- class << self
- alias_method :response_class, :old_response_class
- end
- EVAL
- end
-
- def bucket_name(name)
- name || current_bucket
- end
-
- class RequestOptions < Hash #:nodoc:
- attr_reader :options, :verb
-
- class << self
- def process(*args, &block)
- new(*args, &block).process!
- end
- end
-
- def initialize(options, verb = :get)
- @options = options.to_normalized_options
- @verb = verb
- super()
- end
-
- def process!
- set_access_controls! if verb == :put
- replace(options)
- end
-
- private
- def set_access_controls!
- ACL::OptionProcessor.process!(options)
- end
- end
- end
-
- def initialize(attributes = {}) #:nodoc:
- @attributes = attributes
- end
-
- private
- attr_reader :attributes
-
- def connection
- self.class.connection
- end
-
- def http
- connection.http
- end
-
- def request(*args, &block)
- self.class.request(*args, &block)
- end
-
- def method_missing(method, *args, &block)
- case
- when attributes.has_key?(method.to_s)
- attributes[method.to_s]
- when attributes.has_key?(method)
- attributes[method]
- else
- super
- end
- end
- end
- end
-end
diff --git a/lib/aws/s3/bittorrent.rb b/lib/aws/s3/bittorrent.rb
deleted file mode 100644
index 46bd130..0000000
--- a/lib/aws/s3/bittorrent.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module AWS
- module S3
- # Objects on S3 can be distributed via the BitTorrent file sharing protocol.
- #
- # You can get a torrent file for an object by calling torrent_for:
- #
- # S3Object.torrent_for 'kiss.jpg', 'marcel'
- #
- # Or just call the torrent method if you already have the object:
- #
- # song = S3Object.find 'kiss.jpg', 'marcel'
- # song.torrent
- #
- # Calling grant_torrent_access_to on a object will allow anyone to anonymously
- # fetch the torrent file for that object:
- #
- # S3Object.grant_torrent_access_to 'kiss.jpg', 'marcel'
- #
- # Anonymous requests to
- #
- # http://s3.amazonaws.com/marcel/kiss.jpg?torrent
- #
- # will serve up the torrent file for that object.
- module BitTorrent
- def self.included(klass) #:nodoc:
- klass.extend ClassMethods
- end
-
- # Adds methods to S3Object for accessing the torrent of a given object.
- module ClassMethods
- # Returns the torrent file for the object with the given key.
- def torrent_for(key, bucket = nil)
- get(path!(bucket, key) << '?torrent').body
- end
- alias_method :torrent, :torrent_for
-
- # Grants access to the object with the given key to be accessible as a torrent.
- def grant_torrent_access_to(key, bucket = nil)
- policy = acl(key, bucket)
- return true if policy.grants.include?(:public_read)
- policy.grants << ACL::Grant.grant(:public_read)
- acl(key, bucket, policy)
- end
- alias_method :grant_torrent_access, :grant_torrent_access_to
- end
-
- # Returns the torrent file for the object.
- def torrent
- self.class.torrent_for(key, bucket.name)
- end
-
- # Grants torrent access publicly to anyone who requests it on this object.
- def grant_torrent_access
- self.class.grant_torrent_access_to(key, bucket.name)
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/aws/s3/bucket.rb b/lib/aws/s3/bucket.rb
deleted file mode 100644
index 84d89d3..0000000
--- a/lib/aws/s3/bucket.rb
+++ /dev/null
@@ -1,319 +0,0 @@
-module AWS
- module S3
- # Buckets are containers for objects (the files you store on S3). To create a new bucket you just specify its name.
- #
- # # Pick a unique name, or else you'll get an error
- # # if the name is already taken.
- # Bucket.create('jukebox')
- #
- # Bucket names must be unique across the entire S3 system, sort of like domain names across the internet. If you try
- # to create a bucket with a name that is already taken, you will get an error.
- #
- # Assuming the name you chose isn't already taken, your new bucket will now appear in the bucket list:
- #
- # Service.buckets
- # # => [#"jukebox"}>]
- #
- # Once you have succesfully created a bucket you can you can fetch it by name using Bucket.find.
- #
- # music_bucket = Bucket.find('jukebox')
- #
- # The bucket that is returned will contain a listing of all the objects in the bucket.
- #
- # music_bucket.objects.size
- # # => 0
- #
- # If all you are interested in is the objects of the bucket, you can get to them directly using Bucket.objects.
- #
- # Bucket.objects('jukebox').size
- # # => 0
- #
- # By default all objects will be returned, though there are several options you can use to limit what is returned, such as
- # specifying that only objects whose name is after a certain place in the alphabet be returned, and etc. Details about these options can
- # be found in the documentation for Bucket.find.
- #
- # To add an object to a bucket you specify the name of the object, its value, and the bucket to put it in.
- #
- # file = 'black-flowers.mp3'
- # S3Object.store(file, open(file), 'jukebox')
- #
- # You'll see your file has been added to it:
- #
- # music_bucket.objects
- # # => [#]
- #
- # You can treat your bucket like a hash and access objects by name:
- #
- # jukebox['black-flowers.mp3']
- # # => #
- #
- # In the event that you want to delete a bucket, you can use Bucket.delete.
- #
- # Bucket.delete('jukebox')
- #
- # Keep in mind, like unix directories, you can not delete a bucket unless it is empty. Trying to delete a bucket
- # that contains objects will raise a BucketNotEmpty exception.
- #
- # Passing the :force => true option to delete will take care of deleting all the bucket's objects for you.
- #
- # Bucket.delete('photos', :force => true)
- # # => true
- class Bucket < Base
- class << self
- # Creates a bucket named name.
- #
- # Bucket.create('jukebox')
- #
- # Your bucket name must be unique across all of S3. If the name
- # you request has already been taken, you will get a 409 Conflict response, and a BucketAlreadyExists exception
- # will be raised.
- #
- # By default new buckets have their access level set to private. You can override this using the :access option.
- #
- # Bucket.create('internet_drop_box', :access => :public_read_write)
- #
- # The full list of access levels that you can set on Bucket and S3Object creation are listed in the README[link:files/README.html]
- # in the section called 'Setting access levels'.
- def create(name, options = {})
- validate_name!(name)
- put("/#{name}", options).success?
- end
-
- # Fetches the bucket named name.
- #
- # Bucket.find('jukebox')
- #
- # If a default bucket is inferable from the current connection's subdomain, or if set explicitly with Base.set_current_bucket,
- # it will be used if no bucket is specified.
- #
- # MusicBucket.current_bucket
- # => 'jukebox'
- # MusicBucket.find.name
- # => 'jukebox'
- #
- # By default all objects contained in the bucket will be returned (sans their data) along with the bucket.
- # You can access your objects using the Bucket#objects method.
- #
- # Bucket.find('jukebox').objects
- #
- # There are several options which allow you to limit which objects are retrieved. The list of object filtering options
- # are listed in the documentation for Bucket.objects.
- def find(name = nil, options = {})
- new(get(path(name, options)).bucket)
- end
-
- # Return just the objects in the bucket named name.
- #
- # By default all objects of the named bucket will be returned. There are options, though, for filtering
- # which objects are returned.
- #
- # === Object filtering options
- #
- # * :max_keys - The maximum number of keys you'd like to see in the response body.
- # The server may return fewer than this many keys, but will not return more.
- #
- # Bucket.objects('jukebox').size
- # # => 3
- # Bucket.objects('jukebox', :max_keys => 1).size
- # # => 1
- #
- # * :prefix - Restricts the response to only contain results that begin with the specified prefix.
- #
- # Bucket.objects('jukebox')
- # # => [, , ]
- # Bucket.objects('jukebox', :prefix => 'classical')
- # # => []
- #
- # * :marker - Marker specifies where in the result set to resume listing. It restricts the response
- # to only contain results that occur alphabetically _after_ the value of marker. To retrieve the next set of results,
- # use the last key from the current page of results as the marker in your next request.
- #
- # # Skip 'mahler'
- # Bucket.objects('jukebox', :marker => 'mb')
- # # => []
- #
- # === Examples
- #
- # # Return no more than 2 objects whose key's are listed alphabetically after the letter 'm'.
- # Bucket.objects('jukebox', :marker => 'm', :max_keys => 2)
- # # => [, ]
- #
- # # Return no more than 2 objects whose key's are listed alphabetically after the letter 'm' and have the 'jazz' prefix.
- # Bucket.objects('jukebox', :marker => 'm', :max_keys => 2, :prefix => 'jazz')
- # # => []
- def objects(name = nil, options = {})
- find(name, options).object_cache
- end
-
- # Deletes the bucket named name.
- #
- # All objects in the bucket must be deleted before the bucket can be deleted. If the bucket is not empty,
- # BucketNotEmpty will be raised.
- #
- # You can side step this issue by passing the :force => true option to delete which will take care of
- # emptying the bucket before deleting it.
- #
- # Bucket.delete('photos', :force => true)
- #
- # Only the owner of a bucket can delete a bucket, regardless of the bucket's access control policy.
- def delete(name = nil, options = {})
- find(name).delete_all if options[:force]
-
- name = path(name)
- Base.delete(name).success?
- end
-
- # List all your buckets. This is a convenient wrapper around AWS::S3::Service.buckets.
- def list(reload = false)
- Service.buckets(reload)
- end
-
- private
- def validate_name!(name)
- raise InvalidBucketName.new(name) unless name =~ /^[-\w.]{3,255}$/
- end
-
- def path(name, options = {})
- if name.is_a?(Hash)
- options = name
- name = nil
- end
- "/#{bucket_name(name)}#{RequestOptions.process(options).to_query_string}"
- end
- end
-
- attr_reader :object_cache #:nodoc:
-
- include Enumerable
-
- def initialize(attributes = {}) #:nodoc:
- super
- @object_cache = []
- build_contents!
- end
-
- # Fetches the object named object_key, or nil if the bucket does not contain an object with the
- # specified key.
- #
- # bucket.objects
- # => [#,
- # #]
- # bucket['beluga_baby.jpg']
- # => #
- def [](object_key)
- detect {|file| file.key == object_key.to_s}
- end
-
- # Initializes a new S3Object belonging to the current bucket.
- #
- # object = bucket.new_object
- # object.value = data
- # object.key = 'classical/mahler.mp3'
- # object.store
- # bucket.objects.include?(object)
- # => true
- def new_object(attributes = {})
- object = S3Object.new(attributes)
- register(object)
- object
- end
-
- # List S3Object's of the bucket.
- #
- # Once fetched the objects will be cached. You can reload the objects by passing :reload.
- #
- # bucket.objects(:reload)
- #
- # You can also filter the objects using the same options listed in Bucket.objects.
- #
- # bucket.objects(:prefix => 'jazz')
- #
- # Using these filtering options will implictly reload the objects.
- #
- # To reclaim all the objects for the bucket you can pass in :reload again.
- def objects(options = {})
- if options.is_a?(Hash)
- reload = !options.empty?
- else
- reload = options
- options = {}
- end
-
- reload!(options) if reload || object_cache.empty?
- object_cache
- end
-
- # Iterates over the objects in the bucket.
- #
- # bucket.each do |object|
- # # Do something with the object ...
- # end
- def each(&block)
- # Dup the collection since we might be destructively modifying the object_cache during the iteration.
- objects.dup.each(&block)
- end
-
- # Returns true if there are no objects in the bucket.
- def empty?
- objects.empty?
- end
-
- # Returns the number of objects in the bucket.
- def size
- objects.size
- end
-
- # Deletes the bucket. See its class method counter part Bucket.delete for caveats about bucket deletion and how to ensure
- # a bucket is deleted no matter what.
- def delete(options = {})
- self.class.delete(name, options)
- end
-
- # Delete all files in the bucket. Use with caution. Can not be undone.
- def delete_all
- each do |object|
- object.delete
- end
- self
- end
- alias_method :clear, :delete_all
-
- # Buckets observe their objects and have this method called when one of their objects
- # is either stored or deleted.
- def update(action, object) #:nodoc:
- case action
- when :stored then add object unless objects.include?(object)
- when :deleted then object_cache.delete(object)
- end
- end
-
- private
- def build_contents!
- return unless has_contents?
- attributes.delete('contents').each do |content|
- add new_object(content)
- end
- end
-
- def has_contents?
- attributes.has_key?('contents')
- end
-
- def add(object)
- register(object)
- object_cache << object
- end
-
- def register(object)
- object.bucket = self
- end
-
- def reload!(options = {})
- object_cache.clear
- self.class.objects(name, options).each do |object|
- add object
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/aws/s3/connection.rb b/lib/aws/s3/connection.rb
deleted file mode 100644
index dac8700..0000000
--- a/lib/aws/s3/connection.rb
+++ /dev/null
@@ -1,278 +0,0 @@
-module AWS
- module S3
- class Connection #:nodoc:
- class << self
- def connect(options = {})
- new(options)
- end
-
- def prepare_path(path)
- path = path.remove_extended unless path.valid_utf8?
- AWS::S3.escape_uri(path)
- end
- end
-
- attr_reader :access_key_id, :secret_access_key, :http, :options
-
- # Creates a new connection. Connections make the actual requests to S3, though these requests are usually
- # called from subclasses of Base.
- #
- # For details on establishing connections, check the Connection::Management::ClassMethods.
- def initialize(options = {})
- @options = Options.new(options)
- connect
- end
-
- def request(verb, path, headers = {}, body = nil, attempts = 0, &block)
- body.rewind if body.respond_to?(:rewind) unless attempts.zero?
-
- requester = Proc.new do
- path = self.class.prepare_path(path) if attempts.zero? # Only escape the path once
- request = request_method(verb).new(path, headers)
- ensure_content_type!(request)
- add_user_agent!(request)
- authenticate!(request)
- if body
- if body.respond_to?(:read)
- request.body_stream = body
- else
- request.body = body
- end
- request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
- else
- request.content_length = 0
- end
- http.request(request, &block)
- end
-
- if persistent?
- http.start unless http.started?
- requester.call
- else
- http.start(&requester)
- end
- rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError, Errno::ECONNRESET
- @http = create_connection
- attempts == 3 ? raise : (attempts += 1; retry)
- end
-
- def url_for(path, options = {})
- authenticate = options.delete(:authenticated)
- # Default to true unless explicitly false
- authenticate = true if authenticate.nil?
- path = path.valid_utf8? ? path : path.remove_extended
- request = request_method(:get).new(path, {})
- query_string = query_string_authentication(request, options)
- "#{protocol(options)}#{http.address}#{port_string}#{path}".tap do |url|
- (url << (path[/\?/] ? '&' : '?') << "#{query_string}") if authenticate
- end
- end
-
- def subdomain
- http.address[/^([^.]+).#{DEFAULT_HOST}$/, 1]
- end
-
- def persistent?
- options[:persistent]
- end
-
- def protocol(options = {})
- # This always trumps http.use_ssl?
- if options[:use_ssl] == false
- 'http://'
- elsif options[:use_ssl] || http.use_ssl?
- 'https://'
- else
- 'http://'
- end
- end
-
- private
- def extract_keys!
- missing_keys = []
- extract_key = Proc.new {|key| options[key] || (missing_keys.push(key); nil)}
- @access_key_id = extract_key[:access_key_id]
- @secret_access_key = extract_key[:secret_access_key]
- raise MissingAccessKey.new(missing_keys) unless missing_keys.empty?
- end
-
- def create_connection
- http = http_class.new(options[:server], options[:port])
- http.use_ssl = options[:use_ssl] || options[:port] == 443
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- http
- end
-
- def http_class
- if options.connecting_through_proxy?
- Net::HTTP::Proxy(*options.proxy_settings)
- else
- Net::HTTP
- end
- end
-
- def connect
- extract_keys!
- @http = create_connection
- end
-
- def port_string
- default_port = options[:use_ssl] ? 443 : 80
- http.port == default_port ? '' : ":#{http.port}"
- end
-
- def ensure_content_type!(request)
- request['Content-Type'] ||= 'binary/octet-stream'
- end
-
- # Just do Header authentication for now
- def authenticate!(request)
- request['Authorization'] = Authentication::Header.new(request, access_key_id, secret_access_key)
- end
-
- def add_user_agent!(request)
- request['User-Agent'] ||= "AWS::S3/#{Version}"
- end
-
- def query_string_authentication(request, options = {})
- Authentication::QueryString.new(request, access_key_id, secret_access_key, options)
- end
-
- def request_method(verb)
- Net::HTTP.const_get(verb.to_s.capitalize)
- end
-
- def method_missing(method, *args, &block)
- options[method] || super
- end
-
- module Management #:nodoc:
- def self.included(base)
- base.cattr_accessor :connections
- base.connections = {}
- base.extend ClassMethods
- end
-
- # Manage the creation and destruction of connections for AWS::S3::Base and its subclasses. Connections are
- # created with establish_connection!.
- module ClassMethods
- # Creates a new connection with which to make requests to the S3 servers for the calling class.
- #
- # AWS::S3::Base.establish_connection!(:access_key_id => '...', :secret_access_key => '...')
- #
- # You can set connections for every subclass of AWS::S3::Base. Once the initial connection is made on
- # Base, all subsequent connections will inherit whatever values you don't specify explictly. This allows you to
- # customize details of the connection, such as what server the requests are made to, by just specifying one
- # option.
- #
- # AWS::S3::Bucket.established_connection!(:use_ssl => true)
- #
- # The Bucket connection would inherit the :access_key_id and the :secret_access_key from
- # Base's connection. Unlike the Base connection, all Bucket requests would be made over SSL.
- #
- # == Required arguments
- #
- # * :access_key_id - The access key id for your S3 account. Provided by Amazon.
- # * :secret_access_key - The secret access key for your S3 account. Provided by Amazon.
- #
- # If any of these required arguments is missing, a MissingAccessKey exception will be raised.
- #
- # == Optional arguments
- #
- # * :server - The server to make requests to. You can use this to specify your bucket in the subdomain,
- # or your own domain's cname if you are using virtual hosted buckets. Defaults to s3.amazonaws.com.
- # * :port - The port to the requests should be made on. Defaults to 80 or 443 if the :use_ssl
- # argument is set.
- # * :use_ssl - Whether requests should be made over SSL. If set to true, the :port argument
- # will be implicitly set to 443, unless specified otherwise. Defaults to false.
- # * :persistent - Whether to use a persistent connection to the server. Having this on provides around a two fold
- # performance increase but for long running processes some firewalls may find the long lived connection suspicious and close the connection.
- # If you run into connection errors, try setting :persistent to false. Defaults to false.
- # * :proxy - If you need to connect through a proxy, you can specify your proxy settings by specifying a :host, :port, :user, and :password
- # with the :proxy option.
- # The :host setting is required if specifying a :proxy.
- #
- # AWS::S3::Bucket.established_connection!(:proxy => {
- # :host => '...', :port => 8080, :user => 'marcel', :password => 'secret'
- # })
- def establish_connection!(options = {})
- # After you've already established the default connection, just specify
- # the difference for subsequent connections
- options = default_connection.options.merge(options) if connected?
- connections[connection_name] = Connection.connect(options)
- end
-
- # Returns the connection for the current class, or Base's default connection if the current class does not
- # have its own connection.
- #
- # If not connection has been established yet, NoConnectionEstablished will be raised.
- def connection
- if connected?
- connections[connection_name] || default_connection
- else
- raise NoConnectionEstablished
- end
- end
-
- # Returns true if a connection has been made yet.
- def connected?
- !connections.empty?
- end
-
- # Removes the connection for the current class. If there is no connection for the current class, the default
- # connection will be removed.
- def disconnect(name = connection_name)
- name = default_connection unless connections.has_key?(name)
- connection = connections[name]
- connection.http.finish if connection.persistent?
- connections.delete(name)
- end
-
- # Clears *all* connections, from all classes, with prejudice.
- def disconnect!
- connections.each_key {|connection| disconnect(connection)}
- end
-
- private
- def connection_name
- name
- end
-
- def default_connection_name
- 'AWS::S3::Base'
- end
-
- def default_connection
- connections[default_connection_name]
- end
- end
- end
-
- class Options < Hash #:nodoc:
- VALID_OPTIONS = [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy].freeze
-
- def initialize(options = {})
- super()
- validate(options)
- replace(:server => DEFAULT_HOST, :port => (options[:use_ssl] ? 443 : 80))
- merge!(options)
- end
-
- def connecting_through_proxy?
- !self[:proxy].nil?
- end
-
- def proxy_settings
- self[:proxy].values_at(:host, :port, :user, :password)
- end
-
- private
- def validate(options)
- invalid_options = options.keys - VALID_OPTIONS
- raise InvalidConnectionOption.new(invalid_options) unless invalid_options.empty?
- raise ArgumentError, "Missing proxy settings. Must specify at least :host." if options[:proxy] && !options[:proxy][:host]
- end
- end
- end
- end
-end
diff --git a/lib/aws/s3/error.rb b/lib/aws/s3/error.rb
deleted file mode 100644
index f4c4011..0000000
--- a/lib/aws/s3/error.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module AWS
- module S3
- # Anything you do that makes a request to S3 could result in an error. If it does, the AWS::S3 library will raise an exception
- # specific to the error. All exception that are raised as a result of a request returning an error response inherit from the
- # ResponseError exception. So should you choose to rescue any such exception, you can simple rescue ResponseError.
- #
- # Say you go to delete a bucket, but the bucket turns out to not be empty. This results in a BucketNotEmpty error (one of the many
- # errors listed at http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ErrorCodeList.html):
- #
- # begin
- # Bucket.delete('jukebox')
- # rescue ResponseError => error
- # # ...
- # end
- #
- # Once you've captured the exception, you can extract the error message from S3, as well as the full error response, which includes
- # things like the HTTP response code:
- #
- # error
- # # => #
- # error.message
- # # => "The bucket you tried to delete is not empty"
- # error.response.code
- # # => 409
- #
- # You could use this information to redisplay the error in a way you see fit, or just to log the error and continue on.
- class Error
- #:stopdoc:
- attr_accessor :response
- def initialize(error, response = nil)
- @error = error
- @response = response
- @container = AWS::S3
- find_or_create_exception!
- end
-
- def raise
- Kernel.raise exception.new(message, response)
- end
-
- private
- attr_reader :error, :exception, :container
-
- def find_or_create_exception!
- @exception = container.const_defined?(code) ? find_exception : create_exception
- end
-
- def find_exception
- exception_class = container.const_get(code)
- Kernel.raise ExceptionClassClash.new(exception_class) unless exception_class.ancestors.include?(ResponseError)
- exception_class
- end
-
- def create_exception
- container.const_set(code, Class.new(ResponseError))
- end
-
- def method_missing(method, *args, &block)
- # We actually want nil if the attribute is nil. So we use has_key? rather than [] + ||.
- if error.has_key?(method.to_s)
- error[method.to_s]
- else
- super
- end
- end
- end
- end
-end
-#:startdoc:
\ No newline at end of file
diff --git a/lib/aws/s3/exceptions.rb b/lib/aws/s3/exceptions.rb
deleted file mode 100644
index 9dab1a2..0000000
--- a/lib/aws/s3/exceptions.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-module AWS
- module S3
-
- # Abstract super class of all AWS::S3 exceptions
- class S3Exception < StandardError
- end
-
- # All responses with a code between 300 and 599 that contain an body are wrapped in an
- # ErrorResponse which contains an Error object. This Error class generates a custom exception with the name
- # of the xml Error and its message. All such runtime generated exception classes descend from ResponseError
- # and contain the ErrorResponse object so that all code that makes a request can rescue ResponseError and get
- # access to the ErrorResponse.
- class ResponseError < S3Exception
- attr_reader :response
- def initialize(message, response)
- @response = response
- super(message)
- end
- end
-
- #:stopdoc:
-
- # Most ResponseError's are created just time on a need to have basis, but we explicitly define the
- # InternalError exception because we want to explicitly rescue InternalError in some cases.
- class InternalError < ResponseError
- end
-
- class NoSuchKey < ResponseError
- end
-
- class RequestTimeout < ResponseError
- end
-
- # Abstract super class for all invalid options.
- class InvalidOption < S3Exception
- end
-
- # Raised if an invalid value is passed to the :access option when creating a Bucket or an S3Object.
- class InvalidAccessControlLevel < InvalidOption
- def initialize(valid_levels, access_level)
- super("Valid access control levels are #{valid_levels.inspect}. You specified `#{access_level}'.")
- end
- end
-
- # Raised if either the access key id or secret access key arguments are missing when establishing a connection.
- class MissingAccessKey < InvalidOption
- def initialize(missing_keys)
- key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
- super("You did not provide both required access keys. Please provide the #{key_list}.")
- end
- end
-
- # Raised if a request is attempted before any connections have been established.
- class NoConnectionEstablished < S3Exception
- end
-
- # Raised if an unrecognized option is passed when establishing a connection.
- class InvalidConnectionOption < InvalidOption
- def initialize(invalid_options)
- message = "The following connection options are invalid: #{invalid_options.join(', ')}. " +
- "The valid connection options are: #{Connection::Options::VALID_OPTIONS.join(', ')}."
- super(message)
- end
- end
-
- # Raised if an invalid bucket name is passed when creating a new Bucket.
- class InvalidBucketName < S3Exception
- def initialize(invalid_name)
- message = "`#{invalid_name}' is not a valid bucket name. " +
- "Bucket names must be between 3 and 255 bytes and " +
- "can contain letters, numbers, dashes and underscores."
- super(message)
- end
- end
-
- # Raised if an invalid key name is passed when creating an S3Object.
- class InvalidKeyName < S3Exception
- def initialize(invalid_name)
- message = "`#{invalid_name}' is not a valid key name. " +
- "Key names must be no more than 1024 bytes long."
- super(message)
- end
- end
-
- # Raised if an invalid value is assigned to an S3Object's specific metadata name.
- class InvalidMetadataValue < S3Exception
- def initialize(invalid_names)
- message = "The following metadata names have invalid values: #{invalid_names.join(', ')}. " +
- "Metadata can not be larger than 2kilobytes."
- super(message)
- end
- end
-
- # Raised if the current bucket can not be inferred when not explicitly specifying the target bucket in the calling
- # method's arguments.
- class CurrentBucketNotSpecified < S3Exception
- def initialize(address)
- message = "No bucket name can be inferred from your current connection's address (`#{address}')"
- super(message)
- end
- end
-
- # Raised when an orphaned S3Object belonging to no bucket tries to access its (non-existant) bucket.
- class NoBucketSpecified < S3Exception
- def initialize
- super('The current object must have its bucket set')
- end
- end
-
- # Raised if an attempt is made to save an S3Object that does not have a key set.
- class NoKeySpecified < S3Exception
- def initialize
- super('The current object must have its key set')
- end
- end
-
- # Raised if you try to save a deleted object.
- class DeletedObject < S3Exception
- def initialize
- super('You can not save a deleted object')
- end
- end
-
- class ExceptionClassClash < S3Exception #:nodoc:
- def initialize(klass)
- message = "The exception class you tried to create (`#{klass}') exists and is not an exception"
- super(message)
- end
- end
-
- #:startdoc:
- end
-end
\ No newline at end of file
diff --git a/lib/aws/s3/extensions.rb b/lib/aws/s3/extensions.rb
deleted file mode 100644
index aa40767..0000000
--- a/lib/aws/s3/extensions.rb
+++ /dev/null
@@ -1,344 +0,0 @@
-#encoding: BINARY
-#:stopdoc:
-
-class Hash
- def to_query_string(include_question_mark = true)
- query_string = ''
- unless empty?
- query_string << '?' if include_question_mark
- query_string << inject([]) do |params, (key, value)|
- params << "#{key}=#{AWS::S3.escape_uri_component(value)}"
- end.join('&')
- end
- query_string
- end
-
- def to_normalized_options
- # Convert all option names to downcased strings, and replace underscores with hyphens
- inject({}) do |normalized_options, (name, value)|
- normalized_options[name.to_header] = value.to_s
- normalized_options
- end
- end
-
- def to_normalized_options!
- replace(to_normalized_options)
- end
-end
-
-class String
- if RUBY_VERSION <= '1.9'
- def previous!
- self[-1] -= 1
- self
- end
- else
- def previous!
- self[-1] = (self[-1].ord - 1).chr
- self
- end
- end
-
- def tap
- yield(self)
- self
- end unless ''.respond_to?(:tap)
-
- def previous
- dup.previous!
- end
-
- def to_header
- downcase.tr('_', '-')
- end
-
- # ActiveSupport adds an underscore method to String so let's just use that one if
- # we find that the method is already defined
- def underscore
- gsub(/::/, '/').
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
- tr("-", "_").downcase
- end unless public_method_defined? :underscore
-
- if RUBY_VERSION >= '1.9'
- def valid_utf8?
- dup.force_encoding('UTF-8').valid_encoding?
- end
- else
- def valid_utf8?
- scan(Regexp.new('[^\x00-\xa0]', nil, 'u')) { |s| s.unpack('U') }
- true
- rescue ArgumentError
- false
- end
- end
-
- # All paths in in S3 have to be valid unicode so this takes care of
- # cleaning up any strings that aren't valid utf-8 according to String#valid_utf8?
- if RUBY_VERSION >= '1.9'
- def remove_extended!
- sanitized_string = ''
- each_byte do |byte|
- character = byte.chr
- sanitized_string << character if character.ascii_only?
- end
- sanitized_string
- end
- else
- def remove_extended!
- gsub!(/[\x80-\xFF]/) { "%02X" % $&[0] }
- end
- end
-
- def remove_extended
- dup.remove_extended!
- end
-end
-
-class CoercibleString < String
- class << self
- def coerce(string)
- new(string).coerce
- end
- end
-
- def coerce
- case self
- when 'true'; true
- when 'false'; false
- # Don't coerce numbers that start with zero
- when /^[1-9]+\d*$/; Integer(self)
- when datetime_format; Time.parse(self)
- else
- self
- end
- end
-
- private
- # Lame hack since Date._parse is so accepting. S3 dates are of the form: '2006-10-29T23:14:47.000Z'
- # so unless the string looks like that, don't even try, otherwise it might convert an object's
- # key from something like '03 1-2-3-Apple-Tree.mp3' to Sat Feb 03 00:00:00 CST 2001.
- def datetime_format
- /^\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}/
- end
-end
-
-class Symbol
- def to_header
- to_s.to_header
- end
-end
-
-module Kernel
- def __method__(depth = 0)
- caller[depth][/`([^']+)'/, 1]
- end if RUBY_VERSION <= '1.8.7'
-
- def __called_from__
- caller[1][/`([^']+)'/, 1]
- end if RUBY_VERSION > '1.8.7'
-
- def expirable_memoize(reload = false, storage = nil)
- current_method = RUBY_VERSION > '1.8.7' ? __called_from__ : __method__(1)
- storage = "@#{storage || current_method}"
- if reload
- instance_variable_set(storage, nil)
- else
- if cache = instance_variable_get(storage)
- return cache
- end
- end
- instance_variable_set(storage, yield)
- end
-
- def require_library_or_gem(library, gem_name = nil)
- if RUBY_VERSION >= '1.9'
- gem(gem_name || library, '>=0')
- end
- require library
- rescue LoadError => library_not_installed
- begin
- require 'rubygems'
- require library
- rescue LoadError
- raise library_not_installed
- end
- end
-end
-
-class Object
- def returning(value)
- yield(value)
- value
- end
-end
-
-class Module
- def memoized(method_name)
- original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
- alias_method original_method, method_name
- module_eval(<<-EVAL, __FILE__, __LINE__)
- def #{method_name}(reload = false, *args, &block)
- expirable_memoize(reload) do
- send(:#{original_method}, *args, &block)
- end
- end
- EVAL
- end
-
- def constant(name, value)
- unless const_defined?(name)
- const_set(name, value)
- module_eval(<<-EVAL, __FILE__, __LINE__)
- def self.#{name.to_s.downcase}
- #{name.to_s}
- end
- EVAL
- end
- end
-end
-
-class Class # :nodoc:
- def cattr_reader(*syms)
- syms.flatten.each do |sym|
- class_eval(<<-EOS, __FILE__, __LINE__)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}
- @@#{sym}
- end
-
- def #{sym}
- @@#{sym}
- end
- EOS
- end
- end
-
- def cattr_writer(*syms)
- syms.flatten.each do |sym|
- class_eval(<<-EOS, __FILE__, __LINE__)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}=(obj)
- @@#{sym} = obj
- end
-
- def #{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
- end
- end
-
- def cattr_accessor(*syms)
- cattr_reader(*syms)
- cattr_writer(*syms)
- end
-end if Class.instance_methods(false).grep(/^cattr_(?:reader|writer|accessor)$/).empty?
-
-module SelectiveAttributeProxy
- def self.included(klass)
- klass.extend(ClassMethods)
- klass.class_eval(<<-EVAL, __FILE__, __LINE__)
- cattr_accessor :attribute_proxy
- cattr_accessor :attribute_proxy_options
-
- # Default name for attribute storage
- self.attribute_proxy = :attributes
- self.attribute_proxy_options = {:exclusively => true}
-
- private
- # By default proxy all attributes
- def proxiable_attribute?(name)
- return true unless self.class.attribute_proxy_options[:exclusively]
- send(self.class.attribute_proxy).has_key?(name)
- end
-
- def method_missing(method, *args, &block)
- # Autovivify attribute storage
- if method == self.class.attribute_proxy
- ivar = "@\#{method}"
- instance_variable_set(ivar, {}) unless instance_variable_get(ivar).is_a?(Hash)
- instance_variable_get(ivar)
- # Delegate to attribute storage
- elsif method.to_s =~ /^(\\w+)(=?)$/ && proxiable_attribute?($1)
- attributes_hash_name = self.class.attribute_proxy
- $2.empty? ? send(attributes_hash_name)[$1] : send(attributes_hash_name)[$1] = args.first
- else
- super
- end
- end
- EVAL
- end
-
- module ClassMethods
- def proxy_to(attribute_name, options = {})
- if attribute_name.is_a?(Hash)
- options = attribute_name
- else
- self.attribute_proxy = attribute_name
- end
- self.attribute_proxy_options = options
- end
- end
-end
-
-# When streaming data up, Net::HTTPGenericRequest hard codes a chunk size of 1k. For large files this
-# is an unfortunately low chunk size, so here we make it use a much larger default size and move it into a method
-# so that the implementation of send_request_with_body_stream doesn't need to be changed to change the chunk size (at least not anymore
-# than I've already had to...).
-module Net
- class HTTPGenericRequest
- def send_request_with_body_stream(sock, ver, path, f)
- raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked?
- unless content_type()
- warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
- set_content_type 'application/x-www-form-urlencoded'
- end
- write_header sock, ver, path
- if chunked?
- while s = f.read(chunk_size)
- sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
- end
- sock.write "0\r\n\r\n"
- else
- while s = f.read(chunk_size)
- sock.write s
- end
- end
- end
-
- def chunk_size
- 1048576 # 1 megabyte
- end
- end
-
- # Net::HTTP before 1.8.4 doesn't have the use_ssl? method or the Delete request type
- class HTTP
- def use_ssl?
- @use_ssl
- end unless public_method_defined? :use_ssl?
-
- class Delete < HTTPRequest
- METHOD = 'DELETE'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = true
- end unless const_defined? :Delete
- end
-end
-
-class XmlGenerator < String #:nodoc:
- attr_reader :xml
- def initialize
- @xml = Builder::XmlMarkup.new(:indent => 2, :target => self)
- super()
- build
- end
-end
-#:startdoc:
diff --git a/lib/aws/s3/logging.rb b/lib/aws/s3/logging.rb
deleted file mode 100644
index 4ffc5e4..0000000
--- a/lib/aws/s3/logging.rb
+++ /dev/null
@@ -1,314 +0,0 @@
-module AWS
- module S3
- # A bucket can be set to log the requests made on it. By default logging is turned off. You can check if a bucket has logging enabled:
- #
- # Bucket.logging_enabled_for? 'jukebox'
- # # => false
- #
- # Enabling it is easy:
- #
- # Bucket.enable_logging_for('jukebox')
- #
- # Unless you specify otherwise, logs will be written to the bucket you want to log. The logs are just like any other object. By default they will start with the prefix 'log-'. You can customize what bucket you want the logs to be delivered to, as well as customize what the log objects' key is prefixed with by setting the target_bucket and target_prefix option:
- #
- # Bucket.enable_logging_for(
- # 'jukebox', 'target_bucket' => 'jukebox-logs'
- # )
- #
- # Now instead of logging right into the jukebox bucket, the logs will go into the bucket called jukebox-logs.
- #
- # Once logs have accumulated, you can access them using the logs method:
- #
- # pp Bucket.logs('jukebox')
- # [#,
- # #,
- # #]
- #
- # Each log has a lines method that gives you information about each request in that log. All the fields are available
- # as named methods. More information is available in Logging::Log::Line.
- #
- # logs = Bucket.logs('jukebox')
- # log = logs.first
- # line = log.lines.first
- # line.operation
- # # => 'REST.GET.LOGGING_STATUS'
- # line.request_uri
- # # => 'GET /jukebox?logging HTTP/1.1'
- # line.remote_ip
- # # => "67.165.183.125"
- #
- # Disabling logging is just as simple as enabling it:
- #
- # Bucket.disable_logging_for('jukebox')
- module Logging
- # Logging status captures information about the calling bucket's logging settings. If logging is enabled for the bucket
- # the status object will indicate what bucket the logs are written to via the target_bucket method as well as
- # the logging key prefix with via target_prefix.
- #
- # See the documentation for Logging::Management::ClassMethods for more information on how to get the logging status of a bucket.
- class Status
- include SelectiveAttributeProxy
- attr_reader :enabled
- alias_method :logging_enabled?, :enabled
-
- def initialize(attributes = {}) #:nodoc:
- attributes = {'target_bucket' => nil, 'target_prefix' => nil}.merge(attributes)
- @enabled = attributes.has_key?('logging_enabled')
- @attributes = attributes.delete('logging_enabled') || attributes
- end
-
- def to_xml #:nodoc:
- Builder.new(self).to_s
- end
-
- private
- attr_reader :attributes
-
- class Builder < XmlGenerator #:nodoc:
- attr_reader :logging_status
- def initialize(logging_status)
- @logging_status = logging_status
- super()
- end
-
- def build
- xml.tag!('BucketLoggingStatus', 'xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do
- if logging_status.target_bucket && logging_status.target_prefix
- xml.LoggingEnabled do
- xml.TargetBucket logging_status.target_bucket
- xml.TargetPrefix logging_status.target_prefix
- end
- end
- end
- end
- end
- end
-
- # A bucket log exposes requests made on the given bucket. Lines of the log represent a single request. The lines of a log
- # can be accessed with the lines method.
- #
- # log = Bucket.logs_for('marcel').first
- # log.lines
- #
- # More information about the logged requests can be found in the documentation for Log::Line.
- class Log
- def initialize(log_object) #:nodoc:
- @log = log_object
- end
-
- # Returns the lines for the log. Each line is wrapped in a Log::Line.
- if RUBY_VERSION >= '1.8.7'
- def lines
- log.value.lines.map {|line| Line.new(line)}
- end
- else
- def lines
- log.value.map {|line| Line.new(line)}
- end
- end
- memoized :lines
-
- def path
- log.path
- end
-
- def inspect #:nodoc:
- "#<%s:0x%s '%s'>" % [self.class.name, object_id, path]
- end
-
- private
- attr_reader :log
-
- # Each line of a log exposes the raw line, but it also has method accessors for all the fields of the logged request.
- #
- # The list of supported log line fields are listed in the S3 documentation: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/LogFormat.html
- #
- # line = log.lines.first
- # line.remote_ip
- # # => '72.21.206.5'
- #
- # If a certain field does not apply to a given request (for example, the key field does not apply to a bucket request),
- # or if it was unknown or unavailable, it will return nil.
- #
- # line.operation
- # # => 'REST.GET.BUCKET'
- # line.key
- # # => nil
- class Line < String
- DATE = /\[([^\]]+)\]/
- QUOTED_STRING = /"([^"]+)"/
- REST = /(\S+)/
- LINE_SCANNER = /#{DATE}|#{QUOTED_STRING}|#{REST}/
-
- cattr_accessor :decorators
- @@decorators = Hash.new {|hash, key| hash[key] = lambda {|entry| CoercibleString.coerce(entry)}}
- cattr_reader :fields
- @@fields = []
-
- class << self
- def field(name, offset, type = nil, &block) #:nodoc:
- decorators[name] = block if block_given?
- fields << name
- class_eval(<<-EVAL, __FILE__, __LINE__)
- def #{name}
- value = parts[#{offset} - 1]
- if value == '-'
- nil
- else
- self.class.decorators[:#{name}].call(value)
- end
- end
- memoized :#{name}
- EVAL
- end
-
- # Time.parse doesn't like %d/%B/%Y:%H:%M:%S %z so we have to transform it unfortunately
- def typecast_time(datetime) #:nodoc:
- datetime.sub!(%r|^(\w{2})/(\w{3})/(\w{4})|, '\2 \1 \3')
- datetime.sub!(':', ' ')
- Time.parse(datetime)
- end
- end
-
- def initialize(line) #:nodoc:
- super(line)
- @parts = parse
- end
-
- field(:owner, 1) {|entry| Owner.new('id' => entry) }
- field :bucket, 2
- field(:time, 3) {|entry| typecast_time(entry)}
- field :remote_ip, 4
- field(:requestor, 5) {|entry| Owner.new('id' => entry) }
- field :request_id, 6
- field :operation, 7
- field :key, 8
- field :request_uri, 9
- field :http_status, 10
- field :error_code, 11
- field :bytes_sent, 12
- field :object_size, 13
- field :total_time, 14
- field :turn_around_time, 15
- field :referrer, 16
- field :user_agent, 17
-
- # Returns all fields of the line in a hash of the form :field_name => :field_value.
- #
- # line.attributes.values_at(:bucket, :key)
- # # => ['marcel', 'kiss.jpg']
- def attributes
- self.class.fields.inject({}) do |attribute_hash, field|
- attribute_hash[field] = send(field)
- attribute_hash
- end
- end
-
- private
- attr_reader :parts
-
- def parse
- scan(LINE_SCANNER).flatten.compact
- end
- end
- end
-
- module Management #:nodoc:
- def self.included(klass) #:nodoc:
- klass.extend(ClassMethods)
- klass.extend(LoggingGrants)
- end
-
- module ClassMethods
- # Returns the logging status for the bucket named name. From the logging status you can determine the bucket logs are delivered to
- # and what the bucket object's keys are prefixed with. For more information see the Logging::Status class.
- #
- # Bucket.logging_status_for 'marcel'
- def logging_status_for(name = nil, status = nil)
- if name.is_a?(Status)
- status = name
- name = nil
- end
-
- path = path(name) << '?logging'
- status ? put(path, {}, status.to_xml) : Status.new(get(path).parsed)
- end
- alias_method :logging_status, :logging_status_for
-
- # Enables logging for the bucket named name. You can specify what bucket to log to with the 'target_bucket' option as well
- # as what prefix to add to the log files with the 'target_prefix' option. Unless you specify otherwise, logs will be delivered to
- # the same bucket that is being logged and will be prefixed with log-.
- def enable_logging_for(name = nil, options = {})
- name = bucket_name(name)
- default_options = {'target_bucket' => name, 'target_prefix' => 'log-'}
- options = default_options.merge(options)
- grant_logging_access_to_target_bucket(options['target_bucket'])
- logging_status(name, Status.new(options))
- end
- alias_method :enable_logging, :enable_logging_for
-
- # Disables logging for the bucket named name.
- def disable_logging_for(name = nil)
- logging_status(bucket_name(name), Status.new)
- end
- alias_method :disable_logging, :disable_logging_for
-
- # Returns true if logging has been enabled for the bucket named name.
- def logging_enabled_for?(name = nil)
- logging_status(bucket_name(name)).logging_enabled?
- end
- alias_method :logging_enabled?, :logging_enabled_for?
-
- # Returns the collection of logs for the bucket named name.
- #
- # Bucket.logs_for 'marcel'
- #
- # Accepts the same options as Bucket.find, such as :max_keys and :marker.
- def logs_for(name = nil, options = {})
- if name.is_a?(Hash)
- options = name
- name = nil
- end
-
- name = bucket_name(name)
- logging_status = logging_status_for(name)
- return [] unless logging_status.logging_enabled?
- objects(logging_status.target_bucket, options.merge(:prefix => logging_status.target_prefix)).map do |log_object|
- Log.new(log_object)
- end
- end
- alias_method :logs, :logs_for
- end
-
- module LoggingGrants #:nodoc:
- def grant_logging_access_to_target_bucket(target_bucket)
- acl = acl(target_bucket)
- acl.grants << ACL::Grant.grant(:logging_write)
- acl.grants << ACL::Grant.grant(:logging_read_acp)
- acl(target_bucket, acl)
- end
- end
-
- def logging_status
- self.class.logging_status_for(name)
- end
-
- def enable_logging(*args)
- self.class.enable_logging_for(name, *args)
- end
-
- def disable_logging(*args)
- self.class.disable_logging_for(name, *args)
- end
-
- def logging_enabled?
- self.class.logging_enabled_for?(name)
- end
-
- def logs(options = {})
- self.class.logs_for(name, options)
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/aws/s3/object.rb b/lib/aws/s3/object.rb
deleted file mode 100644
index 78a8607..0000000
--- a/lib/aws/s3/object.rb
+++ /dev/null
@@ -1,616 +0,0 @@
-module AWS
- module S3
- # S3Objects represent the data you store on S3. They have a key (their name) and a value (their data). All objects belong to a
- # bucket.
- #
- # You can store an object on S3 by specifying a key, its data and the name of the bucket you want to put it in:
- #
- # S3Object.store('me.jpg', open('headshot.jpg'), 'photos')
- #
- # The content type of the object will be inferred by its extension. If the appropriate content type can not be inferred, S3 defaults
- # to binary/octet-stream.
- #
- # If you want to override this, you can explicitly indicate what content type the object should have with the :content_type option:
- #
- # file = 'black-flowers.m4a'
- # S3Object.store(
- # file,
- # open(file),
- # 'jukebox',
- # :content_type => 'audio/mp4a-latm'
- # )
- #
- # You can read more about storing files on S3 in the documentation for S3Object.store.
- #
- # If you just want to fetch an object you've stored on S3, you just specify its name and its bucket:
- #
- # picture = S3Object.find 'headshot.jpg', 'photos'
- #
- # N.B. The actual data for the file is not downloaded in both the example where the file appeared in the bucket and when fetched directly.
- # You get the data for the file like this:
- #
- # picture.value
- #
- # You can fetch just the object's data directly:
- #
- # S3Object.value 'headshot.jpg', 'photos'
- #
- # Or stream it by passing a block to stream:
- #
- # open('song.mp3', 'w') do |file|
- # S3Object.stream('song.mp3', 'jukebox') do |chunk|
- # file.write chunk
- # end
- # end
- #
- # The data of the file, once download, is cached, so subsequent calls to value won't redownload the file unless you
- # tell the object to reload its value:
- #
- # # Redownloads the file's data
- # song.value(:reload)
- #
- # Other functionality includes:
- #
- # # Check if an object exists?
- # S3Object.exists? 'headshot.jpg', 'photos'
- #
- # # Copying an object
- # S3Object.copy 'headshot.jpg', 'headshot2.jpg', 'photos'
- #
- # # Renaming an object
- # S3Object.rename 'headshot.jpg', 'portrait.jpg', 'photos'
- #
- # # Deleting an object
- # S3Object.delete 'headshot.jpg', 'photos'
- #
- # ==== More about objects and their metadata
- #
- # You can find out the content type of your object with the content_type method:
- #
- # song.content_type
- # # => "audio/mpeg"
- #
- # You can change the content type as well if you like:
- #
- # song.content_type = 'application/pdf'
- # song.store
- #
- # (Keep in mind that due to limitations in S3's exposed API, the only way to change things like the content_type
- # is to PUT the object onto S3 again. In the case of large files, this will result in fully re-uploading the file.)
- #
- # A bevy of information about an object can be had using the about method:
- #
- # pp song.about
- # {"last-modified" => "Sat, 28 Oct 2006 21:29:26 GMT",
- # "content-type" => "binary/octet-stream",
- # "etag" => "\"dc629038ffc674bee6f62eb64ff3a\"",
- # "date" => "Sat, 28 Oct 2006 21:30:41 GMT",
- # "x-amz-request-id" => "B7BC68F55495B1C8",
- # "server" => "AmazonS3",
- # "content-length" => "3418766"}
- #
- # You can get and set metadata for an object:
- #
- # song.metadata
- # # => {}
- # song.metadata[:album] = "A River Ain't Too Much To Love"
- # # => "A River Ain't Too Much To Love"
- # song.metadata[:released] = 2005
- # pp song.metadata
- # {"x-amz-meta-released" => 2005,
- # "x-amz-meta-album" => "A River Ain't Too Much To Love"}
- # song.store
- #
- # That metadata will be saved in S3 and is hence forth available from that object:
- #
- # song = S3Object.find('black-flowers.mp3', 'jukebox')
- # pp song.metadata
- # {"x-amz-meta-released" => "2005",
- # "x-amz-meta-album" => "A River Ain't Too Much To Love"}
- # song.metadata[:released]
- # # => "2005"
- # song.metadata[:released] = 2006
- # pp song.metadata
- # {"x-amz-meta-released" => 2006,
- # "x-amz-meta-album" => "A River Ain't Too Much To Love"}
- class S3Object < Base
- class << self
- # Returns the value of the object with key in the specified bucket.
- #
- # === Conditional GET options
- #
- # * :if_modified_since - Return the object only if it has been modified since the specified time,
- # otherwise return a 304 (not modified).
- # * :if_unmodified_since - Return the object only if it has not been modified since the specified time,
- # otherwise raise PreconditionFailed.
- # * :if_match - Return the object only if its entity tag (ETag) is the same as the one specified,
- # otherwise raise PreconditionFailed.
- # * :if_none_match - Return the object only if its entity tag (ETag) is different from the one specified,
- # otherwise return a 304 (not modified).
- #
- # === Other options
- # * :range - Return only the bytes of the object in the specified range.
- def value(key, bucket = nil, options = {}, &block)
- Value.new(get(path!(bucket, key, options), options, &block))
- end
-
- def stream(key, bucket = nil, options = {}, &block)
- value(key, bucket, options) do |response|
- response.read_body(&block)
- end
- end
-
- # Returns the object whose key is name in the specified bucket. If the specified key does not
- # exist, a NoSuchKey exception will be raised.
- def find(key, bucket = nil)
- # N.B. This is arguably a hack. From what the current S3 API exposes, when you retrieve a bucket, it
- # provides a listing of all the files in that bucket (assuming you haven't limited the scope of what it returns).
- # Each file in the listing contains information about that file. It is from this information that an S3Object is built.
- #
- # If you know the specific file that you want, S3 allows you to make a get request for that specific file and it returns
- # the value of that file in its response body. This response body is used to build an S3Object::Value object.
- # If you want information about that file, you can make a head request and the headers of the response will contain
- # information about that file. There is no way, though, to say, give me the representation of just this given file the same
- # way that it would appear in a bucket listing.
- #
- # When fetching a bucket, you can provide options which narrow the scope of what files should be returned in that listing.
- # Of those options, one is marker which is a string and instructs the bucket to return only object's who's key comes after
- # the specified marker according to alphabetic order. Another option is max-keys which defaults to 1000 but allows you
- # to dictate how many objects should be returned in the listing. With a combination of marker and max-keys you can
- # *almost* specify exactly which file you'd like it to return, but marker is not inclusive. In other words, if there is a bucket
- # which contains three objects who's keys are respectively 'a', 'b' and 'c', then fetching a bucket listing with marker set to 'b' will only
- # return 'c', not 'b'.
- #
- # Given all that, my hack to fetch a bucket with only one specific file, is to set the marker to the result of calling String#previous on
- # the desired object's key, which functionally makes the key ordered one degree higher than the desired object key according to
- # alphabetic ordering. This is a hack, but it should work around 99% of the time. I can't think of a scenario where it would return
- # something incorrect.
-
- # We need to ensure the key doesn't have extended characters but not uri escape it before doing the lookup and comparing since if the object exists,
- # the key on S3 will have been normalized
- key = key.remove_extended unless key.valid_utf8?
- bucket = Bucket.find(bucket_name(bucket), :marker => key.previous, :max_keys => 1)
- # If our heuristic failed, trigger a NoSuchKey exception
- if (object = bucket.objects.first) && object.key == key
- object
- else
- raise NoSuchKey.new("No such key `#{key}'", bucket)
- end
- end
-
- # Makes a copy of the object with key to copy_key, preserving the ACL of the existing object if the :copy_acl option is true (default false).
- def copy(key, copy_key, bucket = nil, options = {})
- bucket = bucket_name(bucket)
- source_key = path!(bucket, key)
- default_options = {'x-amz-copy-source' => source_key}
- target_key = path!(bucket, copy_key)
- returning put(target_key, default_options.merge(options)) do
- acl(copy_key, bucket, acl(key, bucket)) if options[:copy_acl]
- end
- end
-
- # Rename the object with key from to have key in to.
- def rename(from, to, bucket = nil, options = {})
- copy(from, to, bucket, options)
- delete(from, bucket)
- end
-
- # Fetch information about the object with key from bucket. Information includes content type, content length,
- # last modified time, and others.
- #
- # If the specified key does not exist, NoSuchKey is raised.
- def about(key, bucket = nil, options = {})
- response = head(path!(bucket, key, options), options)
- raise NoSuchKey.new("No such key `#{key}'", bucket) if response.code == 404
- About.new(response.headers)
- end
-
- # Checks if the object with key in bucket exists.
- #
- # S3Object.exists? 'kiss.jpg', 'marcel'
- # # => true
- def exists?(key, bucket = nil)
- about(key, bucket)
- true
- rescue NoSuchKey
- false
- end
-
- # Delete object with key from bucket.
- def delete(key, bucket = nil, options = {})
- # A bit confusing. Calling super actually makes an HTTP DELETE request. The delete method is
- # defined in the Base class. It happens to have the same name.
- super(path!(bucket, key, options), options).success?
- end
-
- # When storing an object on the S3 servers using S3Object.store, the data argument can be a string or an I/O stream.
- # If data is an I/O stream it will be read in segments and written to the socket incrementally. This approach
- # may be desirable for very large files so they are not read into memory all at once.
- #
- # # Non streamed upload
- # S3Object.store('greeting.txt', 'hello world!', 'marcel')
- #
- # # Streamed upload
- # S3Object.store('roots.mpeg', open('roots.mpeg'), 'marcel')
- def store(key, data, bucket = nil, options = {})
- validate_key!(key)
- # Must build path before infering content type in case bucket is being used for options
- path = path!(bucket, key, options)
- infer_content_type!(key, options)
-
- put(path, options, data) # Don't call .success? on response. We want to get the etag.
- end
- alias_method :create, :store
- alias_method :save, :store
-
- # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
- # authenticated url for an object like this:
- #
- # S3Object.url_for('beluga_baby.jpg', 'marcel_molina')
- #
- # By default authenticated urls expire 5 minutes after they were generated.
- #
- # Expiration options can be specified either with an absolute time since the epoch with the :expires options,
- # or with a number of seconds relative to now with the :expires_in options:
- #
- # # Absolute expiration date
- # # (Expires January 18th, 2038)
- # doomsday = Time.mktime(2038, 1, 18).to_i
- # S3Object.url_for('beluga_baby.jpg',
- # 'marcel',
- # :expires => doomsday)
- #
- # # Expiration relative to now specified in seconds
- # # (Expires in 3 hours)
- # S3Object.url_for('beluga_baby.jpg',
- # 'marcel',
- # :expires_in => 60 * 60 * 3)
- #
- # You can specify whether the url should go over SSL with the :use_ssl option:
- #
- # # Url will use https protocol
- # S3Object.url_for('beluga_baby.jpg',
- # 'marcel',
- # :use_ssl => true)
- #
- # By default, the ssl settings for the current connection will be used.
- #
- # If you have an object handy, you can use its url method with the same objects:
- #
- # song.url(:expires_in => 30)
- #
- # To get an unauthenticated url for the object, such as in the case
- # when the object is publicly readable, pass the
- # :authenticated option with a value of false.
- #
- # S3Object.url_for('beluga_baby.jpg',
- # 'marcel',
- # :authenticated => false)
- # # => http://s3.amazonaws.com/marcel/beluga_baby.jpg
- def url_for(name, bucket = nil, options = {})
- connection.url_for(path!(bucket, name, options), options) # Do not normalize options
- end
-
- def path!(bucket, name, options = {}) #:nodoc:
- # We're using the second argument for options
- if bucket.is_a?(Hash)
- options.replace(bucket)
- bucket = nil
- end
- path = '/' << File.join(bucket_name(bucket), name)
- if (query = options[:query]).respond_to?(:to_query_string)
- path << query.to_query_string
- end
- path
- end
-
- private
-
- def validate_key!(key)
- raise InvalidKeyName.new(key) unless key && key.size <= 1024
- end
-
- def infer_content_type!(key, options)
- return if options.has_key?(:content_type)
- if mime_type = MIME::Types.type_for(key).first
- options[:content_type] = mime_type.content_type
- end
- end
- end
-
- class Value < String #:nodoc:
- attr_reader :response
- def initialize(response)
- super(response.body)
- @response = response
- end
- end
-
- class About < Hash #:nodoc:
- def initialize(headers)
- super()
- replace(headers)
- metadata
- end
-
- def [](header)
- super(header.to_header)
- end
-
- def []=(header, value)
- super(header.to_header, value)
- end
-
- def to_headers
- self.merge(metadata.to_headers)
- end
-
- def metadata
- Metadata.new(self)
- end
- memoized :metadata
- end
-
- class Metadata < Hash #:nodoc:
- HEADER_PREFIX = 'x-amz-meta-'
- SIZE_LIMIT = 2048 # 2 kilobytes
-
- def initialize(headers)
- @headers = headers
- super()
- extract_metadata!
- end
-
- def []=(header, value)
- super(header_name(header.to_header), value)
- end
-
- def [](header)
- super(header_name(header.to_header))
- end
-
- def to_headers
- validate!
- self
- end
-
- private
- attr_reader :headers
-
- def extract_metadata!
- headers.keys.grep(Regexp.new(HEADER_PREFIX)).each do |metadata_header|
- self[metadata_header] = headers.delete(metadata_header)
- end
- end
-
- def header_name(name)
- name =~ Regexp.new(HEADER_PREFIX) ? name : [HEADER_PREFIX, name].join
- end
-
- def validate!
- invalid_headers = inject([]) do |invalid, (name, value)|
- invalid << name unless valid?(value)
- invalid
- end
-
- raise InvalidMetadataValue.new(invalid_headers) unless invalid_headers.empty?
- end
-
- def valid?(value)
- value && value.size < SIZE_LIMIT
- end
- end
-
- attr_writer :value #:nodoc:
-
- # Provides readers and writers for all valid header settings listed in valid_header_settings.
- # Subsequent saves to the object after setting any of the valid headers settings will be reflected in
- # information about the object.
- #
- # some_s3_object.content_type
- # => nil
- # some_s3_object.content_type = 'text/plain'
- # => "text/plain"
- # some_s3_object.content_type
- # => "text/plain"
- # some_s3_object.store
- # S3Object.about(some_s3_object.key, some_s3_object.bucket.name)['content-type']
- # => "text/plain"
- include SelectiveAttributeProxy #:nodoc
-
- proxy_to :about, :exclusively => false
-
- # Initializes a new S3Object.
- def initialize(attributes = {}, &block)
- super
- self.value = attributes.delete(:value)
- self.bucket = attributes.delete(:bucket)
- yield self if block_given?
- end
-
- # The current object's bucket. If no bucket has been set, a NoBucketSpecified exception will be raised. For
- # cases where you are not sure if the bucket has been set, you can use the belongs_to_bucket? method.
- def bucket
- @bucket or raise NoBucketSpecified
- end
-
- # Sets the bucket that the object belongs to.
- def bucket=(bucket)
- @bucket = bucket
- self
- end
-
- # Returns true if the current object has been assigned to a bucket yet. Objects must belong to a bucket before they
- # can be saved onto S3.
- def belongs_to_bucket?
- !@bucket.nil?
- end
- alias_method :orphan?, :belongs_to_bucket?
-
- # Returns the key of the object. If the key is not set, a NoKeySpecified exception will be raised. For cases
- # where you are not sure if the key has been set, you can use the key_set? method. Objects must have a key
- # set to be saved onto S3. Objects which have already been saved onto S3 will always have their key set.
- def key
- attributes['key'] or raise NoKeySpecified
- end
-
- # Sets the key for the current object.
- def key=(value)
- attributes['key'] = value
- end
-
- # Returns true if the current object has had its key set yet. Objects which have already been saved will
- # always return true. This method is useful for objects which have not been saved yet so you know if you
- # need to set the object's key since you can not save an object unless its key has been set.
- #
- # object.store if object.key_set? && object.belongs_to_bucket?
- def key_set?
- !attributes['key'].nil?
- end
-
- # Lazily loads object data.
- #
- # Force a reload of the data by passing :reload.
- #
- # object.value(:reload)
- #
- # When loading the data for the first time you can optionally yield to a block which will
- # allow you to stream the data in segments.
- #
- # object.value do |segment|
- # send_data segment
- # end
- #
- # The full list of options are listed in the documentation for its class method counter part, S3Object::value.
- def value(options = {}, &block)
- if options.is_a?(Hash)
- reload = !options.empty?
- else
- reload = options
- options = {}
- end
- expirable_memoize(reload) do
- self.class.stream(key, bucket.name, options, &block)
- end
- end
-
- # Interface to information about the current object. Information is read only, though some of its data
- # can be modified through specific methods, such as content_type and content_type=.
- #
- # pp some_object.about
- # {"last-modified" => "Sat, 28 Oct 2006 21:29:26 GMT",
- # "x-amz-id-2" => "LdcQRk5qLwxJQiZ8OH50HhoyKuqyWoJ67B6i+rOE5MxpjJTWh1kCkL+I0NQzbVQn",
- # "content-type" => "binary/octet-stream",
- # "etag" => "\"dc629038ffc674bee6f62eb68454ff3a\"",
- # "date" => "Sat, 28 Oct 2006 21:30:41 GMT",
- # "x-amz-request-id" => "B7BC68F55495B1C8",
- # "server" => "AmazonS3",
- # "content-length" => "3418766"}
- #
- # some_object.content_type
- # # => "binary/octet-stream"
- # some_object.content_type = 'audio/mpeg'
- # some_object.content_type
- # # => 'audio/mpeg'
- # some_object.store
- def about
- stored? ? self.class.about(key, bucket.name) : About.new
- end
- memoized :about
-
- # Interface to viewing and editing metadata for the current object. To be treated like a Hash.
- #
- # some_object.metadata
- # # => {}
- # some_object.metadata[:author] = 'Dave Thomas'
- # some_object.metadata
- # # => {"x-amz-meta-author" => "Dave Thomas"}
- # some_object.metadata[:author]
- # # => "Dave Thomas"
- def metadata
- about.metadata
- end
- memoized :metadata
-
- # Saves the current object with the specified options. Valid options are listed in the documentation for S3Object::store.
- def store(options = {})
- raise DeletedObject if frozen?
- options = about.to_headers.merge(options) if stored?
- response = self.class.store(key, value, bucket.name, options)
- bucket.update(:stored, self)
- response.success?
- end
- alias_method :create, :store
- alias_method :save, :store
-
- # Deletes the current object. Trying to save an object after it has been deleted with
- # raise a DeletedObject exception.
- def delete
- bucket.update(:deleted, self)
- freeze
- self.class.delete(key, bucket.name)
- end
-
- # Copies the current object, given it the name copy_name. Keep in mind that due to limitations in
- # S3's API, this operation requires retransmitting the entire object to S3.
- def copy(copy_name, options = {})
- self.class.copy(key, copy_name, bucket.name, options)
- end
-
- # Rename the current object. Keep in mind that due to limitations in S3's API, this operation requires
- # retransmitting the entire object to S3.
- def rename(to, options = {})
- self.class.rename(key, to, bucket.name, options)
- end
-
- def etag(reload = false)
- return nil unless stored?
- expirable_memoize(reload) do
- reload ? about(reload)['etag'][1...-1] : attributes['e_tag'][1...-1]
- end
- end
-
- # Returns the owner of the current object.
- def owner
- Owner.new(attributes['owner'])
- end
- memoized :owner
-
- # Generates an authenticated url for the current object. Accepts the same options as its class method
- # counter part S3Object.url_for.
- def url(options = {})
- self.class.url_for(key, bucket.name, options)
- end
-
- # Returns true if the current object has been stored on S3 yet.
- def stored?
- !attributes['e_tag'].nil?
- end
-
- def ==(s3object) #:nodoc:
- path == s3object.path
- end
-
- def path #:nodoc:
- self.class.path!(
- belongs_to_bucket? ? bucket.name : '(no bucket)',
- key_set? ? key : '(no key)'
- )
- end
-
- # Don't dump binary data :)
- def inspect #:nodoc:
- "#<%s:0x%s '%s'>" % [self.class, object_id, path]
- end
-
- private
- def proxiable_attribute?(name)
- valid_header_settings.include?(name)
- end
-
- def valid_header_settings
- %w(cache_control content_type content_length content_md5 content_disposition content_encoding expires)
- end
- end
- end
-end
diff --git a/lib/aws/s3/owner.rb b/lib/aws/s3/owner.rb
deleted file mode 100644
index e628f0f..0000000
--- a/lib/aws/s3/owner.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-module AWS
- module S3
- # Entities in S3 have an associated owner (the person who created them). The owner is a canonical representation of an
- # entity in the S3 system. It has an id and a display_name.
- #
- # These attributes can be used when specifying a ACL::Grantee for an ACL::Grant.
- #
- # You can retrieve the owner of the current account by calling Owner.current.
- class Owner
- undef_method :id if method_defined?(:id) # Get rid of Object#id
- include SelectiveAttributeProxy
-
- class << self
- # The owner of the current account.
- def current
- response = Service.get('/')
- new(response.parsed['owner']) if response.parsed['owner']
- end
- memoized :current
- end
-
- def initialize(attributes = {}) #:nodoc:
- @attributes = attributes
- end
-
- def ==(other_owner) #:nodoc:
- hash == other_owner.hash
- end
-
- def hash #:nodoc
- [id, display_name].join.hash
- end
-
- private
- def proxiable_attribute?(name)
- valid_attributes.include?(name)
- end
-
- def valid_attributes
- %w(id display_name)
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/aws/s3/parsing.rb b/lib/aws/s3/parsing.rb
deleted file mode 100644
index c74679b..0000000
--- a/lib/aws/s3/parsing.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-#:stopdoc:
-module AWS
- module S3
- module Parsing
- class << self
- def parser=(parsing_library)
- XmlParser.parsing_library = parsing_library
- end
-
- def parser
- XmlParser.parsing_library
- end
- end
-
- module Typecasting
- def typecast(object)
- case object
- when Hash
- typecast_hash(object)
- when Array
- object.map {|element| typecast(element)}
- when String
- CoercibleString.coerce(object)
- else
- object
- end
- end
-
- def typecast_hash(hash)
- if content = hash['__content__']
- typecast(content)
- else
- keys = hash.keys.map {|key| key.underscore}
- values = hash.values.map {|value| typecast(value)}
- keys.inject({}) do |new_hash, key|
- new_hash[key] = values.slice!(0)
- new_hash
- end
- end
- end
- end
-
- class XmlParser < Hash
- include Typecasting
-
- class << self
- attr_accessor :parsing_library
- end
-
- attr_reader :body, :xml_in, :root
-
- def initialize(body)
- @body = body
- unless body.strip.empty?
- parse
- set_root
- typecast_xml_in
- end
- end
-
- private
-
- def parse
- @xml_in = self.class.parsing_library.xml_in(body, parsing_options)
- end
-
- def parsing_options
- {
- # Includes the enclosing tag as the top level key
- 'keeproot' => true,
- # Makes tag value available via the '__content__' key
- 'contentkey' => '__content__',
- # Always parse tags into a hash, even when there are no attributes
- # (unless there is also no value, in which case it is nil)
- 'forcecontent' => true,
- # If a tag is empty, makes its content nil
- 'suppressempty' => nil,
- # Force nested elements to be put into an array, even if there is only one of them
- 'forcearray' => ['Contents', 'Bucket', 'Grant']
- }
- end
-
- def set_root
- @root = @xml_in.keys.first.underscore
- end
-
- def typecast_xml_in
- typecast_xml = {}
- @xml_in.dup.each do |key, value| # Some typecasting is destructive so we dup
- typecast_xml[key.underscore] = typecast(value)
- end
- # An empty body will try to update with a string so only update if the result is a hash
- update(typecast_xml[root]) if typecast_xml[root].is_a?(Hash)
- end
- end
- end
- end
-end
-#:startdoc:
\ No newline at end of file
diff --git a/lib/aws/s3/response.rb b/lib/aws/s3/response.rb
deleted file mode 100644
index fa3b8a1..0000000
--- a/lib/aws/s3/response.rb
+++ /dev/null
@@ -1,180 +0,0 @@
-#:stopdoc:
-module AWS
- module S3
- class Base
- class Response < String
- attr_reader :response, :body, :parsed
- def initialize(response)
- @response = response
- @body = response.body.to_s
- super(body)
- end
-
- def headers
- headers = {}
- response.each do |header, value|
- headers[header] = value
- end
- headers
- end
- memoized :headers
-
- def [](header)
- headers[header]
- end
-
- def each(&block)
- headers.each(&block)
- end
-
- def code
- response.code.to_i
- end
-
- {:success => 200..299, :redirect => 300..399,
- :client_error => 400..499, :server_error => 500..599}.each do |result, code_range|
- class_eval(<<-EVAL, __FILE__, __LINE__)
- def #{result}?
- return false unless response
- (#{code_range}).include? code
- end
- EVAL
- end
-
- def error?
- !success? && response['content-type'] == 'application/xml' && parsed.root == 'error'
- end
-
- def error
- Error.new(parsed, self)
- end
- memoized :error
-
- def parsed
- # XmlSimple is picky about what kind of object it parses, so we pass in body rather than self
- Parsing::XmlParser.new(body)
- end
- memoized :parsed
-
- def inspect
- "#<%s:0x%s %s %s>" % [self.class, object_id, response.code, response.message]
- end
- end
- end
-
- class Bucket
- class Response < Base::Response
- def bucket
- parsed
- end
- end
- end
-
- class S3Object
- class Response < Base::Response
- def etag
- headers['etag'][1...-1]
- end
- end
- end
-
- class Service
- class Response < Base::Response
- def empty?
- parsed['buckets'].nil?
- end
-
- def buckets
- parsed['buckets']['bucket'] || []
- end
- end
- end
-
- module ACL
- class Policy
- class Response < Base::Response
- alias_method :policy, :parsed
- end
- end
- end
-
- # Requests whose response code is between 300 and 599 and contain an in their body
- # are wrapped in an Error::Response. This Error::Response contains an Error object which raises an exception
- # that corresponds to the error in the response body. The exception object contains the ErrorResponse, so
- # in all cases where a request happens, you can rescue ResponseError and have access to the ErrorResponse and
- # its Error object which contains information about the ResponseError.
- #
- # begin
- # Bucket.create(..)
- # rescue ResponseError => exception
- # exception.response
- # # =>
- # exception.response.error
- # # =>
- # end
- class Error
- class Response < Base::Response
- def error?
- true
- end
-
- def inspect
- "#<%s:0x%s %s %s: '%s'>" % [self.class.name, object_id, response.code, error.code, error.message]
- end
- end
- end
-
- # Guess response class name from current class name. If the guessed response class doesn't exist
- # do the same thing to the current class's parent class, up the inheritance heirarchy until either
- # a response class is found or until we get to the top of the heirarchy in which case we just use
- # the the Base response class.
- #
- # Important: This implemantation assumes that the Base class has a corresponding Base::Response.
- class FindResponseClass #:nodoc:
- class << self
- def for(start)
- new(start).find
- end
- end
-
- def initialize(start)
- @container = AWS::S3
- @current_class = start
- end
-
- def find
- self.current_class = current_class.superclass until response_class_found?
- target.const_get(class_to_find)
- end
-
- private
- attr_reader :container
- attr_accessor :current_class
-
- def target
- container.const_get(current_name)
- end
-
- def target?
- container.const_defined?(current_name)
- end
-
- def response_class_found?
- target? && target.const_defined?(class_to_find)
- end
-
- def class_to_find
- :Response
- end
-
- def current_name
- truncate(current_class)
- end
-
- def truncate(klass)
- klass.name[/[^:]+$/]
- end
- end
- end
-end
-#:startdoc:
\ No newline at end of file
diff --git a/lib/aws/s3/service.rb b/lib/aws/s3/service.rb
deleted file mode 100644
index 23939e5..0000000
--- a/lib/aws/s3/service.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-module AWS
- module S3
- # The service lets you find out general information about your account, like what buckets you have.
- #
- # Service.buckets
- # # => []
- class Service < Base
- @@response = nil #:nodoc:
-
- class << self
- # List all your buckets.
- #
- # Service.buckets
- # # => []
- #
- # For performance reasons, the bucket list will be cached. If you want avoid all caching, pass the :reload
- # as an argument:
- #
- # Service.buckets(:reload)
- def buckets
- response = get('/')
- if response.empty?
- []
- else
- response.buckets.map {|attributes| Bucket.new(attributes)}
- end
- end
- memoized :buckets
-
- # Sometimes methods that make requests to the S3 servers return some object, like a Bucket or an S3Object.
- # Other times they return just true. Other times they raise an exception that you may want to rescue. Despite all these
- # possible outcomes, every method that makes a request stores its response object for you in Service.response. You can always
- # get to the last request's response via Service.response.
- #
- # objects = Bucket.objects('jukebox')
- # Service.response.success?
- # # => true
- #
- # This is also useful when an error exception is raised in the console which you weren't expecting. You can
- # root around in the response to get more details of what might have gone wrong.
- def response
- @@response
- end
-
- def response=(response) #:nodoc:
- @@response = response
- end
- end
- end
- end
-end
diff --git a/lib/aws/s3/version.rb b/lib/aws/s3/version.rb
deleted file mode 100644
index 92547b5..0000000
--- a/lib/aws/s3/version.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module AWS
- module S3
- module VERSION #:nodoc:
- MAJOR = '0'
- MINOR = '6'
- TINY = '3'
- BETA = nil # Time.now.to_i.to_s
- end
-
- Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY, VERSION::BETA].compact * '.'
- end
-end
diff --git a/lib/aws/writer.rb b/lib/aws/writer.rb
new file mode 100644
index 0000000..f2c49f9
--- /dev/null
+++ b/lib/aws/writer.rb
@@ -0,0 +1,15 @@
+class Writer
+
+ def initialize
+ end
+
+ def write(file, write_options)
+ s3 = Aws::S3::Resource.new(region: 'us-west-1')
+ s3.bucket('exportspdf').object('s3filename').upload_file(file.path)
+ end
+
+ def [](val)
+ self
+ end
+
+end
\ No newline at end of file