sparqcode-waz-storage 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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