waz-storage 0.5.4 → 0.5.6

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.
@@ -80,11 +80,23 @@ module WAZ
80
80
  self.class.service_instance.delete_blob(path)
81
81
  end
82
82
 
83
+ # Copies the blob to the destination and returns
84
+ # the copied blob instance.
85
+ #
86
+ # destination should be formed as "container/blob"
87
+ def copy(destination)
88
+ self.class.service_instance.copy_blob(self.path, destination)
89
+ properties = self.class.service_instance.get_blob_properties(destination)
90
+ return BlobObject.new(:name => destination,
91
+ :url => self.class.service_instance.generate_request_uri(destination),
92
+ :content_type => properties[:content_type])
93
+ end
94
+
83
95
  # Returns the blob path. This is specially important when simulating containers inside containers
84
96
  # by enabling the API to point to the appropiated resource.
85
97
  def path
86
98
  url.gsub(/https?:\/\/[^\/]+\//i, '').scan(/([^&]+)/i).first().first()
87
- end
99
+ end
88
100
  end
89
101
  end
90
102
  end
@@ -7,16 +7,12 @@ module WAZ
7
7
 
8
8
  # Creates a container on the current Windows Azure Storage account.
9
9
  def create_container(container_name)
10
- url = generate_request_uri(container_name)
11
- request = generate_request("PUT", url)
12
- request.execute()
10
+ execute :put, container_name
13
11
  end
14
12
 
15
13
  # Retrieves all the properties existing on the container.
16
14
  def get_container_properties(container_name)
17
- url = generate_request_uri(container_name)
18
- request = generate_request("GET", url)
19
- request.execute().headers
15
+ execute(:get, container_name).headers
20
16
  end
21
17
 
22
18
  # Set the container properties (metadata).
@@ -24,18 +20,15 @@ module WAZ
24
20
  # Remember that custom properties should be named as :x_ms_meta_{propertyName} in order
25
21
  # to have Windows Azure to persist them.
26
22
  def set_container_properties(container_name, properties = {})
27
- url = generate_request_uri(container_name, :comp => 'metadata')
28
- request = generate_request("PUT", url, properties)
29
- request.execute()
23
+ execute :put, container_name, { :comp => 'metadata' }, properties
30
24
  end
31
25
 
32
26
  # Retrieves the value of the :x_ms_prop_publicaccess header from the
33
27
  # container properties indicating whether the container is publicly
34
28
  # accessible or not.
35
29
  def get_container_acl(container_name)
36
- url = generate_request_uri(container_name, :comp => 'acl')
37
- request = generate_request("GET", url)
38
- request.execute().headers[:x_ms_prop_publicaccess].downcase == true.to_s
30
+ headers = execute(:get, container_name, { :comp => 'acl' }).headers
31
+ headers[:x_ms_prop_publicaccess].downcase == true.to_s
39
32
  end
40
33
 
41
34
  # Sets the value of the :x_ms_prop_publicaccess header from the
@@ -44,16 +37,13 @@ module WAZ
44
37
  #
45
38
  # Default is _false_
46
39
  def set_container_acl(container_name, public_available = false)
47
- url = generate_request_uri(container_name, :comp => 'acl')
48
- request = generate_request("PUT", url, "x-ms-prop-publicaccess" => public_available.to_s)
49
- request.execute()
40
+ execute :put, container_name, { :comp => 'acl' }, { :x_ms_prop_publicaccess => public_available.to_s }
50
41
  end
51
42
 
52
43
  # Lists all the containers existing on the current storage account.
53
44
  def list_containers(options = {})
54
- url = generate_request_uri( nil, options.merge(:comp => 'list'))
55
- request = generate_request("GET", url)
56
- doc = REXML::Document.new(request.execute())
45
+ content = execute(:get, nil, options.merge(:comp => 'list'))
46
+ doc = REXML::Document.new(content)
57
47
  containers = []
58
48
  REXML::XPath.each(doc, '//Container/') do |item|
59
49
  containers << { :name => REXML::XPath.first(item, "Name").text,
@@ -65,16 +55,13 @@ module WAZ
65
55
 
66
56
  # Deletes the given container from the Windows Azure Storage account.
67
57
  def delete_container(container_name)
68
- url = generate_request_uri(container_name)
69
- request = generate_request("DELETE", url)
70
- request.execute()
58
+ execute :delete, container_name
71
59
  end
72
60
 
73
61
  # Lists all the blobs inside the given container.
74
62
  def list_blobs(container_name)
75
- url = generate_request_uri(container_name, :comp => 'list')
76
- request = generate_request("GET", url)
77
- doc = REXML::Document.new(request.execute())
63
+ content = execute(:get, container_name, { :comp => 'list' })
64
+ doc = REXML::Document.new(content)
78
65
  containers = []
79
66
  REXML::XPath.each(doc, '//Blob/') do |item|
80
67
  containers << { :name => REXML::XPath.first(item, "Name").text,
@@ -92,37 +79,51 @@ module WAZ
92
79
  #
93
80
  # metadata is a hash that stores all the properties that you want to add to the blob when creating it.
94
81
  def put_blob(path, payload, content_type = "application/octet-stream", metadata = {})
95
- url = generate_request_uri( path)
96
- request = generate_request("PUT", url, metadata.merge("Content-Type" => content_type), payload)
97
- request.execute()
82
+ execute :put, path, nil, metadata.merge("Content-Type" => content_type), payload
98
83
  end
99
84
 
100
85
  # Retrieves a blob (content + headers) from the current path.
101
- def get_blob(path)
102
- url = generate_request_uri( path)
103
- request = generate_request("GET", url)
104
- request.execute()
86
+ def get_blob(path, options = {})
87
+ execute :get, path, options
105
88
  end
106
89
 
107
90
  # Deletes the blob existing on the current path.
108
91
  def delete_blob(path)
109
- url = generate_request_uri( path)
110
- request = generate_request("DELETE", url)
111
- request.execute()
92
+ execute :delete, path
112
93
  end
113
94
 
114
95
  # Retrieves the properties associated with the blob at the given path.
115
- def get_blob_properties(path)
116
- url = generate_request_uri( path)
117
- request = generate_request("HEAD", url)
118
- request.execute().headers
96
+ def get_blob_properties(path, options = {})
97
+ execute(:head, path, options).headers
119
98
  end
120
99
 
121
100
  # Sets the properties (metadata) associated to the blob at given path.
122
101
  def set_blob_properties(path, properties ={})
123
- url = generate_request_uri( path, :comp => 'metadata')
124
- request = generate_request("PUT", url, properties)
125
- request.execute()
102
+ execute :put, path, { :comp => 'metadata' }, properties
103
+ end
104
+
105
+ # Copies a blob within the same account (not necessarily to the same container)
106
+ def copy_blob(source_path, dest_path)
107
+ execute :put, dest_path, nil, { :x_ms_version => "2009-04-14", :x_ms_copy_source => canonicalize_message(source_path) }
108
+ end
109
+
110
+ # Adds a block to the block list of the given blob
111
+ def put_block(path, identifier, payload)
112
+ execute :put, path, { :comp => 'block', :blockid => identifier }, {'Content-Type' => "application/octet-stream"}, payload
113
+ end
114
+
115
+ # Retrieves the list of blocks associated with a single blob. The list is filtered (or not) by type of blob
116
+ def list_blocks(path, block_list_type = 'all')
117
+ raise WAZ::Storage::InvalidParameterValue , {:name => :bloclisttype, :values => ['all', 'uncommitted', 'committed']} unless (block_list_type or "") =~ /all|committed|uncommitted/i
118
+ content = execute(:get, path, {:comp => 'blocklist'}.merge(:blocklisttype => block_list_type.downcase), { :x_ms_version => "2009-04-14" })
119
+ doc = REXML::Document.new(content)
120
+ blocks = []
121
+ REXML::XPath.each(doc, '//Block/') do |item|
122
+ blocks << { :name => REXML::XPath.first(item, "Name").text,
123
+ :size => REXML::XPath.first(item, "Size").text,
124
+ :committed => item.parent.name == "CommittedBlocks" }
125
+ end
126
+ return blocks
126
127
  end
127
128
  end
128
129
  end
@@ -33,7 +33,7 @@ module WAZ
33
33
  end
34
34
  end
35
35
 
36
- attr_accessor :message_id, :message_text, :pop_receipt, :expiration_time, :insertion_time, :time_next_visible
36
+ attr_accessor :message_id, :message_text, :pop_receipt, :expiration_time, :insertion_time, :time_next_visible, :dequeue_count
37
37
 
38
38
  # Creates an instance of Message class, this method is intended to be used internally from the
39
39
  # Queue.
@@ -44,6 +44,7 @@ module WAZ
44
44
  self.expiration_time = params[:expiration_time]
45
45
  self.insertion_time = params[:insertion_time]
46
46
  self.time_next_visible = params[:time_next_visible]
47
+ self.dequeue_count = params[:dequeue_count]
47
48
  @queue_name = params[:queue_name]
48
49
  end
49
50
 
@@ -42,8 +42,11 @@ module WAZ
42
42
  class << self
43
43
  # Returns an array of the queues (WAZ::Queues::Queue) existing on the current
44
44
  # Windows Azure Storage account.
45
- def list
46
- service_instance.list_queues.map do |queue|
45
+ #
46
+ # include_metadata defines if the metadata is retrieved along with queue data.
47
+ def list(include_metadata = false)
48
+ options = include_metadata ? { :include => 'metadata' } : {}
49
+ service_instance.list_queues(options).map do |queue|
47
50
  WAZ::Queues::Queue.new(queue)
48
51
  end
49
52
  end
@@ -60,8 +63,8 @@ module WAZ
60
63
  # return nil shilding the user from a ResourceNotFound exception.
61
64
  def find(queue_name)
62
65
  begin
63
- service_instance.get_queue_metadata(queue_name)
64
- WAZ::Queues::Queue.new(:name => queue_name, :url => service_instance.generate_request_uri(queue_name))
66
+ metadata = service_instance.get_queue_metadata(queue_name)
67
+ WAZ::Queues::Queue.new(:name => queue_name, :url => service_instance.generate_request_uri(queue_name), :metadata => metadata)
65
68
  rescue RestClient::ResourceNotFound
66
69
  return nil
67
70
  end
@@ -76,13 +79,14 @@ module WAZ
76
79
  end
77
80
  end
78
81
 
79
- attr_accessor :name, :url
82
+ attr_accessor :name, :url, :metadata
80
83
 
81
84
  def initialize(options = {})
82
85
  raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name)
83
86
  raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url)
84
87
  self.name = options[:name]
85
88
  self.url = options[:url]
89
+ self.metadata = options[:metadata]
86
90
  end
87
91
 
88
92
  # Deletes the queue from the current storage account.
@@ -92,7 +96,7 @@ module WAZ
92
96
 
93
97
  # Retrieves the metadata headers associated with the quere.
94
98
  def metadata
95
- self.class.service_instance.get_queue_metadata(self.name)
99
+ metadata ||= self.class.service_instance.get_queue_metadata(self.name)
96
100
  end
97
101
 
98
102
  # Sets the metadata given on the new_metadata, when overwrite passed different
@@ -6,15 +6,25 @@ module WAZ
6
6
  include WAZ::Storage::SharedKeyCoreService
7
7
 
8
8
  # Lists the queues on the given storage account.
9
+ #
10
+ # When the options :include => 'metadata' is passed it returns
11
+ # the corresponding metadata for each queue on the listing.
9
12
  def list_queues(options ={})
10
- url = generate_request_uri(nil, :comp => 'list')
11
- request = generate_request("GET", url)
12
- doc = REXML::Document.new(request.execute())
13
+ content = execute(:get, nil, { :comp => 'list' }.merge!(options), { :x_ms_version => "2009-09-19" })
14
+ doc = REXML::Document.new(content)
13
15
  queues = []
14
- REXML::XPath.each(doc, '//Queue/') do |item|
15
- queues << { :name => REXML::XPath.first(item, "QueueName").text,
16
- :url => REXML::XPath.first(item, "Url").text }
17
- end
16
+
17
+ REXML::XPath.each(doc, '//Queue/') do |item|
18
+ metadata = {}
19
+
20
+ item.elements['Metadata'].elements.each do |element|
21
+ metadata.merge!(element.name.gsub(/-/, '_').downcase.to_sym => element.text)
22
+ end unless item.elements['Metadata'].nil?
23
+
24
+ queues << { :name => REXML::XPath.first(item, "Name").text,
25
+ :url => REXML::XPath.first(item, "Url").text,
26
+ :metadata => metadata}
27
+ end
18
28
  return queues
19
29
  end
20
30
 
@@ -22,9 +32,7 @@ module WAZ
22
32
  # existing metadata and given metadata differ.
23
33
  def create_queue(queue_name, metadata = {})
24
34
  begin
25
- url = generate_request_uri(queue_name)
26
- request = generate_request("PUT", url, metadata)
27
- request.execute()
35
+ execute :put, queue_name, nil, metadata.merge!(:x_ms_version => '2009-09-19')
28
36
  rescue RestClient::RequestFailed
29
37
  raise WAZ::Queues::QueueAlreadyExists, queue_name if $!.http_code == 409
30
38
  end
@@ -32,23 +40,17 @@ module WAZ
32
40
 
33
41
  # Deletes the given queue from the current storage account.
34
42
  def delete_queue(queue_name)
35
- url = generate_request_uri(queue_name)
36
- request = generate_request("DELETE", url)
37
- request.execute()
43
+ execute(:delete, queue_name, {}, {:x_ms_version => '2009-09-19'})
38
44
  end
39
45
 
40
46
  # Gets the given queue metadata.
41
47
  def get_queue_metadata(queue_name)
42
- url = generate_request_uri(queue_name, :comp => 'metadata')
43
- request = generate_request("HEAD", url)
44
- request.execute().headers
48
+ execute(:head, queue_name, { :comp => 'metadata'}, :x_ms_version => '2009-09-19').headers
45
49
  end
46
50
 
47
51
  # Sets the given queue metadata.
48
52
  def set_queue_metadata(queue_name, metadata = {})
49
- url = generate_request_uri(queue_name, :comp => 'metadata')
50
- request = generate_request("PUT", url, metadata)
51
- request.execute()
53
+ execute(:put, queue_name, { :comp => 'metadata' }, metadata.merge!(:x_ms_version => '2009-09-19'))
52
54
  end
53
55
 
54
56
  # Enqueues a message on the current queue.
@@ -56,10 +58,8 @@ module WAZ
56
58
  # ttl Specifies the time-to-live interval for the message, in seconds. The maximum time-to-live allowed is 7 days. If this parameter
57
59
  # is omitted, the default time-to-live is 7 days.
58
60
  def enqueue(queue_name, message_payload, ttl = 604800)
59
- url = generate_request_uri("#{queue_name}/messages", "messagettl" => ttl)
60
61
  payload = "<?xml version=\"1.0\" encoding=\"utf-8\"?><QueueMessage><MessageText>#{message_payload}</MessageText></QueueMessage>"
61
- request = generate_request("POST", url, { "Content-Type" => "application/xml" }, payload)
62
- request.execute()
62
+ execute(:post, "#{queue_name}/messages", { :messagettl => ttl }, { 'Content-Type' => 'application/xml', :x_ms_version => "2009-09-19"}, payload)
63
63
  end
64
64
 
65
65
  # Locks N messages (1 default) from the given queue.
@@ -70,13 +70,13 @@ module WAZ
70
70
  def get_messages(queue_name, options = {})
71
71
  raise WAZ::Queues::OptionOutOfRange, {:name => :num_of_messages, :min => 1, :max => 32} if (options.keys.include?(:num_of_messages) && (options[:num_of_messages].to_i < 1 || options[:num_of_messages].to_i > 32))
72
72
  raise WAZ::Queues::OptionOutOfRange, {:name => :visibility_timeout, :min => 1, :max => 7200} if (options.keys.include?(:visibility_timeout) && (options[:visibility_timeout].to_i < 1 || options[:visibility_timeout].to_i > 7200))
73
- url = generate_request_uri("#{queue_name}/messages", options)
74
- request = generate_request("GET", url)
75
- doc = REXML::Document.new(request.execute())
73
+ content = execute(:get, "#{queue_name}/messages", options, {:x_ms_version => "2009-09-19"})
74
+ doc = REXML::Document.new(content)
76
75
  messages = []
77
76
  REXML::XPath.each(doc, '//QueueMessage/') do |item|
78
77
  message = { :message_id => REXML::XPath.first(item, "MessageId").text,
79
78
  :message_text => REXML::XPath.first(item, "MessageText").text,
79
+ :dequeue_count => REXML::XPath.first(item, "DequeueCount").nil? ? nil : REXML::XPath.first(item, "DequeueCount").text.to_i,
80
80
  :expiration_time => Time.httpdate(REXML::XPath.first(item, "ExpirationTime").text),
81
81
  :insertion_time => Time.httpdate(REXML::XPath.first(item, "InsertionTime").text) }
82
82
 
@@ -98,16 +98,12 @@ module WAZ
98
98
  # Deletes the given message from the queue, correlating the operation with the pop_receipt
99
99
  # in order to avoid eventually inconsistent scenarios.
100
100
  def delete_message(queue_name, message_id, pop_receipt)
101
- url = generate_request_uri("#{queue_name}/messages/#{message_id}", :pop_receipt => pop_receipt)
102
- request = generate_request("DELETE", url)
103
- request.execute()
101
+ execute :delete, "#{queue_name}/messages/#{message_id}", { :pop_receipt => pop_receipt }
104
102
  end
105
103
 
106
104
  # Marks every message on the given queue for deletion.
107
105
  def clear_queue(queue_name)
108
- url = generate_request_uri("#{queue_name}/messages")
109
- request = generate_request("DELETE", url)
110
- request.execute()
106
+ execute :delete, "#{queue_name}/messages", {}, :x_ms_version => '2009-09-19'
111
107
  end
112
108
  end
113
109
  end
@@ -19,7 +19,7 @@ module WAZ
19
19
  def generate_request(verb, url, headers = {}, payload = nil)
20
20
  http_headers = {}
21
21
  headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v} unless headers.nil?
22
- request = RestClient::Request.new(:method => verb.downcase.to_sym, :url => url, :headers => http_headers, :payload => payload)
22
+ request = RestClient::Request.new(:method => verb.to_s.downcase.to_sym, :url => url, :headers => http_headers, :payload => payload)
23
23
  request.headers["x-ms-Date"] = Time.new.httpdate
24
24
  request.headers["Content-Length"] = (request.payload or "").length
25
25
  request.headers["Authorization"] = "SharedKey #{account_name}:#{generate_signature(request)}"
@@ -56,15 +56,53 @@ module WAZ
56
56
  # the canonicalized header line and the canonical form of the message, all of the joined by \n character. Encoded with
57
57
  # Base64 and encrypted with SHA256 using the access_key as the seed.
58
58
  def generate_signature(request)
59
- signature = request.method.to_s.upcase + "\x0A" +
59
+ return generate_signature20090919(request) if request.headers["x-ms-version"] == "2009-09-19"
60
+ signature = request.method.to_s.upcase + "\x0A" +
60
61
  (request.headers["Content-MD5"] or "") + "\x0A" +
61
62
  (request.headers["Content-Type"] or "") + "\x0A" +
62
63
  (request.headers["Date"] or "")+ "\x0A" +
63
64
  canonicalize_headers(request.headers) + "\x0A" +
64
65
  canonicalize_message(request.url)
65
66
 
66
- return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
67
- end
67
+ return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
68
+ end
69
+
70
+
71
+ def generate_signature20090919(request)
72
+ signature = request.method.to_s.upcase + "\x0A" +
73
+ (request.headers["Content-Encoding"] or "") + "\x0A" +
74
+ (request.headers["Content-Language"] or "") + "\x0A" +
75
+ (request.headers["Content-Length"] or "").to_s + "\x0A" +
76
+ (request.headers["Content-MD5"] or "") + "\x0A" +
77
+ (request.headers["Content-Type"] or "") + "\x0A" +
78
+ (request.headers["Date"] or "")+ "\x0A" +
79
+ (request.headers["If-Modified-Since"] or "")+ "\x0A" +
80
+ (request.headers["If-Match"] or "")+ "\x0A" +
81
+ (request.headers["If-None-Match"] or "")+ "\x0A" +
82
+ (request.headers["If-Unmodified-Since"] or "")+ "\x0A" +
83
+ (request.headers["Range"] or "")+ "\x0A" +
84
+ canonicalize_headers(request.headers) + "\x0A" +
85
+ canonicalize_message20090919(request.url)
86
+
87
+ return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
88
+ end
89
+
90
+ def canonicalize_message20090919(url)
91
+ uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
92
+ query_component = (url.scan(/\?(.*)/i).first() or []).first()
93
+ query_component = query_component.downcase.split('&').sort{|a, b| a <=> b}.map{ |p| p.split('=').join(':') }.join("\n") if query_component
94
+ canonicalized_message = "/#{self.account_name}/#{uri_component}"
95
+ canonicalized_message << "\n#{query_component}" if query_component
96
+ return canonicalized_message
97
+ end
98
+
99
+ # Generates a Windows Azure Storage call, it internally calls url generation method
100
+ # and the request generation message.
101
+ def execute(verb, path, query = {}, headers = {}, payload = nil)
102
+ url = generate_request_uri(path, query)
103
+ request = generate_request(verb, url, headers, payload)
104
+ request.execute()
105
+ end
68
106
  end
69
107
  end
70
108
  end
@@ -21,5 +21,13 @@ module WAZ
21
21
  super("You should establish connection before using the services, the connection configuration is required.")
22
22
  end
23
23
  end
24
+
25
+ # This exception if raised when the value given for an argument doesn't fall into the permitted values. For example
26
+ # if values on the blocklisttype aren't [all|uncommitted|committed]
27
+ class InvalidParameterValue < StorageException
28
+ def initialize(args = {})
29
+ super("The value supplied for the parameter #{args[:name]} is invalid. Permitted values are #{args[:values].join(',')}")
30
+ end
31
+ end
24
32
  end
25
33
  end
@@ -3,7 +3,7 @@ module WAZ
3
3
  module VERSION #:nodoc:
4
4
  MAJOR = '0'
5
5
  MINOR = '5'
6
- TINY = '4'
6
+ TINY = '6'
7
7
  end
8
8
 
9
9
  Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waz-storage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johnny G. Halife
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-17 00:00:00 -03:00
12
+ date: 2009-11-30 00:00:00 -03:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency