sparqcode-waz-storage 1.1.1

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.
Files changed (42) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG.rdoc +72 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +36 -0
  5. data/LICENSE +19 -0
  6. data/README.rdoc +299 -0
  7. data/lib/waz-blobs.rb +5 -0
  8. data/lib/waz-queues.rb +6 -0
  9. data/lib/waz-storage.rb +39 -0
  10. data/lib/waz-tables.rb +5 -0
  11. data/lib/waz/blobs/blob_object.rb +121 -0
  12. data/lib/waz/blobs/container.rb +160 -0
  13. data/lib/waz/blobs/exceptions.rb +11 -0
  14. data/lib/waz/blobs/service.rb +156 -0
  15. data/lib/waz/queues/exceptions.rb +29 -0
  16. data/lib/waz/queues/message.rb +65 -0
  17. data/lib/waz/queues/queue.rb +165 -0
  18. data/lib/waz/queues/service.rb +106 -0
  19. data/lib/waz/storage/base.rb +70 -0
  20. data/lib/waz/storage/core_service.rb +122 -0
  21. data/lib/waz/storage/exceptions.rb +33 -0
  22. data/lib/waz/storage/validation_rules.rb +26 -0
  23. data/lib/waz/storage/version.rb +11 -0
  24. data/lib/waz/tables/edm_type_helper.rb +45 -0
  25. data/lib/waz/tables/exceptions.rb +45 -0
  26. data/lib/waz/tables/service.rb +178 -0
  27. data/lib/waz/tables/table.rb +75 -0
  28. data/lib/waz/tables/table_array.rb +11 -0
  29. data/rakefile +23 -0
  30. data/tests/configuration.rb +14 -0
  31. data/tests/waz/blobs/blob_object_test.rb +80 -0
  32. data/tests/waz/blobs/container_test.rb +162 -0
  33. data/tests/waz/blobs/service_test.rb +282 -0
  34. data/tests/waz/queues/message_test.rb +33 -0
  35. data/tests/waz/queues/queue_test.rb +206 -0
  36. data/tests/waz/queues/service_test.rb +299 -0
  37. data/tests/waz/storage/base_tests.rb +81 -0
  38. data/tests/waz/storage/shared_key_core_service_test.rb +142 -0
  39. data/tests/waz/tables/service_test.rb +614 -0
  40. data/tests/waz/tables/table_test.rb +98 -0
  41. data/waz-storage.gemspec +29 -0
  42. metadata +187 -0
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'waz-storage'
3
+ # Application Files (massive include)
4
+ app_files = File.expand_path(File.join(File.dirname(__FILE__), 'waz','queues', '*.rb'))
5
+ Dir[app_files].each(&method(:load))
6
+
@@ -0,0 +1,39 @@
1
+ require 'time'
2
+ require 'cgi'
3
+ require 'base64'
4
+ require 'rexml/document'
5
+ require 'rexml/xpath'
6
+ require 'restclient'
7
+ require 'hmac-sha2'
8
+ require 'net/http'
9
+
10
+ app_files = File.expand_path(File.join(File.dirname(__FILE__), 'waz', 'storage', '*.rb'))
11
+ Dir[app_files].each(&method(:load))
12
+
13
+ # It will depende on which version of Ruby (or if you have Rails)
14
+ # but this method is required so we will add it the String class.
15
+ unless String.method_defined? :start_with?
16
+ class String
17
+ def start_with?(prefix)
18
+ prefix = prefix.to_s
19
+ self[0, prefix.length] == prefix
20
+ end
21
+ end
22
+ end
23
+
24
+ # The Merge method is not defined in the RFC 2616
25
+ # and it's required to Merge entities in Windows Azure
26
+ module Net
27
+ class HTTP < Protocol
28
+ class Merge < HTTPRequest
29
+ METHOD = 'MERGE'
30
+ REQUEST_HAS_BODY = true
31
+ RESPONSE_HAS_BODY = false
32
+ end
33
+ end
34
+ end
35
+
36
+ # extendes the Symbol class to assign a type to an entity field
37
+ class Symbol
38
+ attr_accessor :edm_type
39
+ end
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'waz-storage'
3
+ # Application Files (massive include)
4
+ app_files = File.expand_path(File.join(File.dirname(__FILE__), 'waz', 'tables', '*.rb'))
5
+ Dir[app_files].each(&method(:load))
@@ -0,0 +1,121 @@
1
+ module WAZ
2
+ module Blobs
3
+ # This class is used to model the Blob inside Windows Azure Blobs the usage
4
+ # it's pretty simple, here you can see a couple of samples. These are the implemented methods of the blob API up to now.
5
+ # The basics are implemented although blocks management is not yet completed, I consider that the API is pretty usable since
6
+ # it covers the basics
7
+ #
8
+ # # retrieve blob name, uri and content-type
9
+ # blob.name
10
+ # blob.url
11
+ # blob.content_type
12
+ #
13
+ # # retrieve blob value
14
+ # blob.value #=> lazy loaded payload of the blob
15
+ #
16
+ # # retrieve blob metadata (+ properties)
17
+ # blob.metadata #=> hash containing beautified metadata (:x_ms_meta_name)
18
+ #
19
+ # # put blob metadata
20
+ # blob.put_properties(:x_ms_meta_MyProperty => "my value")
21
+ #
22
+ # # update value
23
+ # blob.value = "my new value" #=> this will update the blob content on WAZ
24
+ #
25
+ # *REMARKS*: This class is not meant to be manually instanciated it's basicaly an internal
26
+ # representation returned by the WAZ::Blobs::Container.
27
+ class BlobObject
28
+ class << self
29
+ # This method is internally used by this class. It's the way we keep a single instance of the
30
+ # service that wraps the calls the Windows Azure Blobs API. It's initialized with the values
31
+ # from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
32
+ def service_instance
33
+ options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "blob")
34
+ (@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
35
+ end
36
+ end
37
+
38
+ attr_accessor :name, :url, :content_type, :snapshot_date
39
+
40
+ # Creates a new instance of the Blob object. This constructor is internally used by the Container
41
+ # it's initialized thru a hash that's received as parameter. It has the following requirements:
42
+ # <em>:name</em> which is the blob name (usually the file name), <em>:url</em> that is the url of the blob (used to download or access it via browser)
43
+ # and <em>:content_type</em> which is the content type of the blob and is a required parameter by the Azure API
44
+ def initialize(options = {})
45
+ raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
46
+ raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url) and !options[:url].empty?
47
+ raise WAZ::Storage::InvalidOption, :content_type unless options.keys.include?(:content_type) and !options[:content_type].empty?
48
+ self.name = options[:name]
49
+ self.url = options[:url]
50
+ self.content_type = options[:content_type]
51
+ self.snapshot_date = options[:snapshot_date]
52
+ end
53
+
54
+ # Returns the blob properties from Windows Azure. This properties always come as HTTP Headers and they include standard headers like
55
+ # <em>Content-Type</em>, <em>Content-Length</em>, etc. combined with custom properties like with <em>x-ms-meta-Name</em>.
56
+ def metadata
57
+ self.class.service_instance.get_blob_properties(path)
58
+ end
59
+
60
+ # Returns the actual blob content, this method is specially used to avoid retrieving the whole blob
61
+ # while iterating and retrieving the blob collection from the Container.
62
+ def value
63
+ @value ||= self.class.service_instance.get_blob(path)
64
+ end
65
+
66
+ # Assigns the given value to the blob content. It also stores a local copy of it in order to avoid round trips
67
+ # on scenarios when you do Save and Display on the same context.
68
+ def value=(new_value)
69
+ raise WAZ::Blobs::InvalidOperation if self.snapshot_date
70
+ self.class.service_instance.put_blob(path, new_value, content_type, metadata)
71
+ @value = new_value
72
+ end
73
+
74
+ # Stores the blob properties. Those properties are sent as HTTP Headers it's really important that you name your custom
75
+ # properties with the <em>x-ms-meta</em> prefix, if not the won't be persisted by the Windows Azure Blob Storage API.
76
+ def put_properties!(properties = {})
77
+ raise WAZ::Blobs::InvalidOperation if self.snapshot_date
78
+ self.class.service_instance.set_blob_properties(path, properties)
79
+ end
80
+
81
+ # Stores blob metadata. User metadata must be prefixed with 'x-ms-meta-'. The advantage of this over put_properties
82
+ # is that it only affect user_metadata and doesn't overwrite any system values, like 'content_type'.
83
+ def put_metadata!(metadata = {})
84
+ self.class.service_instance.set_blob_metadata(path, metadata)
85
+ end
86
+
87
+ # Removes the blob from the container.
88
+ def destroy!
89
+ self.class.service_instance.delete_blob(path)
90
+ end
91
+
92
+ # Copies the blob to the destination and returns
93
+ # the copied blob instance.
94
+ #
95
+ # destination should be formed as "container/blob"
96
+ def copy(destination)
97
+ self.class.service_instance.copy_blob(self.path, destination)
98
+ properties = self.class.service_instance.get_blob_properties(destination)
99
+ return BlobObject.new(:name => destination,
100
+ :url => self.class.service_instance.generate_request_uri(destination),
101
+ :content_type => properties[:content_type])
102
+ end
103
+
104
+ # Creates and returns a read-only snapshot of a blob as it looked like in time.
105
+ def snapshot
106
+ date = self.class.service_instance.snapshot_blob(self.path)
107
+ properties = self.class.service_instance.get_blob_properties(self.path)
108
+ return BlobObject.new(:name => self.name,
109
+ :url => self.class.service_instance.generate_request_uri(self.path) + "?snapshot=#{date}",
110
+ :content_type => properties[:content_type],
111
+ :snapshot_date => date)
112
+ end
113
+
114
+ # Returns the blob path. This is specially important when simulating containers inside containers
115
+ # by enabling the API to point to the appropiated resource.
116
+ def path
117
+ url.gsub(/https?:\/\/[^\/]+\//i, '').scan(/([^&]+)/i).first().first()
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,160 @@
1
+ module WAZ
2
+ module Blobs
3
+ # This class is used to model the Container inside Windows Azure Blobs the usage
4
+ # it's pretty simple, here you can see a couple of samples. Here you can find Microsoft's REST API description available on MSDN
5
+ # at http://msdn.microsoft.com/en-us/library/dd179361.aspx
6
+ #
7
+ # # list available containers
8
+ # WAZ::Blobs::Container.list
9
+ #
10
+ # # create a container
11
+ # WAZ::Blobs::Container.create('my-container')
12
+ #
13
+ # # get a specific container
14
+ # my_container = WAZ::Blobs::Container.find('my-container')
15
+ #
16
+ # # get container properties (including default headers)
17
+ # my_container.metadata #=> hash containing beautified metadata (:x_ms_meta_name)
18
+ #
19
+ # # set container properties (should follow x-ms-meta to be persisted)
20
+ # my_container.put_properties(:x_ms_meta_MyProperty => "my value")
21
+ #
22
+ # # get a the value indicating whether the container is public or not
23
+ # my_container.public_access? #=> true or false based on x-ms-prop-publicaccess
24
+ #
25
+ # # set a value indicating whether the container is public or not
26
+ # my_container.public_access = false
27
+ #
28
+ # # delete container
29
+ # my_container.destroy!
30
+ #
31
+ # # store a blob on the given container
32
+ # my_container.store('my-blob', blob_content, 'application/xml')
33
+ #
34
+ # # retrieve a particular blob from a container
35
+ # my_container['my-blob']
36
+ #
37
+ # # retrieve a blob list from a container
38
+ # my_container.blobs #=> WAZ::Blobs::BlobObject collection
39
+ #
40
+ class Container
41
+ class << self
42
+ # Creates a new container with the given name.
43
+ def create(name)
44
+ raise WAZ::Storage::InvalidParameterValue, {:name => "name", :values => ["lower letters, numbers or - (hypen), and must not start or end with - (hyphen)"]} unless WAZ::Storage::ValidationRules.valid_name?(name)
45
+ service_instance.create_container(name)
46
+ return Container.new(:name => name)
47
+ end
48
+
49
+ # Finds a container by name. It will return nil if no container was found.
50
+ def find(name)
51
+ begin
52
+ properties = service_instance.get_container_properties(name)
53
+ return Container.new(properties.merge(:name => name))
54
+ rescue RestClient::ResourceNotFound
55
+ return nil
56
+ end
57
+ end
58
+
59
+ # Returns all the containers on the given account.
60
+ def list(options = {})
61
+ service_instance.list_containers(options).map { |container| Container.new(container) }
62
+ end
63
+
64
+ # This method is internally used by this class. It's the way we keep a single instance of the
65
+ # service that wraps the calls the Windows Azure Blobs API. It's initialized with the values
66
+ # from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
67
+ def service_instance
68
+ options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "blob")
69
+ (@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
70
+ end
71
+ end
72
+
73
+ attr_accessor :name
74
+
75
+ # Creates a new instance of the WAZ::Blobs::Container. This class isn't intended for external use
76
+ # to access or create a container you should use the class methods provided like list, create, or find.
77
+ def initialize(options = {})
78
+ raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
79
+ self.name = options[:name]
80
+ end
81
+
82
+ # Returns the container metadata.
83
+ def metadata
84
+ self.class.service_instance.get_container_properties(self.name)
85
+ end
86
+
87
+ # Adds metadata for the container.Those properties are sent as HTTP Headers it's really important that you name your custom
88
+ # properties with the <em>x-ms-meta</em> prefix, if not the won't be persisted by the Windows Azure Blob Storage API.
89
+ def put_properties!(properties = {})
90
+ self.class.service_instance.set_container_properties(self.name, properties)
91
+ end
92
+
93
+ # Removes the container from the current account.
94
+ def destroy!
95
+ self.class.service_instance.delete_container(self.name)
96
+ end
97
+
98
+ # Retuns a value indicating whether the container is public accessible (i.e. from a Web Browser) or not.
99
+ def public_access?
100
+ self.class.service_instance.get_container_acl(self.name)
101
+ end
102
+
103
+ # Sets a value indicating whether the container is public accessible (i.e. from a Web Browser) or not.
104
+ def public_access=(value)
105
+ self.class.service_instance.set_container_acl(self.name, value)
106
+ end
107
+
108
+ # Returns a list of blobs (WAZ::Blobs::BlobObject) contained on the current container.
109
+ def blobs
110
+ self.class.service_instance.list_blobs(name).map { |blob| WAZ::Blobs::BlobObject.new(blob) }
111
+ end
112
+
113
+ # Stores a blob on the container with under the given name, with the given content and
114
+ # the required <em>content_type</em>. <strong>The <em>options</em> parameters if provided
115
+ # will set the default metadata properties for the blob</strong>.
116
+ def store(blob_name, payload, content_type, options = {})
117
+ blob_name.gsub!(%r{^/}, '')
118
+ self.class.service_instance.put_blob("#{self.name}/#{blob_name}", payload, content_type, options)
119
+ return BlobObject.new(:name => blob_name,
120
+ :url => self.class.service_instance.generate_request_uri("#{self.name}/#{blob_name}"),
121
+ :content_type => content_type)
122
+ end
123
+
124
+ # Uploads the contents of a stream to the specified blob within this container, using
125
+ # the required <em>content_type</em>. The stream will be uploaded in blocks of size
126
+ # <em>block_size</em> bytes, which defaults to four megabytes. <strong>The <em>options</em>
127
+ # parameter, if provided, will set the default metadata properties for the blob</strong>.
128
+ def upload(blob_name, stream, content_type, options = {}, block_size = 4 * 2**20)
129
+ blob_name.gsub!(%r{^/}, '')
130
+ path = "#{self.name}/#{blob_name}"
131
+ n = 0
132
+ until stream.eof?
133
+ self.class.service_instance.put_block path, Base64.encode64('%064d' % n), stream.read(block_size)
134
+ n += 1
135
+ end
136
+ self.class.service_instance.put_block_list path, (0...n).map{|id| Base64.encode64('%064d' % id)}, content_type, options
137
+ return BlobObject.new(:name => blob_name, :url => self.class.service_instance.generate_request_uri("#{self.name}/#{blob_name}"), :content_type => content_type)
138
+ end
139
+
140
+ # Retrieves the blob by the given name. If the blob isn't found on the current container
141
+ # it will return nil instead of throwing an exception.
142
+ def [](blob_name)
143
+ begin
144
+ blob_name.gsub!(%r{^/}, '')
145
+ properties = self.class.service_instance.get_blob_properties("#{self.name}/#{blob_name}")
146
+ return BlobObject.new(:name => blob_name,
147
+ :url => self.class.service_instance.generate_request_uri("#{self.name}/#{blob_name}"),
148
+ :content_type => properties[:content_type])
149
+ rescue RestClient::ResourceNotFound
150
+ return nil
151
+ end
152
+ end
153
+ end
154
+ class BlobSecurity
155
+ Container = 'container'
156
+ Blob = 'blob'
157
+ Private = ''
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,11 @@
1
+ module WAZ
2
+ module Blobs
3
+ # This exception is raised when the user tries to perform an operation over a snapshoted blob. Since
4
+ # Snapshots are read-only copies of the original blob they cannot be modified
5
+ class InvalidOperation < WAZ::Storage::StorageException
6
+ def initialize()
7
+ super("A snapshoted blob cannot be modified.")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,156 @@
1
+ module WAZ
2
+ module Blobs
3
+ # This is internally used by the waz-blobs part of the gem and it exposes the Windows Azure Blob API REST methods
4
+ # implementation. You can use this class to perform an specific operation that isn't provided by the current API.
5
+ class Service
6
+ include WAZ::Storage::SharedKeyCoreService
7
+
8
+ # Creates a container on the current Windows Azure Storage account.
9
+ def create_container(container_name)
10
+ execute :put, container_name, {:restype => 'container'}, {:x_ms_version => '2009-09-19'}
11
+ end
12
+
13
+ # Retrieves all the properties existing on the container.
14
+ def get_container_properties(container_name)
15
+ execute(:get, container_name, {:restype => 'container'}, {:x_ms_version => '2009-09-19'}).headers
16
+ end
17
+
18
+ # Set the container properties (metadata).
19
+ #
20
+ # Remember that custom properties should be named as :x_ms_meta_{propertyName} in order
21
+ # to have Windows Azure to persist them.
22
+ def set_container_properties(container_name, properties = {})
23
+ execute :put, container_name, { :restype => 'container', :comp => 'metadata' }, properties.merge!({:x_ms_version => '2009-09-19'})
24
+ end
25
+
26
+ # Retrieves the value of the :x_ms_prop_publicaccess header from the
27
+ # container properties indicating whether the container is publicly
28
+ # accessible or not.
29
+ def get_container_acl(container_name)
30
+ headers = execute(:get, container_name, { :restype => 'container', :comp => 'acl' }, {:x_ms_version => '2009-09-19'}).headers
31
+ headers[:x_ms_blob_public_access]
32
+ end
33
+
34
+ # Sets the value of the :x_ms_prop_publicaccess header from the
35
+ # container properties indicating whether the container is publicly
36
+ # accessible or not.
37
+ #
38
+ # Default is _false_
39
+ def set_container_acl(container_name, public_available = WAZ::Blobs::BlobSecurity::Private)
40
+ publicity = {:x_ms_version => '2009-09-19' }
41
+ publicity[:x_ms_blob_public_access] = public_available unless public_available == WAZ::Blobs::BlobSecurity::Private
42
+ execute :put, container_name, { :restype => 'container', :comp => 'acl' }, publicity
43
+ end
44
+
45
+ # Lists all the containers existing on the current storage account.
46
+ def list_containers(options = {})
47
+ content = execute(:get, nil, options.merge(:comp => 'list'))
48
+ doc = REXML::Document.new(content)
49
+ containers = []
50
+ REXML::XPath.each(doc, '//Container/') do |item|
51
+ containers << { :name => REXML::XPath.first(item, "Name").text,
52
+ :url => REXML::XPath.first(item, "Url").text,
53
+ :last_modified => REXML::XPath.first(item, "LastModified").text}
54
+ end
55
+ return containers
56
+ end
57
+
58
+ # Deletes the given container from the Windows Azure Storage account.
59
+ def delete_container(container_name)
60
+ execute :delete, container_name, {:restype => 'container'}, {:x_ms_version => '2009-09-19'}
61
+ end
62
+
63
+ # Lists all the blobs inside the given container.
64
+ def list_blobs(container_name)
65
+ content = execute(:get, container_name, { :restype => 'container', :comp => 'list'}, {:x_ms_version => '2009-09-19'})
66
+ doc = REXML::Document.new(content)
67
+ containers = []
68
+ REXML::XPath.each(doc, '//Blob/') do |item|
69
+ containers << { :name => REXML::XPath.first(item, "Name").text,
70
+ :url => REXML::XPath.first(item, "Url").text,
71
+ :content_type => REXML::XPath.first(item.elements["Properties"], "Content-Type").text }
72
+
73
+ end
74
+ return containers
75
+ end
76
+
77
+ # Stores a blob on the given container.
78
+ #
79
+ # Remarks path and payload are just text.
80
+ #
81
+ # content_type is required by the blobs api, but on this method is defaulted to "application/octect-stream"
82
+ #
83
+ # metadata is a hash that stores all the properties that you want to add to the blob when creating it.
84
+ def put_blob(path, payload, content_type = "application/octet-stream", metadata = {})
85
+ default_headers = {"Content-Type" => content_type, :x_ms_version => "2009-09-19", :x_ms_blob_type => "BlockBlob"}
86
+ execute :put, path, nil, metadata.merge(default_headers), payload
87
+ end
88
+
89
+ # Commits a list of blocks to the given blob.
90
+ #
91
+ # blockids is a list of valid, already-uploaded block IDs (base64-encoded)
92
+ #
93
+ # content_type is required by the blobs api, but on this method is defaulted to "application/octect-stream"
94
+ #
95
+ # metadata is a hash that stores all the properties that you want to add to the blob when creating it.
96
+ def put_block_list(path, blockids, content_type = "application/octet-stream", metadata = {})
97
+ default_headers = {"Content-Type" => content_type, :x_ms_version => "2009-09-19"}
98
+ execute :put, path, { :comp => 'blocklist' }, metadata.merge(default_headers), '<?xml version="1.0" encoding="utf-8"?><BlockList>' + blockids.map {|id| "<Latest>#{id.rstrip}</Latest>"}.join + '</BlockList>'
99
+ end
100
+
101
+ # Retrieves a blob (content + headers) from the current path.
102
+ def get_blob(path, options = {})
103
+ execute :get, path, options, {:x_ms_version => "2009-09-19"}
104
+ end
105
+
106
+ # Deletes the blob existing on the current path.
107
+ def delete_blob(path)
108
+ execute :delete, path, nil, {:x_ms_version => "2009-09-19"}
109
+ end
110
+
111
+ # Retrieves the properties associated with the blob at the given path.
112
+ def get_blob_properties(path, options = {})
113
+ execute(:head, path, options, {:x_ms_version => "2009-09-19"}).headers
114
+ end
115
+
116
+ # Sets the properties (metadata) associated to the blob at given path.
117
+ def set_blob_properties(path, properties ={})
118
+ execute :put, path, { :comp => 'properties' }, properties.merge({:x_ms_version => "2009-09-19"})
119
+ end
120
+
121
+ # Set user defined metadata - overwrites any previous metadata key:value pairs
122
+ def set_blob_metadata(path, metadata = {})
123
+ execute :put, path, { :comp => 'metadata' }, metadata.merge({:x_ms_version => "2009-09-19"})
124
+ end
125
+
126
+ # Copies a blob within the same account (not necessarily to the same container)
127
+ def copy_blob(source_path, dest_path)
128
+ execute :put, dest_path, nil, { :x_ms_version => "2009-09-19", :x_ms_copy_source => canonicalize_message(source_path) }
129
+ end
130
+
131
+ # Adds a block to the block list of the given blob
132
+ def put_block(path, identifier, payload)
133
+ execute :put, path, { :comp => 'block', :blockid => identifier }, {'Content-Type' => "application/octet-stream"}, payload
134
+ end
135
+
136
+ # Retrieves the list of blocks associated with a single blob. The list is filtered (or not) by type of blob
137
+ def list_blocks(path, block_list_type = 'all')
138
+ raise WAZ::Storage::InvalidParameterValue , {:name => :blocklisttype, :values => ['all', 'uncommitted', 'committed']} unless (block_list_type or "") =~ /all|committed|uncommitted/i
139
+ content = execute(:get, path, {:comp => 'blocklist'}.merge(:blocklisttype => block_list_type.downcase), { :x_ms_version => "2009-04-14" })
140
+ doc = REXML::Document.new(content)
141
+ blocks = []
142
+ REXML::XPath.each(doc, '//Block/') do |item|
143
+ blocks << { :name => REXML::XPath.first(item, "Name").text,
144
+ :size => REXML::XPath.first(item, "Size").text,
145
+ :committed => item.parent.name == "CommittedBlocks" }
146
+ end
147
+ return blocks
148
+ end
149
+
150
+ # Creates a read-only snapshot of a blob as it looked like in time.
151
+ def snapshot_blob(path)
152
+ execute(:put, path, { :comp => 'snapshot' }, {:x_ms_version => "2009-09-19"}).headers[:x_ms_snapshot]
153
+ end
154
+ end
155
+ end
156
+ end