openstack 1.0.0

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.
@@ -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