rackspace-cloudfiles 1.3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +16 -0
- data/README.rdoc +60 -0
- data/Rakefile +37 -0
- data/TODO +0 -0
- data/cloudfiles.gemspec +38 -0
- data/lib/cloudfiles.rb +69 -0
- data/lib/cloudfiles/authentication.rb +38 -0
- data/lib/cloudfiles/connection.rb +271 -0
- data/lib/cloudfiles/container.rb +252 -0
- data/lib/cloudfiles/storage_object.rb +246 -0
- data/test/cf-testunit.rb +157 -0
- data/test/cloudfiles_authentication_test.rb +37 -0
- data/test/cloudfiles_connection_test.rb +279 -0
- data/test/cloudfiles_container_test.rb +190 -0
- data/test/cloudfiles_storage_object_test.rb +170 -0
- data/test/test_helper.rb +5 -0
- metadata +102 -0
@@ -0,0 +1,252 @@
|
|
1
|
+
module CloudFiles
|
2
|
+
class Container
|
3
|
+
# See COPYING for license information.
|
4
|
+
# Copyright (c) 2009, Rackspace US, Inc.
|
5
|
+
|
6
|
+
# Name of the container which corresponds to the instantiated container class
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# Size of the container (in bytes)
|
10
|
+
attr_reader :bytes
|
11
|
+
|
12
|
+
# Number of objects in the container
|
13
|
+
attr_reader :count
|
14
|
+
|
15
|
+
# True if container is public, false if container is private
|
16
|
+
attr_reader :cdn_enabled
|
17
|
+
|
18
|
+
# CDN container TTL (if container is public)
|
19
|
+
attr_reader :cdn_ttl
|
20
|
+
|
21
|
+
# CDN container URL (if container if public)
|
22
|
+
attr_reader :cdn_url
|
23
|
+
|
24
|
+
# The parent CloudFiles::Connection object for this container
|
25
|
+
attr_reader :connection
|
26
|
+
|
27
|
+
# Retrieves an existing CloudFiles::Container object tied to the current CloudFiles::Connection. If the requested
|
28
|
+
# container does not exist, it will raise a NoSuchContainerException.
|
29
|
+
#
|
30
|
+
# Will likely not be called directly, instead use connection.container('container_name') to retrieve the object.
|
31
|
+
def initialize(connection,name)
|
32
|
+
@connection = connection
|
33
|
+
@name = name
|
34
|
+
@storagehost = self.connection.storagehost
|
35
|
+
@storagepath = self.connection.storagepath + "/" + URI.encode(@name).gsub(/&/,'%26')
|
36
|
+
@cdnmgmthost = self.connection.cdnmgmthost
|
37
|
+
@cdnmgmtpath = self.connection.cdnmgmtpath + "/" + URI.encode(@name).gsub(/&/,'%26')
|
38
|
+
populate
|
39
|
+
end
|
40
|
+
|
41
|
+
# Retrieves data about the container and populates class variables. It is automatically called
|
42
|
+
# when the Container class is instantiated. If you need to refresh the variables, such as
|
43
|
+
# size, count, cdn_enabled, cdn_ttl, and cdn_url, this method can be called again.
|
44
|
+
#
|
45
|
+
# container.count
|
46
|
+
# => 2
|
47
|
+
# [Upload new file to the container]
|
48
|
+
# container.count
|
49
|
+
# => 2
|
50
|
+
# container.populate
|
51
|
+
# container.count
|
52
|
+
# => 3
|
53
|
+
def populate
|
54
|
+
# Get the size and object count
|
55
|
+
response = self.connection.cfreq("HEAD",@storagehost,@storagepath+"/")
|
56
|
+
raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "204")
|
57
|
+
@bytes = response["x-container-bytes-used"].to_i
|
58
|
+
@count = response["x-container-object-count"].to_i
|
59
|
+
|
60
|
+
# Get the CDN-related details
|
61
|
+
response = self.connection.cfreq("HEAD",@cdnmgmthost,@cdnmgmtpath)
|
62
|
+
if (response.code == "204")
|
63
|
+
@cdn_enabled = true
|
64
|
+
@cdn_ttl = response["x-ttl"]
|
65
|
+
@cdn_url = response["x-cdn-uri"]
|
66
|
+
else
|
67
|
+
@cdn_enabled = false
|
68
|
+
@cdn_ttl = false
|
69
|
+
@cdn_url = false
|
70
|
+
end
|
71
|
+
true
|
72
|
+
end
|
73
|
+
alias :refresh :populate
|
74
|
+
|
75
|
+
# Returns the CloudFiles::StorageObject for the named object. Refer to the CloudFiles::StorageObject class for available
|
76
|
+
# methods. If the object exists, it will be returned. If the object does not exist, a NoSuchObjectException will be thrown.
|
77
|
+
#
|
78
|
+
# object = container.object('test.txt')
|
79
|
+
# object.data
|
80
|
+
# => "This is test data"
|
81
|
+
#
|
82
|
+
# object = container.object('newfile.txt')
|
83
|
+
# => NoSuchObjectException: Object newfile.txt does not exist
|
84
|
+
def object(objectname)
|
85
|
+
o = CloudFiles::StorageObject.new(self,objectname,true)
|
86
|
+
return o
|
87
|
+
end
|
88
|
+
alias :get_object :object
|
89
|
+
|
90
|
+
|
91
|
+
# Gathers a list of all available objects in the current container and returns an array of object names.
|
92
|
+
# container = cf.container("My Container")
|
93
|
+
# container.objects #=> [ "dog", "cat", "donkey", "monkeydir/capuchin"]
|
94
|
+
# Pass a limit argument to limit the list to a number of objects:
|
95
|
+
# container.objects(:limit => 1) #=> [ "dog" ]
|
96
|
+
# Pass an offset with or without a limit to start the list at a certain object:
|
97
|
+
# container.objects(:limit => 1, :offset => 2) #=> [ "donkey" ]
|
98
|
+
# Pass a prefix to search for objects that start with a certain string:
|
99
|
+
# container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
|
100
|
+
# Only search within a certain pseudo-filesystem path:
|
101
|
+
# container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
|
102
|
+
# All arguments to this method are optional.
|
103
|
+
#
|
104
|
+
# Returns an empty array if no object exist in the container. Throws an InvalidResponseException
|
105
|
+
# if the request fails.
|
106
|
+
def objects(params = {})
|
107
|
+
paramarr = []
|
108
|
+
paramarr << ["limit=#{URI.encode(params[:limit].to_i).gsub(/&/,'%26')}"] if params[:limit]
|
109
|
+
paramarr << ["offset=#{URI.encode(params[:offset].to_i).gsub(/&/,'%26')}"] if params[:offset]
|
110
|
+
paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
|
111
|
+
paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
|
112
|
+
paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
|
113
|
+
response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}")
|
114
|
+
return [] if (response.code == "204")
|
115
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
|
116
|
+
return response.body.to_a.map { |x| x.chomp }
|
117
|
+
end
|
118
|
+
alias :list_objects :objects
|
119
|
+
|
120
|
+
# Retrieves a list of all objects in the current container along with their size in bytes, hash, and content_type.
|
121
|
+
# If no objects exist, an empty hash is returned. Throws an InvalidResponseException if the request fails. Takes a
|
122
|
+
# parameter hash as an argument, in the same form as the objects method.
|
123
|
+
#
|
124
|
+
# Returns a hash in the same format as the containers_detail from the CloudFiles class.
|
125
|
+
#
|
126
|
+
# container.objects_detail
|
127
|
+
# => {"test.txt"=>{:content_type=>"application/octet-stream",
|
128
|
+
# :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
|
129
|
+
# :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
|
130
|
+
# :bytes=>"16"},
|
131
|
+
# "new.txt"=>{:content_type=>"application/octet-stream",
|
132
|
+
# :hash=>"0aa820d91aed05d2ef291d324e47bc96",
|
133
|
+
# :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
|
134
|
+
# :bytes=>"22"}
|
135
|
+
# }
|
136
|
+
def objects_detail(params = {})
|
137
|
+
paramarr = []
|
138
|
+
paramarr << ["format=xml"]
|
139
|
+
paramarr << ["limit=#{URI.encode(params[:limit].to_i).gsub(/&/,'%26')}"] if params[:limit]
|
140
|
+
paramarr << ["offset=#{URI.encode(params[:offset].to_i).gsub(/&/,'%26')}"] if params[:offset]
|
141
|
+
paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
|
142
|
+
paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
|
143
|
+
paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
|
144
|
+
response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}")
|
145
|
+
return {} if (response.code == "204")
|
146
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
|
147
|
+
doc = REXML::Document.new(response.body)
|
148
|
+
detailhash = {}
|
149
|
+
doc.elements.each("container/object") { |o|
|
150
|
+
detailhash[o.elements["name"].text] = { :bytes => o.elements["bytes"].text, :hash => o.elements["hash"].text, :content_type => o.elements["content_type"].text, :last_modified => Time.parse(o.elements["last_modified"].text) }
|
151
|
+
}
|
152
|
+
doc = nil
|
153
|
+
return detailhash
|
154
|
+
end
|
155
|
+
alias :list_objects_info :objects_detail
|
156
|
+
|
157
|
+
# Returns true if the container is public and CDN-enabled. Returns false otherwise.
|
158
|
+
#
|
159
|
+
# public_container.public?
|
160
|
+
# => true
|
161
|
+
#
|
162
|
+
# private_container.public?
|
163
|
+
# => false
|
164
|
+
def public?
|
165
|
+
return @cdn_enabled
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns true if a container is empty and returns false otherwise.
|
169
|
+
#
|
170
|
+
# new_container.empty?
|
171
|
+
# => true
|
172
|
+
#
|
173
|
+
# full_container.empty?
|
174
|
+
# => false
|
175
|
+
def empty?
|
176
|
+
return (@count.to_i == 0)? true : false
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns true if object exists and returns false otherwise.
|
180
|
+
#
|
181
|
+
# container.object_exists?('goodfile.txt')
|
182
|
+
# => true
|
183
|
+
#
|
184
|
+
# container.object_exists?('badfile.txt')
|
185
|
+
# => false
|
186
|
+
def object_exists?(objectname)
|
187
|
+
response = self.connection.cfreq("HEAD",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}")
|
188
|
+
return (response.code == "204")? true : false
|
189
|
+
end
|
190
|
+
|
191
|
+
# Creates a new CloudFiles::StorageObject in the current container.
|
192
|
+
#
|
193
|
+
# If an object with the specified name exists in the current container, that object will be returned. Otherwise,
|
194
|
+
# an empty new object will be returned.
|
195
|
+
#
|
196
|
+
# Passing in the optional make_path argument as true will create zero-byte objects to simulate a filesystem path
|
197
|
+
# to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
|
198
|
+
# be used in the Container.objects method.
|
199
|
+
def create_object(objectname,make_path = false)
|
200
|
+
CloudFiles::StorageObject.new(self,objectname,false,make_path)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
|
204
|
+
# NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request fails.
|
205
|
+
#
|
206
|
+
# container.delete_object('new.txt')
|
207
|
+
# => true
|
208
|
+
#
|
209
|
+
# container.delete_object('nonexistent_file.txt')
|
210
|
+
# => NoSuchObjectException: Object nonexistent_file.txt does not exist
|
211
|
+
def delete_object(objectname)
|
212
|
+
response = self.connection.cfreq("DELETE",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}")
|
213
|
+
raise NoSuchObjectException, "Object #{objectname} does not exist" if (response.code == "404")
|
214
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "204")
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
218
|
+
# Makes a container publicly available via the Cloud Files CDN and returns true upon success. Throws NoSuchContainerException
|
219
|
+
# if the container doesn't exist or if the request fails.
|
220
|
+
#
|
221
|
+
# Takes an optional argument, which is the CDN cache TTL in seconds (default 86400 seconds or 1 day)
|
222
|
+
#
|
223
|
+
# container.make_public(432000)
|
224
|
+
# => true
|
225
|
+
def make_public(ttl = 86400)
|
226
|
+
headers = { "X-CDN-Enabled" => "True", "X-TTL" => ttl.to_s }
|
227
|
+
response = self.connection.cfreq("PUT",@cdnmgmthost,@cdnmgmtpath,headers)
|
228
|
+
raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
232
|
+
# Makes a container private and returns true upon success. Throws NoSuchContainerException
|
233
|
+
# if the container doesn't exist or if the request fails.
|
234
|
+
#
|
235
|
+
# Note that if the container was previously public, it will continue to exist out on the CDN until it expires.
|
236
|
+
#
|
237
|
+
# container.make_private
|
238
|
+
# => true
|
239
|
+
def make_private
|
240
|
+
headers = { "X-CDN-Enabled" => "False" }
|
241
|
+
response = self.connection.cfreq("PUT",@cdnmgmthost,@cdnmgmtpath,headers)
|
242
|
+
raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
|
243
|
+
true
|
244
|
+
end
|
245
|
+
|
246
|
+
def to_s # :nodoc:
|
247
|
+
@name
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
module CloudFiles
|
2
|
+
class StorageObject
|
3
|
+
# See COPYING for license information.
|
4
|
+
# Copyright (c) 2009, Rackspace US, Inc.
|
5
|
+
|
6
|
+
# Name of the object corresponding to the instantiated object
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# Size of the object (in bytes)
|
10
|
+
attr_reader :bytes
|
11
|
+
|
12
|
+
# The parent CloudFiles::Container object
|
13
|
+
attr_reader :container
|
14
|
+
|
15
|
+
# Date of the object's last modification
|
16
|
+
attr_reader :last_modified
|
17
|
+
|
18
|
+
# ETag of the object data
|
19
|
+
attr_reader :etag
|
20
|
+
|
21
|
+
# Content type of the object data
|
22
|
+
attr_reader :content_type
|
23
|
+
|
24
|
+
# Builds a new CloudFiles::StorageObject in the current container. If force_exist is set, the object must exist or a
|
25
|
+
# NoSuchObjectException will be raised. If not, an "empty" CloudFiles::StorageObject will be returned, ready for data
|
26
|
+
# via CloudFiles::StorageObject.write
|
27
|
+
def initialize(container,objectname,force_exists=false,make_path=false)
|
28
|
+
if objectname.match(/\?/)
|
29
|
+
raise SyntaxException, "Object #{objectname} contains an invalid character in the name (? not allowed)"
|
30
|
+
end
|
31
|
+
@container = container
|
32
|
+
@containername = container.name
|
33
|
+
@name = objectname
|
34
|
+
@make_path = make_path
|
35
|
+
@storagehost = self.container.connection.storagehost
|
36
|
+
@storagepath = self.container.connection.storagepath+"/#{URI.encode(@containername).gsub(/&/,'%26')}/#{URI.encode(@name).gsub(/&/,'%26')}"
|
37
|
+
if container.object_exists?(objectname)
|
38
|
+
populate
|
39
|
+
else
|
40
|
+
raise NoSuchObjectException, "Object #{@name} does not exist" if force_exists
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Caches data about the CloudFiles::StorageObject for fast retrieval. This method is automatically called when the
|
45
|
+
# class is initialized, but it can be called again if the data needs to be updated.
|
46
|
+
def populate
|
47
|
+
response = self.container.connection.cfreq("HEAD",@storagehost,@storagepath)
|
48
|
+
raise NoSuchObjectException, "Object #{@name} does not exist" if (response.code != "204")
|
49
|
+
@bytes = response["content-length"]
|
50
|
+
@last_modified = Time.parse(response["last-modified"])
|
51
|
+
@etag = response["etag"]
|
52
|
+
@content_type = response["content-type"]
|
53
|
+
resphash = {}
|
54
|
+
response.to_hash.select { |k,v| k.match(/^x-object-meta/) }.each { |x| resphash[x[0]] = x[1][0].to_s }
|
55
|
+
@metadata = resphash
|
56
|
+
true
|
57
|
+
end
|
58
|
+
alias :refresh :populate
|
59
|
+
|
60
|
+
# Retrieves the data from an object and stores the data in memory. The data is returned as a string.
|
61
|
+
# Throws a NoSuchObjectException if the object doesn't exist.
|
62
|
+
#
|
63
|
+
# If the optional size and range arguments are provided, the call will return the number of bytes provided by
|
64
|
+
# size, starting from the offset provided in offset.
|
65
|
+
#
|
66
|
+
# object.data
|
67
|
+
# => "This is the text stored in the file"
|
68
|
+
def data(size=-1,offset=0,headers = {})
|
69
|
+
if size.to_i > 0
|
70
|
+
range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
|
71
|
+
headers['Range'] = range
|
72
|
+
end
|
73
|
+
response = self.container.connection.cfreq("GET",@storagehost,@storagepath,headers)
|
74
|
+
print "DEBUG: Code is #{response.code}\n"
|
75
|
+
raise NoSuchObjectException, "Object #{@name} does not exist" unless (response.code =~ /^20/)
|
76
|
+
response.body.chomp
|
77
|
+
end
|
78
|
+
|
79
|
+
# Retrieves the data from an object and returns a stream that must be passed to a block. Throws a
|
80
|
+
# NoSuchObjectException if the object doesn't exist.
|
81
|
+
#
|
82
|
+
# If the optional size and range arguments are provided, the call will return the number of bytes provided by
|
83
|
+
# size, starting from the offset provided in offset.
|
84
|
+
#
|
85
|
+
# data = ""
|
86
|
+
# object.data_stream do |chunk|
|
87
|
+
# data += chunk
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# data
|
91
|
+
# => "This is the text stored in the file"
|
92
|
+
def data_stream(size=-1,offset=0,headers = {},&block)
|
93
|
+
if size.to_i > 0
|
94
|
+
range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
|
95
|
+
headers['Range'] = range
|
96
|
+
end
|
97
|
+
self.container.connection.cfreq("GET",@storagehost,@storagepath,headers,nil) do |response|
|
98
|
+
raise NoSuchObjectException, "Object #{@name} does not exist" unless (response.code == "200")
|
99
|
+
response.read_body(&block)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the object's metadata as a nicely formatted hash, stripping off the X-Meta-Object- prefix that the system prepends to the
|
104
|
+
# key name.
|
105
|
+
#
|
106
|
+
# object.metadata
|
107
|
+
# => {"ruby"=>"cool", "foo"=>"bar"}
|
108
|
+
def metadata
|
109
|
+
metahash = {}
|
110
|
+
@metadata.each{|key, value| metahash[key.gsub(/x-object-meta-/,'').gsub(/\+\-/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ')}
|
111
|
+
metahash
|
112
|
+
end
|
113
|
+
|
114
|
+
# Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
|
115
|
+
# However, setting metadata will overwrite any existing metadata for the object.
|
116
|
+
#
|
117
|
+
# Throws NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request
|
118
|
+
# fails.
|
119
|
+
def set_metadata(metadatahash)
|
120
|
+
headers = {}
|
121
|
+
metadatahash.each{|key, value| headers['X-Object-Meta-' + key.to_s.capitalize] = value.to_s}
|
122
|
+
response = self.container.connection.cfreq("POST",@storagehost,@storagepath,headers)
|
123
|
+
raise NoSuchObjectException, "Object #{@name} does not exist" if (response.code == "404")
|
124
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "202")
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
# Takes supplied data and writes it to the object, saving it. You can supply an optional hash of headers, including
|
129
|
+
# Content-Type and ETag, that will be applied to the object.
|
130
|
+
#
|
131
|
+
# If you would rather stream the data in chunks, instead of reading it all into memory at once, you can pass an
|
132
|
+
# IO object for the data, such as: object.write(open('/path/to/file.mp3'))
|
133
|
+
#
|
134
|
+
# You can compute your own MD5 sum and send it in the "ETag" header. If you provide yours, it will be compared to
|
135
|
+
# the MD5 sum on the server side. If they do not match, the server will return a 422 status code and a MisMatchedChecksumException
|
136
|
+
# will be raised. If you do not provide an MD5 sum as the ETag, one will be computed on the server side.
|
137
|
+
#
|
138
|
+
# Updates the container cache and returns true on success, raises exceptions if stuff breaks.
|
139
|
+
#
|
140
|
+
# object = container.create_object("newfile.txt")
|
141
|
+
#
|
142
|
+
# object.write("This is new data")
|
143
|
+
# => true
|
144
|
+
#
|
145
|
+
# object.data
|
146
|
+
# => "This is new data"
|
147
|
+
def write(data=nil,headers={})
|
148
|
+
#raise SyntaxException, "No data was provided for object '#{@name}'" if (data.nil?)
|
149
|
+
# Try to get the content type
|
150
|
+
raise SyntaxException, "No data or header updates supplied" if (data.nil? and headers.empty?)
|
151
|
+
if headers['Content-Type'].nil?
|
152
|
+
type = MIME::Types.type_for(self.name).first.to_s
|
153
|
+
if type.empty?
|
154
|
+
headers['Content-Type'] = "application/octet-stream"
|
155
|
+
else
|
156
|
+
headers['Content-Type'] = type
|
157
|
+
end
|
158
|
+
end
|
159
|
+
response = self.container.connection.cfreq("PUT",@storagehost,"#{@storagepath}",headers,data)
|
160
|
+
raise InvalidResponseException, "Invalid content-length header sent" if (response.code == "412")
|
161
|
+
raise MisMatchedChecksumException, "Mismatched etag" if (response.code == "422")
|
162
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "201")
|
163
|
+
make_path(File.dirname(self.name)) if @make_path == true
|
164
|
+
self.populate
|
165
|
+
true
|
166
|
+
end
|
167
|
+
|
168
|
+
# A convenience method to stream data into an object from a local file (or anything that can be loaded by Ruby's open method)
|
169
|
+
#
|
170
|
+
# Throws an Errno::ENOENT if the file cannot be read.
|
171
|
+
#
|
172
|
+
# object.data
|
173
|
+
# => "This is my data"
|
174
|
+
#
|
175
|
+
# object.load_from_filename("/tmp/file.txt")
|
176
|
+
# => true
|
177
|
+
#
|
178
|
+
# object.data
|
179
|
+
# => "This data was in the file /tmp/file.txt"
|
180
|
+
#
|
181
|
+
# object.load_from_filename("/tmp/nonexistent.txt")
|
182
|
+
# => Errno::ENOENT: No such file or directory - /tmp/nonexistent.txt
|
183
|
+
def load_from_filename(filename)
|
184
|
+
f = open(filename)
|
185
|
+
self.write(f)
|
186
|
+
f.close
|
187
|
+
true
|
188
|
+
end
|
189
|
+
|
190
|
+
# A convenience method to stream data from an object into a local file
|
191
|
+
#
|
192
|
+
# Throws an Errno::ENOENT if the file cannot be opened for writing due to a path error,
|
193
|
+
# and Errno::EACCES if the file cannot be opened for writing due to permissions.
|
194
|
+
#
|
195
|
+
# object.data
|
196
|
+
# => "This is my data"
|
197
|
+
#
|
198
|
+
# object.save_to_filename("/tmp/file.txt")
|
199
|
+
# => true
|
200
|
+
#
|
201
|
+
# $ cat /tmp/file.txt
|
202
|
+
# "This is my data"
|
203
|
+
#
|
204
|
+
# object.save_to_filename("/tmp/owned_by_root.txt")
|
205
|
+
# => Errno::EACCES: Permission denied - /tmp/owned_by_root.txt
|
206
|
+
def save_to_filename(filename)
|
207
|
+
File.open(filename, 'w+') do |f|
|
208
|
+
self.data_stream do |chunk|
|
209
|
+
f.write chunk
|
210
|
+
end
|
211
|
+
end
|
212
|
+
true
|
213
|
+
end
|
214
|
+
|
215
|
+
# If the parent container is public (CDN-enabled), returns the CDN URL to this object. Otherwise, return nil
|
216
|
+
#
|
217
|
+
# public_object.public_url
|
218
|
+
# => "http://cdn.cloudfiles.mosso.com/c10181/rampage.jpg"
|
219
|
+
#
|
220
|
+
# private_object.public_url
|
221
|
+
# => nil
|
222
|
+
def public_url
|
223
|
+
self.container.public? ? self.container.cdn_url + "/#{URI.encode(@name).gsub(/&/,'%26')}" : nil
|
224
|
+
end
|
225
|
+
|
226
|
+
def to_s # :nodoc:
|
227
|
+
@name
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def make_path(path) # :nodoc:
|
233
|
+
if path == "." || path == "/"
|
234
|
+
return
|
235
|
+
else
|
236
|
+
unless self.container.object_exists?(path)
|
237
|
+
o = self.container.create_object(path)
|
238
|
+
o.write(nil,{'Content-Type' => 'application/directory'})
|
239
|
+
end
|
240
|
+
make_path(File.dirname(path))
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|