cloudfiles 1.4.16 → 1.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ doc/*
4
4
  pkg/*
5
5
  *.gem
6
6
  .idea
7
+ Gemfile.lock
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ ================================================================================
2
+ 1.4.17 (2011/05/27)
3
+ ================================================================================
4
+ o Added Manifest support for Large Objects
5
+ o Add support for container metadata
6
+ o Option to check the MD5 of a file uploaded via the load_from_filename method
7
+
1
8
  ================================================================================
2
9
  1.4.16 (2011/03/17)
3
10
  ================================================================================
data/CONTRIBUTORS CHANGED
@@ -29,3 +29,5 @@ megaphone
29
29
  Nugroho Herucahyono
30
30
  CvX
31
31
  Topper Bowers
32
+ Xavier Shay
33
+ Dillon Amburgey
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :gemcutter
2
+
3
+ gemspec
data/Rakefile CHANGED
@@ -1,22 +1,3 @@
1
- require './lib/cloudfiles.rb'
2
-
3
- begin
4
- require 'jeweler'
5
- Jeweler::Tasks.new do |gemspec|
6
- gemspec.name = "cloudfiles"
7
- gemspec.summary = "A Ruby API into Rackspace Cloud Files"
8
- gemspec.description = "A Ruby version of the Rackspace Cloud Files API."
9
- gemspec.email = "minter@lunenburg.org"
10
- gemspec.homepage = "http://www.rackspacecloud.com/cloud_hosting_products/files"
11
- gemspec.authors = ["H. Wade Minter", "Rackspace Hosting"]
12
- gemspec.add_dependency('mime-types', '>= 1.16')
13
- gemspec.add_development_dependency "mocha", "~> 0.9.8"
14
- end
15
- Jeweler::GemcutterTasks.new
16
- rescue LoadError
17
- puts "Jeweler not available. Install it with: sudo gem install jeweler"
18
- end
19
-
20
1
  namespace :test do
21
2
  desc 'Check test coverage'
22
3
  task :coverage do
data/cloudfiles.gemspec CHANGED
@@ -1,15 +1,11 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
- # -*- encoding: utf-8 -*-
1
+ require 'lib/cloudfiles/version'
5
2
 
6
3
  Gem::Specification.new do |s|
7
4
  s.name = %q{cloudfiles}
8
- s.version = "1.4.16"
5
+ s.version = CloudFiles::VERSION
9
6
 
10
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
8
  s.authors = ["H. Wade Minter", "Rackspace Hosting"]
12
- s.date = %q{2011-03-17}
13
9
  s.description = %q{A Ruby version of the Rackspace Cloud Files API.}
14
10
  s.email = %q{minter@lunenburg.org}
15
11
  s.extra_rdoc_files = [
@@ -21,10 +17,10 @@ Gem::Specification.new do |s|
21
17
  "CHANGELOG",
22
18
  "CONTRIBUTORS",
23
19
  "COPYING",
20
+ "Gemfile",
24
21
  "README.rdoc",
25
22
  "Rakefile",
26
23
  "TODO",
27
- "VERSION",
28
24
  "cloudfiles.gemspec",
29
25
  "lib/cloudfiles.rb",
30
26
  "lib/cloudfiles/authentication.rb",
@@ -32,6 +28,7 @@ Gem::Specification.new do |s|
32
28
  "lib/cloudfiles/container.rb",
33
29
  "lib/cloudfiles/exception.rb",
34
30
  "lib/cloudfiles/storage_object.rb",
31
+ "lib/cloudfiles/version.rb",
35
32
  "test/cf-testunit.rb",
36
33
  "test/cloudfiles_authentication_test.rb",
37
34
  "test/cloudfiles_connection_test.rb",
data/lib/cloudfiles.rb CHANGED
@@ -22,7 +22,6 @@ module CloudFiles
22
22
  AUTH_USA = "https://auth.api.rackspacecloud.com/v1.0"
23
23
  AUTH_UK = "https://lon.auth.api.rackspacecloud.com/v1.0"
24
24
 
25
- VERSION = IO.read(File.dirname(__FILE__) + '/../VERSION')
26
25
  require 'net/http'
27
26
  require 'net/https'
28
27
  require 'rexml/document'
@@ -39,6 +38,7 @@ module CloudFiles
39
38
  end
40
39
 
41
40
  $:.unshift(File.dirname(__FILE__))
41
+ require 'cloudfiles/version'
42
42
  require 'cloudfiles/exception'
43
43
  require 'cloudfiles/authentication'
44
44
  require 'cloudfiles/connection'
@@ -22,7 +22,7 @@ module CloudFiles
22
22
  end
23
23
  server.start
24
24
  rescue
25
- raise CloudFiles::Exception::Connection, "Unable to connect to #{server}"
25
+ raise CloudFiles::Exception::Connection, "Unable to connect to #{server.address}"
26
26
  end
27
27
  response = server.get(path, hdrhash)
28
28
  if (response.code =~ /^20./)
@@ -270,10 +270,11 @@ module CloudFiles
270
270
  # This method actually makes the HTTP calls out to the server
271
271
  def cfreq(method, server, path, port, scheme, headers = {}, data = nil, attempts = 0, &block) # :nodoc:
272
272
  start = Time.now
273
- headers['Transfer-Encoding'] = "chunked" if data.is_a?(IO)
273
+ headers['Transfer-Encoding'] = "chunked" if data.respond_to?(:read)
274
274
  hdrhash = headerprep(headers)
275
275
  start_http(server, path, port, scheme, hdrhash)
276
276
  request = Net::HTTP.const_get(method.to_s.capitalize).new(path, hdrhash)
277
+ data.rewind if data.respond_to?(:rewind)
277
278
  if data
278
279
  if data.respond_to?(:read)
279
280
  request.body_stream = data
@@ -291,7 +292,7 @@ module CloudFiles
291
292
  response
292
293
  rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError, IOError
293
294
  # Server closed the connection, retry
294
- raise CloudFiles::Exception::Connection, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
295
+ raise CloudFiles::Exception::Connection, "Unable to reconnect to #{server.address} after #{attempts} attempts" if attempts >= 5
295
296
  attempts += 1
296
297
  begin
297
298
  @http[server].finish
@@ -329,7 +330,7 @@ module CloudFiles
329
330
  end
330
331
  @http[server].start
331
332
  rescue
332
- raise CloudFiles::Exception::Connection, "Unable to connect to #{server}"
333
+ raise CloudFiles::Exception::Connection, "Unable to connect to #{server.address}"
333
334
  end
334
335
  end
335
336
  end
@@ -28,9 +28,9 @@ module CloudFiles
28
28
  end
29
29
  # Load the metadata now, so we'll get a CloudFiles::Exception::NoSuchContainer exception should the container
30
30
  # not exist.
31
- self.metadata
31
+ self.container_metadata
32
32
  end
33
-
33
+
34
34
  # Refreshes data about the container and populates class variables. Items are otherwise
35
35
  # loaded in a lazy loaded fashion.
36
36
  #
@@ -49,11 +49,13 @@ module CloudFiles
49
49
  alias :populate :refresh
50
50
 
51
51
  # Retrieves Metadata for the container
52
- def metadata
52
+ def container_metadata
53
53
  @metadata ||= (
54
54
  response = self.connection.cfreq("HEAD", @storagehost, @storagepath + "/", @storageport, @storagescheme)
55
55
  raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code =~ /^20/)
56
- {:bytes => response["x-container-bytes-used"].to_i, :count => response["x-container-object-count"].to_i}
56
+ resphash = {}
57
+ response.to_hash.select { |k,v| k.match(/^x-container-meta/) }.each { |x| resphash[x[0]] = x[1].to_s }
58
+ {:bytes => response["x-container-bytes-used"].to_i, :count => response["x-container-object-count"].to_i, :metadata => resphash}
57
59
  )
58
60
  end
59
61
 
@@ -78,7 +80,32 @@ module CloudFiles
78
80
  @cdn_metadata = {}
79
81
  end
80
82
  end
83
+
84
+ # Returns the container's metadata as a nicely formatted hash, stripping off the X-Meta-Object- prefix that the system prepends to the
85
+ # key name.
86
+ #
87
+ # object.metadata
88
+ # => {"ruby"=>"cool", "foo"=>"bar"}
89
+ def metadata
90
+ metahash = {}
91
+ self.container_metadata[:metadata].each{ |key, value| metahash[key.gsub(/x-container-meta-/, '').gsub(/%20/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ') }
92
+ metahash
93
+ end
81
94
 
95
+ # Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
96
+ # New calls to set metadata are additive. To remove metadata, set the value of the key to nil.
97
+ #
98
+ # Throws NoSuchObjectException if the container doesn't exist. Throws InvalidResponseException if the request
99
+ # fails.
100
+ def set_metadata(metadatahash)
101
+ headers = {}
102
+ metadatahash.each{ |key, value| headers['X-Container-Meta-' + CloudFiles.escape(key.to_s.capitalize)] = value.to_s }
103
+ response = self.connection.cfreq("POST", @storagehost, @storagepath, @storageport, @storagescheme, headers)
104
+ raise CloudFiles::Exception::NoSuchObject, "Container #{@name} does not exist" if (response.code == "404")
105
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
106
+ true
107
+ end
108
+
82
109
  # Size of the container (in bytes)
83
110
  def bytes
84
111
  self.metadata[:bytes]
@@ -200,6 +227,8 @@ module CloudFiles
200
227
  # If no objects exist, an empty hash is returned. Throws an InvalidResponseException if the request fails. Takes a
201
228
  # parameter hash as an argument, in the same form as the objects method.
202
229
  #
230
+ # Accepts the same options as objects to limit the returned set.
231
+ #
203
232
  # Returns a hash in the same format as the containers_detail from the CloudFiles class.
204
233
  #
205
234
  # container.objects_detail
@@ -241,7 +270,7 @@ module CloudFiles
241
270
  # full_container.empty?
242
271
  # => false
243
272
  def empty?
244
- return (metadata[:count].to_i == 0)? true : false
273
+ return (container_metadata[:count].to_i == 0)? true : false
245
274
  end
246
275
 
247
276
  # Returns true if object exists and returns false otherwise.
@@ -13,9 +13,6 @@ module CloudFiles
13
13
  # CloudFiles::Exception::NoSuchObject Exception will be raised. If not, an "empty" CloudFiles::StorageObject will be returned, ready for data
14
14
  # via CloudFiles::StorageObject.write
15
15
  def initialize(container, objectname, force_exists = false, make_path = false)
16
- if objectname.match(/\?/)
17
- raise CloudFiles::Exception::Syntax, "Object #{objectname} contains an invalid character in the name (? not allowed)"
18
- end
19
16
  @container = container
20
17
  @containername = container.name
21
18
  @name = objectname
@@ -50,6 +47,7 @@ module CloudFiles
50
47
  resphash = {}
51
48
  response.to_hash.select { |k,v| k.match(/^x-object-meta/) }.each { |x| resphash[x[0]] = x[1].to_s }
52
49
  {
50
+ :manifest => response["x-object-manifest"],
53
51
  :bytes => response["content-length"],
54
52
  :last_modified => Time.parse(response["last-modified"]),
55
53
  :etag => response["etag"],
@@ -78,7 +76,7 @@ module CloudFiles
78
76
  def content_type
79
77
  self.object_metadata[:content_type]
80
78
  end
81
-
79
+
82
80
  # Retrieves the data from an object and stores the data in memory. The data is returned as a string.
83
81
  # Throws a NoSuchObjectException if the object doesn't exist.
84
82
  #
@@ -96,6 +94,7 @@ module CloudFiles
96
94
  raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" unless (response.code =~ /^20/)
97
95
  response.body
98
96
  end
97
+ alias :read :data
99
98
 
100
99
  # Retrieves the data from an object and returns a stream that must be passed to a block. Throws a
101
100
  # NoSuchObjectException if the object doesn't exist.
@@ -145,6 +144,30 @@ module CloudFiles
145
144
  raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "202")
146
145
  true
147
146
  end
147
+
148
+
149
+ # Returns the object's manifest.
150
+ #
151
+ # object.manifest
152
+ # => "container/prefix"
153
+ def manifest
154
+ self.object_metadata[:manifest]
155
+ end
156
+
157
+
158
+ # Sets the manifest for an object. By passing a string as an argument, you can set the manifest for an object.
159
+ # However, setting manifest will overwrite any existing manifest for the object.
160
+ #
161
+ # Throws NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request
162
+ # fails.
163
+ def set_manifest(manifest)
164
+ headers = {'X-Object-Manifest' => manifest}
165
+ response = self.container.connection.cfreq("PUT", @storagehost, @storagepath, @storageport, @storagescheme, headers)
166
+ raise CloudFiles::Exception::NoSuchObject, "Object #{@name} does not exist" if (response.code == "404")
167
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
168
+ true
169
+ end
170
+
148
171
 
149
172
  # Takes supplied data and writes it to the object, saving it. You can supply an optional hash of headers, including
150
173
  # Content-Type and ETag, that will be applied to the object.
@@ -244,8 +267,13 @@ module CloudFiles
244
267
  #
245
268
  # object.load_from_filename("/tmp/nonexistent.txt")
246
269
  # => Errno::ENOENT: No such file or directory - /tmp/nonexistent.txt
247
- def load_from_filename(filename, headers = {})
270
+ def load_from_filename(filename, headers = {}, check_md5 = false)
248
271
  f = open(filename)
272
+ if check_md5
273
+ require 'digest/md5'
274
+ md5_hash = Digest::MD5.file(filename)
275
+ headers["Etag"] = md5_hash.to_s()
276
+ end
249
277
  self.write(f, headers)
250
278
  f.close
251
279
  true
@@ -0,0 +1,3 @@
1
+ module CloudFiles
2
+ VERSION = '1.4.17'
3
+ end
@@ -220,7 +220,7 @@ class CloudfilesConnectionTest < Test::Unit::TestCase
220
220
  end
221
221
 
222
222
  def test_fetch_nonexistent_container
223
- CloudFiles::Container.any_instance.stubs(:metadata).raises(CloudFiles::Exception::NoSuchContainer)
223
+ CloudFiles::Container.any_instance.stubs(:container_metadata).raises(CloudFiles::Exception::NoSuchContainer)
224
224
  build_net_http_object
225
225
  assert_raise(CloudFiles::Exception::NoSuchContainer) do
226
226
  container = @connection.container('bad_container')
@@ -263,7 +263,8 @@ class CloudfilesContainerTest < Test::Unit::TestCase
263
263
 
264
264
  def build_net_http_object(args={:code => '204' }, cfreq_expectations={})
265
265
  CloudFiles::Container.any_instance.stubs(:populate).returns(true)
266
- CloudFiles::Container.any_instance.stubs(:metadata).returns({})
266
+ CloudFiles::Container.any_instance.stubs(:metadata).returns()
267
+ CloudFiles::Container.any_instance.stubs(:container_metadata).returns({:bytes => 99, :count => 2})
267
268
  connection = stub(:storagehost => 'test.storage.example', :storagepath => '/dummy/path', :storageport => 443, :storagescheme => 'https', :cdnmgmthost => 'cdm.test.example', :cdnmgmtpath => '/dummy/path', :cdnmgmtport => 443, :cdnmgmtscheme => 'https', :cdn_available? => true)
268
269
  args[:response] = {} unless args[:response]
269
270
  response = {'x-cdn-management-url' => 'http://cdn.example.com/path', 'x-storage-url' => 'http://cdn.example.com/storage', 'authtoken' => 'dummy_token', 'last-modified' => Time.now.to_s}.merge(args[:response])
@@ -27,18 +27,6 @@ class CloudfilesStorageObjectTest < Test::Unit::TestCase
27
27
  assert_equal @object.to_s, 'test_object'
28
28
  end
29
29
 
30
- def test_object_creation_with_invalid_name
31
- connection = stub(:storagehost => 'test.storage.example', :storagepath => '/dummy/path', :storageport => 443, :storagescheme => 'https', :cdnmgmthost => 'cdm.test.example', :cdnmgmtpath => '/dummy/path', :cdnmgmtport => 443, :cdnmgmtscheme => 'https', :cdn_available? => true)
32
- response = {'x-container-bytes-used' => '42', 'x-container-object-count' => '5', 'last-modified' => Time.now.to_s}
33
- response.stubs(:code).returns('204')
34
- connection.stubs(:cfreq => response)
35
- container = CloudFiles::Container.new(connection, 'test_container')
36
- assert_raises CloudFiles::Exception::Syntax do
37
- @object = CloudFiles::StorageObject.new(container, 'test_object?')
38
- end
39
- end
40
-
41
-
42
30
  def test_public_url_exists
43
31
  build_net_http_object(:public => true, :name => 'test object')
44
32
  assert_equal @object.public_url, "http://cdn.test.example/test%20object"
@@ -214,6 +202,7 @@ class CloudfilesStorageObjectTest < Test::Unit::TestCase
214
202
  def build_net_http_object(args={:code => '204' })
215
203
  CloudFiles::Container.any_instance.stubs(:metadata).returns({})
216
204
  CloudFiles::Container.any_instance.stubs(:populate).returns(true)
205
+ CloudFiles::Container.any_instance.stubs(:container_metadata).returns({:bytes => 99, :count => 2})
217
206
  connection = stub(:storagehost => 'test.storage.example', :storagepath => '/dummy/path', :storageport => 443, :storagescheme => 'https', :cdnmgmthost => 'cdm.test.example', :cdnmgmtpath => '/dummy/path', :cdnmgmtport => 443, :cdnmgmtscheme => 'https', :cdn_available? => true)
218
207
  args[:response] = {} unless args[:response]
219
208
  response = {'x-cdn-management-url' => 'http://cdn.example.com/path', 'x-storage-url' => 'http://cdn.example.com/storage', 'authtoken' => 'dummy_token', 'last-modified' => Time.now.to_s}.merge(args[:response])
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudfiles
3
3
  version: !ruby/object:Gem::Version
4
- hash: 39
4
+ hash: 37
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 4
9
- - 16
10
- version: 1.4.16
9
+ - 17
10
+ version: 1.4.17
11
11
  platform: ruby
12
12
  authors:
13
13
  - H. Wade Minter
@@ -16,8 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-03-17 00:00:00 -04:00
20
- default_executable:
19
+ date: 2011-05-27 00:00:00 Z
21
20
  dependencies:
22
21
  - !ruby/object:Gem::Dependency
23
22
  name: mime-types
@@ -64,10 +63,10 @@ files:
64
63
  - CHANGELOG
65
64
  - CONTRIBUTORS
66
65
  - COPYING
66
+ - Gemfile
67
67
  - README.rdoc
68
68
  - Rakefile
69
69
  - TODO
70
- - VERSION
71
70
  - cloudfiles.gemspec
72
71
  - lib/cloudfiles.rb
73
72
  - lib/cloudfiles/authentication.rb
@@ -75,13 +74,13 @@ files:
75
74
  - lib/cloudfiles/container.rb
76
75
  - lib/cloudfiles/exception.rb
77
76
  - lib/cloudfiles/storage_object.rb
77
+ - lib/cloudfiles/version.rb
78
78
  - test/cf-testunit.rb
79
79
  - test/cloudfiles_authentication_test.rb
80
80
  - test/cloudfiles_connection_test.rb
81
81
  - test/cloudfiles_container_test.rb
82
82
  - test/cloudfiles_storage_object_test.rb
83
83
  - test/test_helper.rb
84
- has_rdoc: true
85
84
  homepage: http://www.rackspacecloud.com/cloud_hosting_products/files
86
85
  licenses: []
87
86
 
@@ -111,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
110
  requirements: []
112
111
 
113
112
  rubyforge_project:
114
- rubygems_version: 1.5.0
113
+ rubygems_version: 1.7.2
115
114
  signing_key:
116
115
  specification_version: 3
117
116
  summary: A Ruby API into Rackspace Cloud Files
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.4.16