waz-storage 0.5.4 → 0.5.6

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