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.
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Cloud Files API
4
+ # ==== Connects Ruby Applications to Rackspace's {Cloud Files service}[http://www.rackspacecloud.com/cloud_hosting_products/files]
5
+ # Initial work by Major Hayden <major.hayden@rackspace.com>
6
+ #
7
+ # Subsequent work by H. Wade Minter <minter@lunenburg.org> and Dan Prince <dan.prince@rackspace.com>
8
+ #
9
+ # See COPYING for license information.
10
+ # Copyright (c) 2011, Rackspace US, Inc.
11
+ # ----
12
+ #
13
+ # === Documentation & Examples
14
+ # To begin reviewing the available methods and examples, peruse the README file, or begin by looking at documentation for the
15
+ # CloudFiles::Connection class.
16
+ #
17
+ # The CloudFiles class is the base class. Not much of note happens here.
18
+ # To create a new CloudFiles connection, use the CloudFiles::Connection.new(:username => 'user_name', :api_key => 'api_key') method.
19
+
20
+ module CloudFiles
21
+
22
+ AUTH_USA = "https://auth.api.rackspacecloud.com/v1.0"
23
+ AUTH_UK = "https://lon.auth.api.rackspacecloud.com/v1.0"
24
+
25
+ require 'net/http'
26
+ require 'net/https'
27
+ require 'rexml/document'
28
+ require 'cgi'
29
+ require 'uri'
30
+ require 'digest/md5'
31
+ require 'date'
32
+ require 'time'
33
+ require 'rubygems'
34
+
35
+ unless "".respond_to? :each_char
36
+ require "jcode"
37
+ $KCODE = 'u'
38
+ end
39
+
40
+ $:.unshift(File.dirname(__FILE__))
41
+ require 'client'
42
+ require 'cloudfiles/version'
43
+ require 'cloudfiles/exception'
44
+ require 'cloudfiles/authentication'
45
+ require 'cloudfiles/connection'
46
+ require 'cloudfiles/container'
47
+ require 'cloudfiles/storage_object'
48
+
49
+ def self.lines(str)
50
+ (str.respond_to?(:lines) ? str.lines : str).to_a.map { |x| x.chomp }
51
+ end
52
+
53
+ def self.escape(str)
54
+ URI.encode(str)
55
+ end
56
+ end
57
+
58
+
59
+
60
+ class SyntaxException < StandardError # :nodoc:
61
+ end
62
+ class ConnectionException < StandardError # :nodoc:
63
+ end
64
+ class AuthenticationException < StandardError # :nodoc:
65
+ end
66
+ class InvalidResponseException < StandardError # :nodoc:
67
+ end
68
+ class NonEmptyContainerException < StandardError # :nodoc:
69
+ end
70
+ class NoSuchObjectException < StandardError # :nodoc:
71
+ end
72
+ class NoSuchContainerException < StandardError # :nodoc:
73
+ end
74
+ class NoSuchAccountException < StandardError # :nodoc:
75
+ end
76
+ class MisMatchedChecksumException < StandardError # :nodoc:
77
+ end
78
+ class IOException < StandardError # :nodoc:
79
+ end
80
+ class CDNNotEnabledException < StandardError # :nodoc:
81
+ end
82
+ class ObjectExistsException < StandardError # :nodoc:
83
+ end
84
+ class ExpiredAuthTokenException < StandardError # :nodoc:
85
+ end
@@ -0,0 +1,52 @@
1
+ module CloudFiles
2
+ class Authentication
3
+ # See COPYING for license information.
4
+ # Copyright (c) 2011, Rackspace US, Inc.
5
+
6
+ # Performs an authentication to the Cloud Files servers. Opens a new HTTP connection to the API server,
7
+ # sends the credentials, and looks for a successful authentication. If it succeeds, it sets the cdmmgmthost,
8
+ # cdmmgmtpath, storagehost, storagepath, authtoken, and authok variables on the connection. If it fails, it raises
9
+ # an CloudFiles::Exception::Authentication exception.
10
+ #
11
+ # Should never be called directly.
12
+ def initialize(connection)
13
+ begin
14
+ storage_url, auth_token, headers = SwiftClient.get_auth(connection.auth_url, connection.authuser, connection.authkey, connection.snet?)
15
+ rescue => e
16
+ # uncomment if you suspect a problem with this branch of code
17
+ # $stderr.puts "got error #{e.class}: #{e.message.inspect}\n" << e.traceback.map{|n| "\t#{n}"}.join("\n")
18
+ raise CloudFiles::Exception::Connection, "Unable to connect to #{connection.auth_url}", caller
19
+ end
20
+ if auth_token
21
+ if headers["x-cdn-management-url"]
22
+ connection.cdn_available = true
23
+ parsed_cdn_url = URI.parse(headers["x-cdn-management-url"])
24
+ connection.cdnmgmthost = parsed_cdn_url.host
25
+ connection.cdnmgmtpath = parsed_cdn_url.path
26
+ connection.cdnmgmtport = parsed_cdn_url.port
27
+ connection.cdnmgmtscheme = parsed_cdn_url.scheme
28
+ end
29
+ parsed_storage_url = URI.parse(headers["x-storage-url"])
30
+ connection.storagehost = set_snet(connection, parsed_storage_url.host)
31
+ connection.storagepath = parsed_storage_url.path
32
+ connection.storageport = parsed_storage_url.port
33
+ connection.storagescheme = parsed_storage_url.scheme
34
+ connection.authtoken = headers["x-auth-token"]
35
+ connection.authok = true
36
+ else
37
+ connection.authtoken = false
38
+ raise CloudFiles::Exception::Authentication, "Authentication failed"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def set_snet(connection, hostname)
45
+ if connection.snet?
46
+ "snet-#{hostname}"
47
+ else
48
+ hostname
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,286 @@
1
+ module CloudFiles
2
+ class Connection
3
+ # See COPYING for license information.
4
+ # Copyright (c) 2011, Rackspace US, Inc.
5
+
6
+ # Authentication key provided when the CloudFiles class was instantiated
7
+ attr_reader :authkey
8
+
9
+ # Token returned after a successful authentication
10
+ attr_accessor :authtoken
11
+
12
+ # Authentication username provided when the CloudFiles class was instantiated
13
+ attr_reader :authuser
14
+
15
+ # API host to authenticate to
16
+ attr_reader :auth_url
17
+
18
+ # Set at auth to see if a CDN is available for use
19
+ attr_accessor :cdn_available
20
+ alias :cdn_available? :cdn_available
21
+
22
+ # Hostname of the CDN management server
23
+ attr_accessor :cdnmgmthost
24
+
25
+ # Path for managing containers on the CDN management server
26
+ attr_accessor :cdnmgmtpath
27
+
28
+ # Port number for the CDN server
29
+ attr_accessor :cdnmgmtport
30
+
31
+ # URI scheme for the CDN server
32
+ attr_accessor :cdnmgmtscheme
33
+
34
+ # Hostname of the storage server
35
+ attr_accessor :storagehost
36
+
37
+ # Path for managing containers/objects on the storage server
38
+ attr_accessor :storagepath
39
+
40
+ # Port for managing the storage server
41
+ attr_accessor :storageport
42
+
43
+ # URI scheme for the storage server
44
+ attr_accessor :storagescheme
45
+
46
+ # Instance variable that is set when authorization succeeds
47
+ attr_accessor :authok
48
+
49
+ # Optional proxy variables
50
+ attr_reader :proxy_host
51
+ attr_reader :proxy_port
52
+
53
+ # Creates a new CloudFiles::Connection object. Uses CloudFiles::Authentication to perform the login for the connection.
54
+ # The authuser is the Rackspace Cloud username, the authkey is the Rackspace Cloud API key.
55
+ #
56
+ # Setting the :retry_auth option to false will cause an exception to be thrown if your authorization token expires.
57
+ # Otherwise, it will attempt to reauthenticate.
58
+ #
59
+ # Setting the :snet option to true or setting an environment variable of RACKSPACE_SERVICENET to any value will cause
60
+ # storage URLs to be returned with a prefix pointing them to the internal Rackspace service network, instead of a public URL.
61
+ #
62
+ # This is useful if you are using the library on a Rackspace-hosted system, as it provides faster speeds, keeps traffic off of
63
+ # the public network, and the bandwidth is not billed.
64
+ #
65
+ # If you need to connect to a Cloud Files installation that is NOT the standard Rackspace one, set the :auth_url option to the URL
66
+ # of your authentication endpoint. The old option name of :authurl is deprecated. The default is CloudFiles::AUTH_USA (https://auth.api.rackspacecloud.com/v1.0)
67
+ #
68
+ # There are two predefined constants to represent the United States-based authentication endpoint and the United Kingdom-based endpoint:
69
+ # CloudFiles::AUTH_USA (the default) and CloudFiles::AUTH_UK - both can be passed to the :auth_url option to quickly choose one or the other.
70
+ #
71
+ # This will likely be the base class for most operations.
72
+ #
73
+ # With gem 1.4.8, the connection style has changed. It is now a hash of arguments. Note that the proxy options are currently only
74
+ # supported in the new style.
75
+ #
76
+ # cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :auth_url => CloudFiles::AUTH_UK, :retry_auth => true, :snet => false, :proxy_host => "localhost", :proxy_port => "1234")
77
+ #
78
+ # The old style (positional arguments) is deprecated and will be removed at some point in the future.
79
+ #
80
+ # cf = CloudFiles::Connection.new(MY_USERNAME, MY_API_KEY, RETRY_AUTH, USE_SNET)
81
+ def initialize(*args)
82
+ if args[0].is_a?(Hash)
83
+ options = args[0]
84
+ @authuser = options[:username] ||( raise CloudFiles::Exception::Authentication, "Must supply a :username")
85
+ @authkey = options[:api_key] || (raise CloudFiles::Exception::Authentication, "Must supply an :api_key")
86
+ @auth_url = options[:authurl] || CloudFiles::AUTH_USA
87
+ @auth_url = options[:auth_url] || CloudFiles::AUTH_USA
88
+ @retry_auth = options[:retry_auth] || true
89
+ @snet = ENV['RACKSPACE_SERVICENET'] || options[:snet]
90
+ @proxy_host = options[:proxy_host]
91
+ @proxy_port = options[:proxy_port]
92
+ else
93
+ @authuser = args[0] ||( raise CloudFiles::Exception::Authentication, "Must supply the username as the first argument")
94
+ @authkey = args[1] || (raise CloudFiles::Exception::Authentication, "Must supply the API key as the second argument")
95
+ @retry_auth = args[2] || true
96
+ @snet = (ENV['RACKSPACE_SERVICENET'] || args[3]) ? true : false
97
+ @auth_url = CloudFiles::AUTH_USA
98
+ end
99
+ @authok = false
100
+ @http = {}
101
+ CloudFiles::Authentication.new(self)
102
+ end
103
+
104
+ # Returns true if the authentication was successful and returns false otherwise.
105
+ #
106
+ # cf.authok?
107
+ # => true
108
+ def authok?
109
+ @authok
110
+ end
111
+
112
+ # Returns true if the library is requesting the use of the Rackspace service network
113
+ def snet?
114
+ @snet
115
+ end
116
+
117
+ # Returns an CloudFiles::Container object that can be manipulated easily. Throws a NoSuchContainerException if
118
+ # the container doesn't exist.
119
+ #
120
+ # container = cf.container('test')
121
+ # container.count
122
+ # => 2
123
+ def container(name)
124
+ CloudFiles::Container.new(self, name)
125
+ end
126
+ alias :get_container :container
127
+
128
+ # Sets instance variables for the bytes of storage used for this account/connection, as well as the number of containers
129
+ # stored under the account. Returns a hash with :bytes and :count keys, and also sets the instance variables.
130
+ #
131
+ # cf.get_info
132
+ # => {:count=>8, :bytes=>42438527}
133
+ # cf.bytes
134
+ # => 42438527
135
+ # Hostname of the storage server
136
+
137
+ def get_info
138
+ begin
139
+ raise CloudFiles::Exception::AuthenticationException, "Not authenticated" unless self.authok?
140
+ response = SwiftClient.head_account(storageurl, self.authtoken)
141
+ @bytes = response["x-account-bytes-used"].to_i
142
+ @count = response["x-account-container-count"].to_i
143
+ {:bytes => @bytes, :count => @count}
144
+ rescue ClientException => e
145
+ raise CloudFiles::Exception::InvalidResponse, "Unable to obtain account size" unless (e.status.to_s == "204")
146
+ end
147
+ end
148
+
149
+ # The total size in bytes under this connection
150
+ def bytes
151
+ get_info[:bytes]
152
+ end
153
+
154
+ # The total number of containers under this connection
155
+ def count
156
+ get_info[:count]
157
+ end
158
+
159
+ # Gathers a list of the containers that exist for the account and returns the list of container names
160
+ # as an array. If no containers exist, an empty array is returned. Throws an InvalidResponseException
161
+ # if the request fails.
162
+ #
163
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
164
+ # specified in limit, starting after the object named in marker.
165
+ #
166
+ # cf.containers
167
+ # => ["backup", "Books", "cftest", "test", "video", "webpics"]
168
+ #
169
+ # cf.containers(2,'cftest')
170
+ # => ["test", "video"]
171
+ def containers(limit = nil, marker = nil)
172
+ begin
173
+ response = SwiftClient.get_account(storageurl, self.authtoken, marker, limit)
174
+ response[1].collect{|c| c['name']}
175
+ rescue ClientException => e
176
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status.to_s}" unless (e.status.to_s == "200")
177
+ end
178
+ end
179
+ alias :list_containers :containers
180
+
181
+ # Retrieves a list of containers on the account along with their sizes (in bytes) and counts of the objects
182
+ # held within them. If no containers exist, an empty hash is returned. Throws an InvalidResponseException
183
+ # if the request fails.
184
+ #
185
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
186
+ # specified in limit, starting after the object named in marker.
187
+ #
188
+ # cf.containers_detail
189
+ # => { "container1" => { :bytes => "36543", :count => "146" },
190
+ # "container2" => { :bytes => "105943", :count => "25" } }
191
+ def containers_detail(limit = nil, marker = nil)
192
+ begin
193
+ response = SwiftClient.get_account(storageurl, self.authtoken, marker, limit)
194
+ Hash[*response[1].collect{|c| [c['name'], {:bytes => c['bytes'], :count => c['count']}]}.flatten]
195
+ rescue ClientException => e
196
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status.to_s}" unless (e.status.to_s == "200")
197
+ end
198
+ end
199
+ alias :list_containers_info :containers_detail
200
+
201
+ # Returns true if the requested container exists and returns false otherwise.
202
+ #
203
+ # cf.container_exists?('good_container')
204
+ # => true
205
+ #
206
+ # cf.container_exists?('bad_container')
207
+ # => false
208
+ def container_exists?(containername)
209
+ begin
210
+ response = SwiftClient.head_container(storageurl, self.authtoken, containername)
211
+ true
212
+ rescue ClientException => e
213
+ false
214
+ end
215
+ end
216
+
217
+ # Creates a new container and returns the CloudFiles::Container object. Throws an InvalidResponseException if the
218
+ # request fails.
219
+ #
220
+ # "/" is not valid in a container name. The container name is limited to
221
+ # 256 characters or less.
222
+ #
223
+ # container = cf.create_container('new_container')
224
+ # container.name
225
+ # => "new_container"
226
+ #
227
+ # container = cf.create_container('bad/name')
228
+ # => SyntaxException: Container name cannot contain '/'
229
+ def create_container(containername)
230
+ raise CloudFiles::Exception::Syntax, "Container name cannot contain '/'" if containername.match("/")
231
+ raise CloudFiles::Exception::Syntax, "Container name is limited to 256 characters" if containername.length > 256
232
+ begin
233
+ SwiftClient.put_container(storageurl, self.authtoken, containername)
234
+ CloudFiles::Container.new(self, containername)
235
+ rescue ClientException => e
236
+ raise CloudFiles::Exception::InvalidResponse, "Unable to create container #{containername}" unless (e.status.to_s == "201" || e.status.to_s == "202")
237
+ end
238
+ end
239
+
240
+ # Deletes a container from the account. Throws a NonEmptyContainerException if the container still contains
241
+ # objects. Throws a NoSuchContainerException if the container doesn't exist.
242
+ #
243
+ # cf.delete_container('new_container')
244
+ # => true
245
+ #
246
+ # cf.delete_container('video')
247
+ # => NonEmptyContainerException: Container video is not empty
248
+ #
249
+ # cf.delete_container('nonexistent')
250
+ # => NoSuchContainerException: Container nonexistent does not exist
251
+ def delete_container(containername)
252
+ begin
253
+ SwiftClient.delete_container(storageurl, self.authtoken, containername)
254
+ rescue ClientException => e
255
+ raise CloudFiles::Exception::NonEmptyContainer, "Container #{containername} is not empty" if (e.status.to_s == "409")
256
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{containername} does not exist" unless (e.status.to_s == "204")
257
+ end
258
+ true
259
+ end
260
+
261
+ # Gathers a list of public (CDN-enabled) containers that exist for an account and returns the list of container names
262
+ # as an array. If no containers are public, an empty array is returned. Throws a InvalidResponseException if
263
+ # the request fails.
264
+ #
265
+ # If you pass the optional argument as true, it will only show containers that are CURRENTLY being shared on the CDN,
266
+ # as opposed to the default behavior which is to show all containers that have EVER been public.
267
+ #
268
+ # cf.public_containers
269
+ # => ["video", "webpics"]
270
+ def public_containers(enabled_only = false)
271
+ begin
272
+ response = SwiftClient.get_account(cdnurl, self.authtoken)
273
+ response[1].collect{|c| c['name']}
274
+ rescue ClientException => e
275
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status.to_s}" unless (e.status.to_s == "200")
276
+ end
277
+ end
278
+
279
+ def storageurl
280
+ "#{self.storagescheme}://#{self.storagehost}:#{self.storageport.to_s}#{self.storagepath}"
281
+ end
282
+ def cdnurl
283
+ "#{self.cdnmgmtscheme}://#{self.cdnmgmthost}:#{self.cdnmgmtport.to_s}#{self.cdnmgmtpath}"
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,451 @@
1
+ module CloudFiles
2
+ class Container
3
+ # See COPYING for license information.
4
+ # Copyright (c) 2011, Rackspace US, Inc.
5
+
6
+ # Name of the container which corresponds to the instantiated container class
7
+ attr_reader :name
8
+
9
+ # The parent CloudFiles::Connection object for this container
10
+ attr_reader :connection
11
+
12
+ # Retrieves an existing CloudFiles::Container object tied to the current CloudFiles::Connection. If the requested
13
+ # container does not exist, it will raise a CloudFiles::Exception::NoSuchContainer Exception.
14
+ #
15
+ # Will likely not be called directly, instead use connection.container('container_name') to retrieve the object.
16
+ def initialize(connection, name)
17
+ @connection = connection
18
+ @name = name
19
+ # Load the metadata now, so we'll get a CloudFiles::Exception::NoSuchContainer exception should the container
20
+ # not exist.
21
+ self.container_metadata
22
+ end
23
+
24
+ # Refreshes data about the container and populates class variables. Items are otherwise
25
+ # loaded in a lazy loaded fashion.
26
+ #
27
+ # container.count
28
+ # => 2
29
+ # [Upload new file to the container]
30
+ # container.count
31
+ # => 2
32
+ # container.populate
33
+ # container.count
34
+ # => 3
35
+ def refresh
36
+ @metadata = @cdn_metadata = nil
37
+ true
38
+ end
39
+ alias :populate :refresh
40
+
41
+ # Retrieves Metadata for the container
42
+ def container_metadata
43
+ @metadata ||= (
44
+ begin
45
+ response = SwiftClient.head_container(self.connection.storageurl, self.connection.authtoken, escaped_name)
46
+ resphash = {}
47
+ response.to_hash.select { |k,v| k.match(/^x-container-meta/) }.each { |x| resphash[x[0]] = x[1].to_s }
48
+ {:bytes => response["x-container-bytes-used"].to_i, :count => response["x-container-object-count"].to_i, :metadata => resphash, :container_read => response["x-container-read"], :container_write => response["x-container-write"]}
49
+ rescue ClientException => e
50
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (e.status.to_s =~ /^20/)
51
+ end
52
+ )
53
+ end
54
+
55
+ # Retrieves CDN-Enabled Meta Data
56
+ def cdn_metadata
57
+ return @cdn_metadata if @cdn_metadata
58
+ if cdn_available?
59
+ @cdn_metadata = (
60
+ begin
61
+ response = SwiftClient.head_container(self.connection.cdnurl, self.connection.authtoken, escaped_name)
62
+ cdn_enabled = ((response["x-cdn-enabled"] || "").downcase == "true") ? true : false
63
+ rescue ClientException => e
64
+ cdn_enabled = false
65
+ end
66
+ {
67
+ :cdn_enabled => cdn_enabled,
68
+ :cdn_ttl => cdn_enabled ? response["x-ttl"].to_i : nil,
69
+ :cdn_url => cdn_enabled ? response["x-cdn-uri"] : nil,
70
+ :cdn_ssl_url => cdn_enabled ? response["x-cdn-ssl-uri"] : nil,
71
+ :cdn_streaming_url => cdn_enabled ? response["x-cdn-streaming-uri"] : nil,
72
+ :cdn_log => (cdn_enabled and response["x-log-retention"] == "True") ? true : false
73
+ }
74
+ )
75
+ else
76
+ @cdn_metadata = {}
77
+ end
78
+ end
79
+
80
+ # Returns the container's metadata as a nicely formatted hash, stripping off the X-Meta-Object- prefix that the system prepends to the
81
+ # key name.
82
+ #
83
+ # object.metadata
84
+ # => {"ruby"=>"cool", "foo"=>"bar"}
85
+ def metadata
86
+ metahash = {}
87
+ self.container_metadata[:metadata].each{ |key, value| metahash[key.gsub(/x-container-meta-/, '').gsub(/%20/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ') }
88
+ metahash
89
+ end
90
+
91
+ # Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
92
+ # New calls to set metadata are additive. To remove metadata, set the value of the key to nil.
93
+ #
94
+ # Throws NoSuchObjectException if the container doesn't exist. Throws InvalidResponseException if the request
95
+ # fails.
96
+ def set_metadata(metadatahash)
97
+ headers = {}
98
+ metadatahash.each{ |key, value| headers['X-Container-Meta-' + CloudFiles.escape(key.to_s.capitalize)] = value.to_s }
99
+ begin
100
+ SwiftClient.post_container(self.connection.storageurl, self.connection.authtoken, escaped_name, headers)
101
+ self.refresh
102
+ true
103
+ rescue ClientException => e
104
+ raise CloudFiles::Exception::NoSuchObject, "Container #{@name} does not exist" if (e.status.to_s == "404")
105
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status}" unless (e.status.to_s =~ /^20/)
106
+ end
107
+ end
108
+
109
+ # Size of the container (in bytes)
110
+ def bytes
111
+ self.container_metadata[:bytes]
112
+ end
113
+
114
+ # Number of objects in the container
115
+ def count
116
+ self.container_metadata[:count]
117
+ end
118
+
119
+ # Returns true if the container is public and CDN-enabled. Returns false otherwise.
120
+ #
121
+ # Aliased as container.public?
122
+ #
123
+ # public_container.cdn_enabled?
124
+ # => true
125
+ #
126
+ # private_container.public?
127
+ # => false
128
+ def cdn_enabled
129
+ cdn_available? && self.cdn_metadata[:cdn_enabled]
130
+ end
131
+ alias :cdn_enabled? :cdn_enabled
132
+ alias :public? :cdn_enabled
133
+
134
+ # CDN container TTL (if container is public)
135
+ def cdn_ttl
136
+ self.cdn_metadata[:cdn_ttl]
137
+ end
138
+
139
+ # CDN container URL (if container is public)
140
+ def cdn_url
141
+ self.cdn_metadata[:cdn_url]
142
+ end
143
+
144
+ # CDN SSL container URL (if container is public)
145
+ def cdn_ssl_url
146
+ self.cdn_metadata[:cdn_ssl_url]
147
+ end
148
+
149
+ # CDN Streaming container URL (if container is public)
150
+ def cdn_streaming_url
151
+ self.cdn_metadata[:cdn_streaming_url]
152
+ end
153
+
154
+ #used by openstack swift
155
+ def read_acl
156
+ self.container_metadata[:container_read]
157
+ end
158
+
159
+ #used by openstack swift
160
+ def write_acl
161
+ self.container_metadata[:container_write]
162
+ end
163
+
164
+ # Returns true if log retention is enabled on this container, false otherwise
165
+ def cdn_log
166
+ self.cdn_metadata[:cdn_log]
167
+ end
168
+ alias :log_retention? :cdn_log
169
+ alias :cdn_log? :cdn_log
170
+
171
+
172
+ # Change the log retention status for this container. Values are true or false.
173
+ #
174
+ # These logs will be periodically (at unpredictable intervals) compressed and uploaded
175
+ # to a ".CDN_ACCESS_LOGS" container in the form of "container_name.YYYYMMDDHH-XXXX.gz".
176
+ def log_retention=(value)
177
+ raise Exception::CDNNotAvailable unless cdn_available?
178
+ begin
179
+ SwiftClient.post_container(self.connection.cdnurl, self.connection.authtoken, escaped_name, {"x-log-retention" => value.to_s.capitalize})
180
+ true
181
+ rescue ClientException => e
182
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status}" unless (e.status.to_s == "201" or e.status.to_s == "202")
183
+ end
184
+ end
185
+
186
+
187
+ # Returns the CloudFiles::StorageObject for the named object. Refer to the CloudFiles::StorageObject class for available
188
+ # methods. If the object exists, it will be returned. If the object does not exist, a NoSuchObjectException will be thrown.
189
+ #
190
+ # object = container.object('test.txt')
191
+ # object.data
192
+ # => "This is test data"
193
+ #
194
+ # object = container.object('newfile.txt')
195
+ # => NoSuchObjectException: Object newfile.txt does not exist
196
+ def object(objectname)
197
+ o = CloudFiles::StorageObject.new(self, objectname, true)
198
+ return o
199
+ end
200
+ alias :get_object :object
201
+
202
+
203
+ # Gathers a list of all available objects in the current container and returns an array of object names.
204
+ # container = cf.container("My Container")
205
+ # container.objects #=> [ "cat", "dog", "donkey", "monkeydir", "monkeydir/capuchin"]
206
+ # Pass a limit argument to limit the list to a number of objects:
207
+ # container.objects(:limit => 1) #=> [ "cat" ]
208
+ # Pass an marker with or without a limit to start the list at a certain object:
209
+ # container.objects(:limit => 1, :marker => 'dog') #=> [ "donkey" ]
210
+ # Pass a prefix to search for objects that start with a certain string:
211
+ # container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
212
+ # Only search within a certain pseudo-filesystem path:
213
+ # container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
214
+ # Only grab "virtual directories", based on a single-character delimiter (no "directory" objects required):
215
+ # container.objects(:delimiter => '/') #=> ["monkeydir"]
216
+ # All arguments to this method are optional.
217
+ #
218
+ # Returns an empty array if no object exist in the container. Throws an InvalidResponseException
219
+ # if the request fails.
220
+ def objects(params = {})
221
+ params[:marker] ||= params[:offset] unless params[:offset].nil?
222
+ query = []
223
+ params.each do |param, value|
224
+ if [:limit, :marker, :prefix, :path, :delimiter].include? param
225
+ query << "#{param}=#{CloudFiles.escape(value.to_s)}"
226
+ end
227
+ end
228
+ begin
229
+ response = SwiftClient.get_container(self.connection.storageurl, self.connection.authtoken, escaped_name, params[:marker], params[:limit], params[:prefix], params[:delimiter])
230
+ return response[1].collect{|o| o['name']}
231
+ rescue ClientException => e
232
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status}" unless (e.status.to_s == "200")
233
+ end
234
+ end
235
+ alias :list_objects :objects
236
+
237
+ # Retrieves a list of all objects in the current container along with their size in bytes, hash, and content_type.
238
+ # If no objects exist, an empty hash is returned. Throws an InvalidResponseException if the request fails. Takes a
239
+ # parameter hash as an argument, in the same form as the objects method.
240
+ #
241
+ # Accepts the same options as objects to limit the returned set.
242
+ #
243
+ # Returns a hash in the same format as the containers_detail from the CloudFiles class.
244
+ #
245
+ # container.objects_detail
246
+ # => {"test.txt"=>{:content_type=>"application/octet-stream",
247
+ # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
248
+ # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
249
+ # :bytes=>"16"},
250
+ # "new.txt"=>{:content_type=>"application/octet-stream",
251
+ # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
252
+ # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
253
+ # :bytes=>"22"}
254
+ # }
255
+ def objects_detail(params = {})
256
+ params[:marker] ||= params[:offset] unless params[:offset].nil?
257
+ query = ["format=xml"]
258
+ params.each do |param, value|
259
+ if [:limit, :marker, :prefix, :path, :delimiter].include? param
260
+ query << "#{param}=#{CloudFiles.escape(value.to_s)}"
261
+ end
262
+ end
263
+ begin
264
+ response = SwiftClient.get_container(self.connection.storageurl, self.connection.authtoken, escaped_name, params[:marker], params[:limit], params[:prefix], params[:delimiter])
265
+ return Hash[*response[1].collect{|o| [o['name'],{ :bytes => o["bytes"], :hash => o["hash"], :content_type => o["content_type"], :last_modified => DateTime.parse(o["last_modified"])}] }.flatten]
266
+ rescue ClientException => e
267
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status}" unless (e.status.to_s == "200")
268
+ end
269
+ end
270
+ alias :list_objects_info :objects_detail
271
+
272
+ # Returns true if a container is empty and returns false otherwise.
273
+ #
274
+ # new_container.empty?
275
+ # => true
276
+ #
277
+ # full_container.empty?
278
+ # => false
279
+ def empty?
280
+ return (container_metadata[:count].to_i == 0)? true : false
281
+ end
282
+
283
+ # Returns true if object exists and returns false otherwise.
284
+ #
285
+ # container.object_exists?('goodfile.txt')
286
+ # => true
287
+ #
288
+ # container.object_exists?('badfile.txt')
289
+ # => false
290
+ def object_exists?(objectname)
291
+ begin
292
+ response = SwiftClient.head_object(self.connection.storageurl, self.connection.authtoken, escaped_name, objectname)
293
+ true
294
+ rescue ClientException => e
295
+ false
296
+ end
297
+ end
298
+
299
+ # Creates a new CloudFiles::StorageObject in the current container.
300
+ #
301
+ # If an object with the specified name exists in the current container, that object will be returned. Otherwise,
302
+ # an empty new object will be returned.
303
+ #
304
+ # Passing in the optional make_path argument as true will create zero-byte objects to simulate a filesystem path
305
+ # to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
306
+ # be used in the Container.objects method.
307
+ def create_object(objectname, make_path = false)
308
+ CloudFiles::StorageObject.new(self, objectname, false, make_path)
309
+ end
310
+
311
+ # Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
312
+ # NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request fails.
313
+ #
314
+ # container.delete_object('new.txt')
315
+ # => true
316
+ #
317
+ # container.delete_object('nonexistent_file.txt')
318
+ # => NoSuchObjectException: Object nonexistent_file.txt does not exist
319
+ def delete_object(objectname)
320
+ begin
321
+ SwiftClient.delete_object(self.connection.storageurl, self.connection.authtoken, escaped_name, objectname)
322
+ true
323
+ rescue ClientException => e
324
+ raise CloudFiles::Exception::NoSuchObject, "Object #{objectname} does not exist" if (e.status.to_s == "404")
325
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{e.status}" unless (e.status.to_s =~ /^20/)
326
+ end
327
+ end
328
+
329
+ # Makes a container publicly available via the Cloud Files CDN and returns true upon success. Throws NoSuchContainerException
330
+ # if the container doesn't exist or if the request fails.
331
+ #
332
+ # Takes an optional hash of options, including:
333
+ #
334
+ # :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)
335
+ #
336
+ # :user_agent_acl, a Perl-compatible regular expression limiting access to this container to user agents matching the given regular expression
337
+ #
338
+ # :referrer_acl, a Perl-compatible regular expression limiting access to this container to HTTP referral URLs matching the given regular expression
339
+ #
340
+ # container.make_public(:ttl => 8900, :user_agent_acl => "/Mozilla/", :referrer_acl => "/^http://rackspace.com")
341
+ # => true
342
+ def make_public(options = {:ttl => 86400})
343
+ raise Exception::CDNNotAvailable unless cdn_available?
344
+ if options.is_a?(Fixnum)
345
+ print "DEPRECATED: make_public takes a hash of options now, instead of a TTL number"
346
+ ttl = options
347
+ options = {:ttl => ttl}
348
+ end
349
+
350
+ begin
351
+ SwiftClient.put_container(self.connection.cdnurl, self.connection.authtoken, escaped_name)
352
+ rescue ClientException => e
353
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (e.status.to_s == "201" || e.status.to_s == "202")
354
+ end
355
+ headers = { "X-TTL" => options[:ttl].to_s , "X-CDN-Enabled" => "True" }
356
+ headers["X-User-Agent-ACL"] = options[:user_agent_acl] if options[:user_agent_acl]
357
+ headers["X-Referrer-ACL"] = options[:referrer_acl] if options[:referrer_acl]
358
+
359
+ post_with_headers(headers)
360
+ # raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
361
+ refresh
362
+ true
363
+ end
364
+
365
+ # Only to be used with openstack swift
366
+ def set_write_acl(write_string)
367
+ refresh
368
+ headers = {"X-Container-Write" => write_string}
369
+ post_with_headers(headers)
370
+ true
371
+ end
372
+
373
+ # Only to be used with openstack swift
374
+ def set_read_acl(read_string)
375
+ refresh
376
+ headers = {"X-Container-Read" => read_string}
377
+ post_with_headers(headers)
378
+ true
379
+ end
380
+
381
+ def post_with_headers(headers = {})
382
+ begin
383
+ SwiftClient.post_container(cdn_enabled? ? self.connection.cdnurl : self.connection.storageurl, self.connection.authtoken, escaped_name, headers)
384
+ rescue ClientException => e
385
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist (response code: #{e.status.to_s})" unless (e.status.to_s =~ /^20/)
386
+ end
387
+ end
388
+
389
+ # Makes a container private and returns true upon success. Throws NoSuchContainerException
390
+ # if the container doesn't exist or if the request fails.
391
+ #
392
+ # Note that if the container was previously public, it will continue to exist out on the CDN until it expires.
393
+ #
394
+ # container.make_private
395
+ # => true
396
+ def make_private
397
+ raise Exception::CDNNotAvailable unless cdn_available?
398
+ headers = { "X-CDN-Enabled" => "False" }
399
+ begin
400
+ SwiftClient.post_container(self.connection.cdnurl, self.connection.authtoken, escaped_name, headers)
401
+ refresh
402
+ true
403
+ rescue ClientException => e
404
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (e.status.to_s == "201" || e.status.to_s == "202")
405
+ end
406
+ end
407
+
408
+ # Purges CDN Edge Cache for all objects inside of this container
409
+ #
410
+ # :email, An valid email address or comma seperated
411
+ # list of emails to be notified once purge is complete .
412
+ #
413
+ # container.purge_from_cdn
414
+ # => true
415
+ #
416
+ # or
417
+ #
418
+ # container.purge_from_cdn("User@domain.com")
419
+ # => true
420
+ #
421
+ # or
422
+ #
423
+ # container.purge_from_cdn("User@domain.com, User2@domain.com")
424
+ # => true
425
+ def purge_from_cdn(email=nil)
426
+ raise Exception::CDNNotAvailable unless cdn_available?
427
+ headers = {}
428
+ headers = {"X-Purge-Email" => email} if email
429
+ begin
430
+ SwiftClient.delete_container(self.connection.cdnurl, self.connection.authtoken, escaped_name, headers)
431
+ true
432
+ rescue ClientException => e
433
+ raise CloudFiles::Exception::Connection, "Error Unable to Purge Container: #{@name}" unless (e.status.to_s > "200" && e.status.to_s < "299")
434
+ end
435
+ end
436
+
437
+ def to_s # :nodoc:
438
+ @name
439
+ end
440
+
441
+ def cdn_available?
442
+ self.connection.cdn_available?
443
+ end
444
+
445
+ def escaped_name
446
+ CloudFiles.escape(@name)
447
+ end
448
+
449
+ end
450
+
451
+ end