jaf-s3 0.6.3.1286550476

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/COPYING +19 -0
  2. data/INSTALL +55 -0
  3. data/README +545 -0
  4. data/Rakefile +334 -0
  5. data/bin/s3sh +6 -0
  6. data/bin/setup.rb +10 -0
  7. data/lib/aws/s3.rb +60 -0
  8. data/lib/aws/s3/acl.rb +636 -0
  9. data/lib/aws/s3/authentication.rb +221 -0
  10. data/lib/aws/s3/base.rb +240 -0
  11. data/lib/aws/s3/bittorrent.rb +58 -0
  12. data/lib/aws/s3/bucket.rb +319 -0
  13. data/lib/aws/s3/connection.rb +278 -0
  14. data/lib/aws/s3/error.rb +69 -0
  15. data/lib/aws/s3/exceptions.rb +133 -0
  16. data/lib/aws/s3/extensions.rb +340 -0
  17. data/lib/aws/s3/logging.rb +314 -0
  18. data/lib/aws/s3/object.rb +612 -0
  19. data/lib/aws/s3/owner.rb +44 -0
  20. data/lib/aws/s3/parsing.rb +99 -0
  21. data/lib/aws/s3/response.rb +180 -0
  22. data/lib/aws/s3/service.rb +51 -0
  23. data/lib/aws/s3/version.rb +12 -0
  24. data/support/faster-xml-simple/lib/faster_xml_simple.rb +187 -0
  25. data/support/faster-xml-simple/test/regression_test.rb +47 -0
  26. data/support/faster-xml-simple/test/test_helper.rb +17 -0
  27. data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +46 -0
  28. data/support/rdoc/code_info.rb +211 -0
  29. data/test/acl_test.rb +254 -0
  30. data/test/authentication_test.rb +114 -0
  31. data/test/base_test.rb +136 -0
  32. data/test/bucket_test.rb +74 -0
  33. data/test/connection_test.rb +215 -0
  34. data/test/error_test.rb +70 -0
  35. data/test/extensions_test.rb +340 -0
  36. data/test/fixtures.rb +89 -0
  37. data/test/fixtures/buckets.yml +133 -0
  38. data/test/fixtures/errors.yml +34 -0
  39. data/test/fixtures/headers.yml +3 -0
  40. data/test/fixtures/logging.yml +15 -0
  41. data/test/fixtures/loglines.yml +5 -0
  42. data/test/fixtures/logs.yml +7 -0
  43. data/test/fixtures/policies.yml +16 -0
  44. data/test/logging_test.rb +89 -0
  45. data/test/mocks/fake_response.rb +26 -0
  46. data/test/object_test.rb +205 -0
  47. data/test/parsing_test.rb +66 -0
  48. data/test/remote/acl_test.rb +117 -0
  49. data/test/remote/bittorrent_test.rb +45 -0
  50. data/test/remote/bucket_test.rb +146 -0
  51. data/test/remote/logging_test.rb +82 -0
  52. data/test/remote/object_test.rb +371 -0
  53. data/test/remote/test_file.data +0 -0
  54. data/test/remote/test_helper.rb +33 -0
  55. data/test/response_test.rb +68 -0
  56. data/test/service_test.rb +23 -0
  57. data/test/test_helper.rb +110 -0
  58. metadata +200 -0
@@ -0,0 +1,44 @@
1
+ module AWS
2
+ module S3
3
+ # Entities in S3 have an associated owner (the person who created them). The owner is a canonical representation of an
4
+ # entity in the S3 system. It has an <tt>id</tt> and a <tt>display_name</tt>.
5
+ #
6
+ # These attributes can be used when specifying a ACL::Grantee for an ACL::Grant.
7
+ #
8
+ # You can retrieve the owner of the current account by calling Owner.current.
9
+ class Owner
10
+ undef_method :id if method_defined?(:id) # Get rid of Object#id
11
+ include SelectiveAttributeProxy
12
+
13
+ class << self
14
+ # The owner of the current account.
15
+ def current
16
+ response = Service.get('/')
17
+ new(response.parsed['owner']) if response.parsed['owner']
18
+ end
19
+ memoized :current
20
+ end
21
+
22
+ def initialize(attributes = {}) #:nodoc:
23
+ @attributes = attributes
24
+ end
25
+
26
+ def ==(other_owner) #:nodoc:
27
+ hash == other_owner.hash
28
+ end
29
+
30
+ def hash #:nodoc
31
+ [id, display_name].join.hash
32
+ end
33
+
34
+ private
35
+ def proxiable_attribute?(name)
36
+ valid_attributes.include?(name)
37
+ end
38
+
39
+ def valid_attributes
40
+ %w(id display_name)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,99 @@
1
+ #:stopdoc:
2
+ module AWS
3
+ module S3
4
+ module Parsing
5
+ class << self
6
+ def parser=(parsing_library)
7
+ XmlParser.parsing_library = parsing_library
8
+ end
9
+
10
+ def parser
11
+ XmlParser.parsing_library
12
+ end
13
+ end
14
+
15
+ module Typecasting
16
+ def typecast(object)
17
+ case object
18
+ when Hash
19
+ typecast_hash(object)
20
+ when Array
21
+ object.map {|element| typecast(element)}
22
+ when String
23
+ CoercibleString.coerce(object)
24
+ else
25
+ object
26
+ end
27
+ end
28
+
29
+ def typecast_hash(hash)
30
+ if content = hash['__content__']
31
+ typecast(content)
32
+ else
33
+ keys = hash.keys.map {|key| key.underscore}
34
+ values = hash.values.map {|value| typecast(value)}
35
+ keys.inject({}) do |new_hash, key|
36
+ new_hash[key] = values.slice!(0)
37
+ new_hash
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ class XmlParser < Hash
44
+ include Typecasting
45
+
46
+ class << self
47
+ attr_accessor :parsing_library
48
+ end
49
+
50
+ attr_reader :body, :xml_in, :root
51
+
52
+ def initialize(body)
53
+ @body = body
54
+ unless body.strip.empty?
55
+ parse
56
+ set_root
57
+ typecast_xml_in
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def parse
64
+ @xml_in = self.class.parsing_library.xml_in(body, parsing_options)
65
+ end
66
+
67
+ def parsing_options
68
+ {
69
+ # Includes the enclosing tag as the top level key
70
+ 'keeproot' => true,
71
+ # Makes tag value available via the '__content__' key
72
+ 'contentkey' => '__content__',
73
+ # Always parse tags into a hash, even when there are no attributes
74
+ # (unless there is also no value, in which case it is nil)
75
+ 'forcecontent' => true,
76
+ # If a tag is empty, makes its content nil
77
+ 'suppressempty' => nil,
78
+ # Force nested elements to be put into an array, even if there is only one of them
79
+ 'forcearray' => ['Contents', 'Bucket', 'Grant']
80
+ }
81
+ end
82
+
83
+ def set_root
84
+ @root = @xml_in.keys.first.underscore
85
+ end
86
+
87
+ def typecast_xml_in
88
+ typecast_xml = {}
89
+ @xml_in.dup.each do |key, value| # Some typecasting is destructive so we dup
90
+ typecast_xml[key.underscore] = typecast(value)
91
+ end
92
+ # An empty body will try to update with a string so only update if the result is a hash
93
+ update(typecast_xml[root]) if typecast_xml[root].is_a?(Hash)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ #:startdoc:
@@ -0,0 +1,180 @@
1
+ #:stopdoc:
2
+ module AWS
3
+ module S3
4
+ class Base
5
+ class Response < String
6
+ attr_reader :response, :body, :parsed
7
+ def initialize(response)
8
+ @response = response
9
+ @body = response.body.to_s
10
+ super(body)
11
+ end
12
+
13
+ def headers
14
+ headers = {}
15
+ response.each do |header, value|
16
+ headers[header] = value
17
+ end
18
+ headers
19
+ end
20
+ memoized :headers
21
+
22
+ def [](header)
23
+ headers[header]
24
+ end
25
+
26
+ def each(&block)
27
+ headers.each(&block)
28
+ end
29
+
30
+ def code
31
+ response.code.to_i
32
+ end
33
+
34
+ {:success => 200..299, :redirect => 300..399,
35
+ :client_error => 400..499, :server_error => 500..599}.each do |result, code_range|
36
+ class_eval(<<-EVAL, __FILE__, __LINE__)
37
+ def #{result}?
38
+ return false unless response
39
+ (#{code_range}).include? code
40
+ end
41
+ EVAL
42
+ end
43
+
44
+ def error?
45
+ !success? && response['content-type'] == 'application/xml' && parsed.root == 'error'
46
+ end
47
+
48
+ def error
49
+ Error.new(parsed, self)
50
+ end
51
+ memoized :error
52
+
53
+ def parsed
54
+ # XmlSimple is picky about what kind of object it parses, so we pass in body rather than self
55
+ Parsing::XmlParser.new(body)
56
+ end
57
+ memoized :parsed
58
+
59
+ def inspect
60
+ "#<%s:0x%s %s %s>" % [self.class, object_id, response.code, response.message]
61
+ end
62
+ end
63
+ end
64
+
65
+ class Bucket
66
+ class Response < Base::Response
67
+ def bucket
68
+ parsed
69
+ end
70
+ end
71
+ end
72
+
73
+ class S3Object
74
+ class Response < Base::Response
75
+ def etag
76
+ headers['etag'][1...-1]
77
+ end
78
+ end
79
+ end
80
+
81
+ class Service
82
+ class Response < Base::Response
83
+ def empty?
84
+ parsed['buckets'].nil?
85
+ end
86
+
87
+ def buckets
88
+ parsed['buckets']['bucket'] || []
89
+ end
90
+ end
91
+ end
92
+
93
+ module ACL
94
+ class Policy
95
+ class Response < Base::Response
96
+ alias_method :policy, :parsed
97
+ end
98
+ end
99
+ end
100
+
101
+ # Requests whose response code is between 300 and 599 and contain an <Error></Error> in their body
102
+ # are wrapped in an Error::Response. This Error::Response contains an Error object which raises an exception
103
+ # that corresponds to the error in the response body. The exception object contains the ErrorResponse, so
104
+ # in all cases where a request happens, you can rescue ResponseError and have access to the ErrorResponse and
105
+ # its Error object which contains information about the ResponseError.
106
+ #
107
+ # begin
108
+ # Bucket.create(..)
109
+ # rescue ResponseError => exception
110
+ # exception.response
111
+ # # => <Error::Response>
112
+ # exception.response.error
113
+ # # => <Error>
114
+ # end
115
+ class Error
116
+ class Response < Base::Response
117
+ def error?
118
+ true
119
+ end
120
+
121
+ def inspect
122
+ "#<%s:0x%s %s %s: '%s'>" % [self.class.name, object_id, response.code, error.code, error.message]
123
+ end
124
+ end
125
+ end
126
+
127
+ # Guess response class name from current class name. If the guessed response class doesn't exist
128
+ # do the same thing to the current class's parent class, up the inheritance heirarchy until either
129
+ # a response class is found or until we get to the top of the heirarchy in which case we just use
130
+ # the the Base response class.
131
+ #
132
+ # Important: This implemantation assumes that the Base class has a corresponding Base::Response.
133
+ class FindResponseClass #:nodoc:
134
+ class << self
135
+ def for(start)
136
+ new(start).find
137
+ end
138
+ end
139
+
140
+ def initialize(start)
141
+ @container = AWS::S3
142
+ @current_class = start
143
+ end
144
+
145
+ def find
146
+ self.current_class = current_class.superclass until response_class_found?
147
+ target.const_get(class_to_find)
148
+ end
149
+
150
+ private
151
+ attr_reader :container
152
+ attr_accessor :current_class
153
+
154
+ def target
155
+ container.const_get(current_name)
156
+ end
157
+
158
+ def target?
159
+ container.const_defined?(current_name)
160
+ end
161
+
162
+ def response_class_found?
163
+ target? && target.const_defined?(class_to_find)
164
+ end
165
+
166
+ def class_to_find
167
+ :Response
168
+ end
169
+
170
+ def current_name
171
+ truncate(current_class)
172
+ end
173
+
174
+ def truncate(klass)
175
+ klass.name[/[^:]+$/]
176
+ end
177
+ end
178
+ end
179
+ end
180
+ #:startdoc:
@@ -0,0 +1,51 @@
1
+ module AWS
2
+ module S3
3
+ # The service lets you find out general information about your account, like what buckets you have.
4
+ #
5
+ # Service.buckets
6
+ # # => []
7
+ class Service < Base
8
+ @@response = nil #:nodoc:
9
+
10
+ class << self
11
+ # List all your buckets.
12
+ #
13
+ # Service.buckets
14
+ # # => []
15
+ #
16
+ # For performance reasons, the bucket list will be cached. If you want avoid all caching, pass the <tt>:reload</tt>
17
+ # as an argument:
18
+ #
19
+ # Service.buckets(:reload)
20
+ def buckets
21
+ response = get('/')
22
+ if response.empty?
23
+ []
24
+ else
25
+ response.buckets.map {|attributes| Bucket.new(attributes)}
26
+ end
27
+ end
28
+ memoized :buckets
29
+
30
+ # Sometimes methods that make requests to the S3 servers return some object, like a Bucket or an S3Object.
31
+ # Othertimes they return just <tt>true</tt>. Other times they raise an exception that you may want to rescue. Despite all these
32
+ # possible outcomes, every method that makes a request stores its response object for you in Service.response. You can always
33
+ # get to the last request's response via Service.response.
34
+ #
35
+ # objects = Bucket.objects('jukebox')
36
+ # Service.response.success?
37
+ # # => true
38
+ #
39
+ # This is also useful when an error exception is raised in the console which you weren't expecting. You can
40
+ # root around in the response to get more details of what might have gone wrong.
41
+ def response
42
+ @@response
43
+ end
44
+
45
+ def response=(response) #:nodoc:
46
+ @@response = response
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,12 @@
1
+ module AWS
2
+ module S3
3
+ module VERSION #:nodoc:
4
+ MAJOR = '0'
5
+ MINOR = '6'
6
+ TINY = '3'
7
+ BETA = Time.now.to_i.to_s
8
+ end
9
+
10
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY, VERSION::BETA].compact * '.'
11
+ end
12
+ end
@@ -0,0 +1,187 @@
1
+ #
2
+ # Copyright (c) 2006 Michael Koziarski
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in the
6
+ # Software without restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8
+ # Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18
+ # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+
21
+ require 'rubygems'
22
+ require 'xml/libxml'
23
+
24
+ class FasterXmlSimple
25
+ Version = '0.5.0'
26
+ class << self
27
+ # Take an string containing XML, and returns a hash representing that
28
+ # XML document. For example:
29
+ #
30
+ # FasterXmlSimple.xml_in("<root><something>1</something></root>")
31
+ # {"root"=>{"something"=>{"__content__"=>"1"}}}
32
+ #
33
+ # Faster XML Simple is designed to be a drop in replacement for the xml_in
34
+ # functionality of http://xml-simple.rubyforge.org
35
+ #
36
+ # The following options are supported:
37
+ #
38
+ # * <tt>contentkey</tt>: The key to use for the content of text elements,
39
+ # defaults to '\_\_content__'
40
+ # * <tt>forcearray</tt>: The list of elements which should always be returned
41
+ # as arrays. Under normal circumstances single element arrays are inlined.
42
+ # * <tt>suppressempty</tt>: The value to return for empty elements, pass +true+
43
+ # to remove empty elements entirely.
44
+ # * <tt>keeproot</tt>: By default the hash returned has a single key with the
45
+ # name of the root element. If the name of the root element isn't
46
+ # interesting to you, pass +false+.
47
+ # * <tt>forcecontent</tt>: By default a text element with no attributes, will
48
+ # be collapsed to just a string instead of a hash with a single key.
49
+ # Pass +true+ to prevent this.
50
+ #
51
+ #
52
+ def xml_in(string, options={})
53
+ new(string, options).out
54
+ end
55
+ end
56
+
57
+ def initialize(string, options) #:nodoc:
58
+ @doc = parse(string)
59
+ @options = default_options.merge options
60
+ end
61
+
62
+ def out #:nodoc:
63
+ if @options['keeproot']
64
+ {@doc.root.name => collapse(@doc.root)}
65
+ else
66
+ collapse(@doc.root)
67
+ end
68
+ end
69
+
70
+ private
71
+ def default_options
72
+ {'contentkey' => '__content__', 'forcearray' => [], 'keeproot'=>true}
73
+ end
74
+
75
+ def collapse(element)
76
+ result = hash_of_attributes(element)
77
+ if text_node? element
78
+ text = collapse_text(element)
79
+ result[content_key] = text if text =~ /\S/
80
+ elsif element.children?
81
+ element.inject(result) do |hash, child|
82
+ unless child.text?
83
+ child_result = collapse(child)
84
+ (hash[child.name] ||= []) << child_result
85
+ end
86
+ hash
87
+ end
88
+ end
89
+ if result.empty?
90
+ return empty_element
91
+ end
92
+ # Compact them to ensure it complies with the user's requests
93
+ inline_single_element_arrays(result)
94
+ remove_empty_elements(result) if suppress_empty?
95
+ if content_only?(result) && !force_content?
96
+ result[content_key]
97
+ else
98
+ result
99
+ end
100
+ end
101
+
102
+ def content_only?(result)
103
+ result.keys == [content_key]
104
+ end
105
+
106
+ def content_key
107
+ @options['contentkey']
108
+ end
109
+
110
+ def force_array?(key_name)
111
+ Array(@options['forcearray']).include?(key_name)
112
+ end
113
+
114
+ def inline_single_element_arrays(result)
115
+ result.each do |key, value|
116
+ if value.size == 1 && value.is_a?(Array) && !force_array?(key)
117
+ result[key] = value.first
118
+ end
119
+ end
120
+ end
121
+
122
+ def remove_empty_elements(result)
123
+ result.each do |key, value|
124
+ if value == empty_element
125
+ result.delete key
126
+ end
127
+ end
128
+ end
129
+
130
+ def suppress_empty?
131
+ @options['suppressempty'] == true
132
+ end
133
+
134
+ def empty_element
135
+ if !@options.has_key? 'suppressempty'
136
+ {}
137
+ else
138
+ @options['suppressempty']
139
+ end
140
+ end
141
+
142
+ # removes the content if it's nothing but blanks, prevents
143
+ # the hash being polluted with lots of content like "\n\t\t\t"
144
+ def suppress_empty_content(result)
145
+ result.delete content_key if result[content_key] !~ /\S/
146
+ end
147
+
148
+ def force_content?
149
+ @options['forcecontent']
150
+ end
151
+
152
+ # a text node is one with 1 or more child nodes which are
153
+ # text nodes, and no non-text children, there's no sensible
154
+ # way to support nodes which are text and markup like:
155
+ # <p>Something <b>Bold</b> </p>
156
+ def text_node?(element)
157
+ !element.text? && element.all? {|c| c.text?}
158
+ end
159
+
160
+ # takes a text node, and collapses it into a string
161
+ def collapse_text(element)
162
+ element.map {|c| c.content } * ''
163
+ end
164
+
165
+ def hash_of_attributes(element)
166
+ result = {}
167
+ element.each_attr do |attribute|
168
+ name = attribute.name
169
+ name = [attribute.ns, attribute.name].join(':') if attribute.ns?
170
+ result[name] = attribute.value
171
+ end
172
+ result
173
+ end
174
+
175
+ def parse(string)
176
+ if string == ''
177
+ string = ' '
178
+ end
179
+ XML::Parser.string(string).parse
180
+ end
181
+ end
182
+
183
+ class XmlSimple # :nodoc:
184
+ def self.xml_in(*args)
185
+ FasterXmlSimple.xml_in *args
186
+ end
187
+ end