cloudfiles 1.4.10 → 1.4.11

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,11 @@
1
+ ================================================================================
2
+ 1.4.1 (2011/02/04)
3
+ ================================================================================
4
+ o Configurable :auth_url support for OpenStack Swift and non-US deployments. Add constants for Cloud Servers USA and UK. (Chmouel Boudjnah, Dan Prince)
5
+ o Moved exceptions under the CloudFiles::Exception scope
6
+ o Added support for configurable path delimiters (Corey Ward)
7
+ o Improvements in path escaping (Corey Ward)
8
+ o Support for the new COPY method on objects, via storage_object.copy and storage_object.move
9
+ o Reduced the number of API calls for loading metadata (Edmund Salvacion)
10
+ o Allow setting of content_type on more operations (suggestion by Bo Benson)
11
+
data/CONTRIBUTORS ADDED
@@ -0,0 +1,28 @@
1
+ The following people have contributed to this software, either with code, patches, bug reports, or suggestions.
2
+
3
+ Thank you for your help! If you have contributed and are not on the list, feel free to email minter@lunenburg.org
4
+
5
+ Major Hayden
6
+ H. Wade Minter
7
+ Niels Ganser
8
+ phillc
9
+ Todd Eichel
10
+ Cory Forsyth
11
+ Dan Prince
12
+ Carl Woodward
13
+ Conrad Weidenkeller
14
+ Chmouel Boudjnah
15
+ Corey Ward
16
+ Edmund Salvacion
17
+ mindgap
18
+ Bo Benson
19
+ Ryuujinx
20
+ Ivan Torres
21
+ creiht
22
+ Vladimir Zhukov
23
+ JCallicoat
24
+ Jeremy McNevin
25
+ Ryan Williams
26
+ drue
27
+ mkcode
28
+ megaphone
data/COPYING CHANGED
@@ -1,6 +1,6 @@
1
1
  Unless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them.
2
2
 
3
- Copyright (C) 2008 Rackspace US, Inc.
3
+ Copyright (C) 2011 Rackspace US, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
data/README.rdoc CHANGED
@@ -37,6 +37,9 @@ See the class definitions for documentation on specific methods and operations.
37
37
  # Log into the Cloud Files system
38
38
  cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY")
39
39
 
40
+ # Or, if you want to access the United Kingdom cloud installations, there's a handy constant:
41
+ cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :auth_url => CloudFiles::AUTH_UK)
42
+
40
43
  # Get a listing of all containers under this account
41
44
  cf.containers
42
45
  => ["backup", "Books", "cftest", "test", "video", "webpics"]
@@ -47,6 +50,10 @@ See the class definitions for documentation on specific methods and operations.
47
50
  # See how many objects are under this container
48
51
  container.count
49
52
  => 3
53
+
54
+ # Upload a file
55
+ object = container.create_object 'filename.txt', false
56
+ object.write file
50
57
 
51
58
  # List the objects
52
59
  container.objects
@@ -63,9 +70,9 @@ See the class definitions for documentation on specific methods and operations.
63
70
 
64
71
  Initial work by Major Hayden <major.hayden@rackspace.com>
65
72
 
66
- Subsequent work by H. Wade Minter <minter@lunenburg.org>
73
+ Subsequent work by H. Wade Minter <minter@lunenburg.org> and Dan Prince <dan.prince@rackspace.com>
67
74
 
68
75
  == License
69
76
 
70
77
  See COPYING for license information.
71
- Copyright (c) 2009, Rackspace US, Inc.
78
+ Copyright (c) 2011, Rackspace US, Inc.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.10
1
+ 1.4.11
data/cloudfiles.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{cloudfiles}
8
- s.version = "1.4.10"
8
+ s.version = "1.4.11"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["H. Wade Minter", "Rackspace Hosting"]
12
- s.date = %q{2010-11-19}
12
+ s.date = %q{2011-02-05}
13
13
  s.description = %q{A Ruby version of the Rackspace Cloud Files API.}
14
14
  s.email = %q{minter@lunenburg.org}
15
15
  s.extra_rdoc_files = [
@@ -18,8 +18,9 @@ Gem::Specification.new do |s|
18
18
  ]
19
19
  s.files = [
20
20
  ".gitignore",
21
+ "CHANGELOG",
22
+ "CONTRIBUTORS",
21
23
  "COPYING",
22
- "Manifest",
23
24
  "README.rdoc",
24
25
  "Rakefile",
25
26
  "TODO",
@@ -29,6 +30,7 @@ Gem::Specification.new do |s|
29
30
  "lib/cloudfiles/authentication.rb",
30
31
  "lib/cloudfiles/connection.rb",
31
32
  "lib/cloudfiles/container.rb",
33
+ "lib/cloudfiles/exception.rb",
32
34
  "lib/cloudfiles/storage_object.rb",
33
35
  "test/cf-testunit.rb",
34
36
  "test/cloudfiles_authentication_test.rb",
@@ -40,7 +42,7 @@ Gem::Specification.new do |s|
40
42
  s.homepage = %q{http://www.rackspacecloud.com/cloud_hosting_products/files}
41
43
  s.rdoc_options = ["--charset=UTF-8"]
42
44
  s.require_paths = ["lib"]
43
- s.rubygems_version = %q{1.3.7}
45
+ s.rubygems_version = %q{1.5.0}
44
46
  s.summary = %q{A Ruby API into Rackspace Cloud Files}
45
47
  s.test_files = [
46
48
  "test/cf-testunit.rb",
@@ -52,7 +54,6 @@ Gem::Specification.new do |s|
52
54
  ]
53
55
 
54
56
  if s.respond_to? :specification_version then
55
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
57
  s.specification_version = 3
57
58
 
58
59
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
data/lib/cloudfiles.rb CHANGED
@@ -1,27 +1,32 @@
1
1
  #!/usr/bin/env ruby
2
- #
2
+ #
3
3
  # == Cloud Files API
4
4
  # ==== Connects Ruby Applications to Rackspace's {Cloud Files service}[http://www.rackspacecloud.com/cloud_hosting_products/files]
5
5
  # Initial work by Major Hayden <major.hayden@rackspace.com>
6
- #
7
- # Subsequent work by H. Wade Minter <minter@lunenburg.org>
6
+ #
7
+ # Subsequent work by H. Wade Minter <minter@lunenburg.org> and Dan Prince <dan.prince@rackspace.com>
8
8
  #
9
9
  # See COPYING for license information.
10
- # Copyright (c) 2009, Rackspace US, Inc.
10
+ # Copyright (c) 2011, Rackspace US, Inc.
11
11
  # ----
12
- #
12
+ #
13
13
  # === Documentation & Examples
14
- # To begin reviewing the available methods and examples, peruse the README file, or begin by looking at documentation for the
14
+ # To begin reviewing the available methods and examples, peruse the README file, or begin by looking at documentation for the
15
15
  # CloudFiles::Connection class.
16
16
  #
17
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('user_name', 'api_key') method.
18
+ # To create a new CloudFiles connection, use the CloudFiles::Connection.new(:username => 'user_name', :api_key => 'api_key') method.
19
+
19
20
  module CloudFiles
20
21
 
22
+ AUTH_USA = "https://auth.api.rackspacecloud.com/v1.0"
23
+ AUTH_UK = "https://lon.auth.api.rackspacecloud.com/v1.0"
24
+
21
25
  VERSION = IO.read(File.dirname(__FILE__) + '/../VERSION')
22
26
  require 'net/http'
23
27
  require 'net/https'
24
28
  require 'rexml/document'
29
+ require 'cgi'
25
30
  require 'uri'
26
31
  require 'digest/md5'
27
32
  require 'time'
@@ -34,6 +39,7 @@ module CloudFiles
34
39
  end
35
40
 
36
41
  $:.unshift(File.dirname(__FILE__))
42
+ require 'cloudfiles/exception'
37
43
  require 'cloudfiles/authentication'
38
44
  require 'cloudfiles/connection'
39
45
  require 'cloudfiles/container'
@@ -42,6 +48,13 @@ module CloudFiles
42
48
  def self.lines(str)
43
49
  (str.respond_to?(:lines) ? str.lines : str).to_a.map { |x| x.chomp }
44
50
  end
51
+
52
+ # CGI.escape, but without special treatment on spaces
53
+ def self.escape(str)
54
+ str.gsub(/([^a-zA-Z0-9_.-]+)/) do
55
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
56
+ end
57
+ end
45
58
  end
46
59
 
47
60
 
@@ -1,33 +1,38 @@
1
1
  module CloudFiles
2
2
  class Authentication
3
3
  # See COPYING for license information.
4
- # Copyright (c) 2009, Rackspace US, Inc.
5
-
4
+ # Copyright (c) 2011, Rackspace US, Inc.
5
+
6
6
  # Performs an authentication to the Cloud Files servers. Opens a new HTTP connection to the API server,
7
7
  # sends the credentials, and looks for a successful authentication. If it succeeds, it sets the cdmmgmthost,
8
8
  # cdmmgmtpath, storagehost, storagepath, authtoken, and authok variables on the connection. If it fails, it raises
9
- # an AuthenticationException.
9
+ # an CloudFiles::Exception::Authentication exception.
10
10
  #
11
11
  # Should probably never be called directly.
12
12
  def initialize(connection)
13
- parsed_authurl = URI.parse(connection.authurl)
14
- path = parsed_authurl.path
13
+ parsed_auth_url = URI.parse(connection.auth_url)
14
+ path = parsed_auth_url.path
15
15
  hdrhash = { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey }
16
16
  begin
17
- server = get_server(connection, parsed_authurl)
18
- server.use_ssl = true
19
- server.verify_mode = OpenSSL::SSL::VERIFY_NONE
17
+ server = get_server(connection, parsed_auth_url)
18
+
19
+ if parsed_auth_url.scheme == "https"
20
+ server.use_ssl = true
21
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
22
+ end
20
23
  server.start
21
24
  rescue
22
- raise ConnectionException, "Unable to connect to #{server}"
25
+ raise CloudFiles::Exception::Connection, "Unable to connect to #{server}"
23
26
  end
24
- response = server.get(path,hdrhash)
27
+ response = server.get(path, hdrhash)
25
28
  if (response.code == "204")
26
- connection.cdnmgmthost = URI.parse(response["x-cdn-management-url"]).host
27
- connection.cdnmgmtpath = URI.parse(response["x-cdn-management-url"]).path
28
- connection.cdnmgmtport = URI.parse(response["x-cdn-management-url"]).port
29
- connection.cdnmgmtscheme = URI.parse(response["x-cdn-management-url"]).scheme
30
- connection.storagehost = set_snet(connection,URI.parse(response["x-storage-url"]).host)
29
+ if response["x-cdn-management-url"]
30
+ connection.cdnmgmthost = URI.parse(response["x-cdn-management-url"]).host
31
+ connection.cdnmgmtpath = URI.parse(response["x-cdn-management-url"]).path
32
+ connection.cdnmgmtport = URI.parse(response["x-cdn-management-url"]).port
33
+ connection.cdnmgmtscheme = URI.parse(response["x-cdn-management-url"]).scheme
34
+ end
35
+ connection.storagehost = set_snet(connection, URI.parse(response["x-storage-url"]).host)
31
36
  connection.storagepath = URI.parse(response["x-storage-url"]).path
32
37
  connection.storageport = URI.parse(response["x-storage-url"]).port
33
38
  connection.storagescheme = URI.parse(response["x-storage-url"]).scheme
@@ -35,23 +40,23 @@ module CloudFiles
35
40
  connection.authok = true
36
41
  else
37
42
  connection.authtoken = false
38
- raise AuthenticationException, "Authentication failed"
43
+ raise CloudFiles::Exception::Authentication, "Authentication failed"
39
44
  end
40
45
  server.finish
41
46
  end
42
-
47
+
43
48
  private
44
-
45
- def get_server(connection, parsed_authurl)
46
- Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new(parsed_authurl.host,parsed_authurl.port)
47
- end
48
-
49
- def set_snet(connection,hostname)
50
- if connection.snet?
51
- "snet-#{hostname}"
52
- else
53
- hostname
49
+
50
+ def get_server(connection, parsed_auth_url)
51
+ Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new(parsed_auth_url.host, parsed_auth_url.port)
52
+ end
53
+
54
+ def set_snet(connection, hostname)
55
+ if connection.snet?
56
+ "snet-#{hostname}"
57
+ else
58
+ hostname
59
+ end
54
60
  end
55
- end
56
61
  end
57
62
  end
@@ -1,8 +1,8 @@
1
1
  module CloudFiles
2
2
  class Connection
3
3
  # See COPYING for license information.
4
- # Copyright (c) 2009, Rackspace US, Inc.
5
-
4
+ # Copyright (c) 2011, Rackspace US, Inc.
5
+
6
6
  # Authentication key provided when the CloudFiles class was instantiated
7
7
  attr_reader :authkey
8
8
 
@@ -11,19 +11,19 @@ module CloudFiles
11
11
 
12
12
  # Authentication username provided when the CloudFiles class was instantiated
13
13
  attr_reader :authuser
14
-
14
+
15
15
  # API host to authenticate to
16
- attr_reader :authurl
16
+ attr_reader :auth_url
17
17
 
18
18
  # Hostname of the CDN management server
19
19
  attr_accessor :cdnmgmthost
20
20
 
21
21
  # Path for managing containers on the CDN management server
22
22
  attr_accessor :cdnmgmtpath
23
-
23
+
24
24
  # Port number for the CDN server
25
25
  attr_accessor :cdnmgmtport
26
-
26
+
27
27
  # URI scheme for the CDN server
28
28
  attr_accessor :cdnmgmtscheme
29
29
 
@@ -32,67 +32,65 @@ module CloudFiles
32
32
 
33
33
  # Path for managing containers/objects on the storage server
34
34
  attr_accessor :storagepath
35
-
35
+
36
36
  # Port for managing the storage server
37
37
  attr_accessor :storageport
38
-
38
+
39
39
  # URI scheme for the storage server
40
40
  attr_accessor :storagescheme
41
-
41
+
42
42
  # Instance variable that is set when authorization succeeds
43
43
  attr_accessor :authok
44
-
45
- # The total size in bytes under this connection
46
- attr_reader :bytes
47
-
48
- # The total number of containers under this connection
49
- attr_reader :count
50
-
44
+
51
45
  # Optional proxy variables
52
46
  attr_reader :proxy_host
53
47
  attr_reader :proxy_port
54
-
48
+
55
49
  # Creates a new CloudFiles::Connection object. Uses CloudFiles::Authentication to perform the login for the connection.
56
50
  # The authuser is the Rackspace Cloud username, the authkey is the Rackspace Cloud API key.
57
51
  #
58
- # Setting the optional retry_auth variable to false will cause an exception to be thrown if your authorization token expires.
52
+ # Setting the :retry_auth option to false will cause an exception to be thrown if your authorization token expires.
59
53
  # Otherwise, it will attempt to reauthenticate.
60
54
  #
61
- # Setting the optional snet variable to true or setting an environment variable of RACKSPACE_SERVICENET to any value will cause
62
- # storage URLs to be returned with a prefix pointing them to the internal Rackspace service network, instead of a public URL.
55
+ # Setting the :snet option to true or setting an environment variable of RACKSPACE_SERVICENET to any value will cause
56
+ # storage URLs to be returned with a prefix pointing them to the internal Rackspace service network, instead of a public URL.
63
57
  #
64
58
  # This is useful if you are using the library on a Rackspace-hosted system, as it provides faster speeds, keeps traffic off of
65
59
  # the public network, and the bandwidth is not billed.
66
60
  #
67
- # If you need to connect to a Cloud Files installation that is NOT the standard Rackspace one, set the :authurl option to the URL
68
- # of your authentication endpoint. The default is https://auth.api.rackspacecloud.com/v1.0
61
+ # 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
62
+ # 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)
63
+ #
64
+ # There are two predefined constants to represent the United States-based authentication endpoint and the United Kingdom-based endpoint:
65
+ # 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.
69
66
  #
70
67
  # This will likely be the base class for most operations.
71
- #
68
+ #
72
69
  # 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
73
70
  # supported in the new style.
74
71
  #
75
- # cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :authurl => "https://auth.api.rackspacecloud.com/v1.0", :retry_auth => true, :snet => false, :proxy_host => "localhost", :proxy_port => "1234")
72
+ # 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")
76
73
  #
77
74
  # The old style (positional arguments) is deprecated and will be removed at some point in the future.
78
- #
75
+ #
79
76
  # cf = CloudFiles::Connection.new(MY_USERNAME, MY_API_KEY, RETRY_AUTH, USE_SNET)
80
77
  def initialize(*args)
81
78
  if args[0].is_a?(Hash)
82
79
  options = args[0]
83
- @authuser = options[:username] ||( raise AuthenticationException, "Must supply a :username")
84
- @authkey = options[:api_key] || (raise AuthenticationException, "Must supply an :api_key")
85
- @authurl = options[:authurl] || "https://auth.api.rackspacecloud.com/v1.0"
80
+ @authuser = options[:username] ||( raise CloudFiles::Exception::Authentication, "Must supply a :username")
81
+ @authkey = options[:api_key] || (raise CloudFiles::Exception::Authentication, "Must supply an :api_key")
82
+ @auth_url = options[:authurl] || CloudFiles::AUTH_USA
83
+ @auth_url = options[:auth_url] || CloudFiles::AUTH_USA
86
84
  @retry_auth = options[:retry_auth] || true
87
85
  @snet = ENV['RACKSPACE_SERVICENET'] || options[:snet]
88
86
  @proxy_host = options[:proxy_host]
89
87
  @proxy_port = options[:proxy_port]
90
88
  elsif args[0].is_a?(String)
91
- @authuser = args[0] ||( raise AuthenticationException, "Must supply the username as the first argument")
92
- @authkey = args[1] || (raise AuthenticationException, "Must supply the API key as the second argument")
89
+ @authuser = args[0] ||( raise CloudFiles::Exception::Authentication, "Must supply the username as the first argument")
90
+ @authkey = args[1] || (raise CloudFiles::Exception::Authentication, "Must supply the API key as the second argument")
93
91
  @retry_auth = args[2] || true
94
92
  @snet = (ENV['RACKSPACE_SERVICENET'] || args[3]) ? true : false
95
- @authurl = "https://auth.api.rackspacecloud.com/v1.0"
93
+ @auth_url = CloudFiles::AUTH_USA
96
94
  end
97
95
  @authok = false
98
96
  @http = {}
@@ -106,7 +104,7 @@ module CloudFiles
106
104
  def authok?
107
105
  @authok
108
106
  end
109
-
107
+
110
108
  # Returns true if the library is requesting the use of the Rackspace service network
111
109
  def snet?
112
110
  @snet
@@ -119,7 +117,7 @@ module CloudFiles
119
117
  # container.count
120
118
  # => 2
121
119
  def container(name)
122
- CloudFiles::Container.new(self,name)
120
+ CloudFiles::Container.new(self, name)
123
121
  end
124
122
  alias :get_container :container
125
123
 
@@ -129,15 +127,25 @@ module CloudFiles
129
127
  # cf.get_info
130
128
  # => {:count=>8, :bytes=>42438527}
131
129
  # cf.bytes
132
- # => 42438527
130
+ # => 42438527
133
131
  def get_info
134
- response = cfreq("HEAD",@storagehost,@storagepath,@storageport,@storagescheme)
135
- raise InvalidResponseException, "Unable to obtain account size" unless (response.code == "204")
132
+ response = cfreq("HEAD", @storagehost, @storagepath, @storageport, @storagescheme)
133
+ raise CloudFiles::Exception::InvalidResponse, "Unable to obtain account size" unless (response.code == "204")
136
134
  @bytes = response["x-account-bytes-used"].to_i
137
135
  @count = response["x-account-container-count"].to_i
138
136
  {:bytes => @bytes, :count => @count}
139
137
  end
140
138
 
139
+ # The total size in bytes under this connection
140
+ def bytes
141
+ get_info[:bytes]
142
+ end
143
+
144
+ # The total number of containers under this connection
145
+ def count
146
+ get_info[:count]
147
+ end
148
+
141
149
  # Gathers a list of the containers that exist for the account and returns the list of container names
142
150
  # as an array. If no containers exist, an empty array is returned. Throws an InvalidResponseException
143
151
  # if the request fails.
@@ -146,18 +154,17 @@ module CloudFiles
146
154
  # specified in limit, starting after the object named in marker.
147
155
  #
148
156
  # cf.containers
149
- # => ["backup", "Books", "cftest", "test", "video", "webpics"]
157
+ # => ["backup", "Books", "cftest", "test", "video", "webpics"]
150
158
  #
151
159
  # cf.containers(2,'cftest')
152
160
  # => ["test", "video"]
153
- def containers(limit=0,marker="")
154
- paramarr = []
155
- paramarr << ["limit=#{URI.encode(limit.to_s).gsub(/&/,'%26')}"] if limit.to_i > 0
156
- paramarr << ["marker=#{URI.encode(marker.to_s).gsub(/&/,'%26')}"] unless marker.to_s.empty?
157
- paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
158
- response = cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
161
+ def containers(limit = 0, marker = "")
162
+ query = []
163
+ query << "limit=#{CloudFiles.escape limit.to_s}" if limit.to_i > 0
164
+ query << "marker=#{CloudFiles.escape marker.to_s}" unless marker.to_s.empty?
165
+ response = cfreq("GET", @storagehost, "#{@storagepath}?#{query.join '&'}", @storageport, @storagescheme)
159
166
  return [] if (response.code == "204")
160
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
167
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
161
168
  CloudFiles.lines(response.body)
162
169
  end
163
170
  alias :list_containers :containers
@@ -168,18 +175,17 @@ module CloudFiles
168
175
  #
169
176
  # If you supply the optional limit and marker parameters, the call will return the number of containers
170
177
  # specified in limit, starting after the object named in marker.
171
- #
172
- # cf.containers_detail
173
- # => { "container1" => { :bytes => "36543", :count => "146" },
178
+ #
179
+ # cf.containers_detail
180
+ # => { "container1" => { :bytes => "36543", :count => "146" },
174
181
  # "container2" => { :bytes => "105943", :count => "25" } }
175
- def containers_detail(limit=0,marker="")
176
- paramarr = []
177
- paramarr << ["limit=#{URI.encode(limit.to_s).gsub(/&/,'%26')}"] if limit.to_i > 0
178
- paramarr << ["marker=#{URI.encode(marker.to_s).gsub(/&/,'%26')}"] unless marker.to_s.empty?
179
- paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
180
- response = cfreq("GET",@storagehost,"#{@storagepath}?format=xml&#{paramstr}",@storageport,@storagescheme)
182
+ def containers_detail(limit = 0, marker = "")
183
+ query = ['format=xml']
184
+ query << "limit=#{CloudFiles.escape limit.to_s}" if limit.to_i > 0
185
+ query << "marker=#{CloudFiles.escape marker.to_s}" unless marker.to_s.empty?
186
+ response = cfreq("GET", @storagehost, "#{@storagepath}?#{query.join '&'}", @storageport, @storagescheme)
181
187
  return {} if (response.code == "204")
182
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
188
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
183
189
  doc = REXML::Document.new(response.body)
184
190
  detailhash = {}
185
191
  doc.elements.each("account/container/") { |c|
@@ -191,21 +197,21 @@ module CloudFiles
191
197
  alias :list_containers_info :containers_detail
192
198
 
193
199
  # Returns true if the requested container exists and returns false otherwise.
194
- #
200
+ #
195
201
  # cf.container_exists?('good_container')
196
202
  # => true
197
- #
203
+ #
198
204
  # cf.container_exists?('bad_container')
199
205
  # => false
200
206
  def container_exists?(containername)
201
- response = cfreq("HEAD",@storagehost,"#{@storagepath}/#{URI.encode(containername).gsub(/&/,'%26')}",@storageport,@storagescheme)
207
+ response = cfreq("HEAD", @storagehost, "#{@storagepath}/#{CloudFiles.escape containername}", @storageport, @storagescheme)
202
208
  return (response.code == "204")? true : false ;
203
209
  end
204
210
 
205
- # Creates a new container and returns the CloudFiles::Container object. Throws an InvalidResponseException if the
211
+ # Creates a new container and returns the CloudFiles::Container object. Throws an InvalidResponseException if the
206
212
  # request fails.
207
213
  #
208
- # Slash (/) and question mark (?) are invalid characters, and will be stripped out. The container name is limited to
214
+ # Slash (/) and question mark (?) are invalid characters, and will be stripped out. The container name is limited to
209
215
  # 256 characters or less.
210
216
  #
211
217
  # container = cf.create_container('new_container')
@@ -215,16 +221,16 @@ module CloudFiles
215
221
  # container = cf.create_container('bad/name')
216
222
  # => SyntaxException: Container name cannot contain the characters '/' or '?'
217
223
  def create_container(containername)
218
- raise SyntaxException, "Container name cannot contain the characters '/' or '?'" if containername.match(/[\/\?]/)
219
- raise SyntaxException, "Container name is limited to 256 characters" if containername.length > 256
220
- response = cfreq("PUT",@storagehost,"#{@storagepath}/#{URI.encode(containername).gsub(/&/,'%26')}",@storageport,@storagescheme)
221
- raise InvalidResponseException, "Unable to create container #{containername}" unless (response.code == "201" || response.code == "202")
222
- CloudFiles::Container.new(self,containername)
224
+ raise CloudFiles::Exception::Syntax, "Container name cannot contain the characters '/' or '?'" if containername.match(/[\/\?]/)
225
+ raise CloudFiles::Exception::Syntax, "Container name is limited to 256 characters" if containername.length > 256
226
+ response = cfreq("PUT", @storagehost, "#{@storagepath}/#{CloudFiles.escape containername}", @storageport, @storagescheme)
227
+ raise CloudFiles::Exception::InvalidResponse, "Unable to create container #{containername}" unless (response.code == "201" || response.code == "202")
228
+ CloudFiles::Container.new(self, containername)
223
229
  end
224
230
 
225
231
  # Deletes a container from the account. Throws a NonEmptyContainerException if the container still contains
226
232
  # objects. Throws a NoSuchContainerException if the container doesn't exist.
227
- #
233
+ #
228
234
  # cf.delete_container('new_container')
229
235
  # => true
230
236
  #
@@ -234,9 +240,9 @@ module CloudFiles
234
240
  # cf.delete_container('nonexistent')
235
241
  # => NoSuchContainerException: Container nonexistent does not exist
236
242
  def delete_container(containername)
237
- response = cfreq("DELETE",@storagehost,"#{@storagepath}/#{URI.encode(containername).gsub(/&/,'%26')}",@storageport,@storagescheme)
238
- raise NonEmptyContainerException, "Container #{containername} is not empty" if (response.code == "409")
239
- raise NoSuchContainerException, "Container #{containername} does not exist" unless (response.code == "204")
243
+ response = cfreq("DELETE", @storagehost, "#{@storagepath}/#{CloudFiles.escape containername}", @storageport, @storagescheme)
244
+ raise CloudFiles::Exception::NonEmptyContainer, "Container #{containername} is not empty" if (response.code == "409")
245
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{containername} does not exist" unless (response.code == "204")
240
246
  true
241
247
  end
242
248
 
@@ -244,26 +250,26 @@ module CloudFiles
244
250
  # as an array. If no containers are public, an empty array is returned. Throws a InvalidResponseException if
245
251
  # the request fails.
246
252
  #
247
- # If you pass the optional argument as true, it will only show containers that are CURRENTLY being shared on the CDN,
253
+ # If you pass the optional argument as true, it will only show containers that are CURRENTLY being shared on the CDN,
248
254
  # as opposed to the default behavior which is to show all containers that have EVER been public.
249
255
  #
250
256
  # cf.public_containers
251
257
  # => ["video", "webpics"]
252
258
  def public_containers(enabled_only = false)
253
259
  paramstr = enabled_only == true ? "enabled_only=true" : ""
254
- response = cfreq("GET",@cdnmgmthost,"#{@cdnmgmtpath}?#{paramstr}",@cdnmgmtport,@cdnmgmtscheme)
260
+ response = cfreq("GET", @cdnmgmthost, "#{@cdnmgmtpath}?#{paramstr}", @cdnmgmtport, @cdnmgmtscheme)
255
261
  return [] if (response.code == "204")
256
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
262
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
257
263
  CloudFiles.lines(response.body)
258
264
  end
259
265
 
260
266
  # This method actually makes the HTTP calls out to the server
261
- def cfreq(method,server,path,port,scheme,headers = {},data = nil,attempts = 0,&block) # :nodoc:
267
+ def cfreq(method, server, path, port, scheme, headers = {}, data = nil, attempts = 0, &block) # :nodoc:
262
268
  start = Time.now
263
269
  headers['Transfer-Encoding'] = "chunked" if data.is_a?(IO)
264
270
  hdrhash = headerprep(headers)
265
- start_http(server,path,port,scheme,hdrhash)
266
- request = Net::HTTP.const_get(method.to_s.capitalize).new(path,hdrhash)
271
+ start_http(server, path, port, scheme, hdrhash)
272
+ request = Net::HTTP.const_get(method.to_s.capitalize).new(path, hdrhash)
267
273
  if data
268
274
  if data.respond_to?(:read)
269
275
  request.body_stream = data
@@ -276,24 +282,24 @@ module CloudFiles
276
282
  else
277
283
  request.content_length = 0
278
284
  end
279
- response = @http[server].request(request,&block)
280
- raise ExpiredAuthTokenException if response.code == "401"
285
+ response = @http[server].request(request, &block)
286
+ raise CloudFiles::Exception::ExpiredAuthToken if response.code == "401"
281
287
  response
282
288
  rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
283
289
  # Server closed the connection, retry
284
- raise ConnectionException, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
290
+ raise CloudFiles::Exception::Connection, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
285
291
  attempts += 1
286
292
  @http[server].finish
287
- start_http(server,path,port,scheme,headers)
293
+ start_http(server, path, port, scheme, headers)
288
294
  retry
289
295
  rescue ExpiredAuthTokenException
290
- raise ConnectionException, "Authentication token expired and you have requested not to retry" if @retry_auth == false
296
+ raise CloudFiles::Exception::Connection, "Authentication token expired and you have requested not to retry" if @retry_auth == false
291
297
  CloudFiles::Authentication.new(self)
292
298
  retry
293
299
  end
294
-
300
+
295
301
  private
296
-
302
+
297
303
  # Sets up standard HTTP headers
298
304
  def headerprep(headers = {}) # :nodoc:
299
305
  default_headers = {}
@@ -303,19 +309,19 @@ module CloudFiles
303
309
  default_headers["User-Agent"] = "CloudFiles Ruby API #{VERSION}"
304
310
  default_headers.merge(headers)
305
311
  end
306
-
312
+
307
313
  # Starts (or restarts) the HTTP connection
308
- def start_http(server,path,port,scheme,headers) # :nodoc:
314
+ def start_http(server, path, port, scheme, headers) # :nodoc:
309
315
  if (@http[server].nil?)
310
316
  begin
311
- @http[server] = Net::HTTP::Proxy(self.proxy_host, self.proxy_port).new(server,port)
317
+ @http[server] = Net::HTTP::Proxy(self.proxy_host, self.proxy_port).new(server, port)
312
318
  if scheme == "https"
313
319
  @http[server].use_ssl = true
314
320
  @http[server].verify_mode = OpenSSL::SSL::VERIFY_NONE
315
321
  end
316
322
  @http[server].start
317
323
  rescue
318
- raise ConnectionException, "Unable to connect to #{server}"
324
+ raise CloudFiles::Exception::Connection, "Unable to connect to #{server}"
319
325
  end
320
326
  end
321
327
  end