rackspace-cloudfiles 1.3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +16 -0
- data/README.rdoc +60 -0
- data/Rakefile +37 -0
- data/TODO +0 -0
- data/cloudfiles.gemspec +38 -0
- data/lib/cloudfiles.rb +69 -0
- data/lib/cloudfiles/authentication.rb +38 -0
- data/lib/cloudfiles/connection.rb +271 -0
- data/lib/cloudfiles/container.rb +252 -0
- data/lib/cloudfiles/storage_object.rb +246 -0
- data/test/cf-testunit.rb +157 -0
- data/test/cloudfiles_authentication_test.rb +37 -0
- data/test/cloudfiles_connection_test.rb +279 -0
- data/test/cloudfiles_container_test.rb +190 -0
- data/test/cloudfiles_storage_object_test.rb +170 -0
- data/test/test_helper.rb +5 -0
- metadata +102 -0
data/Manifest
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
cloudfiles.gemspec
|
2
|
+
lib/cloudfiles/authentication.rb
|
3
|
+
lib/cloudfiles/connection.rb
|
4
|
+
lib/cloudfiles/container.rb
|
5
|
+
lib/cloudfiles/storage_object.rb
|
6
|
+
lib/cloudfiles.rb
|
7
|
+
Manifest
|
8
|
+
Rakefile
|
9
|
+
README.rdoc
|
10
|
+
test/cf-testunit.rb
|
11
|
+
test/cloudfiles_authentication_test.rb
|
12
|
+
test/cloudfiles_connection_test.rb
|
13
|
+
test/cloudfiles_container_test.rb
|
14
|
+
test/cloudfiles_storage_object_test.rb
|
15
|
+
test/test_helper.rb
|
16
|
+
TODO
|
data/README.rdoc
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
= Mosso Cloud Files
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
This is a Ruby interface into the Rackspace[http://rackspace.com/] {Mosso Cloud Files}[http://www.mosso.com/cloudfiles.jsp] service. Cloud Files is reliable, scalable and affordable web-based storage hosting for backing up and archiving all your static content. Cloud Files is the first and only cloud service that leverages a tier one CDN provider to create such an easy and complete storage-to-delivery solution for media content.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
This gem is available on Github[http://github.com/rackspace/ruby-cloudfiles/]. To install it, do
|
10
|
+
|
11
|
+
gem sources -a http://gems.github.com
|
12
|
+
|
13
|
+
sudo gem install rackspace-cloudfiles
|
14
|
+
|
15
|
+
To use it in a Rails application, add the following information to your config/environment.rb
|
16
|
+
|
17
|
+
config.gem "rackspace-cloudfiles", :source => "http://gems.github.com", :lib => "cloudfiles"
|
18
|
+
|
19
|
+
|
20
|
+
== Examples
|
21
|
+
|
22
|
+
See the class definitions for documentation on specific methods and operations.
|
23
|
+
|
24
|
+
require 'cloudfiles'
|
25
|
+
|
26
|
+
# Log into the Cloud Files system
|
27
|
+
cf = CloudFiles::Connection.new(USERNAME, API_KEY)
|
28
|
+
|
29
|
+
# Get a listing of all containers under this account
|
30
|
+
cf.containers
|
31
|
+
=> ["backup", "Books", "cftest", "test", "video", "webpics"]
|
32
|
+
|
33
|
+
# Access a specific container
|
34
|
+
container = cf.container('test')
|
35
|
+
|
36
|
+
# See how many objects are under this container
|
37
|
+
container.count
|
38
|
+
=> 3
|
39
|
+
|
40
|
+
# List the objects
|
41
|
+
container.objects
|
42
|
+
=> ["bigfile.txt", "new.txt", "test.txt"]
|
43
|
+
|
44
|
+
# Select an object
|
45
|
+
object = container.object('test.txt')
|
46
|
+
|
47
|
+
# Get that object's data
|
48
|
+
object.data
|
49
|
+
=> "This is test data"
|
50
|
+
|
51
|
+
== Authors
|
52
|
+
|
53
|
+
Initial work by Major Hayden <major.hayden@rackspace.com>
|
54
|
+
|
55
|
+
Subsequent work by H. Wade Minter <wade.minter@rackspace.com>
|
56
|
+
|
57
|
+
== License
|
58
|
+
|
59
|
+
See COPYING for license information.
|
60
|
+
Copyright (c) 2009, Rackspace US, Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'echoe', '~> 3.0.1'
|
3
|
+
require 'echoe'
|
4
|
+
require './lib/cloudfiles.rb'
|
5
|
+
|
6
|
+
echoe = Echoe.new('cloudfiles') do |p|
|
7
|
+
p.author = ["H. Wade Minter", "Rackspace Hosting"]
|
8
|
+
p.email = 'wade.minter@rackspace.com'
|
9
|
+
p.version = CloudFiles::VERSION
|
10
|
+
p.summary = "A Ruby API into Mosso Cloud Files"
|
11
|
+
p.description = 'A Ruby version of the Mosso Cloud Files API.'
|
12
|
+
p.url = "http://www.mosso.com/cloudfiles.jsp"
|
13
|
+
p.runtime_dependencies = ["mime-types >=1.0"]
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate the .gemspec file in the root directory'
|
17
|
+
task :gemspec do
|
18
|
+
File.open("#{echoe.name}.gemspec", "w") {|f| f << echoe.spec.to_ruby }
|
19
|
+
end
|
20
|
+
task :package => :gemspec
|
21
|
+
|
22
|
+
namespace :test do
|
23
|
+
desc 'Check test coverage'
|
24
|
+
task :coverage do
|
25
|
+
rm_f "coverage"
|
26
|
+
system("rcov -x '/Library/Ruby/Gems/1.8/gems/' --sort coverage #{File.join(File.dirname(__FILE__), 'test/*_test.rb')}")
|
27
|
+
system("open #{File.join(File.dirname(__FILE__), 'coverage/index.html')}") if PLATFORM['darwin']
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Remove coverage products'
|
31
|
+
task :clobber_coverage do
|
32
|
+
rm_r 'coverage' rescue nil
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
data/TODO
ADDED
File without changes
|
data/cloudfiles.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{cloudfiles}
|
5
|
+
s.version = "1.3.0.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["H. Wade Minter, Rackspace Hosting"]
|
9
|
+
s.date = %q{2009-05-14}
|
10
|
+
s.description = %q{A Ruby version of the Mosso Cloud Files API.}
|
11
|
+
s.email = %q{wade.minter@rackspace.com}
|
12
|
+
s.extra_rdoc_files = ["lib/cloudfiles/authentication.rb", "lib/cloudfiles/connection.rb", "lib/cloudfiles/container.rb", "lib/cloudfiles/storage_object.rb", "lib/cloudfiles.rb", "README.rdoc", "TODO"]
|
13
|
+
s.files = ["cloudfiles.gemspec", "lib/cloudfiles/authentication.rb", "lib/cloudfiles/connection.rb", "lib/cloudfiles/container.rb", "lib/cloudfiles/storage_object.rb", "lib/cloudfiles.rb", "Manifest", "Rakefile", "README.rdoc", "test/cf-testunit.rb", "test/cloudfiles_authentication_test.rb", "test/cloudfiles_connection_test.rb", "test/cloudfiles_container_test.rb", "test/cloudfiles_storage_object_test.rb", "test/test_helper.rb", "TODO"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://www.mosso.com/cloudfiles.jsp}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Cloudfiles", "--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{cloudfiles}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{A Ruby API into Mosso Cloud Files}
|
21
|
+
s.test_files = ["test/cloudfiles_authentication_test.rb", "test/cloudfiles_connection_test.rb", "test/cloudfiles_container_test.rb", "test/cloudfiles_storage_object_test.rb", "test/test_helper.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<mime-types>, [">= 1.0"])
|
29
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<mime-types>, [">= 1.0"])
|
32
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<mime-types>, [">= 1.0"])
|
36
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
37
|
+
end
|
38
|
+
end
|
data/lib/cloudfiles.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# == Cloud Files API
|
4
|
+
# ==== Connects Ruby Applications to Rackspace's {Mosso Cloud Files service}[http://www.mosso.com/cloudfiles.jsp]
|
5
|
+
# Initial work by Major Hayden <major.hayden@rackspace.com>
|
6
|
+
#
|
7
|
+
# Subsequent work by H. Wade Minter <wade.minter@rackspace.com>
|
8
|
+
#
|
9
|
+
# See COPYING for license information.
|
10
|
+
# Copyright (c) 2009, 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('user_name', 'api_key') method.
|
19
|
+
module CloudFiles
|
20
|
+
|
21
|
+
VERSION = '1.3.0.2'
|
22
|
+
require 'net/http'
|
23
|
+
require 'net/https'
|
24
|
+
require 'rexml/document'
|
25
|
+
require 'uri'
|
26
|
+
require 'digest/md5'
|
27
|
+
require 'jcode'
|
28
|
+
require 'time'
|
29
|
+
require 'rubygems'
|
30
|
+
require 'mime/types'
|
31
|
+
|
32
|
+
$KCODE = 'u'
|
33
|
+
|
34
|
+
$:.unshift(File.dirname(__FILE__))
|
35
|
+
require 'cloudfiles/authentication'
|
36
|
+
require 'cloudfiles/connection'
|
37
|
+
require 'cloudfiles/container'
|
38
|
+
require 'cloudfiles/storage_object'
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
class SyntaxException < StandardError # :nodoc:
|
45
|
+
end
|
46
|
+
class ConnectionException < StandardError # :nodoc:
|
47
|
+
end
|
48
|
+
class AuthenticationException < StandardError # :nodoc:
|
49
|
+
end
|
50
|
+
class InvalidResponseException < StandardError # :nodoc:
|
51
|
+
end
|
52
|
+
class NonEmptyContainerException < StandardError # :nodoc:
|
53
|
+
end
|
54
|
+
class NoSuchObjectException < StandardError # :nodoc:
|
55
|
+
end
|
56
|
+
class NoSuchContainerException < StandardError # :nodoc:
|
57
|
+
end
|
58
|
+
class NoSuchAccountException < StandardError # :nodoc:
|
59
|
+
end
|
60
|
+
class MisMatchedChecksumException < StandardError # :nodoc:
|
61
|
+
end
|
62
|
+
class IOException < StandardError # :nodoc:
|
63
|
+
end
|
64
|
+
class CDNNotEnabledException < StandardError # :nodoc:
|
65
|
+
end
|
66
|
+
class ObjectExistsException < StandardError # :nodoc:
|
67
|
+
end
|
68
|
+
class ExpiredAuthTokenException < StandardError # :nodoc:
|
69
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module CloudFiles
|
2
|
+
class Authentication
|
3
|
+
# See COPYING for license information.
|
4
|
+
# Copyright (c) 2009, 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 AuthenticationException.
|
10
|
+
#
|
11
|
+
# Should probably never be called directly.
|
12
|
+
def initialize(connection)
|
13
|
+
path = '/auth'
|
14
|
+
hdrhash = { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey }
|
15
|
+
begin
|
16
|
+
server = Net::HTTP.new('api.mosso.com',443)
|
17
|
+
server.use_ssl = true
|
18
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
19
|
+
server.start
|
20
|
+
rescue
|
21
|
+
raise ConnectionException, "Unable to connect to #{server}"
|
22
|
+
end
|
23
|
+
response = server.get(path,hdrhash)
|
24
|
+
if (response.code == "204")
|
25
|
+
connection.cdnmgmthost = URI.parse(response["x-cdn-management-url"]).host
|
26
|
+
connection.cdnmgmtpath = URI.parse(response["x-cdn-management-url"]).path
|
27
|
+
connection.storagehost = URI.parse(response["x-storage-url"]).host
|
28
|
+
connection.storagepath = URI.parse(response["x-storage-url"]).path
|
29
|
+
connection.authtoken = response["x-auth-token"]
|
30
|
+
connection.authok = true
|
31
|
+
else
|
32
|
+
connection.authtoken = false
|
33
|
+
raise AuthenticationException, "Authentication failed"
|
34
|
+
end
|
35
|
+
server.finish
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
module CloudFiles
|
2
|
+
class Connection
|
3
|
+
# See COPYING for license information.
|
4
|
+
# Copyright (c) 2009, 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
|
+
# Hostname of the CDN management server
|
16
|
+
attr_accessor :cdnmgmthost
|
17
|
+
|
18
|
+
# Path for managing containers on the CDN management server
|
19
|
+
attr_accessor :cdnmgmtpath
|
20
|
+
|
21
|
+
# Array of requests that have been made so far
|
22
|
+
attr_reader :reqlog
|
23
|
+
|
24
|
+
# Hostname of the storage server
|
25
|
+
attr_accessor :storagehost
|
26
|
+
|
27
|
+
# Path for managing containers/objects on the storage server
|
28
|
+
attr_accessor :storagepath
|
29
|
+
|
30
|
+
# Instance variable that is set when authorization succeeds
|
31
|
+
attr_accessor :authok
|
32
|
+
|
33
|
+
# The total size in bytes under this connection
|
34
|
+
attr_reader :bytes
|
35
|
+
|
36
|
+
# The total number of containers under this connection
|
37
|
+
attr_reader :count
|
38
|
+
|
39
|
+
# Creates a new CloudFiles::Connection object. Uses CloudFiles::Authentication to perform the login for the connection.
|
40
|
+
# The authuser is the Mosso username, the authkey is the Mosso API key.
|
41
|
+
#
|
42
|
+
# Setting the optional retry_auth variable to false will cause an exception to be thrown if your authorization token expires.
|
43
|
+
# Otherwise, it will attempt to reauthenticate.
|
44
|
+
#
|
45
|
+
# This will likely be the base class for most operations.
|
46
|
+
#
|
47
|
+
# cf = CloudFiles::Connection.new(MY_USERNAME, MY_API_KEY)
|
48
|
+
def initialize(authuser,authkey,retry_auth = true)
|
49
|
+
@authuser = authuser
|
50
|
+
@authkey = authkey
|
51
|
+
@retry_auth = retry_auth
|
52
|
+
@authok = false
|
53
|
+
@http = {}
|
54
|
+
@reqlog = []
|
55
|
+
CloudFiles::Authentication.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if the authentication was successful and returns false otherwise.
|
59
|
+
#
|
60
|
+
# cf.authok?
|
61
|
+
# => true
|
62
|
+
def authok?
|
63
|
+
@authok
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an CloudFiles::Container object that can be manipulated easily. Throws a NoSuchContainerException if
|
67
|
+
# the container doesn't exist.
|
68
|
+
#
|
69
|
+
# container = cf.container('test')
|
70
|
+
# container.count
|
71
|
+
# => 2
|
72
|
+
def container(name)
|
73
|
+
CloudFiles::Container.new(self,name)
|
74
|
+
end
|
75
|
+
alias :get_container :container
|
76
|
+
|
77
|
+
# Sets instance variables for the bytes of storage used for this account/connection, as well as the number of containers
|
78
|
+
# stored under the account. Returns a hash with :bytes and :count keys, and also sets the instance variables.
|
79
|
+
#
|
80
|
+
# cf.get_info
|
81
|
+
# => {:count=>8, :bytes=>42438527}
|
82
|
+
# cf.bytes
|
83
|
+
# => 42438527
|
84
|
+
def get_info
|
85
|
+
response = cfreq("HEAD",@storagehost,@storagepath)
|
86
|
+
raise InvalidResponseException, "Unable to obtain account size" unless (response.code == "204")
|
87
|
+
@bytes = response["x-account-bytes-used"].to_i
|
88
|
+
@count = response["x-account-container-count"].to_i
|
89
|
+
{:bytes => @bytes, :count => @count}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Gathers a list of the containers that exist for the account and returns the list of container names
|
93
|
+
# as an array. If no containers exist, an empty array is returned. Throws an InvalidResponseException
|
94
|
+
# if the request fails.
|
95
|
+
#
|
96
|
+
# If you supply the optional limit and marker parameters, the call will return the number of containers
|
97
|
+
# specified in limit, starting after the object named in marker.
|
98
|
+
#
|
99
|
+
# cf.containers
|
100
|
+
# => ["backup", "Books", "cftest", "test", "video", "webpics"]
|
101
|
+
#
|
102
|
+
# cf.containers(2,'cftest')
|
103
|
+
# => ["test", "video"]
|
104
|
+
def containers(limit=0,marker="")
|
105
|
+
paramarr = []
|
106
|
+
paramarr << ["limit=#{URI.encode(limit.to_s).gsub(/&/,'%26')}"] if limit.to_i > 0
|
107
|
+
paramarr << ["offset=#{URI.encode(marker.to_s).gsub(/&/,'%26')}"] unless marker.to_s.empty?
|
108
|
+
paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
|
109
|
+
response = cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}")
|
110
|
+
return [] if (response.code == "204")
|
111
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
|
112
|
+
response.body.to_a.map { |x| x.chomp }
|
113
|
+
end
|
114
|
+
alias :list_containers :containers
|
115
|
+
|
116
|
+
# Retrieves a list of containers on the account along with their sizes (in bytes) and counts of the objects
|
117
|
+
# held within them. If no containers exist, an empty hash is returned. Throws an InvalidResponseException
|
118
|
+
# if the request fails.
|
119
|
+
#
|
120
|
+
# If you supply the optional limit and marker parameters, the call will return the number of containers
|
121
|
+
# specified in limit, starting after the object named in marker.
|
122
|
+
#
|
123
|
+
# cf.containers_detail
|
124
|
+
# => { "container1" => { :bytes => "36543", :count => "146" },
|
125
|
+
# "container2" => { :bytes => "105943", :count => "25" } }
|
126
|
+
def containers_detail(limit=0,marker="")
|
127
|
+
paramarr = []
|
128
|
+
paramarr << ["limit=#{URI.encode(limit.to_s).gsub(/&/,'%26')}"] if limit.to_i > 0
|
129
|
+
paramarr << ["offset=#{URI.encode(marker.to_s).gsub(/&/,'%26')}"] unless marker.to_s.empty?
|
130
|
+
paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
|
131
|
+
response = cfreq("GET",@storagehost,"#{@storagepath}?format=xml&#{paramstr}")
|
132
|
+
return {} if (response.code == "204")
|
133
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
|
134
|
+
doc = REXML::Document.new(response.body)
|
135
|
+
detailhash = {}
|
136
|
+
doc.elements.each("account/container/") { |c|
|
137
|
+
detailhash[c.elements["name"].text] = { :bytes => c.elements["bytes"].text, :count => c.elements["count"].text }
|
138
|
+
}
|
139
|
+
doc = nil
|
140
|
+
return detailhash
|
141
|
+
end
|
142
|
+
alias :list_containers_info :containers_detail
|
143
|
+
|
144
|
+
# Returns true if the requested container exists and returns false otherwise.
|
145
|
+
#
|
146
|
+
# cf.container_exists?('good_container')
|
147
|
+
# => true
|
148
|
+
#
|
149
|
+
# cf.container_exists?('bad_container')
|
150
|
+
# => false
|
151
|
+
def container_exists?(containername)
|
152
|
+
response = cfreq("HEAD",@storagehost,"#{@storagepath}/#{containername}")
|
153
|
+
return (response.code == "204")? true : false ;
|
154
|
+
end
|
155
|
+
|
156
|
+
# Creates a new container and returns the CloudFiles::Container object. Throws an InvalidResponseException if the
|
157
|
+
# request fails.
|
158
|
+
#
|
159
|
+
# Slash (/) and question mark (?) are invalid characters, and will be stripped out. The container name is limited to
|
160
|
+
# 256 characters or less.
|
161
|
+
#
|
162
|
+
# container = cf.create_container('new_container')
|
163
|
+
# container.name
|
164
|
+
# => "new_container"
|
165
|
+
#
|
166
|
+
# container = cf.create_container('bad/name')
|
167
|
+
# => SyntaxException: Container name cannot contain the characters '/' or '?'
|
168
|
+
def create_container(containername)
|
169
|
+
raise SyntaxException, "Container name cannot contain the characters '/' or '?'" if containername.match(/[\/\?]/)
|
170
|
+
raise SyntaxException, "Container name is limited to 256 characters" if containername.length > 256
|
171
|
+
response = cfreq("PUT",@storagehost,"#{@storagepath}/#{containername}")
|
172
|
+
raise InvalidResponseException, "Unable to create container #{containername}" unless (response.code == "201" || response.code == "202")
|
173
|
+
CloudFiles::Container.new(self,containername)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Deletes a container from the account. Throws a NonEmptyContainerException if the container still contains
|
177
|
+
# objects. Throws a NoSuchContainerException if the container doesn't exist.
|
178
|
+
#
|
179
|
+
# cf.delete_container('new_container')
|
180
|
+
# => true
|
181
|
+
#
|
182
|
+
# cf.delete_container('video')
|
183
|
+
# => NonEmptyContainerException: Container video is not empty
|
184
|
+
#
|
185
|
+
# cf.delete_container('nonexistent')
|
186
|
+
# => NoSuchContainerException: Container nonexistent does not exist
|
187
|
+
def delete_container(containername)
|
188
|
+
response = cfreq("DELETE",@storagehost,"#{@storagepath}/#{containername}")
|
189
|
+
raise NonEmptyContainerException, "Container #{containername} is not empty" if (response.code == "409")
|
190
|
+
raise NoSuchContainerException, "Container #{containername} does not exist" unless (response.code == "204")
|
191
|
+
true
|
192
|
+
end
|
193
|
+
|
194
|
+
# Gathers a list of public (CDN-enabled) containers that exist for an account and returns the list of container names
|
195
|
+
# as an array. If no containers are public, an empty array is returned. Throws a InvalidResponseException if
|
196
|
+
# the request fails.
|
197
|
+
#
|
198
|
+
# If you pass the optional argument as true, it will only show containers that are CURRENTLY being shared on the CDN,
|
199
|
+
# as opposed to the default behavior which is to show all containers that have EVER been public.
|
200
|
+
#
|
201
|
+
# cf.public_containers
|
202
|
+
# => ["video", "webpics"]
|
203
|
+
def public_containers(enabled_only = false)
|
204
|
+
paramstr = enabled_only == true ? "enabled_only=true" : ""
|
205
|
+
response = cfreq("GET",@cdnmgmthost,"#{@cdnmgmtpath}?#{paramstr}")
|
206
|
+
return [] if (response.code == "204")
|
207
|
+
raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
|
208
|
+
response.body.to_a.map { |x| x.chomp }
|
209
|
+
end
|
210
|
+
|
211
|
+
# This method actually makes the HTTP calls out to the server
|
212
|
+
def cfreq(method,server,path,headers = {},data = nil,attempts = 0,&block) # :nodoc:
|
213
|
+
start = Time.now
|
214
|
+
hdrhash = headerprep(headers)
|
215
|
+
start_http(server,path,hdrhash)
|
216
|
+
request = Net::HTTP.const_get(method.to_s.capitalize).new(path,hdrhash)
|
217
|
+
if data
|
218
|
+
if data.respond_to?(:read)
|
219
|
+
request.body_stream = data
|
220
|
+
else
|
221
|
+
request.body = data
|
222
|
+
end
|
223
|
+
request.content_length = data.respond_to?(:lstat) ? data.stat.size : data.size
|
224
|
+
else
|
225
|
+
request.content_length = 0
|
226
|
+
end
|
227
|
+
response = @http[server].request(request,&block)
|
228
|
+
raise ExpiredAuthTokenException if response.code == "401"
|
229
|
+
response
|
230
|
+
rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
|
231
|
+
# Server closed the connection, retry
|
232
|
+
raise ConnectionException, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
|
233
|
+
attempts += 1
|
234
|
+
@http[server].finish
|
235
|
+
start_http(server,path,headers)
|
236
|
+
retry
|
237
|
+
rescue ExpiredAuthTokenException
|
238
|
+
raise ConnectionException, "Authentication token expired and you have requested not to retry" if @retry_auth == false
|
239
|
+
CloudFiles::Authentication.new(self)
|
240
|
+
retry
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
# Sets up standard HTTP headers
|
246
|
+
def headerprep(headers = {}) # :nodoc:
|
247
|
+
default_headers = {}
|
248
|
+
default_headers["X-Auth-Token"] = @authtoken if (authok? && @account.nil?)
|
249
|
+
default_headers["X-Storage-Token"] = @authtoken if (authok? && !@account.nil?)
|
250
|
+
default_headers["Connection"] = "Keep-Alive"
|
251
|
+
default_headers["User-Agent"] = "CloudFiles Ruby API #{VERSION}"
|
252
|
+
default_headers.merge(headers)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Starts (or restarts) the HTTP connection
|
256
|
+
def start_http(server,path,headers) # :nodoc:
|
257
|
+
if (@http[server].nil?)
|
258
|
+
begin
|
259
|
+
@http[server] = Net::HTTP.new(server,443)
|
260
|
+
@http[server].use_ssl = true
|
261
|
+
@http[server].verify_mode = OpenSSL::SSL::VERIFY_NONE
|
262
|
+
@http[server].start
|
263
|
+
rescue
|
264
|
+
raise ConnectionException, "Unable to connect to #{server}"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|