tfe-cloudfiles 1.4.7

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,299 @@
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
+ # The container ACL on the User Agent
28
+ attr_reader :user_agent_acl
29
+
30
+ # The container ACL on the site Referrer
31
+ attr_reader :referrer_acl
32
+
33
+ # Retrieves an existing CloudFiles::Container object tied to the current CloudFiles::Connection. If the requested
34
+ # container does not exist, it will raise a NoSuchContainerException.
35
+ #
36
+ # Will likely not be called directly, instead use connection.container('container_name') to retrieve the object.
37
+ def initialize(connection,name)
38
+ @connection = connection
39
+ @name = name
40
+ @storagehost = self.connection.storagehost
41
+ @storagepath = self.connection.storagepath + "/" + URI.encode(@name).gsub(/&/,'%26')
42
+ @storageport = self.connection.storageport
43
+ @storagescheme = self.connection.storagescheme
44
+ @cdnmgmthost = self.connection.cdnmgmthost
45
+ @cdnmgmtpath = self.connection.cdnmgmtpath + "/" + URI.encode(@name).gsub(/&/,'%26')
46
+ @cdnmgmtport = self.connection.cdnmgmtport
47
+ @cdnmgmtscheme = self.connection.cdnmgmtscheme
48
+ populate
49
+ end
50
+
51
+ # Retrieves data about the container and populates class variables. It is automatically called
52
+ # when the Container class is instantiated. If you need to refresh the variables, such as
53
+ # size, count, cdn_enabled, cdn_ttl, and cdn_url, this method can be called again.
54
+ #
55
+ # container.count
56
+ # => 2
57
+ # [Upload new file to the container]
58
+ # container.count
59
+ # => 2
60
+ # container.populate
61
+ # container.count
62
+ # => 3
63
+ def populate
64
+ # Get the size and object count
65
+ response = self.connection.cfreq("HEAD",@storagehost,@storagepath+"/",@storageport,@storagescheme)
66
+ raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code =~ /^20/)
67
+ @bytes = response["x-container-bytes-used"].to_i
68
+ @count = response["x-container-object-count"].to_i
69
+
70
+ # Get the CDN-related details
71
+ response = self.connection.cfreq("HEAD",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme)
72
+ @cdn_enabled = ((response["x-cdn-enabled"] || "").downcase == "true") ? true : false
73
+ @cdn_ttl = @cdn_enabled ? response["x-ttl"].to_i : false
74
+ @cdn_url = @cdn_enabled ? response["x-cdn-uri"] : false
75
+ @user_agent_acl = response["x-user-agent-acl"]
76
+ @referrer_acl = response["x-referrer-acl"]
77
+ if @cdn_enabled
78
+ @cdn_log = response["x-log-retention"] == "False" ? false : true
79
+ else
80
+ @cdn_log = false
81
+ end
82
+
83
+ true
84
+ end
85
+ alias :refresh :populate
86
+
87
+ # Returns true if log retention is enabled on this container, false otherwise
88
+ def log_retention?
89
+ @cdn_log
90
+ end
91
+
92
+ # Change the log retention status for this container. Values are true or false.
93
+ #
94
+ # These logs will be periodically (at unpredictable intervals) compressed and uploaded
95
+ # to a “.CDN_ACCESS_LOGS” container in the form of “container_name.YYYYMMDDHH-XXXX.gz”.
96
+ def log_retention=(value)
97
+ response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,{"x-log-retention" => value.to_s.capitalize})
98
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "201" or response.code == "202")
99
+ return true
100
+ end
101
+
102
+
103
+ # Returns the CloudFiles::StorageObject for the named object. Refer to the CloudFiles::StorageObject class for available
104
+ # methods. If the object exists, it will be returned. If the object does not exist, a NoSuchObjectException will be thrown.
105
+ #
106
+ # object = container.object('test.txt')
107
+ # object.data
108
+ # => "This is test data"
109
+ #
110
+ # object = container.object('newfile.txt')
111
+ # => NoSuchObjectException: Object newfile.txt does not exist
112
+ def object(objectname)
113
+ o = CloudFiles::StorageObject.new(self,objectname,true)
114
+ return o
115
+ end
116
+ alias :get_object :object
117
+
118
+
119
+ # Gathers a list of all available objects in the current container and returns an array of object names.
120
+ # container = cf.container("My Container")
121
+ # container.objects #=> [ "dog", "cat", "donkey", "monkeydir/capuchin"]
122
+ # Pass a limit argument to limit the list to a number of objects:
123
+ # container.objects(:limit => 1) #=> [ "dog" ]
124
+ # Pass an offset with or without a limit to start the list at a certain object:
125
+ # container.objects(:limit => 1, :offset => 2) #=> [ "donkey" ]
126
+ # Pass a prefix to search for objects that start with a certain string:
127
+ # container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
128
+ # Only search within a certain pseudo-filesystem path:
129
+ # container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
130
+ # All arguments to this method are optional.
131
+ #
132
+ # Returns an empty array if no object exist in the container. Throws an InvalidResponseException
133
+ # if the request fails.
134
+ def objects(params = {})
135
+ paramarr = []
136
+ paramarr << ["limit=#{URI.encode(params[:limit].to_s).gsub(/&/,'%26')}"] if params[:limit]
137
+ paramarr << ["offset=#{URI.encode(params[:offset].to_s).gsub(/&/,'%26')}"] if params[:offset]
138
+ paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
139
+ paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
140
+ paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
141
+ response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
142
+ return [] if (response.code == "204")
143
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
144
+ return CloudFiles.lines(response.body)
145
+ end
146
+ alias :list_objects :objects
147
+
148
+ # Retrieves a list of all objects in the current container along with their size in bytes, hash, and content_type.
149
+ # If no objects exist, an empty hash is returned. Throws an InvalidResponseException if the request fails. Takes a
150
+ # parameter hash as an argument, in the same form as the objects method.
151
+ #
152
+ # Returns a hash in the same format as the containers_detail from the CloudFiles class.
153
+ #
154
+ # container.objects_detail
155
+ # => {"test.txt"=>{:content_type=>"application/octet-stream",
156
+ # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
157
+ # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
158
+ # :bytes=>"16"},
159
+ # "new.txt"=>{:content_type=>"application/octet-stream",
160
+ # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
161
+ # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
162
+ # :bytes=>"22"}
163
+ # }
164
+ def objects_detail(params = {})
165
+ paramarr = []
166
+ paramarr << ["format=xml"]
167
+ paramarr << ["limit=#{URI.encode(params[:limit].to_s).gsub(/&/,'%26')}"] if params[:limit]
168
+ paramarr << ["offset=#{URI.encode(params[:offset].to_s).gsub(/&/,'%26')}"] if params[:offset]
169
+ paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
170
+ paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
171
+ paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
172
+ response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
173
+ return {} if (response.code == "204")
174
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
175
+ doc = REXML::Document.new(response.body)
176
+ detailhash = {}
177
+ doc.elements.each("container/object") { |o|
178
+ detailhash[o.elements["name"].text] = { :bytes => o.elements["bytes"].text, :hash => o.elements["hash"].text, :content_type => o.elements["content_type"].text, :last_modified => DateTime.parse(o.elements["last_modified"].text) }
179
+ }
180
+ doc = nil
181
+ return detailhash
182
+ end
183
+ alias :list_objects_info :objects_detail
184
+
185
+ # Returns true if the container is public and CDN-enabled. Returns false otherwise.
186
+ #
187
+ # public_container.public?
188
+ # => true
189
+ #
190
+ # private_container.public?
191
+ # => false
192
+ def public?
193
+ return @cdn_enabled
194
+ end
195
+
196
+ # Returns true if a container is empty and returns false otherwise.
197
+ #
198
+ # new_container.empty?
199
+ # => true
200
+ #
201
+ # full_container.empty?
202
+ # => false
203
+ def empty?
204
+ return (@count.to_i == 0)? true : false
205
+ end
206
+
207
+ # Returns true if object exists and returns false otherwise.
208
+ #
209
+ # container.object_exists?('goodfile.txt')
210
+ # => true
211
+ #
212
+ # container.object_exists?('badfile.txt')
213
+ # => false
214
+ def object_exists?(objectname)
215
+ response = self.connection.cfreq("HEAD",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}",@storageport,@storagescheme)
216
+ return (response.code =~ /^20/)? true : false
217
+ end
218
+
219
+ # Creates a new CloudFiles::StorageObject in the current container.
220
+ #
221
+ # If an object with the specified name exists in the current container, that object will be returned. Otherwise,
222
+ # an empty new object will be returned.
223
+ #
224
+ # Passing in the optional make_path argument as true will create zero-byte objects to simulate a filesystem path
225
+ # to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
226
+ # be used in the Container.objects method.
227
+ def create_object(objectname,make_path = false)
228
+ CloudFiles::StorageObject.new(self,objectname,false,make_path)
229
+ end
230
+
231
+ # Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
232
+ # NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request fails.
233
+ #
234
+ # container.delete_object('new.txt')
235
+ # => true
236
+ #
237
+ # container.delete_object('nonexistent_file.txt')
238
+ # => NoSuchObjectException: Object nonexistent_file.txt does not exist
239
+ def delete_object(objectname)
240
+ response = self.connection.cfreq("DELETE",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}",@storageport,@storagescheme)
241
+ raise NoSuchObjectException, "Object #{objectname} does not exist" if (response.code == "404")
242
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
243
+ true
244
+ end
245
+
246
+ # Makes a container publicly available via the Cloud Files CDN and returns true upon success. Throws NoSuchContainerException
247
+ # if the container doesn't exist or if the request fails.
248
+ #
249
+ # Takes an optional hash of options, including:
250
+ #
251
+ # :ttl, which is the CDN cache TTL in seconds (default 86400 seconds or 1 day, minimum 3600 or 1 hour, maximum 259200 or 3 days)
252
+ #
253
+ # :user_agent_acl, a Perl-compatible regular expression limiting access to this container to user agents matching the given regular expression
254
+ #
255
+ # :referrer_acl, a Perl-compatible regular expression limiting access to this container to HTTP referral URLs matching the given regular expression
256
+ #
257
+ # container.make_public(:ttl => 8900, :user_agent_acl => "/Mozilla/", :referrer_acl => "/^http://rackspace.com")
258
+ # => true
259
+ def make_public(options = {:ttl => 86400})
260
+ if options.is_a?(Fixnum)
261
+ print "DEPRECATED: make_public takes a hash of options now, instead of a TTL number"
262
+ ttl = options
263
+ options = {:ttl => ttl}
264
+ end
265
+
266
+ response = self.connection.cfreq("PUT",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme)
267
+ raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
268
+
269
+ headers = { "X-TTL" => options[:ttl].to_s , "X-CDN-Enabled" => "True" }
270
+ headers["X-User-Agent-ACL"] = options[:user_agent_acl] if options[:user_agent_acl]
271
+ headers["X-Referrer-ACL"] = options[:referrer_acl] if options[:referrer_acl]
272
+ response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,headers)
273
+ raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
274
+ populate
275
+ true
276
+ end
277
+
278
+ # Makes a container private and returns true upon success. Throws NoSuchContainerException
279
+ # if the container doesn't exist or if the request fails.
280
+ #
281
+ # Note that if the container was previously public, it will continue to exist out on the CDN until it expires.
282
+ #
283
+ # container.make_private
284
+ # => true
285
+ def make_private
286
+ headers = { "X-CDN-Enabled" => "False" }
287
+ response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,headers)
288
+ raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
289
+ populate
290
+ true
291
+ end
292
+
293
+ def to_s # :nodoc:
294
+ @name
295
+ end
296
+
297
+ end
298
+
299
+ end
@@ -0,0 +1,257 @@
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
+ @storageport = self.container.connection.storageport
38
+ @storagescheme = self.container.connection.storagescheme
39
+ if container.object_exists?(objectname)
40
+ populate
41
+ else
42
+ raise NoSuchObjectException, "Object #{@name} does not exist" if force_exists
43
+ end
44
+ end
45
+
46
+ # Caches data about the CloudFiles::StorageObject for fast retrieval. This method is automatically called when the
47
+ # class is initialized, but it can be called again if the data needs to be updated.
48
+ def populate
49
+ response = self.container.connection.cfreq("HEAD",@storagehost,@storagepath,@storageport,@storagescheme)
50
+ raise NoSuchObjectException, "Object #{@name} does not exist" unless (response.code =~ /^20/)
51
+ @bytes = response["content-length"]
52
+ @last_modified = Time.parse(response["last-modified"])
53
+ @etag = response["etag"]
54
+ @content_type = response["content-type"]
55
+ resphash = {}
56
+ response.to_hash.select { |k,v| k.match(/^x-object-meta/) }.each { |x| resphash[x[0]] = x[1].to_s }
57
+ @metadata = resphash
58
+ true
59
+ end
60
+ alias :refresh :populate
61
+
62
+ # Retrieves the data from an object and stores the data in memory. The data is returned as a string.
63
+ # Throws a NoSuchObjectException if the object doesn't exist.
64
+ #
65
+ # If the optional size and range arguments are provided, the call will return the number of bytes provided by
66
+ # size, starting from the offset provided in offset.
67
+ #
68
+ # object.data
69
+ # => "This is the text stored in the file"
70
+ def data(size=-1,offset=0,headers = {})
71
+ if size.to_i > 0
72
+ range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
73
+ headers['Range'] = range
74
+ end
75
+ response = self.container.connection.cfreq("GET",@storagehost,@storagepath,@storageport,@storagescheme,headers)
76
+ raise NoSuchObjectException, "Object #{@name} does not exist" unless (response.code =~ /^20/)
77
+ response.body
78
+ end
79
+
80
+ # Retrieves the data from an object and returns a stream that must be passed to a block. Throws a
81
+ # NoSuchObjectException if the object doesn't exist.
82
+ #
83
+ # If the optional size and range arguments are provided, the call will return the number of bytes provided by
84
+ # size, starting from the offset provided in offset.
85
+ #
86
+ # data = ""
87
+ # object.data_stream do |chunk|
88
+ # data += chunk
89
+ # end
90
+ #
91
+ # data
92
+ # => "This is the text stored in the file"
93
+ def data_stream(size=-1,offset=0,headers = {},&block)
94
+ if size.to_i > 0
95
+ range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
96
+ headers['Range'] = range
97
+ end
98
+ self.container.connection.cfreq("GET",@storagehost,@storagepath,@storageport,@storagescheme,headers,nil) do |response|
99
+ raise NoSuchObjectException, "Object #{@name} does not exist" unless (response.code == "200")
100
+ response.read_body(&block)
101
+ end
102
+ end
103
+
104
+ # Returns the object's metadata as a nicely formatted hash, stripping off the X-Meta-Object- prefix that the system prepends to the
105
+ # key name.
106
+ #
107
+ # object.metadata
108
+ # => {"ruby"=>"cool", "foo"=>"bar"}
109
+ def metadata
110
+ metahash = {}
111
+ @metadata.each{|key, value| metahash[key.gsub(/x-object-meta-/,'').gsub(/\+\-/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ')}
112
+ metahash
113
+ end
114
+
115
+ # Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
116
+ # However, setting metadata will overwrite any existing metadata for the object.
117
+ #
118
+ # Throws NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request
119
+ # fails.
120
+ def set_metadata(metadatahash)
121
+ headers = {}
122
+ metadatahash.each{|key, value| headers['X-Object-Meta-' + key.to_s.capitalize] = value.to_s}
123
+ response = self.container.connection.cfreq("POST",@storagehost,@storagepath,@storageport,@storagescheme,headers)
124
+ raise NoSuchObjectException, "Object #{@name} does not exist" if (response.code == "404")
125
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "202")
126
+ true
127
+ end
128
+
129
+ # Takes supplied data and writes it to the object, saving it. You can supply an optional hash of headers, including
130
+ # Content-Type and ETag, that will be applied to the object.
131
+ #
132
+ # If you would rather stream the data in chunks, instead of reading it all into memory at once, you can pass an
133
+ # IO object for the data, such as: object.write(open('/path/to/file.mp3'))
134
+ #
135
+ # You can compute your own MD5 sum and send it in the "ETag" header. If you provide yours, it will be compared to
136
+ # the MD5 sum on the server side. If they do not match, the server will return a 422 status code and a MisMatchedChecksumException
137
+ # will be raised. If you do not provide an MD5 sum as the ETag, one will be computed on the server side.
138
+ #
139
+ # Updates the container cache and returns true on success, raises exceptions if stuff breaks.
140
+ #
141
+ # object = container.create_object("newfile.txt")
142
+ #
143
+ # object.write("This is new data")
144
+ # => true
145
+ #
146
+ # object.data
147
+ # => "This is new data"
148
+ #
149
+ # If you are passing your data in via STDIN, just do an
150
+ #
151
+ # object.write
152
+ #
153
+ # with no data (or, if you need to pass headers)
154
+ #
155
+ # object.write(nil,{'header' => 'value})
156
+
157
+ def write(data=nil,headers={})
158
+ raise SyntaxException, "No data or header updates supplied" if ((data.nil? && $stdin.tty?) and headers.empty?)
159
+ if headers['Content-Type'].nil?
160
+ type = MIME::Types.type_for(self.name).first.to_s
161
+ if type.empty?
162
+ headers['Content-Type'] = "application/octet-stream"
163
+ else
164
+ headers['Content-Type'] = type
165
+ end
166
+ end
167
+ # If we're taking data from standard input, send that IO object to cfreq
168
+ data = $stdin if (data.nil? && $stdin.tty? == false)
169
+ response = self.container.connection.cfreq("PUT",@storagehost,"#{@storagepath}",@storageport,@storagescheme,headers,data)
170
+ code = response.code
171
+ raise InvalidResponseException, "Invalid content-length header sent" if (code == "412")
172
+ raise MisMatchedChecksumException, "Mismatched etag" if (code == "422")
173
+ raise InvalidResponseException, "Invalid response code #{code}" unless (code == "201")
174
+ make_path(File.dirname(self.name)) if @make_path == true
175
+ self.populate
176
+ true
177
+ end
178
+
179
+ # A convenience method to stream data into an object from a local file (or anything that can be loaded by Ruby's open method)
180
+ #
181
+ # Throws an Errno::ENOENT if the file cannot be read.
182
+ #
183
+ # object.data
184
+ # => "This is my data"
185
+ #
186
+ # object.load_from_filename("/tmp/file.txt")
187
+ # => true
188
+ #
189
+ # object.data
190
+ # => "This data was in the file /tmp/file.txt"
191
+ #
192
+ # object.load_from_filename("/tmp/nonexistent.txt")
193
+ # => Errno::ENOENT: No such file or directory - /tmp/nonexistent.txt
194
+ def load_from_filename(filename)
195
+ f = open(filename)
196
+ self.write(f)
197
+ f.close
198
+ true
199
+ end
200
+
201
+ # A convenience method to stream data from an object into a local file
202
+ #
203
+ # Throws an Errno::ENOENT if the file cannot be opened for writing due to a path error,
204
+ # and Errno::EACCES if the file cannot be opened for writing due to permissions.
205
+ #
206
+ # object.data
207
+ # => "This is my data"
208
+ #
209
+ # object.save_to_filename("/tmp/file.txt")
210
+ # => true
211
+ #
212
+ # $ cat /tmp/file.txt
213
+ # "This is my data"
214
+ #
215
+ # object.save_to_filename("/tmp/owned_by_root.txt")
216
+ # => Errno::EACCES: Permission denied - /tmp/owned_by_root.txt
217
+ def save_to_filename(filename)
218
+ File.open(filename, 'w+') do |f|
219
+ self.data_stream do |chunk|
220
+ f.write chunk
221
+ end
222
+ end
223
+ true
224
+ end
225
+
226
+ # If the parent container is public (CDN-enabled), returns the CDN URL to this object. Otherwise, return nil
227
+ #
228
+ # public_object.public_url
229
+ # => "http://c0001234.cdn.cloudfiles.rackspacecloud.com/myfile.jpg"
230
+ #
231
+ # private_object.public_url
232
+ # => nil
233
+ def public_url
234
+ self.container.public? ? self.container.cdn_url + "/#{URI.encode(@name).gsub(/&/,'%26')}" : nil
235
+ end
236
+
237
+ def to_s # :nodoc:
238
+ @name
239
+ end
240
+
241
+ private
242
+
243
+ def make_path(path) # :nodoc:
244
+ if path == "." || path == "/"
245
+ return
246
+ else
247
+ unless self.container.object_exists?(path)
248
+ o = self.container.create_object(path)
249
+ o.write(nil,{'Content-Type' => 'application/directory'})
250
+ end
251
+ make_path(File.dirname(path))
252
+ end
253
+ end
254
+
255
+ end
256
+
257
+ end