ruby-atmos-pure 1.0.3

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.
@@ -0,0 +1,28 @@
1
+ module Atmos
2
+ module Exceptions
3
+
4
+ class AtmosException < Exception
5
+ end
6
+
7
+ class ArgumentException < AtmosException
8
+ end
9
+
10
+ class AuthException < AtmosException
11
+ end
12
+
13
+ class InvalidStateException < AtmosException
14
+ end
15
+
16
+ class NoSuchObjectException < AtmosException
17
+ end
18
+
19
+ class ServerException < AtmosException
20
+ end
21
+
22
+ class NotImplementedException < AtmosException
23
+ end
24
+
25
+ class InternalLibraryException < AtmosException
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,321 @@
1
+ module Atmos
2
+
3
+ #
4
+ # == Object
5
+ # This class represents an object in an Atmos store.
6
+ #
7
+ # === Object Data
8
+ # When an object is instantiated, it's data is not loaded
9
+ # due to memory considerations. You can access an object's
10
+ # data as a ruby String or as a progressive download via a block.
11
+ #
12
+ # ==== all at once
13
+ #
14
+ # obj.data
15
+ #
16
+ # ==== progressive download
17
+ #
18
+ # obj.data_as_stream do |chunk|
19
+ # datafile.write(chunk)
20
+ # end
21
+ #
22
+ # ==== partial data
23
+ #
24
+ # obj.data_as_stream(0...59) do |chunk|
25
+ # readin.concat(chunk)
26
+ # end
27
+ #
28
+ #
29
+ # === System Metadata
30
+ # Each object has some information about it stored by the system.
31
+ # This information is read only, and available as a hash on the object:
32
+ #
33
+ # obj.system_metadata => Hash
34
+ #
35
+ # obj.system_metadata.each do |key,value|
36
+ # puts "#{key}=#{value}"
37
+ # end
38
+ #
39
+ # See Atmos::Metadata for more detailed information.
40
+ #
41
+ #
42
+ # === User Metadata
43
+ # There are two kinds of user metadata, listable and non-listable.
44
+ # Each of these is available as a hash
45
+ # on the object class. These can both be modified.
46
+ #
47
+ # obj.listable_metadata => Hash
48
+ #
49
+ # obj.listable_metadata.each do |key,value|
50
+ # puts "#{key}=#{value}"
51
+ # end
52
+ #
53
+ # obj.metadata => Hash
54
+ #
55
+ # obj.metadata.each do |key,value|
56
+ # puts "#{key}=#{value}"
57
+ # end
58
+ #
59
+ # See Atmos::Metadata for more detailed information.
60
+ #
61
+ #
62
+ # === Access Control Lists (ACLs)
63
+ #
64
+ # There are two hashes for access control available as properties
65
+ # on the object: +user_acl+ and +group_acl+.
66
+ # The keys are the Atmos usernames and the values are one of
67
+ # <tt>:none</tt>, <tt>:read</tt>, <tt>:write</tt>, <tt>:full</tt>.
68
+ #
69
+ # puts obj.user_acl.inspect => {user => :full}
70
+ # puts obj.group_acl.inspect => {other => :none}
71
+ #
72
+ # See Atmos::ACL for more detailed information.
73
+ #
74
+ #
75
+ class Object
76
+ attr_reader :aoid, :request, :user # :nodoc:
77
+
78
+ @request = nil
79
+ @checksum = nil
80
+ @user_acl = nil
81
+ @group_acl = nil
82
+ @metadata = nil
83
+ @system_metadata = nil
84
+ @listable_metadata = nil
85
+
86
+
87
+ #
88
+ # This constructor is only meant for internal use. Get or create an object
89
+ # with an Atmos::Store object:
90
+ #
91
+ # obj = store.create
92
+ # obj = store.get(:id => obj_id)
93
+ # obj = store.get(:namespace => obj_id)
94
+ #
95
+ def initialize(store, action, options = {})
96
+ Atmos::LOG.debug("obj.new options: #{options.inspect}")
97
+ validate_options(options)
98
+
99
+ @deleted = false
100
+ @aoid = aoid
101
+ @store = store
102
+ @request = Atmos::Request.new(:store => @store)
103
+ @user = @store.user
104
+
105
+ if (action == :create)
106
+
107
+ if (options[:id])
108
+ raise Atmos::Exceptions::ArgumentException, "You can't specify an id on object creation."
109
+ end
110
+
111
+ Atmos::LOG.debug("Object.new: creating new object")
112
+ response = @request.do(:create_object, options)
113
+ @aoid = response.id
114
+
115
+ elsif (action == :get)
116
+
117
+ Atmos::LOG.debug("Retrieving object: id: #{options[:id]}; namespace: #{options[:namespace]}")
118
+ response = @request.do(:get_object_info, options)
119
+ @aoid = Atmos::Parser::response_get_string(response.http_response, '//xmlns:objectId')
120
+ Atmos::LOG.debug("Retrieved object id for namespace: #{@aoid}")
121
+ end
122
+
123
+ end
124
+
125
+ #
126
+ # Lazy evaluation of hash-like object containing user access control properties of the object.
127
+ #
128
+ def user_acl
129
+ #Atmos::LOG.warn("user_acl: #{@user_acl.inspect}")
130
+ if (@user_acl.nil?)
131
+ @user_acl = Atmos::ACL.new(self, Atmos::ACL::USER)
132
+ #Atmos::LOG.warn("user_acl: #{@user_acl.inspect}")
133
+ end
134
+
135
+ @user_acl
136
+ end
137
+
138
+
139
+ #
140
+ # Lazy evaluation of hash-like object containing group access control properties of the object.
141
+ #
142
+ def group_acl
143
+ if (@group_acl.nil?)
144
+ @group_acl = Atmos::ACL.new(self, Atmos::ACL::GROUP)
145
+ end
146
+
147
+ @group_acl
148
+ end
149
+
150
+
151
+ #
152
+ # Lazy evaluation of hash-like object containing non-listable properties of the object.
153
+ #
154
+ def metadata
155
+ if (@metadata.nil?)
156
+ @metadata = Atmos::Metadata.new(self, Atmos::Metadata::NON_LISTABLE)
157
+ end
158
+
159
+ @metadata
160
+ end
161
+
162
+
163
+ #
164
+ # Lazy evaluation of hash-like object containing listable metadata properties of the object.
165
+ #
166
+ def listable_metadata
167
+ if (@listable_metadata.nil?)
168
+ @listable_metadata = Atmos::Metadata.new(self, Atmos::Metadata::LISTABLE)
169
+ end
170
+
171
+ @listable_metadata
172
+ end
173
+
174
+
175
+ #
176
+ # Lazy evaluation of Hash-like object containing read-only system metadata associated with the object.
177
+ #
178
+ def system_metadata
179
+ if (@system_metadata.nil?)
180
+ @system_metadata = Atmos::Metadata.new(self, Atmos::Metadata::SYSTEM)
181
+ end
182
+
183
+ @system_metadata
184
+ end
185
+
186
+
187
+ #
188
+ # Truncates the object to size 0 without changing any of the Metadata or ACLs.
189
+ #
190
+ def truncate
191
+ do_delete_check
192
+ response = @request.do(:trunc_object, :id => @aoid, :data => nil, :length => 0)
193
+ end
194
+
195
+ #
196
+ # Deletes the object from Atmos and invalidates the object.
197
+ #
198
+ # obj = store.create
199
+ # obj.delete
200
+ #
201
+ def delete
202
+ do_delete_check
203
+ response = @request.do(:delete_object, :id => @aoid)
204
+ @deleted = true
205
+ end
206
+
207
+
208
+ #
209
+ # Returns all the object data in a single string. Be judicious about
210
+ # use of this method, since it can load the entire blob into memory.
211
+ #
212
+ # Optional:
213
+ # * <tt>:range</tt> - range of bytes to retrieve (e.g. 0...10000)
214
+ #
215
+ def data(range = nil)
216
+ do_delete_check
217
+ response = @request.do(:read_object, :id => @aoid, 'Range' => range)
218
+ response.http_response.body
219
+ end
220
+
221
+
222
+ #
223
+ # Allows progressive download of the object's data. Takes a block:
224
+ #
225
+ # obj.data_as_stream do |chunk|
226
+ # datafile.write(chunk)
227
+ # end
228
+ #
229
+ # Optional:
230
+ # * <tt>:range</tt> - range of bytes to retrieve (e.g. 0...10000)
231
+ #
232
+ def data_as_stream(range = nil, &block)
233
+ do_delete_check
234
+ @request.do(:read_object, :id => @aoid, 'Range' => range) do |response|
235
+ response.read_body do |chunk|
236
+ block.call(chunk)
237
+ end
238
+ end
239
+ end
240
+
241
+
242
+ def update(data, range=nil)
243
+ response = @request.do(:update_object, :id => @aoid, :data => data, 'Range' => range)
244
+ end
245
+
246
+
247
+ #
248
+ # Checks to see if the represented object exists on Atmos
249
+ # by requesting it's system metadata.
250
+ #
251
+ # Returns boolean +true+ or +false+.
252
+ #
253
+ def exists?
254
+ rv = true
255
+ begin
256
+ @request.do(:list_system_metadata, :id => @aoid)
257
+ rescue Atmos::Exceptions::NoSuchObjectException
258
+ rv = false
259
+ end
260
+ rv
261
+ end
262
+
263
+
264
+ def copy #:nodoc:
265
+ do_delete_check
266
+ end
267
+
268
+
269
+ def headers #:nodoc:
270
+ do_delete_check
271
+ headers = {}
272
+ [@user_acl, @group_acl, @tags, @listable_tags, @metadata, @listable_metadata].each do |attr|
273
+ val = attr.header_value
274
+ next if (val.nil? || val.empty?)
275
+ headers[attr.header_name] = attr.header_value
276
+ end
277
+ headers
278
+ end
279
+
280
+
281
+ private
282
+ def do_delete_check
283
+ raise Atmos::Exceptions::InvalidStateException if (@deleted)
284
+ end
285
+
286
+ def validate_options(options)
287
+ invalid_values = []
288
+
289
+ valid_options = [:checksum, :user_acl, :group_acl, :metadata, :listable_metadata, :mimetype, :length, :data, :namespace, :id].freeze
290
+ invalid_options = options.keys - valid_options
291
+ raise Atmos::Exceptions::ArgumentException, "Unrecognized options: #{invalid_options.inspect}" if (!invalid_options.empty?)
292
+
293
+ options.each do |k,v|
294
+ case k
295
+ when :checksum
296
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Boolean))
297
+ when :user_acl
298
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
299
+ when :group_acl
300
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
301
+ when :metadata
302
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
303
+ when :listable_metadata
304
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
305
+ when :mimetype
306
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(String))
307
+ when :namespace
308
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(String))
309
+ when :length
310
+ invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Integer))
311
+ when :data
312
+ invalid_values.push(k) if (options[k].nil? || (!options[k].kind_of?(String) && !options[k].class.ancestors.include?(IO)))
313
+ end
314
+ end
315
+
316
+ raise Atmos::Exceptions::ArgumentException, "Options of invalid type: #{invalid_values.inspect}" if (!invalid_values.empty?)
317
+ end
318
+
319
+ end
320
+
321
+ end
@@ -0,0 +1,110 @@
1
+ module Atmos
2
+ class Parser
3
+
4
+ NOKOGIRI = "nokogiri"
5
+ REXML = "rexml"
6
+ @@parser = nil
7
+
8
+
9
+ def self.parser
10
+ @@parser
11
+ end
12
+
13
+
14
+ def self.parser=(which)
15
+
16
+ @@parser = which
17
+ if (@@parser == NOKOGIRI)
18
+ require 'nokogiri'
19
+ elsif (@@parser == REXML)
20
+ require 'rexml/document'
21
+ else
22
+ raise Atmos::Exceptions::ArgumentException, "The XML parser has not been set to a known parser."
23
+ end
24
+ end
25
+
26
+
27
+ def self.response_get_array(response, string)
28
+ rv = nil
29
+
30
+ if (parser == NOKOGIRI)
31
+
32
+ doc = Nokogiri::XML(response.body)
33
+ rv = doc.xpath(string).map do |elt|
34
+ elt.content
35
+ end
36
+
37
+ elsif (parser == REXML)
38
+
39
+ doc = ::REXML::Document.new(response.body)
40
+ rv = doc.elements.to_a(string).map do |elt|
41
+ elt.text
42
+ end
43
+
44
+ end
45
+
46
+ rv
47
+ end
48
+
49
+
50
+ def self.response_get_string(response, string)
51
+ response_get_array(response,string)[0]
52
+ end
53
+
54
+
55
+ #
56
+ # Utility method to check the atmos server XML response for errors.
57
+ # Throws exception on error.
58
+ #
59
+ def self.response_check_action_error(action, response)
60
+ doc = nil
61
+ code = nil
62
+ msg = nil
63
+ exclass = Atmos::Exceptions::AtmosException
64
+
65
+ Atmos::LOG.info("body: #{response.body}")
66
+ if (!response.body.nil? && response.body.match(/<\?xml/))
67
+ if (parser == NOKOGIRI)
68
+
69
+ doc = Nokogiri::XML(response.body)
70
+
71
+ code = doc.xpath('//Error/Code')[0]
72
+ code = code.content if (!code.nil?)
73
+
74
+ msg = doc.xpath('//Error/Message')[0]
75
+ msg = msg.content if (!msg.nil?)
76
+
77
+ elsif (parser == REXML)
78
+
79
+ code = response_get_string(response, '//Error/Code')
80
+ msg = response_get_string(response, '//Error/Message')
81
+
82
+ else
83
+ raise Atmos::Exceptions::InvalidStateException, "The XML parser has not been set to a known parser."
84
+ end
85
+ end
86
+
87
+ if (!code.nil?)
88
+
89
+ Atmos::LOG.info("code: #{code}")
90
+ Atmos::LOG.info("msg: #{msg}")
91
+
92
+ if (!REST[action][:errors].nil? && !REST[action][:errors][code].nil?)
93
+ tmp = REST[action][:errors][code][:message]
94
+ msg = tmp if (!tmp.nil?)
95
+
96
+ tmp = REST[action][:errors][code][:class]
97
+ exclass = tmp if (!tmp.nil?)
98
+ end
99
+
100
+ raise exclass, msg
101
+
102
+ end
103
+
104
+ true
105
+ end
106
+
107
+
108
+
109
+ end
110
+ end