cloudfiles 1.4.16 → 1.4.17
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +7 -0
- data/CONTRIBUTORS +2 -0
- data/Gemfile +3 -0
- data/Rakefile +0 -19
- data/cloudfiles.gemspec +4 -7
- data/lib/cloudfiles.rb +1 -1
- data/lib/cloudfiles/authentication.rb +1 -1
- data/lib/cloudfiles/connection.rb +4 -3
- data/lib/cloudfiles/container.rb +34 -5
- data/lib/cloudfiles/storage_object.rb +33 -5
- data/lib/cloudfiles/version.rb +3 -0
- data/test/cloudfiles_connection_test.rb +1 -1
- data/test/cloudfiles_container_test.rb +2 -1
- data/test/cloudfiles_storage_object_test.rb +1 -12
- metadata +7 -8
- data/VERSION +0 -1
data/.gitignore
CHANGED
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
data/Gemfile
ADDED
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
|
-
|
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 =
|
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.
|
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 #{
|
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
|
data/lib/cloudfiles/container.rb
CHANGED
@@ -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.
|
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
|
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
|
-
|
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 (
|
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
|
@@ -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(:
|
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:
|
4
|
+
hash: 37
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 4
|
9
|
-
-
|
10
|
-
version: 1.4.
|
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-
|
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.
|
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
|