cloudfiles-sagamore 1.5.0.1
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.
- data/.gitignore +7 -0
- data/CHANGELOG +56 -0
- data/CONTRIBUTORS +34 -0
- data/COPYING +12 -0
- data/Gemfile +7 -0
- data/README.rdoc +81 -0
- data/Rakefile +21 -0
- data/TODO +0 -0
- data/cloudfiles.gemspec +69 -0
- data/lib/client.rb +618 -0
- data/lib/cloudfiles.rb +85 -0
- data/lib/cloudfiles/authentication.rb +52 -0
- data/lib/cloudfiles/connection.rb +286 -0
- data/lib/cloudfiles/container.rb +451 -0
- data/lib/cloudfiles/exception.rb +65 -0
- data/lib/cloudfiles/storage_object.rb +426 -0
- data/lib/cloudfiles/version.rb +3 -0
- data/test/cf-testunit.rb +157 -0
- data/test/cloudfiles_authentication_test.rb +44 -0
- data/test/cloudfiles_client_test.rb +797 -0
- data/test/cloudfiles_connection_test.rb +214 -0
- data/test/cloudfiles_container_test.rb +494 -0
- data/test/cloudfiles_storage_object_test.rb +211 -0
- data/test/test_helper.rb +6 -0
- metadata +112 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# The deprecated old exception types. Will go away in a couple of releases.
|
2
|
+
|
3
|
+
class SyntaxException < StandardError # :nodoc:
|
4
|
+
end
|
5
|
+
class ConnectionException < StandardError # :nodoc:
|
6
|
+
end
|
7
|
+
class AuthenticationException < StandardError # :nodoc:
|
8
|
+
end
|
9
|
+
class InvalidResponseException < StandardError # :nodoc:
|
10
|
+
end
|
11
|
+
class NonEmptyContainerException < StandardError # :nodoc:
|
12
|
+
end
|
13
|
+
class NoSuchObjectException < StandardError # :nodoc:
|
14
|
+
end
|
15
|
+
class NoSuchContainerException < StandardError # :nodoc:
|
16
|
+
end
|
17
|
+
class NoSuchAccountException < StandardError # :nodoc:
|
18
|
+
end
|
19
|
+
class MisMatchedChecksumException < StandardError # :nodoc:
|
20
|
+
end
|
21
|
+
class IOException < StandardError # :nodoc:
|
22
|
+
end
|
23
|
+
class CDNNotEnabledException < StandardError # :nodoc:
|
24
|
+
end
|
25
|
+
class ObjectExistsException < StandardError # :nodoc:
|
26
|
+
end
|
27
|
+
class ExpiredAuthTokenException < StandardError # :nodoc:
|
28
|
+
end
|
29
|
+
|
30
|
+
# The new properly scoped exceptions.
|
31
|
+
|
32
|
+
module CloudFiles
|
33
|
+
class Exception
|
34
|
+
|
35
|
+
class Syntax < SyntaxException
|
36
|
+
end
|
37
|
+
class Connection < ConnectionException # :nodoc:
|
38
|
+
end
|
39
|
+
class Authentication < AuthenticationException # :nodoc:
|
40
|
+
end
|
41
|
+
class InvalidResponse < InvalidResponseException # :nodoc:
|
42
|
+
end
|
43
|
+
class NonEmptyContainer < NonEmptyContainerException # :nodoc:
|
44
|
+
end
|
45
|
+
class NoSuchObject < NoSuchObjectException # :nodoc:
|
46
|
+
end
|
47
|
+
class NoSuchContainer < NoSuchContainerException # :nodoc:
|
48
|
+
end
|
49
|
+
class NoSuchAccount < NoSuchAccountException # :nodoc:
|
50
|
+
end
|
51
|
+
class MisMatchedChecksum < MisMatchedChecksumException # :nodoc:
|
52
|
+
end
|
53
|
+
class IO < IOException # :nodoc:
|
54
|
+
end
|
55
|
+
class CDNNotEnabled < CDNNotEnabledException # :nodoc:
|
56
|
+
end
|
57
|
+
class ObjectExists < ObjectExistsException # :nodoc:
|
58
|
+
end
|
59
|
+
class ExpiredAuthToken < ExpiredAuthTokenException # :nodoc:
|
60
|
+
end
|
61
|
+
class CDNNotAvailable < StandardError
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,426 @@
|
|
1
|
+
module CloudFiles
|
2
|
+
class StorageObject
|
3
|
+
# See COPYING for license information.
|
4
|
+
# Copyright (c) 2011, Rackspace US, Inc.
|
5
|
+
|
6
|
+
# Name of the object corresponding to the instantiated object
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# The parent CloudFiles::Container object
|
10
|
+
attr_reader :container
|
11
|
+
|
12
|
+
# Builds a new CloudFiles::StorageObject in the current container. If force_exist is set, the object must exist or a
|
13
|
+
# CloudFiles::Exception::NoSuchObject Exception will be raised. If not, an "empty" CloudFiles::StorageObject will be returned, ready for data
|
14
|
+
# via CloudFiles::StorageObject.write
|
15
|
+
def initialize(container, objectname, force_exists = false, make_path = false)
|
16
|
+
@container = container
|
17
|
+
@containername = container.name
|
18
|
+
@name = objectname
|
19
|
+
@make_path = make_path
|
20
|
+
@storagepath = "#{CloudFiles.escape @containername}/#{escaped_name}"
|
21
|
+
|
22
|
+
if force_exists
|
23
|
+
raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" unless container.object_exists?(objectname)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Refreshes the object metadata
|
28
|
+
def refresh
|
29
|
+
@object_metadata = nil
|
30
|
+
true
|
31
|
+
end
|
32
|
+
alias :populate :refresh
|
33
|
+
|
34
|
+
# Retrieves Metadata for the object
|
35
|
+
def object_metadata
|
36
|
+
@object_metadata ||= (
|
37
|
+
begin
|
38
|
+
response = SwiftClient.head_object(self.container.connection.storageurl, self.container.connection.authtoken, self.container.name, @name)
|
39
|
+
rescue ClientException => e
|
40
|
+
raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" unless (e.status.to_s =~ /^20/)
|
41
|
+
end
|
42
|
+
resphash = {}
|
43
|
+
metas = response.to_hash.select { |k,v| k.match(/^x-object-meta/) }
|
44
|
+
|
45
|
+
metas.each do |x,y|
|
46
|
+
resphash[x] = (y.respond_to?(:join) ? y.join('') : y.to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
{
|
50
|
+
:manifest => response["x-object-manifest"],
|
51
|
+
:bytes => response["content-length"],
|
52
|
+
:last_modified => Time.parse(response["last-modified"]),
|
53
|
+
:etag => response["etag"],
|
54
|
+
:content_type => response["content-type"],
|
55
|
+
:metadata => resphash
|
56
|
+
}
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def escaped_name
|
61
|
+
@escaped_name ||= escape_name @name
|
62
|
+
end
|
63
|
+
|
64
|
+
# Size of the object (in bytes)
|
65
|
+
def bytes
|
66
|
+
self.object_metadata[:bytes]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Date of the object's last modification
|
70
|
+
def last_modified
|
71
|
+
self.object_metadata[:last_modified]
|
72
|
+
end
|
73
|
+
|
74
|
+
# ETag of the object data
|
75
|
+
def etag
|
76
|
+
self.object_metadata[:etag]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Content type of the object data
|
80
|
+
def content_type
|
81
|
+
self.object_metadata[:content_type]
|
82
|
+
end
|
83
|
+
|
84
|
+
def content_type=(type)
|
85
|
+
self.copy(:headers => {'Content-Type' => type})
|
86
|
+
end
|
87
|
+
|
88
|
+
# Retrieves the data from an object and stores the data in memory. The data is returned as a string.
|
89
|
+
# Throws a NoSuchObjectException if the object doesn't exist.
|
90
|
+
#
|
91
|
+
# If the optional size and range arguments are provided, the call will return the number of bytes provided by
|
92
|
+
# size, starting from the offset provided in offset.
|
93
|
+
#
|
94
|
+
# object.data
|
95
|
+
# => "This is the text stored in the file"
|
96
|
+
def data(size = -1, offset = 0, headers = {})
|
97
|
+
if size.to_i > 0
|
98
|
+
range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
|
99
|
+
headers['Range'] = range
|
100
|
+
end
|
101
|
+
begin
|
102
|
+
response = SwiftClient.get_object(self.container.connection.storageurl, self.container.connection.authtoken, self.container.name, @name)
|
103
|
+
response[1]
|
104
|
+
rescue ClientException => e
|
105
|
+
raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" unless (e.status.to_s =~ /^20/)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
alias :read :data
|
109
|
+
|
110
|
+
# Retrieves the data from an object and returns a stream that must be passed to a block. Throws a
|
111
|
+
# NoSuchObjectException if the object doesn't exist.
|
112
|
+
#
|
113
|
+
# If the optional size and range arguments are provided, the call will return the number of bytes provided by
|
114
|
+
# size, starting from the offset provided in offset.
|
115
|
+
#
|
116
|
+
# data = ""
|
117
|
+
# object.data_stream do |chunk|
|
118
|
+
# data += chunk
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# data
|
122
|
+
# => "This is the text stored in the file"
|
123
|
+
def data_stream(size = -1, offset = 0, headers = {}, &block)
|
124
|
+
if size.to_i > 0
|
125
|
+
range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
|
126
|
+
headers['Range'] = range
|
127
|
+
end
|
128
|
+
begin
|
129
|
+
SwiftClient.get_object(self.container.connection.storageurl, self.container.connection.authtoken, self.container.name, @name, nil, nil, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the object's metadata as a nicely formatted hash, stripping off the X-Meta-Object- prefix that the system prepends to the
|
134
|
+
# key name.
|
135
|
+
#
|
136
|
+
# object.metadata
|
137
|
+
# => {"ruby"=>"cool", "foo"=>"bar"}
|
138
|
+
def metadata
|
139
|
+
metahash = {}
|
140
|
+
self.object_metadata[:metadata].each{ |key, value| metahash[key.gsub(/x-object-meta-/, '').gsub(/\+\-/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ') }
|
141
|
+
metahash
|
142
|
+
end
|
143
|
+
|
144
|
+
# Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
|
145
|
+
# However, setting metadata will overwrite any existing metadata for the object.
|
146
|
+
#
|
147
|
+
# Throws NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request
|
148
|
+
# fails.
|
149
|
+
def set_metadata(metadatahash)
|
150
|
+
headers = {}
|
151
|
+
metadatahash.each{ |key, value| headers['X-Object-Meta-' + key.to_s.capitalize] = value.to_s }
|
152
|
+
begin
|
153
|
+
SwiftClient.post_object(self.container.connection.storageurl, self.container.connection.authtoken, self.container.name, @name, headers)
|
154
|
+
true
|
155
|
+
rescue ClientException => e
|
156
|
+
raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" if (e.status.to_s == "404")
|
157
|
+
raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status.to_s}" unless (e.status.to_s =~ /^20/)
|
158
|
+
false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
alias :metadata= :set_metadata
|
162
|
+
|
163
|
+
|
164
|
+
# Returns the object's manifest.
|
165
|
+
#
|
166
|
+
# object.manifest
|
167
|
+
# => "container/prefix"
|
168
|
+
def manifest
|
169
|
+
self.object_metadata[:manifest]
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# Sets the manifest for an object. By passing a string as an argument, you can set the manifest for an object.
|
174
|
+
# However, setting manifest will overwrite any existing manifest for the object.
|
175
|
+
#
|
176
|
+
# Throws NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request
|
177
|
+
# fails.
|
178
|
+
def set_manifest(manifest)
|
179
|
+
headers = {'X-Object-Manifest' => manifest}
|
180
|
+
begin
|
181
|
+
SwiftClient.post_object(self.container.connection.storageurl, self.container.connection.authtoken, self.container.name, @name, headers)
|
182
|
+
true
|
183
|
+
rescue ClientException => e
|
184
|
+
raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" if (response.code == "404")
|
185
|
+
raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
|
186
|
+
false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# Takes supplied data and writes it to the object, saving it. You can supply an optional hash of headers, including
|
192
|
+
# Content-Type and ETag, that will be applied to the object.
|
193
|
+
#
|
194
|
+
# If you would rather stream the data in chunks, instead of reading it all into memory at once, you can pass an
|
195
|
+
# IO object for the data, such as: object.write(open('/path/to/file.mp3'))
|
196
|
+
#
|
197
|
+
# You can compute your own MD5 sum and send it in the "ETag" header. If you provide yours, it will be compared to
|
198
|
+
# the MD5 sum on the server side. If they do not match, the server will return a 422 status code and a CloudFiles::Exception::MisMatchedChecksum Exception
|
199
|
+
# will be raised. If you do not provide an MD5 sum as the ETag, one will be computed on the server side.
|
200
|
+
#
|
201
|
+
# Updates the container cache and returns true on success, raises exceptions if stuff breaks.
|
202
|
+
#
|
203
|
+
# object = container.create_object("newfile.txt")
|
204
|
+
#
|
205
|
+
# object.write("This is new data")
|
206
|
+
# => true
|
207
|
+
#
|
208
|
+
# object.data
|
209
|
+
# => "This is new data"
|
210
|
+
#
|
211
|
+
# If you are passing your data in via STDIN, just do an
|
212
|
+
#
|
213
|
+
# object.write
|
214
|
+
#
|
215
|
+
# with no data (or, if you need to pass headers)
|
216
|
+
#
|
217
|
+
# object.write(nil,{'header' => 'value})
|
218
|
+
|
219
|
+
def write(data = nil, headers = {})
|
220
|
+
raise CloudFiles::Exception::Syntax, "No data or header updates supplied" if ((data.nil? && $stdin.tty?) and headers.empty?)
|
221
|
+
# If we're taking data from standard input, send that IO object to cfreq
|
222
|
+
data = $stdin if (data.nil? && $stdin.tty? == false)
|
223
|
+
begin
|
224
|
+
response = SwiftClient.put_object(self.container.connection.storageurl, self.container.connection.authtoken, self.container.name, @name, data, nil, nil, nil, nil, headers)
|
225
|
+
rescue ClientException => e
|
226
|
+
code = e.status.to_s
|
227
|
+
raise CloudFiles::Exception::InvalidResponse, "Invalid content-length header sent" if (code == "412")
|
228
|
+
raise CloudFiles::Exception::MisMatchedChecksum, "Mismatched etag" if (code == "422")
|
229
|
+
raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{code}" unless (code =~ /^20./)
|
230
|
+
end
|
231
|
+
make_path(File.dirname(self.name)) if @make_path == true
|
232
|
+
self.refresh
|
233
|
+
true
|
234
|
+
end
|
235
|
+
# Purges CDN Edge Cache for all objects inside of this container
|
236
|
+
#
|
237
|
+
# :email, An valid email address or comma seperated
|
238
|
+
# list of emails to be notified once purge is complete .
|
239
|
+
#
|
240
|
+
# obj.purge_from_cdn
|
241
|
+
# => true
|
242
|
+
#
|
243
|
+
# or
|
244
|
+
#
|
245
|
+
# obj.purge_from_cdn("User@domain.com")
|
246
|
+
# => true
|
247
|
+
#
|
248
|
+
# or
|
249
|
+
#
|
250
|
+
# obj.purge_from_cdn("User@domain.com, User2@domain.com")
|
251
|
+
# => true
|
252
|
+
def purge_from_cdn(email=nil)
|
253
|
+
raise Exception::CDNNotAvailable unless cdn_available?
|
254
|
+
headers = {}
|
255
|
+
headers = {"X-Purge-Email" => email} if email
|
256
|
+
begin
|
257
|
+
SwiftClient.delete_object(self.container.connection.cdnurl, self.container.connection.authtoken, self.container.name, @name, nil, headers)
|
258
|
+
true
|
259
|
+
rescue ClientException => e
|
260
|
+
raise CloudFiles::Exception::Connection, "Error Unable to Purge Object: #{@name}" unless (e.status.to_s =~ /^20.$/)
|
261
|
+
false
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# A convenience method to stream data into an object from a local file (or anything that can be loaded by Ruby's open method)
|
266
|
+
#
|
267
|
+
# You can provide an optional hash of headers, in case you want to do something like set the Content-Type manually.
|
268
|
+
#
|
269
|
+
# Throws an Errno::ENOENT if the file cannot be read.
|
270
|
+
#
|
271
|
+
# object.data
|
272
|
+
# => "This is my data"
|
273
|
+
#
|
274
|
+
# object.load_from_filename("/tmp/file.txt")
|
275
|
+
# => true
|
276
|
+
#
|
277
|
+
# object.load_from_filename("/home/rackspace/myfile.tmp", 'Content-Type' => 'text/plain')
|
278
|
+
#
|
279
|
+
# object.data
|
280
|
+
# => "This data was in the file /tmp/file.txt"
|
281
|
+
#
|
282
|
+
# object.load_from_filename("/tmp/nonexistent.txt")
|
283
|
+
# => Errno::ENOENT: No such file or directory - /tmp/nonexistent.txt
|
284
|
+
def load_from_filename(filename, headers = {}, check_md5 = false)
|
285
|
+
f = open(filename)
|
286
|
+
if check_md5
|
287
|
+
require 'digest/md5'
|
288
|
+
md5_hash = Digest::MD5.file(filename)
|
289
|
+
headers["Etag"] = md5_hash.to_s()
|
290
|
+
end
|
291
|
+
self.write(f, headers)
|
292
|
+
f.close
|
293
|
+
true
|
294
|
+
end
|
295
|
+
|
296
|
+
# A convenience method to stream data from an object into a local file
|
297
|
+
#
|
298
|
+
# Throws an Errno::ENOENT if the file cannot be opened for writing due to a path error,
|
299
|
+
# and Errno::EACCES if the file cannot be opened for writing due to permissions.
|
300
|
+
#
|
301
|
+
# object.data
|
302
|
+
# => "This is my data"
|
303
|
+
#
|
304
|
+
# object.save_to_filename("/tmp/file.txt")
|
305
|
+
# => true
|
306
|
+
#
|
307
|
+
# $ cat /tmp/file.txt
|
308
|
+
# "This is my data"
|
309
|
+
#
|
310
|
+
# object.save_to_filename("/tmp/owned_by_root.txt")
|
311
|
+
# => Errno::EACCES: Permission denied - /tmp/owned_by_root.txt
|
312
|
+
def save_to_filename(filename)
|
313
|
+
File.open(filename, 'wb+') do |f|
|
314
|
+
self.data_stream do |chunk|
|
315
|
+
f.write chunk
|
316
|
+
end
|
317
|
+
end
|
318
|
+
true
|
319
|
+
end
|
320
|
+
|
321
|
+
# If the parent container is public (CDN-enabled), returns the CDN URL to this object. Otherwise, return nil
|
322
|
+
#
|
323
|
+
# public_object.public_url
|
324
|
+
# => "http://c0001234.cdn.cloudfiles.rackspacecloud.com/myfile.jpg"
|
325
|
+
#
|
326
|
+
# private_object.public_url
|
327
|
+
# => nil
|
328
|
+
def public_url
|
329
|
+
self.container.public? ? self.container.cdn_url + "/#{escaped_name}" : nil
|
330
|
+
end
|
331
|
+
|
332
|
+
# If the parent container is public (CDN-enabled), returns the SSL CDN URL to this object. Otherwise, return nil
|
333
|
+
#
|
334
|
+
# public_object.public_ssl_url
|
335
|
+
# => "https://c61.ssl.cf0.rackcdn.com/myfile.jpg"
|
336
|
+
#
|
337
|
+
# private_object.public_ssl_url
|
338
|
+
# => nil
|
339
|
+
def public_ssl_url
|
340
|
+
self.container.public? ? self.container.cdn_ssl_url + "/#{escaped_name}" : nil
|
341
|
+
end
|
342
|
+
|
343
|
+
# If the parent container is public (CDN-enabled), returns the SSL CDN URL to this object. Otherwise, return nil
|
344
|
+
#
|
345
|
+
# public_object.public_streaming_url
|
346
|
+
# => "https://c61.stream.rackcdn.com/myfile.jpg"
|
347
|
+
#
|
348
|
+
# private_object.public_streaming_url
|
349
|
+
# => nil
|
350
|
+
def public_streaming_url
|
351
|
+
self.container.public? ? self.container.cdn_streaming_url + "/#{escaped_name}" : nil
|
352
|
+
end
|
353
|
+
|
354
|
+
# Copy this object to a new location (optionally in a new container)
|
355
|
+
#
|
356
|
+
# You must supply either a name for the new object or a container name, or both. If a :name is supplied without a :container,
|
357
|
+
# the object is copied within the current container. If the :container is specified with no :name, then the object is copied
|
358
|
+
# to the new container with its current name.
|
359
|
+
#
|
360
|
+
# object.copy(:name => "images/funny/lolcat.jpg", :container => "pictures")
|
361
|
+
#
|
362
|
+
# You may also supply a hash of headers in the :headers option. From there, you can set things like Content-Type, or other
|
363
|
+
# headers as available in the API document.
|
364
|
+
#
|
365
|
+
# object.copy(:name => 'newfile.tmp', :headers => {'Content-Type' => 'text/plain'})
|
366
|
+
#
|
367
|
+
# Returns the new CloudFiles::StorageObject for the copied item.
|
368
|
+
def copy(options = {})
|
369
|
+
raise CloudFiles::Exception::Syntax, "You must provide the :container, :name, or :headers for this operation" unless (options[:container] || options[:name] || options[:headers])
|
370
|
+
new_container = options[:container] || self.container.name
|
371
|
+
new_name = options[:name] || self.name
|
372
|
+
new_headers = options[:headers] || {}
|
373
|
+
raise CloudFiles::Exception::Syntax, "The :headers option must be a hash" unless new_headers.is_a?(Hash)
|
374
|
+
new_name.sub!(/^\//,'')
|
375
|
+
headers = {'X-Copy-From' => "#{self.container.name}/#{self.name}", 'Content-Type' => self.content_type.sub(/;.+/, '')}.merge(new_headers)
|
376
|
+
# , 'Content-Type' => self.content_type
|
377
|
+
new_path = "#{CloudFiles.escape new_container}/#{escape_name new_name}"
|
378
|
+
begin
|
379
|
+
response = SwiftClient.put_object(self.container.connection.storageurl, self.container.connection.authtoken, new_container, new_name, nil, nil, nil, nil, nil, headers)
|
380
|
+
return CloudFiles::Container.new(self.container.connection, new_container).object(new_name)
|
381
|
+
rescue ClientException => e
|
382
|
+
code = e.status.to_s
|
383
|
+
raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Takes the same options as the copy method, only it does a copy followed by a delete on the original object.
|
388
|
+
#
|
389
|
+
# Returns the new CloudFiles::StorageObject for the moved item. You should not attempt to use the old object after doing
|
390
|
+
# a move.
|
391
|
+
def move(options = {})
|
392
|
+
new_object = self.copy(options)
|
393
|
+
self.container.delete_object(self.name)
|
394
|
+
self.freeze
|
395
|
+
return new_object
|
396
|
+
end
|
397
|
+
|
398
|
+
def to_s # :nodoc:
|
399
|
+
@name
|
400
|
+
end
|
401
|
+
|
402
|
+
def escape_name(name)
|
403
|
+
CloudFiles.escape name
|
404
|
+
end
|
405
|
+
|
406
|
+
private
|
407
|
+
|
408
|
+
def cdn_available?
|
409
|
+
@cdn_available ||= self.container.connection.cdn_available?
|
410
|
+
end
|
411
|
+
|
412
|
+
def make_path(path) # :nodoc:
|
413
|
+
if path == "." || path == "/"
|
414
|
+
return
|
415
|
+
else
|
416
|
+
unless self.container.object_exists?(path)
|
417
|
+
o = self.container.create_object(path)
|
418
|
+
o.write(nil, {'Content-Type' => 'application/directory'})
|
419
|
+
end
|
420
|
+
make_path(File.dirname(path))
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
end
|