cloudfiles-sonian 1.5.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/CHANGELOG +56 -0
- data/CONTRIBUTORS +34 -0
- data/COPYING +12 -0
- data/Gemfile +7 -0
- data/README.rdoc +81 -0
- data/Rakefile +21 -0
- data/TODO +0 -0
- data/bin/cloudfiles +118 -0
- data/cloudfiles.gemspec +72 -0
- data/lib/client.rb +618 -0
- data/lib/cloudfiles.rb +85 -0
- data/lib/cloudfiles/authentication.rb +52 -0
- data/lib/cloudfiles/connection.rb +286 -0
- data/lib/cloudfiles/container.rb +454 -0
- data/lib/cloudfiles/exception.rb +65 -0
- data/lib/cloudfiles/storage_object.rb +426 -0
- data/lib/cloudfiles/version.rb +3 -0
- data/test/cf-testunit.rb +157 -0
- data/test/cloudfiles_authentication_test.rb +44 -0
- data/test/cloudfiles_client_test.rb +797 -0
- data/test/cloudfiles_connection_test.rb +214 -0
- data/test/cloudfiles_container_test.rb +494 -0
- data/test/cloudfiles_storage_object_test.rb +211 -0
- data/test/test_helper.rb +6 -0
- metadata +113 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
================================================================================
|
2
|
+
1.5.0.1 (2011/12/05
|
3
|
+
================================================================================
|
4
|
+
o Fixed small bug with encoding or URI's
|
5
|
+
|
6
|
+
================================================================================
|
7
|
+
1.5.0 (2011/10/31)
|
8
|
+
================================================================================
|
9
|
+
o The underlying http wrapper now uses client.rb a simple abstraction to manage
|
10
|
+
each ReST call in its own function
|
11
|
+
|
12
|
+
================================================================================
|
13
|
+
1.4.18 (2011/09/07)
|
14
|
+
================================================================================
|
15
|
+
o Added Streaming URL support
|
16
|
+
|
17
|
+
================================================================================
|
18
|
+
1.4.17 (2011/05/27)
|
19
|
+
================================================================================
|
20
|
+
o Added Manifest support for Large Objects
|
21
|
+
o Add support for container metadata
|
22
|
+
o Option to check the MD5 of a file uploaded via the load_from_filename method
|
23
|
+
|
24
|
+
================================================================================
|
25
|
+
1.4.16 (2011/03/17)
|
26
|
+
================================================================================
|
27
|
+
o Bugfix on CDN purging. (CvX)
|
28
|
+
o Better OpenStack Swift support with regards to handling the lack of
|
29
|
+
available CDN. (Topper Bowers)
|
30
|
+
|
31
|
+
================================================================================
|
32
|
+
1.4.15 (2011/03/09)
|
33
|
+
================================================================================
|
34
|
+
o Added CDN SSL URL stuff
|
35
|
+
|
36
|
+
================================================================================
|
37
|
+
1.4.14 (2011/02/24)
|
38
|
+
================================================================================
|
39
|
+
o Added CDN Purge functionality for containers and objects.
|
40
|
+
|
41
|
+
================================================================================
|
42
|
+
1.4.13 (2011/02/22)
|
43
|
+
================================================================================
|
44
|
+
o Catch an IOError (HTTP session not started) in cfreq and attempt a retry.
|
45
|
+
|
46
|
+
================================================================================
|
47
|
+
1.4.12 (2011/02/04)
|
48
|
+
================================================================================
|
49
|
+
o Configurable :auth_url support for OpenStack Swift and non-US deployments. Add constants for Cloud Servers USA and UK. (Chmouel Boudjnah, Dan Prince)
|
50
|
+
o Moved exceptions under the CloudFiles::Exception scope
|
51
|
+
o Added support for configurable path delimiters (Corey Ward)
|
52
|
+
o Improvements in path escaping (Corey Ward)
|
53
|
+
o Support for the new COPY method on objects, via storage_object.copy and storage_object.move
|
54
|
+
o Reduced the number of API calls for loading metadata (Edmund Salvacion)
|
55
|
+
o Allow setting of content_type on more operations (suggestion by Bo Benson)
|
56
|
+
|
data/CONTRIBUTORS
ADDED
@@ -0,0 +1,34 @@
|
|
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
|
29
|
+
Nugroho Herucahyono
|
30
|
+
CvX
|
31
|
+
Topper Bowers
|
32
|
+
Xavier Shay
|
33
|
+
Dillon Amburgey
|
34
|
+
Scott Simpson
|
data/COPYING
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Unless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them.
|
2
|
+
|
3
|
+
Copyright (C) 2011 Rackspace US, Inc.
|
4
|
+
|
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
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
10
|
+
|
11
|
+
Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
|
12
|
+
|
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
= Rackspace Cloud Files
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
This is a Ruby interface into the Rackspace[http://rackspace.com/] {Cloud Files}[http://www.rackspacecloud.com/cloud_hosting_products/files] 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
|
+
== Upgrade Gotchas
|
8
|
+
|
9
|
+
As of gem version 1.4.8, the connection method has changed from positional arguments to a hash of options. This is the new style:
|
10
|
+
|
11
|
+
cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY")
|
12
|
+
|
13
|
+
This is the old style, which still works but is deprecated:
|
14
|
+
|
15
|
+
cf = CloudFiles::Connection.new("MY_USERNAME","MY_API_KEY")
|
16
|
+
|
17
|
+
== Installation
|
18
|
+
|
19
|
+
This source is available on Github[http://github.com/rackspace/ruby-cloudfiles/] and the gem is available on Gemcutter[http://gemcutter.org/]. To install it, do
|
20
|
+
|
21
|
+
gem sources -a http://gemcutter.org/
|
22
|
+
|
23
|
+
sudo gem install cloudfiles
|
24
|
+
|
25
|
+
To use it in a Rails application, add the following information to your config/environment.rb
|
26
|
+
|
27
|
+
config.gem "cloudfiles"
|
28
|
+
|
29
|
+
|
30
|
+
== Examples
|
31
|
+
|
32
|
+
See the class definitions for documentation on specific methods and operations.
|
33
|
+
|
34
|
+
require 'rubygems'
|
35
|
+
require 'cloudfiles'
|
36
|
+
|
37
|
+
# Log into the Cloud Files system
|
38
|
+
cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY")
|
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
|
+
|
43
|
+
# Or, if you want to access CloudFiles from within the Rackspace network
|
44
|
+
cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :snet => true)
|
45
|
+
|
46
|
+
# Get a listing of all containers under this account
|
47
|
+
cf.containers
|
48
|
+
=> ["backup", "Books", "cftest", "test", "video", "webpics"]
|
49
|
+
|
50
|
+
# Access a specific container
|
51
|
+
container = cf.container('test')
|
52
|
+
|
53
|
+
# See how many objects are under this container
|
54
|
+
container.count
|
55
|
+
=> 3
|
56
|
+
|
57
|
+
# Upload a file
|
58
|
+
object = container.create_object 'filename.txt', false
|
59
|
+
object.write file
|
60
|
+
|
61
|
+
# List the objects
|
62
|
+
container.objects
|
63
|
+
=> ["bigfile.txt", "new.txt", "test.txt"]
|
64
|
+
|
65
|
+
# Select an object
|
66
|
+
object = container.object('test.txt')
|
67
|
+
|
68
|
+
# Get that object's data
|
69
|
+
object.data
|
70
|
+
=> "This is test data"
|
71
|
+
|
72
|
+
== Authors
|
73
|
+
|
74
|
+
Initial work by Major Hayden <major.hayden@rackspace.com>
|
75
|
+
|
76
|
+
Subsequent work by H. Wade Minter <minter@lunenburg.org> and Dan Prince <dan.prince@rackspace.com>
|
77
|
+
|
78
|
+
== License
|
79
|
+
|
80
|
+
See COPYING for license information.
|
81
|
+
Copyright (c) 2011, Rackspace US, Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
namespace :test do
|
2
|
+
desc 'Check test coverage'
|
3
|
+
task :coverage do
|
4
|
+
rm_f "coverage"
|
5
|
+
system("rcov -x '/Library/Ruby/Gems/1.8/gems/' --sort coverage #{File.join(File.dirname(__FILE__), 'test/*_test.rb')}")
|
6
|
+
system("open #{File.join(File.dirname(__FILE__), 'coverage/index.html')}") if PLATFORM['darwin']
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Remove coverage products'
|
10
|
+
task :clobber_coverage do
|
11
|
+
rm_r 'coverage' rescue nil
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'rake/testtask'
|
17
|
+
Rake::TestTask.new(:test) do |test|
|
18
|
+
test.libs << 'lib' << 'test'
|
19
|
+
test.pattern = 'test/**/*_test.rb'
|
20
|
+
test.verbose = true
|
21
|
+
end
|
data/TODO
ADDED
File without changes
|
data/bin/cloudfiles
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'cloudfiles'
|
5
|
+
require 'optparse'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
$opts = {}
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.on('-S', '--split BYTES', 'Split into BYTES-sized segments') do |bytes|
|
11
|
+
$opts[:split] = bytes.to_i
|
12
|
+
end
|
13
|
+
end.parse!(ARGV)
|
14
|
+
|
15
|
+
COMMANDS = %w{ls get put rm mkdir rmdir}
|
16
|
+
|
17
|
+
class CloudFilesCmd
|
18
|
+
def initialize(config)
|
19
|
+
@cf = CloudFiles::Connection.new(config)
|
20
|
+
end
|
21
|
+
def ls(*container_names)
|
22
|
+
if container_names.empty?
|
23
|
+
@cf.containers_detail.each do |container_name, container_detail|
|
24
|
+
puts [container_detail[:count], container_detail[:bytes], container_name].join("\t")
|
25
|
+
end
|
26
|
+
else
|
27
|
+
container_names.each do |container_name|
|
28
|
+
if container_name.include?('/')
|
29
|
+
container_name, obj_name = container_name.split('/', 2)
|
30
|
+
obj = @cf.get_container(container_name).object(obj_name)
|
31
|
+
puts [container_name, obj.bytes, obj.last_modified.to_s, obj.etag, obj.etag, obj_name].join("\t")
|
32
|
+
else
|
33
|
+
@cf.get_container(container_name).objects_detail(:full_listing => true).each do |obj_name, obj_detail|
|
34
|
+
puts [container_name, obj_detail[:bytes], obj_detail[:last_modified].to_s, obj_detail[:hash], obj_name].join("\t")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
def get(container_name, obj_name, obj_file=nil)
|
41
|
+
obj_file ||= obj_name
|
42
|
+
data = @cf.container(container_name).object(obj_name).data
|
43
|
+
if obj_file == '-'
|
44
|
+
STDOUT.write(data)
|
45
|
+
else
|
46
|
+
File.open(obj_file, 'w') do |f|
|
47
|
+
f.write(data)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def put(container_name, obj_name, obj_file=nil)
|
52
|
+
obj_file ||= obj_name
|
53
|
+
if obj_file == '-'
|
54
|
+
input = STDIN
|
55
|
+
else
|
56
|
+
input = File.open(obj_file)
|
57
|
+
end
|
58
|
+
if $opts[:split]
|
59
|
+
object = @cf.container(container_name).create_object(obj_name)
|
60
|
+
object.write('')
|
61
|
+
n = 0
|
62
|
+
until input.eof?
|
63
|
+
chunk_obj = @cf.container(container_name).create_object("#{obj_name}/#{n}", true)
|
64
|
+
chunk_obj.write(input.read($opts[:split]))
|
65
|
+
n += 1
|
66
|
+
end
|
67
|
+
object.set_manifest("#{container_name}/#{obj_name}/")
|
68
|
+
else
|
69
|
+
object = @cf.container(container_name).create_object(obj_name)
|
70
|
+
object.write(input.read)
|
71
|
+
end
|
72
|
+
input.close unless input == STDIN
|
73
|
+
end
|
74
|
+
def rm(container_name, obj_name)
|
75
|
+
@cf.container(container_name).delete_object(obj_name)
|
76
|
+
end
|
77
|
+
def mkdir(container_name)
|
78
|
+
@cf.create_container(container_name)
|
79
|
+
end
|
80
|
+
def rmdir(container_name)
|
81
|
+
begin
|
82
|
+
@cf.delete_container(container_name)
|
83
|
+
rescue CloudFiles::Exception::NonEmptyContainer => e
|
84
|
+
STDERR.puts e.message
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
config_file = "#{ENV['HOME']}/.cloudfiles.yml"
|
91
|
+
config = begin
|
92
|
+
YAML.load_file(config_file)
|
93
|
+
rescue Errno::ENOENT => e
|
94
|
+
no_config = true
|
95
|
+
{ :username => ENV['CLOUDFILES_USERNAME'],
|
96
|
+
:api_key => ENV['CLOUDFILES_API_KEY'],
|
97
|
+
:auth_url => ENV['CLOUDFILES_AUTH_URL'] }
|
98
|
+
end
|
99
|
+
|
100
|
+
if [:username, :api_key].any? {|k| config[k].nil? }
|
101
|
+
STDERR.puts "Could not load #{config_file}" if no_config
|
102
|
+
STDERR.puts "Need username and API key"
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
cf = CloudFilesCmd.new(config)
|
107
|
+
cmd = ARGV.shift
|
108
|
+
|
109
|
+
if COMMANDS.include?(cmd)
|
110
|
+
begin
|
111
|
+
cf.send(cmd.to_sym, *ARGV)
|
112
|
+
rescue ArgumentError => e
|
113
|
+
STDERR.puts e.message
|
114
|
+
end
|
115
|
+
else
|
116
|
+
STDERR.puts "Usage: cloudfiles [#{COMMANDS.join('|')}]"
|
117
|
+
exit 2
|
118
|
+
end
|
data/cloudfiles.gemspec
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path('../lib/cloudfiles/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{cloudfiles-sonian}
|
5
|
+
s.version = CloudFiles::VERSION
|
6
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
7
|
+
s.authors = ["H. Wade Minter", "Rackspace Hosting"]
|
8
|
+
s.description = %q{A Ruby version of the Rackspace Cloud Files API.}
|
9
|
+
s.email = %q{minter@lunenburg.org}
|
10
|
+
s.extra_rdoc_files = [
|
11
|
+
"README.rdoc",
|
12
|
+
"TODO"
|
13
|
+
]
|
14
|
+
s.files = [
|
15
|
+
".gitignore",
|
16
|
+
"CHANGELOG",
|
17
|
+
"CONTRIBUTORS",
|
18
|
+
"COPYING",
|
19
|
+
"Gemfile",
|
20
|
+
"README.rdoc",
|
21
|
+
"Rakefile",
|
22
|
+
"TODO",
|
23
|
+
"cloudfiles.gemspec",
|
24
|
+
"lib/cloudfiles.rb",
|
25
|
+
"lib/client.rb",
|
26
|
+
"lib/cloudfiles/authentication.rb",
|
27
|
+
"lib/cloudfiles/connection.rb",
|
28
|
+
"lib/cloudfiles/container.rb",
|
29
|
+
"lib/cloudfiles/exception.rb",
|
30
|
+
"lib/cloudfiles/storage_object.rb",
|
31
|
+
"lib/cloudfiles/version.rb",
|
32
|
+
"test/cf-testunit.rb",
|
33
|
+
"test/cloudfiles_authentication_test.rb",
|
34
|
+
"test/cloudfiles_connection_test.rb",
|
35
|
+
"test/cloudfiles_container_test.rb",
|
36
|
+
"test/cloudfiles_storage_object_test.rb",
|
37
|
+
"test/cloudfiles_client_test.rb",
|
38
|
+
"test/test_helper.rb"
|
39
|
+
]
|
40
|
+
s.executables = [
|
41
|
+
"cloudfiles"
|
42
|
+
]
|
43
|
+
s.homepage = %q{http://www.rackspacecloud.com/cloud_hosting_products/files}
|
44
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
45
|
+
s.require_paths = ["lib"]
|
46
|
+
s.rubygems_version = %q{1.5.0.1}
|
47
|
+
s.summary = %q{A Ruby API into Rackspace Cloud Files}
|
48
|
+
s.test_files = [
|
49
|
+
"test/cf-testunit.rb",
|
50
|
+
"test/cloudfiles_authentication_test.rb",
|
51
|
+
"test/cloudfiles_connection_test.rb",
|
52
|
+
"test/cloudfiles_container_test.rb",
|
53
|
+
"test/cloudfiles_storage_object_test.rb",
|
54
|
+
"test/cloudfiles_client_test.rb",
|
55
|
+
"test/test_helper.rb"
|
56
|
+
]
|
57
|
+
|
58
|
+
s.add_dependency('json')
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
s.specification_version = 3
|
62
|
+
|
63
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
64
|
+
s.add_development_dependency(%q<mocha>, ["~> 0.9.8"])
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<mocha>, ["~> 0.9.8"])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<mocha>, ["~> 0.9.8"])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
data/lib/client.rb
ADDED
@@ -0,0 +1,618 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class ClientException < StandardError
|
6
|
+
attr_reader :scheme, :host, :port, :path, :query, :status, :reason, :devices
|
7
|
+
def initialize(msg, params={})
|
8
|
+
@msg = msg
|
9
|
+
@scheme = params[:http_scheme]
|
10
|
+
@host = params[:http_host]
|
11
|
+
@port = params[:http_port]
|
12
|
+
@path = params[:http_path]
|
13
|
+
@query = params[:http_query]
|
14
|
+
@status = params[:http_status]
|
15
|
+
@reason = params[:http_reason]
|
16
|
+
@device = params[:http_device]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
a = @msg
|
21
|
+
b = ''
|
22
|
+
b += "#{@scheme}://" if @scheme
|
23
|
+
b += @host if @host
|
24
|
+
b += ":#{@port}" if @port
|
25
|
+
b += @path if @path
|
26
|
+
b += "?#{@query}" if @query
|
27
|
+
b ? b = "#{b} #{@status}" : b = @status.to_s if @status
|
28
|
+
b ? b = "#{b} #{@reason}" : b = "- #{@reason}" if @reason
|
29
|
+
b ? b = "#{b}: device #{@device}" : b = "device #{@device}" if @device
|
30
|
+
b ? "#{a} #{b}" : a
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ChunkedConnectionWrapper
|
35
|
+
def initialize(data, chunk_size)
|
36
|
+
@size = chunk_size
|
37
|
+
@file = data
|
38
|
+
end
|
39
|
+
|
40
|
+
def read(foo)
|
41
|
+
@file.read(@size)
|
42
|
+
end
|
43
|
+
|
44
|
+
def eof!
|
45
|
+
@file.eof!
|
46
|
+
end
|
47
|
+
def eof?
|
48
|
+
@file.eof?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def quote(value)
|
53
|
+
URI.encode(value)
|
54
|
+
end
|
55
|
+
|
56
|
+
class Query
|
57
|
+
def initialize(url_params)
|
58
|
+
if url_params
|
59
|
+
@params = Query.from_url_params(url_params)
|
60
|
+
else
|
61
|
+
@params = {}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
def to_s
|
65
|
+
to_url_params
|
66
|
+
end
|
67
|
+
def to_url_params
|
68
|
+
elements = []
|
69
|
+
@params.each_pair {|k,v| elements << "#{k}=#{v}"}
|
70
|
+
elements.join('&')
|
71
|
+
end
|
72
|
+
def self.from_url_params(url_params)
|
73
|
+
result = {}
|
74
|
+
url_params.split('&').each do |element|
|
75
|
+
element = element.split('=')
|
76
|
+
result[element[0]] = element[1]
|
77
|
+
end
|
78
|
+
result
|
79
|
+
end
|
80
|
+
def has_key?(key)
|
81
|
+
@params.has_key? key
|
82
|
+
end
|
83
|
+
def add(key, value)
|
84
|
+
@params[key] = value
|
85
|
+
end
|
86
|
+
def delete(key)
|
87
|
+
@params.delete(key)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class SwiftClient
|
92
|
+
def initialize(authurl, user, key, retries=5, preauthurl=nil, preauthtoken=nil, snet=false, starting_backoff=1)
|
93
|
+
@authurl = authurl
|
94
|
+
@user = user
|
95
|
+
@key = key
|
96
|
+
@retries = retries
|
97
|
+
@http_conn = nil
|
98
|
+
@url = preauthurl
|
99
|
+
@token = preauthtoken
|
100
|
+
@attempts = 0
|
101
|
+
@snet = snet
|
102
|
+
@starting_backoff = starting_backoff
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def _retry(reset, func, args=nil)
|
107
|
+
@attempts = 0
|
108
|
+
backoff = @starting_backoff
|
109
|
+
|
110
|
+
while @attempts < @retries
|
111
|
+
@attempts += 1
|
112
|
+
begin
|
113
|
+
if !@url or !@token
|
114
|
+
@url, @token = self.get_auth()
|
115
|
+
@http_conn = nil
|
116
|
+
end
|
117
|
+
@http_conn = self.http_connection() if !@http_conn
|
118
|
+
return SwiftClient.method(func).call(@url, @token, *args)
|
119
|
+
rescue Net::HTTPExceptions
|
120
|
+
if @attempts > @retries
|
121
|
+
raise
|
122
|
+
end
|
123
|
+
@http_conn = nil
|
124
|
+
rescue ClientException => err
|
125
|
+
if @attempts > @retries
|
126
|
+
raise
|
127
|
+
end
|
128
|
+
if err.status.to_i == 401
|
129
|
+
@url = @token = nil
|
130
|
+
if @attempts > 1
|
131
|
+
raise
|
132
|
+
end
|
133
|
+
elsif err.status.to_i == 408
|
134
|
+
@http_conn = nil
|
135
|
+
elsif err.status.to_i >= 500 and err.status.to_i <= 599
|
136
|
+
return nil
|
137
|
+
else
|
138
|
+
raise
|
139
|
+
end
|
140
|
+
end
|
141
|
+
sleep(backoff)
|
142
|
+
backoff *= 2
|
143
|
+
if reset
|
144
|
+
reset.call(args)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
public
|
150
|
+
def self.http_connection(url, proxy_host=nil, proxy_port=nil)
|
151
|
+
parsed = URI::parse(url)
|
152
|
+
|
153
|
+
if parsed.scheme == 'http'
|
154
|
+
require 'net/http'
|
155
|
+
conn = Net::HTTP::Proxy(proxy_host, proxy_port).new(parsed.host, parsed.port)
|
156
|
+
[parsed, conn]
|
157
|
+
elsif parsed.scheme == 'https'
|
158
|
+
require 'net/https'
|
159
|
+
conn = Net::HTTP::Proxy(proxy_host, proxy_port).new(parsed.host, parsed.port)
|
160
|
+
conn.use_ssl = true
|
161
|
+
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
162
|
+
[parsed, conn]
|
163
|
+
else
|
164
|
+
raise ClientException.new(
|
165
|
+
"Cannot handle protocol scheme #{parsed.scheme} for #{url} %s")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def http_connection
|
170
|
+
if !@http_conn
|
171
|
+
@http_conn = SwiftClient.http_connection(@url)
|
172
|
+
else
|
173
|
+
@http_conn
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.get_auth(url, user, key, snet=false)
|
178
|
+
parsed, conn = http_connection(url)
|
179
|
+
conn.start if not conn.started?
|
180
|
+
resp = conn.get(URI.encode(parsed.request_uri), {"x-auth-user" => user, "x-auth-key" => key })
|
181
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
182
|
+
raise ClientException.new('Account GET failed', :http_scheme=>parsed.scheme,
|
183
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
184
|
+
:http_path=>parsed.path, :http_query=>parsed.query, :http_status=>resp.code,
|
185
|
+
:http_reason=>resp.message)
|
186
|
+
end
|
187
|
+
url = URI::parse(resp.header['x-storage-url'])
|
188
|
+
if snet
|
189
|
+
url.host = "snet-#{url.host}"
|
190
|
+
end
|
191
|
+
[url.to_s, resp.header['x-auth-token'], resp.header]
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_auth
|
195
|
+
@url, @token = SwiftClient.get_auth(@authurl, @user, @key, @snet)
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.get_account(url, token, marker=nil, limit=nil, prefix=nil,
|
199
|
+
full_listing=false, http_conn=nil)
|
200
|
+
#todo: add in rest of functionality
|
201
|
+
if not http_conn
|
202
|
+
http_conn = http_connection(url)
|
203
|
+
end
|
204
|
+
parsed = http_conn[0].clone
|
205
|
+
conn = http_conn[1]
|
206
|
+
if full_listing
|
207
|
+
rv = get_account(url, token, marker, limit, prefix, false, http_conn)
|
208
|
+
listing = rv[1]
|
209
|
+
while listing.length > 0
|
210
|
+
marker = listing[-1]['name']
|
211
|
+
listing = get_account(url, token, marker, limit, prefix, false, http_conn)[1]
|
212
|
+
if listing.length > 0
|
213
|
+
rv[1] += listing
|
214
|
+
end
|
215
|
+
end
|
216
|
+
return rv
|
217
|
+
end
|
218
|
+
query = Query.new(parsed.query)
|
219
|
+
query.add('format', 'json')
|
220
|
+
query.add('marker', quote(marker.to_s)) if marker
|
221
|
+
query.add('limit', quote(limit.to_s)) if limit
|
222
|
+
query.add('prefix', quote(prefix.to_s)) if prefix
|
223
|
+
parsed.query = query.to_url_params
|
224
|
+
conn.start if !conn.started?
|
225
|
+
resp = conn.get(parsed.request_uri, {'x-auth-token' => token})
|
226
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
227
|
+
raise ClientException.new('Account GET failed', :http_scheme=>parsed.scheme,
|
228
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
229
|
+
:http_path=>parsed.path, :http_query=>parsed.query, :http_status=>resp.code,
|
230
|
+
:http_reason=>resp.message)
|
231
|
+
end
|
232
|
+
resp_headers = {}
|
233
|
+
resp.header.each do |k,v|
|
234
|
+
resp_headers[k.downcase] = v
|
235
|
+
end
|
236
|
+
if resp.code.to_i == 204
|
237
|
+
[resp_headers, []]
|
238
|
+
else
|
239
|
+
[resp_headers, JSON.parse(resp.body)]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def get_account(marker=nil, limit=nil, prefix=nil, full_listing=false)
|
244
|
+
_retry(nil, :get_account, [marker, limit, prefix, @http_conn, full_listing])
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.head_account(url, token, http_conn=nil)
|
248
|
+
if not http_conn
|
249
|
+
http_conn = http_connection(url)
|
250
|
+
end
|
251
|
+
parsed = http_conn[0].clone
|
252
|
+
conn = http_conn[1]
|
253
|
+
conn.start if !conn.started?
|
254
|
+
resp = conn.head(parsed.request_uri, {'x-auth-token' => token})
|
255
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
256
|
+
raise ClientException.new('Account HEAD failed', :http_scheme=>parsed.scheme,
|
257
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
258
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
259
|
+
:http_reason=>resp.message)
|
260
|
+
end
|
261
|
+
resp_headers = {}
|
262
|
+
resp.header.each do |k,v|
|
263
|
+
resp_headers[k.downcase] = v
|
264
|
+
end
|
265
|
+
resp_headers
|
266
|
+
end
|
267
|
+
|
268
|
+
def head_account
|
269
|
+
_retry(nil, :head_account, [@http_conn])
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.post_account(url, token, headers, http_conn=nil)
|
273
|
+
if not http_conn
|
274
|
+
http_conn = http_connection(url)
|
275
|
+
end
|
276
|
+
parsed = http_conn[0].clone
|
277
|
+
conn = http_conn[1]
|
278
|
+
headers['x-auth-token'] = token
|
279
|
+
conn.start if !conn.started?
|
280
|
+
resp = conn.post(parsed.request_uri, nil, headers)
|
281
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
282
|
+
raise ClientException.new('Account POST failed', :http_scheme=>parsed.scheme,
|
283
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
284
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
285
|
+
:http_reason=>resp.message)
|
286
|
+
end
|
287
|
+
resp.body
|
288
|
+
end
|
289
|
+
def post_account(headers=nil)
|
290
|
+
_retry(nil, :post_account, [headers, @http_conn])
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.get_container(url, token, container, marker=nil, limit=nil,
|
294
|
+
prefix=nil, delimiter=nil, full_listing=false, http_conn=nil)
|
295
|
+
#todo: add in rest of functionality
|
296
|
+
if not http_conn
|
297
|
+
http_conn = http_connection(url)
|
298
|
+
end
|
299
|
+
parsed = http_conn[0].clone
|
300
|
+
conn = http_conn[1]
|
301
|
+
|
302
|
+
if full_listing
|
303
|
+
rv = get_container(url, token, container, marker, limit, prefix, delimiter, false, http_conn)
|
304
|
+
listing = rv[1]
|
305
|
+
while listing.length > 0
|
306
|
+
marker = listing[-1]['name']
|
307
|
+
listing = get_container(url, token, container, marker, limit, prefix, delimiter, false, http_conn)[1]
|
308
|
+
if listing.length > 0
|
309
|
+
rv[1] += listing
|
310
|
+
end
|
311
|
+
end
|
312
|
+
return rv
|
313
|
+
end
|
314
|
+
query = Query.new(parsed.query)
|
315
|
+
query.add('format', 'json')
|
316
|
+
query.add('marker', quote(marker.to_s)) if marker
|
317
|
+
query.add('limit', quote(limit.to_s)) if limit
|
318
|
+
query.add('prefix', quote(prefix.to_s)) if prefix
|
319
|
+
parsed.query = query.to_url_params
|
320
|
+
conn.start if !conn.started?
|
321
|
+
parsed.path += "/#{quote(container)}"
|
322
|
+
resp = conn.get(parsed.request_uri, {'x-auth-token' => token})
|
323
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
324
|
+
raise ClientException.new('Container GET failed', :http_scheme=>parsed.scheme,
|
325
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
326
|
+
:http_path=>parsed.path, :http_query=>parsed.query, :http_status=>resp.code,
|
327
|
+
:http_reason=>resp.message)
|
328
|
+
end
|
329
|
+
resp_headers = {}
|
330
|
+
resp.header.each do |k,v|
|
331
|
+
resp_headers[k.downcase] = v
|
332
|
+
end
|
333
|
+
if resp.code.to_i == 204
|
334
|
+
[resp_headers, []]
|
335
|
+
else
|
336
|
+
[resp_headers, JSON.parse(resp.body())]
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def get_container(container, marker=nil, limit=nil, prefix=nil, delimiter=nil, full_listing=nil)
|
341
|
+
_retry(nil, :get_container, [container, marker, limit, prefix, delimiter, full_listing])
|
342
|
+
end
|
343
|
+
|
344
|
+
def self.head_container(url, token, container, http_conn=nil)
|
345
|
+
if not http_conn
|
346
|
+
http_conn = http_connection(url)
|
347
|
+
end
|
348
|
+
parsed = http_conn[0].clone
|
349
|
+
conn = http_conn[1]
|
350
|
+
|
351
|
+
conn.start if !conn.started?
|
352
|
+
parsed.path += "/#{quote(container)}"
|
353
|
+
resp = conn.head(parsed.request_uri, {'x-auth-token' => token})
|
354
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
355
|
+
raise ClientException.new('Container HEAD failed', :http_scheme=>parsed.scheme,
|
356
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
357
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
358
|
+
:http_reason=>resp.message)
|
359
|
+
end
|
360
|
+
resp_headers = {}
|
361
|
+
resp.header.each do |k,v|
|
362
|
+
resp_headers[k.downcase] = v
|
363
|
+
end
|
364
|
+
resp_headers
|
365
|
+
end
|
366
|
+
|
367
|
+
def head_container(container)
|
368
|
+
_retry(nil, :head_container, [container, @http_conn])
|
369
|
+
end
|
370
|
+
|
371
|
+
def self.put_container(url, token, container, headers={}, http_conn=nil)
|
372
|
+
if not http_conn
|
373
|
+
http_conn = http_connection(url)
|
374
|
+
end
|
375
|
+
parsed = http_conn[0].clone
|
376
|
+
conn = http_conn[1]
|
377
|
+
|
378
|
+
conn.start if !conn.started?
|
379
|
+
parsed.path += "/#{quote(container)}"
|
380
|
+
headers['x-auth-token'] = token
|
381
|
+
# headers['content-length'] = 0
|
382
|
+
resp = conn.put(parsed.request_uri, nil, headers)
|
383
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
384
|
+
raise ClientException.new('Container PUT failed', :http_scheme=>parsed.scheme,
|
385
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
386
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
387
|
+
:http_reason=>resp.message)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def put_container(container, headers={})
|
392
|
+
_retry(nil, :put_container, [container, headers, @http_conn])
|
393
|
+
end
|
394
|
+
|
395
|
+
def self.post_container(url, token, container, headers={}, http_conn=nil)
|
396
|
+
if not http_conn
|
397
|
+
http_conn = http_connection(url)
|
398
|
+
end
|
399
|
+
parsed = http_conn[0].clone
|
400
|
+
conn = http_conn[1]
|
401
|
+
|
402
|
+
conn.start if !conn.started?
|
403
|
+
parsed.path += "/#{quote(container)}"
|
404
|
+
headers['x-auth-token'] = token
|
405
|
+
resp = conn.post(parsed.request_uri, nil, headers)
|
406
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
407
|
+
raise ClientException.new('Container POST failed', :http_scheme=>parsed.scheme,
|
408
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
409
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
410
|
+
:http_reason=>resp.message)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def post_container(container, headers={})
|
415
|
+
_retry(nil, :post_container, [container, headers])
|
416
|
+
end
|
417
|
+
|
418
|
+
def self.delete_container(url, token, container, headers={}, http_conn=nil)
|
419
|
+
if not http_conn
|
420
|
+
http_conn = http_connection(url)
|
421
|
+
end
|
422
|
+
parsed = http_conn[0].clone
|
423
|
+
conn = http_conn[1]
|
424
|
+
|
425
|
+
conn.start if !conn.started?
|
426
|
+
parsed.path += "/#{quote(container)}"
|
427
|
+
headers['x-auth-token'] = token
|
428
|
+
resp = conn.delete(parsed.request_uri, headers)
|
429
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
430
|
+
raise ClientException.new('Container DELETE failed', :http_scheme=>parsed.scheme,
|
431
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
432
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
433
|
+
:http_reason=>resp.message)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def delete_container(container)
|
438
|
+
_retry(nil, :delete_container, [container])
|
439
|
+
end
|
440
|
+
|
441
|
+
def self.get_object(url, token, container, name, http_conn=nil, resp_chunk_size=nil, &block)
|
442
|
+
if not http_conn
|
443
|
+
http_conn = http_connection(url)
|
444
|
+
end
|
445
|
+
parsed = http_conn[0].clone
|
446
|
+
conn = http_conn[1]
|
447
|
+
|
448
|
+
|
449
|
+
parsed.path += "/#{quote(container)}/#{quote(name)}"
|
450
|
+
conn.start if not conn.started?
|
451
|
+
headers = {'x-auth-token' => token}
|
452
|
+
if block_given?
|
453
|
+
resp = conn.request_get(parsed.request_uri, headers) do |r|
|
454
|
+
r.read_body do |b|
|
455
|
+
yield b
|
456
|
+
end
|
457
|
+
end
|
458
|
+
object_body = nil
|
459
|
+
else
|
460
|
+
resp = conn.request_get(parsed.request_uri, headers)
|
461
|
+
object_body = resp.body
|
462
|
+
end
|
463
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
464
|
+
raise ClientException.new('Object GET failed', :http_scheme=>parsed.scheme,
|
465
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
466
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
467
|
+
:http_reason=>resp.message)
|
468
|
+
end
|
469
|
+
resp_headers = {}
|
470
|
+
resp.header.each do |k,v|
|
471
|
+
resp_headers[k.downcase] = v
|
472
|
+
end
|
473
|
+
[resp_headers, object_body]
|
474
|
+
end
|
475
|
+
|
476
|
+
def get_object(container, name, resp_chunk_size=nil)
|
477
|
+
_retry(nil, :get_object, [container, name, resp_chunk_size])
|
478
|
+
end
|
479
|
+
|
480
|
+
def self.head_object(url, token, container, name, http_conn=nil)
|
481
|
+
if not http_conn
|
482
|
+
http_conn = http_connection(url)
|
483
|
+
end
|
484
|
+
parsed = http_conn[0].clone
|
485
|
+
conn = http_conn[1]
|
486
|
+
|
487
|
+
|
488
|
+
parsed.path += "/#{quote(container)}/#{quote(name)}"
|
489
|
+
conn.start if not conn.started?
|
490
|
+
resp = conn.head(parsed.request_uri, {'x-auth-token' => token})
|
491
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
492
|
+
raise ClientException.new('Object HEAD failed', :http_scheme=>parsed.scheme,
|
493
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
494
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
495
|
+
:http_reason=>resp.message)
|
496
|
+
end
|
497
|
+
resp_headers = {}
|
498
|
+
resp.header.each do |k,v|
|
499
|
+
resp_headers[k.downcase] = v
|
500
|
+
end
|
501
|
+
resp_headers
|
502
|
+
end
|
503
|
+
|
504
|
+
def head_object(container, name)
|
505
|
+
_retry(nil, :head_object, [container, name])
|
506
|
+
end
|
507
|
+
|
508
|
+
def self.put_object(url, token=nil, container=nil, name=nil, contents=nil,
|
509
|
+
content_length=nil, etag=nil, chunk_size=nil,
|
510
|
+
content_type=nil, headers={}, http_conn=nil, proxy=nil)
|
511
|
+
chunk_size ||= 65536
|
512
|
+
if not http_conn
|
513
|
+
http_conn = http_connection(url)
|
514
|
+
end
|
515
|
+
parsed = http_conn[0].clone
|
516
|
+
conn = http_conn[1]
|
517
|
+
|
518
|
+
parsed.path += "/#{quote(container)}" if container
|
519
|
+
parsed.path += "/#{quote(name)}" if name
|
520
|
+
headers['x-auth-token'] = token if token
|
521
|
+
headers['etag'] = etag if etag
|
522
|
+
if content_length != nil
|
523
|
+
headers['content-length'] = content_length.to_s
|
524
|
+
else
|
525
|
+
headers.each do |k,v|
|
526
|
+
if k.downcase == 'content-length'
|
527
|
+
content_length = v.to_i
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
headers['content-type'] = content_type if content_type
|
532
|
+
headers['content-length'] = '0' if not contents
|
533
|
+
if contents.respond_to? :read
|
534
|
+
request = Net::HTTP::Put.new(parsed.request_uri, headers)
|
535
|
+
chunked = ChunkedConnectionWrapper.new(contents, chunk_size)
|
536
|
+
if content_length == nil
|
537
|
+
request['Transfer-Encoding'] = 'chunked'
|
538
|
+
request.delete('content-length')
|
539
|
+
end
|
540
|
+
request.body_stream = chunked
|
541
|
+
resp = conn.start do |http|
|
542
|
+
http.request(request)
|
543
|
+
end
|
544
|
+
else
|
545
|
+
conn.start if not conn.started?
|
546
|
+
resp = conn.put(parsed.request_uri, contents, headers)
|
547
|
+
end
|
548
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
549
|
+
raise ClientException.new('Object PUT failed', :http_scheme=>parsed.scheme,
|
550
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
551
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
552
|
+
:http_reason=>resp.message)
|
553
|
+
end
|
554
|
+
resp.header['etag']
|
555
|
+
end
|
556
|
+
|
557
|
+
def put_object(container, obj, contents, content_length=nil, etag=nil, chunk_size=65536, content_type=nil, headers={})
|
558
|
+
|
559
|
+
_default_reset = Proc.new do |args|
|
560
|
+
raise ClientException("put_object(#{container}, #{obj}, ...) failure and no ability to reset contents for reupload.")
|
561
|
+
end
|
562
|
+
reset_func = _default_reset
|
563
|
+
if (contents.respond_to? :seek) and (contents.respond_to? :tell)
|
564
|
+
orig_pos = contents.tell
|
565
|
+
reset_func = Proc.new {|a| contents.seek(orig_pos)}
|
566
|
+
elsif !contents
|
567
|
+
reset_func = Proc.new {|a| nil }
|
568
|
+
end
|
569
|
+
_retry(reset_func, :put_object, [container, obj, contents, content_length, etag, chunk_size, content_type, headers])
|
570
|
+
end
|
571
|
+
|
572
|
+
def self.post_object(url, token=nil, container=nil, name=nil, headers={}, http_conn=nil)
|
573
|
+
if not http_conn
|
574
|
+
http_conn = http_connection(url)
|
575
|
+
end
|
576
|
+
parsed = http_conn[0].clone
|
577
|
+
conn = http_conn[1]
|
578
|
+
|
579
|
+
parsed.path += "/#{quote(container)}" if container
|
580
|
+
parsed.path += "/#{quote(name)}" if name
|
581
|
+
headers['x-auth-token'] = token if token
|
582
|
+
resp = conn.post(parsed.request_uri, nil, headers)
|
583
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
584
|
+
raise ClientException.new('Object POST failed', :http_scheme=>parsed.scheme,
|
585
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
586
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
587
|
+
:http_reason=>resp.message)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
def post_object(container, name, headers={})
|
592
|
+
_retry(nil, :post_object, [container, name, headers])
|
593
|
+
end
|
594
|
+
|
595
|
+
def self.delete_object(url, token=nil, container=nil, name=nil, http_conn=nil, headers={}, proxy=nil)
|
596
|
+
if not http_conn
|
597
|
+
http_conn = http_connection(url)
|
598
|
+
end
|
599
|
+
parsed = http_conn[0].clone
|
600
|
+
conn = http_conn[1]
|
601
|
+
|
602
|
+
conn.start if !conn.started?
|
603
|
+
parsed.path += "/#{quote(container)}" if container
|
604
|
+
parsed.path += "/#{quote(name)}" if name
|
605
|
+
headers['x-auth-token'] = token if token
|
606
|
+
resp = conn.delete(parsed.request_uri, headers)
|
607
|
+
if resp.code.to_i < 200 or resp.code.to_i > 300
|
608
|
+
raise ClientException.new('Object DELETE failed', :http_scheme=>parsed.scheme,
|
609
|
+
:http_host=>conn.address, :http_port=>conn.port,
|
610
|
+
:http_path=>parsed.path, :http_status=>resp.code,
|
611
|
+
:http_reason=>resp.message)
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
def delete_object(container, name, headers={})
|
616
|
+
_retry(nil, :delete_object, [container, name, headers])
|
617
|
+
end
|
618
|
+
end
|