openstack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,185 @@
1
+ #Initial version of this code is based on and refactored from the rackspace/ruby-cloudfiles repo
2
+ #@ https://github.com/rackspace/ruby-cloudfiles - Copyright (c) 2011, Rackspace US, Inc.
3
+ # See COPYING for license information
4
+ #
5
+ module OpenStack
6
+ module Swift
7
+ class Connection
8
+
9
+ attr_accessor :connection
10
+
11
+ def initialize(connection)
12
+ @connection = connection
13
+ OpenStack::Authentication.init(@connection)
14
+ end
15
+
16
+ # Returns true if the authentication was successful and returns false otherwise.
17
+ #
18
+ # cf.authok?
19
+ # => true
20
+ def authok?
21
+ @connection.authok
22
+ end
23
+
24
+ # Returns an OpenStack::Swift::Container object that can be manipulated easily.
25
+ # Throws a OpenStack::Exception::ItemNotFound if the container doesn't exist.
26
+ #
27
+ # container = cf.container('test')
28
+ # container.count
29
+ # => 2
30
+ # container = cf.container("no_such_container")
31
+ # => OpenStack::Exception::ItemNotFound: The resource could not be found
32
+ #
33
+ def container(name)
34
+ OpenStack::Swift::Container.new(self, name)
35
+ end
36
+ alias :get_container :container
37
+
38
+ # Sets instance variables for the bytes of storage used for this account/connection, as well as the number of containers
39
+ # stored under the account. Returns a hash with :bytes and :count keys, and also sets the instance variables.
40
+ #
41
+ # cf.get_info
42
+ # => {:count=>8, :bytes=>42438527}
43
+ # cf.bytes
44
+ # => 42438527
45
+ # Hostname of the storage server
46
+ def get_info
47
+ raise OpenStack::Exception::Authentication, "Not authenticated" unless authok?
48
+ response = @connection.req("HEAD", "")
49
+ @bytes = response["x-account-bytes-used"].to_i
50
+ @count = response["x-account-container-count"].to_i
51
+ {:bytes => @bytes, :count => @count}
52
+ end
53
+
54
+ # The total size in bytes under this connection
55
+ def bytes
56
+ get_info[:bytes]
57
+ end
58
+
59
+ # The total number of containers under this connection
60
+ def count
61
+ get_info[:count]
62
+ end
63
+
64
+ # Gathers a list of the containers that exist for the account and returns the list of container names
65
+ # as an array. If no containers exist, an empty array is returned.
66
+ #
67
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
68
+ # specified in limit, starting after the object named in marker.
69
+ #
70
+ # cf.containers
71
+ # => ["backup", "Books", "cftest", "test", "video", "webpics"]
72
+ #
73
+ # cf.containers(2,'cftest')
74
+ # => ["test", "video"]
75
+ def containers(limit = nil, marker = nil)
76
+ path = OpenStack.get_query_params({:limit=>limit, :marker=>marker}, [:limit, :marker], "")
77
+ response = @connection.req("GET", URI.encode(path))
78
+ OpenStack.symbolize_keys(JSON.parse(response.body)).inject([]){|res,cur| res << cur[:name]; res }
79
+ end
80
+ alias :list_containers :containers
81
+
82
+ # Retrieves a list of containers on the account along with their sizes (in bytes) and counts of the objects
83
+ # held within them. If no containers exist, an empty hash is returned.
84
+ #
85
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
86
+ # specified in limit, starting after the object named in marker.
87
+ #
88
+ # cf.containers_detail
89
+ # => { "container1" => { :bytes => "36543", :count => "146" },
90
+ # "container2" => { :bytes => "105943", :count => "25" } }
91
+ def containers_detail(limit = nil, marker = nil)
92
+ path = OpenStack.get_query_params({:limit=>limit, :marker=>marker}, [:limit, :marker], "")
93
+ response = @connection.req("GET", URI.encode(path))
94
+ OpenStack.symbolize_keys(JSON.parse(response.body)).inject({}){|res,current| res.merge!({current[:name]=>{:bytes=>current[:bytes].to_s,:count=>current[:count].to_s}}) ; res }
95
+ end
96
+ alias :list_containers_info :containers_detail
97
+
98
+ # Returns true if the requested container exists and returns false otherwise.
99
+ #
100
+ # cf.container_exists?('good_container')
101
+ # => true
102
+ #
103
+ # cf.container_exists?('bad_container')
104
+ # => false
105
+ def container_exists?(containername)
106
+ path = "/#{URI.encode(containername.to_s)}"
107
+ begin
108
+ response = @connection.req("HEAD", path)
109
+ rescue OpenStack::Exception::ItemNotFound
110
+ return false
111
+ end
112
+ true
113
+ end
114
+
115
+ # Creates a new container and returns the OpenStack::Swift::Container object.
116
+ #
117
+ # "/" is not valid in a container name. The container name is limited to
118
+ # 256 characters or less.
119
+ #
120
+ # container = cf.create_container('new_container')
121
+ # container.name
122
+ # => "new_container"
123
+ #
124
+ # container = cf.create_container('bad/name')
125
+ # => OpenStack::Exception::InvalidArgument: Container name cannot contain '/'
126
+ def create_container(containername)
127
+ raise OpenStack::Exception::InvalidArgument.new("Container name cannot contain '/'") if containername.match("/")
128
+ raise OpenStack::Exception::InvalidArgument.new("Container name is limited to 256 characters") if containername.length > 256
129
+ path = "/#{URI.encode(containername.to_s)}"
130
+ @connection.req("PUT", path)
131
+ OpenStack::Swift::Container.new(self, containername)
132
+ end
133
+
134
+ # Deletes a container from the account. Throws a OpenStack::Exception::ResourceStateConflict
135
+ # if the container still contains objects. Throws a OpenStack::Exception::ItemNotFound if the
136
+ # container doesn't exist.
137
+ #
138
+ # cf.delete_container('new_container')
139
+ # => true
140
+ #
141
+ # cf.delete_container('video')
142
+ # => OpenStack::Exception::ResourceStateConflict: The container: "video" is not empty. There was a conflict with the state of the resource
143
+ #
144
+ #
145
+ # cf.delete_container('nonexistent')
146
+ # => OpenStack::Exception::ItemNotFound: The container: "nonexistant" does not exist. The resource could not be found
147
+ def delete_container(containername)
148
+ path = "/#{URI.encode(containername.to_s)}"
149
+ begin
150
+ @connection.req("DELETE", path)
151
+ rescue OpenStack::Exception::ResourceStateConflict => conflict
152
+ msg = "The container: \"#{containername}\" is not empty. #{conflict.message}"
153
+ raise OpenStack::Exception::ResourceStateConflict.new(msg, conflict.response_code, conflict.response_body)
154
+ rescue OpenStack::Exception::ItemNotFound => not_found
155
+ msg = "The container: \"#{containername}\" does not exist. #{not_found.message}"
156
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
157
+ end
158
+ true
159
+ end
160
+
161
+ end
162
+
163
+ #used for PUT object with body_stream for http data
164
+ #see OpenStack::Connection::put_object
165
+ class ChunkedConnectionWrapper
166
+ def initialize(data, chunk_size)
167
+ @size = chunk_size
168
+ @file = data
169
+ end
170
+
171
+ def read(foo)
172
+ @file.read(@size)
173
+ end
174
+
175
+ def eof!
176
+ @file.eof!
177
+ end
178
+ def eof?
179
+ @file.eof?
180
+ end
181
+ end
182
+
183
+
184
+ end
185
+ end
@@ -0,0 +1,214 @@
1
+ #Initial version of this code is based on and refactored from the rackspace/ruby-cloudfiles repo
2
+ #@ https://github.com/rackspace/ruby-cloudfiles - Copyright (c) 2011, Rackspace US, Inc.
3
+ # See COPYING for license information
4
+ #
5
+ module OpenStack
6
+ module Swift
7
+ class Container
8
+
9
+ attr_reader :name
10
+ attr_reader :swift
11
+ attr_reader :metadata
12
+
13
+ def initialize(swift, name)
14
+ @swift = swift
15
+ @name = name
16
+ @metadata = container_metadata
17
+ end
18
+
19
+ # Retrieves Metadata for the container
20
+ def container_metadata
21
+ path = "/#{URI.encode(@name.to_s)}"
22
+ response = @swift.connection.req("HEAD", path)
23
+ resphash = response.to_hash
24
+ meta = {:bytes=>resphash["x-container-bytes-used"][0], :count=>resphash["x-container-object-count"][0], :metadata=>{}}
25
+ resphash.inject({}){|res, (k,v)| meta[:metadata].merge!({ k.gsub("x-container-meta-", "") => v.first }) if k.match(/^x-container-meta-/)}
26
+ meta
27
+ end
28
+
29
+ # Returns the container's metadata as a Hash, stripping off the X-Meta-Object-
30
+ # prefix that OpenStack prepends to the metadata key name.
31
+ #
32
+ # object.metadata
33
+ # => {"ruby"=>"cool", "foo"=>"bar"}
34
+ def metadata
35
+ metahash = {}
36
+ self.container_metadata[:metadata].each{ |key, value| metahash[key.gsub(/x-container-meta-/, '').gsub(/%20/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ') }
37
+ metahash
38
+ end
39
+
40
+ # Sets the metadata for a container. By passing a hash as an argument, you can set the metadata for an object.
41
+ # New calls to set metadata are additive. To remove metadata, set the value of the key to nil.
42
+ # Including the X-Container-Meta- prefix for each metadata key is optional:
43
+ #
44
+ # container = os.container("foo")
45
+ # container.set_metadata({"X-Container-Meta-Author"=> "msa", "version"=>"1.2", :date=>"today"})
46
+ # => true
47
+ # container.metadata
48
+ # => {"date"=>"today", "author"=>"msa", "version"=>"1.2"}
49
+ #
50
+ # Returns true if operation is successful; Throws OpenStack::Exception::ItemNotFound if the
51
+ # container doesn't exist.
52
+ #
53
+ def set_metadata(metadatahash)
54
+ headers = metadatahash.inject({}){|res, (k,v)| ((k.to_s.match /^X-Container-Meta-/i) ? res[k.to_s]=v : res["X-Container-Meta-#{k}"]=v ) ; res}
55
+ headers.merge!({'content-type'=>'application/json'})
56
+ begin
57
+ response = @swift.connection.req("POST", URI.encode("/#{@name.to_s}"), {:headers=>headers} )
58
+ true
59
+ rescue OpenStack::Exception::ItemNotFound => not_found
60
+ msg = "Cannot set metadata - container: \"#{@name}\" does not exist!. #{not_found.message}"
61
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
62
+ end
63
+ end
64
+
65
+ # Size of the container (in bytes)
66
+ def bytes
67
+ container_metadata[:bytes]
68
+ end
69
+
70
+ # Number of objects in the container
71
+ def count
72
+ container_metadata[:count]
73
+ end
74
+
75
+ # Returns the OpenStack::Swift::StorageObject for the named object.
76
+ # Refer to the OpenStack::Swift::StorageObject class for available methods.
77
+ # If the object exists, it will be returned. If the object does not exist,
78
+ # a OpenStack::Exception::ItemNotFound will be thrown.
79
+ #
80
+ # object = container.object('test.txt')
81
+ # object.data
82
+ # => "This is test data"
83
+ #
84
+ # object = container.object('newfile.txt')
85
+ # => OpenStack::Exception::ItemNotFound: No Object "newfile.txt" found in Container "another_container_foo"
86
+ #
87
+ def object(objectname)
88
+ o = OpenStack::Swift::StorageObject.new(self, objectname, true)
89
+ return o
90
+ end
91
+ alias :get_object :object
92
+
93
+
94
+ # Gathers a list of all available objects in the current container and returns an array of object names.
95
+ # container = cf.container("My Container")
96
+ # container.objects
97
+ # => [ "cat", "dog", "donkey", "monkeydir", "monkeydir/capuchin"]
98
+ # Pass a limit argument to limit the list to a number of objects:
99
+ # container.objects(:limit => 1) #=> [ "cat" ]
100
+ # Pass an marker with or without a limit to start the list at a certain object:
101
+ # container.objects(:limit => 1, :marker => 'dog') #=> [ "donkey" ]
102
+ # Pass a prefix to search for objects that start with a certain string:
103
+ # container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
104
+ # Only search within a certain pseudo-filesystem path:
105
+ # container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
106
+ # Only grab "virtual directories", based on a single-character delimiter (no "directory" objects required):
107
+ # container.objects(:delimiter => '/') #=> ["monkeydir"]
108
+ # All arguments to this method are optional.
109
+ #
110
+ # Returns an empty array if no object exist in the container.
111
+ # if the request fails.
112
+ def objects(params = {})
113
+ path = "/#{@name.to_s}"
114
+ path = (params.empty?)? path : OpenStack.get_query_params(params, [:limit, :marker, :prefix, :path, :delimiter], path)
115
+ response = @swift.connection.req("GET", URI.encode(path))
116
+ OpenStack.symbolize_keys(JSON.parse(response.body)).inject([]){|res, cur| res << cur[:name]; res }
117
+ end
118
+ alias :list_objects :objects
119
+
120
+ # Retrieves a list of all objects in the current container along with their size
121
+ # in bytes, hash, and content_type. If no objects exist, an empty hash is returned.
122
+ #
123
+ # Accepts the same options as 'objects' method to limit the returned set.
124
+ #
125
+ # container.objects_detail
126
+ # => {"test.txt"=>{:content_type=>"application/octet-stream",
127
+ # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
128
+ # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
129
+ # :bytes=>"16"},
130
+ # "new.txt"=>{:content_type=>"application/octet-stream",
131
+ # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
132
+ # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
133
+ # :bytes=>"22"}
134
+ # }
135
+ def objects_detail(params = {})
136
+ path = "/#{@name.to_s}"
137
+ path = (params.empty?)? path : OpenStack.get_query_params(params, [:limit, :marker, :prefix, :path, :delimiter], path)
138
+ response = @swift.connection.req("GET", URI.encode(path))
139
+ OpenStack.symbolize_keys(JSON.parse(response.body)).inject({}){|res, current| res.merge!({current[:name]=>{:bytes=>current[:bytes].to_s, :content_type=>current[:content_type].to_s, :last_modified=>current[:last_modified], :hash=>current[:hash]}}) ; res }
140
+ end
141
+ alias :list_objects_info :objects_detail
142
+
143
+ # Returns true if a container is empty and returns false otherwise.
144
+ #
145
+ # new_container.empty?
146
+ # => true
147
+ #
148
+ # full_container.empty?
149
+ # => false
150
+ def empty?
151
+ return (container_metadata[:count].to_i == 0)? true : false
152
+ end
153
+
154
+ # Returns true if object exists and returns false otherwise.
155
+ #
156
+ # container.object_exists?('goodfile.txt')
157
+ # => true
158
+ #
159
+ # container.object_exists?('badfile.txt')
160
+ # => false
161
+ def object_exists?(objectname)
162
+ path = "/#{@name.to_s}/#{objectname}"
163
+ begin
164
+ response = @swift.connection.req("HEAD", URI.encode(path))
165
+ true
166
+ rescue OpenStack::Exception::ItemNotFound
167
+ false
168
+ end
169
+ end
170
+
171
+ # Creates a new OpenStack::Swift::StorageObject in the current container.
172
+ #
173
+ # If an object with the specified name exists in the current container, that object will be overwritten
174
+ #
175
+ #optional headers: {
176
+ # :metadata=>{key=>value, key1=>value1, ...}
177
+ # :content_type=>content type of created object
178
+ # :etag=>MD5 checksum of object data to be compared to that on server side
179
+ # :manifest=>set manifest header for segmented large object
180
+ # }
181
+ # The optional data can be a File or a String - see StorageObject.create and .write methods
182
+ def create_object(objectname, headers={}, data=nil)
183
+ OpenStack::Swift::StorageObject.create(self, objectname, headers, data)
184
+ end
185
+
186
+ # Removes an OpenStack::Swift::StorageObject from a container.
187
+ # True is returned if the removal is successful.
188
+ # Throws OpenStack::Exception::ItemNotFound if the object doesn't exist.
189
+ #
190
+ # container.delete_object('new.txt')
191
+ # => true
192
+ #
193
+ # container.delete_object('Foo')
194
+ # =>OpenStack::Exception::ItemNotFound: The object: "Foo" does not exist in container "another_containerfoo". The resource could not be found
195
+ #
196
+ def delete_object(objectname)
197
+ path = "/#{@name.to_s}/#{objectname}"
198
+ begin
199
+ response = @swift.connection.req("DELETE", URI.encode(path))
200
+ true
201
+ rescue OpenStack::Exception::ItemNotFound => not_found
202
+ msg = "The object: \"#{objectname}\" does not exist in container \"#{@name}\". #{not_found.message}"
203
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
204
+ end
205
+ end
206
+
207
+
208
+ def to_s # :nodoc:
209
+ @name
210
+ end
211
+
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,311 @@
1
+ #Initial version of this code is based on and refactored from the rackspace/ruby-cloudfiles repo
2
+ #@ https://github.com/rackspace/ruby-cloudfiles - Copyright (c) 2011, Rackspace US, Inc.
3
+ # See COPYING for license information
4
+ #
5
+ module OpenStack
6
+ module Swift
7
+ class StorageObject
8
+
9
+ attr_reader :name
10
+ attr_reader :container
11
+ attr_reader :metadata
12
+
13
+ # Builds a new OpenStack::Swift::StorageObject in the specified container.
14
+ # If force_exist is set, the object must exist or a
15
+ # OpenStack::Exception::ItemNotFound will be raised.
16
+ # If not, an "empty" StorageObject will be returned, ready for data
17
+ # via the write method
18
+ #
19
+ # The container parameter must be an OpenStack::Swift::Container object.
20
+ #
21
+ # This constructor is typically not called directly. You can get a reference to
22
+ # an existing Object via OpenStack::Swift::Container::object method or create a
23
+ # new Object via OpenStack::Swift::Container::create_object method
24
+ #
25
+ def initialize(container, objectname, force_exists = false)
26
+ @container = container
27
+ @containername = container.name
28
+ @name = objectname
29
+
30
+ if force_exists
31
+ raise OpenStack::Exception::ItemNotFound.new("No Object \"#{@name}\" found in Container \"#{@containername}\"", "404", "") unless container.object_exists?(objectname)
32
+ end
33
+ end
34
+
35
+ #create a new Object in a given Container
36
+ #optional headers: {
37
+ # :metadata=>{key=>value, key1=>value1, ...}
38
+ # :content_type=>content type of created object
39
+ # :etag=>MD5 checksum of object data to be compared to that on server side
40
+ # :manifest=>set manifest header for segmented large object
41
+ # }
42
+ #
43
+ # The container parameter must be an OpenStack::Swift::Container object.
44
+ # Typically you'd create an Object by first getting a Container:
45
+ # cont = os.container("foo_container")
46
+ # cont.create_object("my_new_object", {}, "object data")
47
+ #
48
+ def self.create(container, objectname, headers={}, data=nil)
49
+ provided_headers = (headers[:metadata] || {}).inject({}){|res, (k,v)| ((k.match /^X-Object-Meta-/i) ? res[k]=v : res["X-Object-Meta-#{k}"]=v) ;res}
50
+ provided_headers["content-type"] = headers[:content_type] unless headers[:content_type].nil?
51
+ provided_headers["ETag"] = headers[:etag] unless headers[:etag].nil?
52
+ provided_headers["X-Object-Manifest"] = headers[:manifest] unless headers[:manifest].nil?
53
+ if data.nil? #just create an empty object
54
+ path = "/#{container.name}/#{objectname}"
55
+ provided_headers["content-length"] = "0"
56
+ container.swift.connection.req("PUT", URI.encode(path), {:headers=>provided_headers})
57
+ else
58
+ self.new(container, objectname).write(data, provided_headers)
59
+ end
60
+ self.new(container, objectname)
61
+ end
62
+
63
+ # Retrieves Metadata for the object
64
+ # object = container.object("conversion_helper.rb")
65
+ # => #<OpenStack::Swift::StorageObject:0xb7692488 ....
66
+ # object.object_metadata
67
+ # => {:manifest=>nil, :bytes=>"1918", :content_type=>"application/octet-stream", :metadata=>{"foo"=>"bar, "herpa"=>"derp"}, :etag=>"1e5b089a1d92052bcf759d86465143f8", :last_modified=>"Tue, 17 Apr 2012 08:46:35 GMT"}
68
+ #
69
+ def object_metadata
70
+ path = "/#{@containername}/#{@name}"
71
+ response = @container.swift.connection.req("HEAD", path)
72
+ resphash = response.to_hash
73
+ meta = { :bytes=>resphash["content-length"][0],
74
+ :content_type=>resphash["content-type"][0],
75
+ :last_modified=>resphash["last-modified"][0],
76
+ :etag=>resphash["etag"][0],
77
+ :manifest=> (resphash.has_key?("x-object-manifest") ? resphash["x-object-manifest"][0] : nil),
78
+ :metadata=>{}}
79
+ resphash.inject({}){|res, (k,v)| meta[:metadata].merge!({ k.gsub("x-object-meta-", "") => v.first }) if k.match(/^x-object-meta-/)}
80
+ meta
81
+ end
82
+
83
+ #returns just the user defined custom metadata
84
+ # obj.metadata
85
+ # => {"foo"=>"bar, "herpa"=>"derp"}
86
+ def metadata
87
+ self.object_metadata[:metadata]
88
+ end
89
+
90
+ # Size of the object (in bytes)
91
+ # obj.bytes
92
+ # => "493009"
93
+ def bytes
94
+ self.object_metadata[:bytes]
95
+ end
96
+
97
+ # Date of the object's last modification
98
+ # obj.last_modified
99
+ # => "Thu, 26 Apr 2012 09:22:51 GMT"
100
+ def last_modified
101
+ self.object_metadata[:last_modified]
102
+ end
103
+
104
+ # ETag of the object data
105
+ # obj.etag
106
+ # => "494e444f92a8082dabac80a74cdf2c3b"
107
+ def etag
108
+ self.object_metadata[:etag]
109
+ end
110
+
111
+ # Content type of the object data
112
+ # obj.content_type
113
+ # => "application/json"
114
+ def content_type
115
+ self.object_metadata[:content_type]
116
+ end
117
+
118
+ # Retrieves the data from an object and stores the data in memory. The data is returned as a string.
119
+ # Throws a OpenStack::Exception::ItemNotFound if the object doesn't exist.
120
+ #
121
+ # If the optional size and range arguments are provided, the call will return the number of bytes provided by
122
+ # size, starting from the offset provided in offset.
123
+ #
124
+ # object.data
125
+ # => "This is the text stored in the file"
126
+ def data(size = -1, offset = 0, headers = {})
127
+ headers = {'content-type'=>'application/json'}
128
+ if size.to_i > 0
129
+ range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
130
+ headers['Range'] = range
131
+ end
132
+ path = "/#{@containername}/#{@name}"
133
+ begin
134
+ response = @container.swift.connection.req("GET", URI.encode(path), {:headers=>headers})
135
+ response.body
136
+ rescue OpenStack::Exception::ItemNotFound => not_found
137
+ msg = "No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
138
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
139
+ end
140
+ end
141
+ alias :read :data
142
+
143
+ # Retrieves the data from an object and returns a stream that must be passed to a block.
144
+ # Throws a OpenStack::Exception::ItemNotFound if the object doesn't exist.
145
+ #
146
+ # If the optional size and range arguments are provided, the call will return the number of bytes provided by
147
+ # size, starting from the offset provided in offset.
148
+ #
149
+ # The method returns the HTTP response object
150
+ #
151
+ # data = ""
152
+ # object.data_stream do |chunk| data += chunk end
153
+ # => #<Net::HTTPOK 200 OK readbody=true>
154
+ # data
155
+ # => "This is the text stored in the file"
156
+ def data_stream(size = -1, offset = 0, &block)
157
+ headers = {'content-type'=>'application/json'}
158
+ if size.to_i > 0
159
+ range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
160
+ headers['Range'] = range
161
+ end
162
+ server = @container.swift.connection.service_host
163
+ path = @container.swift.connection.service_path + URI.encode("/#{@containername}/#{@name}")
164
+ port = @container.swift.connection.service_port
165
+ scheme = @container.swift.connection.service_scheme
166
+ response = @container.swift.connection.csreq("GET", server, path, port, scheme, headers, nil, 0, &block)
167
+ raise OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
168
+ response
169
+ end
170
+
171
+ # Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
172
+ # However, setting metadata will overwrite any existing metadata for the object. Returns true if the call
173
+ # was successful. Throws OpenStack::Exception::ItemNotFound if the object doesn't exist.
174
+ #
175
+ # The OpenStack mandated 'X-Object-Meta' prefix is optional:
176
+ #
177
+ # obj.set_metadata({:foo=>"bar", "X-Object-Meta-herpa"=>"derp", "author"=>"me"})
178
+ # => true
179
+ #
180
+ # obj.metadata
181
+ # => {"foo"=>"bar", "author"=>"me", "herpa"=>"derp"}
182
+ #
183
+ def set_metadata(metadatahash)
184
+ headers = metadatahash.inject({}){|res, (k,v)| ((k.to_s.match /^X-Object-Meta-/i) ? res[k.to_s]=v : res["X-Object-Meta-#{k.to_s}"]=v ) ;res}
185
+ headers['content-type'] = 'application/json'
186
+ path = "/#{@containername}/#{@name}"
187
+ begin
188
+ response = @container.swift.connection.req("POST", URI.encode(path), {:headers=>headers})
189
+ rescue OpenStack::Exception::ItemNotFound => not_found
190
+ msg = "Can't set metadata: No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
191
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
192
+ end
193
+ true
194
+ end
195
+ alias :metadata= :set_metadata
196
+
197
+
198
+ # Returns the object's manifest.
199
+ #
200
+ # object.manifest
201
+ # => "container/prefix"
202
+ def manifest
203
+ self.object_metadata[:manifest]
204
+ end
205
+
206
+
207
+ # Sets the manifest for an object. By passing a string as an argument, you can set the manifest for an object.
208
+ # However, setting manifest will overwrite any existing manifest for the object.
209
+ #
210
+ # Throws OpenStack::Exception::ItemNotFound if the object doesn't exist. Returns true if the call is successful.
211
+ #
212
+ def set_manifest(manifest)
213
+ headers = {'X-Object-Manifest' => manifest}
214
+ path = "/#{@containername}/#{@name}"
215
+ begin
216
+ response = @container.swift.connection.req("POST", URI.encode(path), {:headers=>headers})
217
+ rescue OpenStack::Exception::ItemNotFound => not_found
218
+ msg = "Can't set manifest: No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
219
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
220
+ end
221
+ true
222
+ end
223
+
224
+
225
+ # Takes supplied data and writes it to the object, saving it. You can supply an optional hash of headers, including
226
+ # Content-Type and ETag, that will be applied to the object.
227
+ #
228
+ # If you would rather stream the data in chunks, instead of reading it all into memory at once, you can pass an
229
+ # IO object for the data, such as: object.write(open('/path/to/file.mp3'))
230
+ #
231
+ # You can compute your own MD5 sum and send it in the "ETag" header. If you provide yours, it will be compared to
232
+ # the MD5 sum on the server side.
233
+ #
234
+ # Returns true on success, raises exceptions if stuff breaks.
235
+ #
236
+ # object = container.create_object("newfile.txt")
237
+ #
238
+ # object.write("This is new data")
239
+ # => true
240
+ #
241
+ # object.data
242
+ # => "This is new data"
243
+ #
244
+ #
245
+ def write(data, headers = {})
246
+ server = @container.swift.connection.service_host
247
+ path = @container.swift.connection.service_path + URI.encode("/#{@containername}/#{@name}")
248
+ port = @container.swift.connection.service_port
249
+ scheme = @container.swift.connection.service_scheme
250
+ body = (data.is_a?(String))? StringIO.new(data) : data
251
+ body.binmode if (body.respond_to?(:binmode))
252
+ response = @container.swift.connection.put_object(server, path, port, scheme, headers, body, 0)
253
+ raise OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
254
+ true
255
+ end
256
+
257
+ # Copy this object to a new location (optionally in a new container)
258
+ #
259
+ # You must supply a name for the new object as well as a container name.
260
+ #
261
+ # new_object = object.copy("new_obj", "my_container")
262
+ #
263
+ # You may also supply a hash of headers to set Content-Type, or custom
264
+ # key=>value metadata:
265
+ #optional headers: {
266
+ # :metadata=>{key=>value, key1=>value1, ...}
267
+ # :content_type=>content type of created object
268
+ # }
269
+ #
270
+ # copied = object.copy('newfile.tmp', "my_container", {:content_type=>"text/plain", :metadata=>{:herp=>"derp", "X-Object-Meta-foo"=>"bar} } )
271
+ # => => #<OpenStack::Swift::StorageObject:0xb728974c .....
272
+ #
273
+ # Returns the new OpenStack::Swift::StorageObject for the copied item.
274
+ #
275
+ def copy(object_name, container_name, headers = {})
276
+ provided_headers = (headers[:metadata] || {}).inject({}){|res, (k,v)| ((k.to_s.match /^X-Object-Meta-/i) ? res[k.to_s]=v : res["X-Object-Meta-#{k.to_s}"]=v) ;res}
277
+ provided_headers["content-type"] = headers[:content_type] unless headers[:content_type].nil?
278
+ provided_headers["X-Copy-From"] = "/#{@containername}/#{@name}"
279
+ provided_headers["content-length"] = "0"
280
+ path = "/#{container_name}/#{object_name}"
281
+ begin
282
+ response = @container.swift.connection.req("PUT", URI.encode(path), {:headers=>provided_headers})
283
+ rescue OpenStack::Exception::ItemNotFound => not_found
284
+ msg = "Can't copy \"#{@name}\": No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
285
+ raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
286
+ end
287
+ OpenStack::Swift::StorageObject.new(@container.swift.container(container_name), object_name)
288
+ end
289
+
290
+ # Takes the same options as the copy method, only it does a copy followed by a delete on the original object.
291
+ #
292
+ # Returns the new OpenStack::Swift::StorageObject for the moved item.
293
+ # You should not attempt to use the old object after doing
294
+ # a move.
295
+ # optional headers: {
296
+ # :metadata=>{key=>value, key1=>value1, ...}
297
+ # :content_type=>content type of created object
298
+ # }
299
+ def move(object_name, container_name, headers={})
300
+ new_object = self.copy(object_name, container_name, headers)
301
+ @container.delete_object(@name)
302
+ new_object
303
+ end
304
+
305
+ def to_s # :nodoc:
306
+ @name
307
+ end
308
+
309
+ end
310
+ end
311
+ end