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 ADDED
@@ -0,0 +1,7 @@
1
+ tmp/*
2
+ coverage/*
3
+ doc/*
4
+ pkg/*
5
+ *.gem
6
+ .idea
7
+ Gemfile.lock
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
@@ -0,0 +1,7 @@
1
+ source :gemcutter
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ end
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
@@ -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