waz-storage 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/waz-blobs.rb +20 -0
- data/lib/waz-queues.rb +21 -0
- data/lib/waz-storage.rb +5 -0
- data/lib/waz/blobs/blob_object.rb +90 -0
- data/lib/waz/blobs/container.rb +135 -0
- data/lib/waz/blobs/service.rb +129 -0
- data/lib/waz/queues/exceptions.rb +29 -0
- data/lib/waz/queues/message.rb +63 -0
- data/lib/waz/queues/queue.rb +154 -0
- data/lib/waz/queues/service.rb +114 -0
- data/lib/waz/storage/base.rb +38 -0
- data/lib/waz/storage/core_service.rb +70 -0
- data/lib/waz/storage/exceptions.rb +25 -0
- data/lib/waz/storage/version.rb +11 -0
- data/rakefile +53 -0
- metadata +90 -0
data/lib/waz-blobs.rb
ADDED
@@ -0,0 +1,20 @@
|
|
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
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__))
|
10
|
+
require 'waz/storage/base'
|
11
|
+
require 'waz/storage/core_service'
|
12
|
+
require 'waz/storage/exceptions'
|
13
|
+
require 'waz/storage/version'
|
14
|
+
require 'waz/blobs/blob_object'
|
15
|
+
require 'waz/blobs/container'
|
16
|
+
require 'waz/blobs/exceptions'
|
17
|
+
require 'waz/blobs/service'
|
18
|
+
require 'waz/blobs/version'
|
19
|
+
|
20
|
+
|
data/lib/waz-queues.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'cgi'
|
3
|
+
require 'base64'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'rexml/xpath'
|
6
|
+
|
7
|
+
require 'restclient'
|
8
|
+
require 'hmac-sha2'
|
9
|
+
|
10
|
+
$:.unshift(File.dirname(__FILE__))
|
11
|
+
require 'waz/storage/base'
|
12
|
+
require 'waz/storage/core_service'
|
13
|
+
require 'waz/storage/exceptions'
|
14
|
+
require 'waz/storage/version'
|
15
|
+
require 'waz/queues/exceptions'
|
16
|
+
require 'waz/queues/message'
|
17
|
+
require 'waz/queues/queue'
|
18
|
+
require 'waz/queues/service'
|
19
|
+
require 'waz/queues/version'
|
20
|
+
|
21
|
+
|
data/lib/waz-storage.rb
ADDED
@@ -0,0 +1,90 @@
|
|
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
|
+
@service_instance ||= Service.new(WAZ::Storage::Base.default_connection.merge(:type_of_service => "blob"))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :name, :url, :content_type
|
38
|
+
|
39
|
+
# Creates a new instance of the Blob object. This constructor is internally used by the Container
|
40
|
+
# it's initialized thru a hash that's received as parameter. It has the following requirements:
|
41
|
+
# <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)
|
42
|
+
# and <em>:content_type</em> which is the content type of the blob and is a required parameter by the Azure API
|
43
|
+
def initialize(options = {})
|
44
|
+
raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
|
45
|
+
raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url) and !options[:url].empty?
|
46
|
+
raise WAZ::Storage::InvalidOption, :content_type unless options.keys.include?(:content_type) and !options[:content_type].empty?
|
47
|
+
self.name = options[:name]
|
48
|
+
self.url = options[:url]
|
49
|
+
self.content_type = options[:content_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the blob properties from Windows Azure. This properties always come as HTTP Headers and they include standard headers like
|
53
|
+
# <em>Content-Type</em>, <em>Content-Length</em>, etc. combined with custom properties like with <em>x-ms-meta-Name</em>.
|
54
|
+
def metadata
|
55
|
+
self.class.service_instance.get_blob_properties(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the actual blob content, this method is specially used to avoid retrieving the whole blob
|
59
|
+
# while iterating and retrieving the blob collection from the Container.
|
60
|
+
def value
|
61
|
+
@value ||= self.class.service_instance.get_blob(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Assigns the given value to the blob content. It also stores a local copy of it in order to avoid round trips
|
65
|
+
# on scenarios when you do Save and Display on the same context.
|
66
|
+
def value=(new_value)
|
67
|
+
self.class.service_instance.put_blob(path, new_value, content_type, metadata)
|
68
|
+
@value = new_value
|
69
|
+
end
|
70
|
+
|
71
|
+
# Stores the blob properties. Those properties are sent as HTTP Headers it's really important that you name your custom
|
72
|
+
# properties with the <em>x-ms-meta</em> prefix, if not the won't be persisted by the Windows Azure Blob Storage API.
|
73
|
+
def put_properties!(properties = {})
|
74
|
+
self.class.service_instance.set_blob_properties(path, properties)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Removes the blob from the container.
|
78
|
+
def destroy!
|
79
|
+
self.class.service_instance.delete_blob(path)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the blob path. This is specially important when simulating containers inside containers
|
83
|
+
# by enabling the API to point to the appropiated resource.
|
84
|
+
def path
|
85
|
+
url.gsub(/https?:\/\/[^\/]+\//i, '').scan(/([^&]+)/i).first().first()
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,135 @@
|
|
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
|
+
service_instance.create_container(name)
|
45
|
+
return Container.new(:name => name)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Finds a container by name. It will return nil if no container was found.
|
49
|
+
def find(name)
|
50
|
+
begin
|
51
|
+
properties = service_instance.get_container_properties(name)
|
52
|
+
return Container.new(properties.merge(:name => name))
|
53
|
+
rescue RestClient::ResourceNotFound
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns all the containers on the given account.
|
59
|
+
def list(options = {})
|
60
|
+
service_instance.list_containers(options).map { |container| Container.new(container) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# This method is internally used by this class. It's the way we keep a single instance of the
|
64
|
+
# service that wraps the calls the Windows Azure Blobs API. It's initialized with the values
|
65
|
+
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
66
|
+
def service_instance
|
67
|
+
@service_instance ||= Service.new(WAZ::Storage::Base.default_connection.merge(:type_of_service => "blob"))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_accessor :name
|
72
|
+
|
73
|
+
# Creates a new instance of the WAZ::Blobs::Container. This class isn't intended for external use
|
74
|
+
# to access or create a container you should use the class methods provided like list, create, or find.
|
75
|
+
def initialize(options = {})
|
76
|
+
raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
|
77
|
+
self.name = options[:name]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the container metadata.
|
81
|
+
def metadata
|
82
|
+
self.class.service_instance.get_container_properties(self.name)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Adds metadata for the container.Those properties are sent as HTTP Headers it's really important that you name your custom
|
86
|
+
# properties with the <em>x-ms-meta</em> prefix, if not the won't be persisted by the Windows Azure Blob Storage API.
|
87
|
+
def put_properties!(properties = {})
|
88
|
+
self.class.service_instance.set_container_properties(self.name, properties)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Removes the container from the current account.
|
92
|
+
def destroy!
|
93
|
+
self.class.service_instance.delete_container(self.name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Retuns a value indicating whether the container is public accessible (i.e. from a Web Browser) or not.
|
97
|
+
def public_access?
|
98
|
+
self.class.service_instance.get_container_acl(self.name)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Sets a value indicating whether the container is public accessible (i.e. from a Web Browser) or not.
|
102
|
+
def public_access=(value)
|
103
|
+
self.class.service_instance.set_container_acl(self.name, value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a list of blobs (WAZ::Blobs::BlobObject) contained on the current container.
|
107
|
+
def blobs
|
108
|
+
self.class.service_instance.list_blobs(name).map { |blob| WAZ::Blobs::BlobObject.new(blob) }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Stores a blob on the container with under the given name, with the given content and
|
112
|
+
# the required <em>content_type</em>. <strong>The <em>options</em> parameters if provided
|
113
|
+
# will set the default metadata properties for the blob</strong>.
|
114
|
+
def store(blob_name, payload, content_type, options = {})
|
115
|
+
self.class.service_instance.put_blob("#{self.name}/#{blob_name}", payload, content_type, options)
|
116
|
+
return BlobObject.new(:name => blob_name,
|
117
|
+
:url => self.class.service_instance.generate_request_uri("#{self.name}/#{blob_name}"),
|
118
|
+
:content_type => content_type)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Retrieves the blob by the given name. If the blob isn't found on the current container
|
122
|
+
# it will return nil instead of throwing an exception.
|
123
|
+
def [](blob_name)
|
124
|
+
begin
|
125
|
+
properties = self.class.service_instance.get_blob_properties("#{self.name}/#{blob_name}")
|
126
|
+
return BlobObject.new(:name => blob_name,
|
127
|
+
:url => self.class.service_instance.generate_request_uri("#{self.name}/#{blob_name}"),
|
128
|
+
:content_type => properties[:content_type])
|
129
|
+
rescue RestClient::ResourceNotFound
|
130
|
+
return nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,129 @@
|
|
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
|
+
url = generate_request_uri(container_name)
|
11
|
+
request = generate_request("PUT", url)
|
12
|
+
request.execute()
|
13
|
+
end
|
14
|
+
|
15
|
+
# Retrieves all the properties existing on the container.
|
16
|
+
def get_container_properties(container_name)
|
17
|
+
url = generate_request_uri(container_name)
|
18
|
+
request = generate_request("GET", url)
|
19
|
+
request.execute().headers
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set the container properties (metadata).
|
23
|
+
#
|
24
|
+
# Remember that custom properties should be named as :x_ms_meta_{propertyName} in order
|
25
|
+
# to have Windows Azure to persist them.
|
26
|
+
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()
|
30
|
+
end
|
31
|
+
|
32
|
+
# Retrieves the value of the :x_ms_prop_publicaccess header from the
|
33
|
+
# container properties indicating whether the container is publicly
|
34
|
+
# accessible or not.
|
35
|
+
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
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets the value of the :x_ms_prop_publicaccess header from the
|
42
|
+
# container properties indicating whether the container is publicly
|
43
|
+
# accessible or not.
|
44
|
+
#
|
45
|
+
# Default is _false_
|
46
|
+
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()
|
50
|
+
end
|
51
|
+
|
52
|
+
# Lists all the containers existing on the current storage account.
|
53
|
+
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())
|
57
|
+
containers = []
|
58
|
+
REXML::XPath.each(doc, '//Container/') do |item|
|
59
|
+
containers << { :name => REXML::XPath.first(item, "Name").text,
|
60
|
+
:url => REXML::XPath.first(item, "Url").text,
|
61
|
+
:last_modified => REXML::XPath.first(item, "LastModified").text}
|
62
|
+
end
|
63
|
+
return containers
|
64
|
+
end
|
65
|
+
|
66
|
+
# Deletes the given container from the Windows Azure Storage account.
|
67
|
+
def delete_container(container_name)
|
68
|
+
url = generate_request_uri(container_name)
|
69
|
+
request = generate_request("DELETE", url)
|
70
|
+
request.execute()
|
71
|
+
end
|
72
|
+
|
73
|
+
# Lists all the blobs inside the given container.
|
74
|
+
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())
|
78
|
+
containers = []
|
79
|
+
REXML::XPath.each(doc, '//Blob/') do |item|
|
80
|
+
containers << { :name => REXML::XPath.first(item, "Name").text,
|
81
|
+
:url => REXML::XPath.first(item, "Url").text,
|
82
|
+
:content_type => REXML::XPath.first(item, "ContentType").text }
|
83
|
+
end
|
84
|
+
return containers
|
85
|
+
end
|
86
|
+
|
87
|
+
# Stores a blob on the given container.
|
88
|
+
#
|
89
|
+
# Remarks path and payload are just text.
|
90
|
+
#
|
91
|
+
# content_type is required by the blobs api, but on this method is defaulted to "application/octect-stream"
|
92
|
+
#
|
93
|
+
# metadata is a hash that stores all the properties that you want to add to the blob when creating it.
|
94
|
+
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()
|
98
|
+
end
|
99
|
+
|
100
|
+
# 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()
|
105
|
+
end
|
106
|
+
|
107
|
+
# Deletes the blob existing on the current path.
|
108
|
+
def delete_blob(path)
|
109
|
+
url = generate_request_uri( path)
|
110
|
+
request = generate_request("DELETE", url)
|
111
|
+
request.execute()
|
112
|
+
end
|
113
|
+
|
114
|
+
# 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
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sets the properties (metadata) associated to the blob at given path.
|
122
|
+
def set_blob_properties(path, properties ={})
|
123
|
+
url = generate_request_uri( path, :comp => 'metadata')
|
124
|
+
request = generate_request("PUT", url, properties)
|
125
|
+
request.execute()
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
# This exception is raised while trying when calling WAZ::Queues::Queue.create('queue_name') and
|
4
|
+
# the metadata existing on the Queues Storage subsytem on the cloud contains different metadata from
|
5
|
+
# the given one.
|
6
|
+
class QueueAlreadyExists < WAZ::Storage::StorageException
|
7
|
+
def initialize(name)
|
8
|
+
super("The queue #{name} already exists on your account.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# This exception is raised when an initialization parameter of any of the WAZ::Queues classes falls of
|
13
|
+
# the specified values.
|
14
|
+
class OptionOutOfRange < WAZ::Storage::StorageException
|
15
|
+
def initialize(args = {})
|
16
|
+
super("The #{args[:name]} parameter is out of range allowed values go from #{args[:min]} to #{args[:max]}.")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# This exception is raised when the user tries to perform a delete operation over a peeked message. Since
|
21
|
+
# peeked messages cannot by deleted given the fact that there's no pop_receipt associated with it
|
22
|
+
# this exception will be raised.
|
23
|
+
class InvalidOperation < WAZ::Storage::StorageException
|
24
|
+
def initialize()
|
25
|
+
super("A peeked message cannot be delete, you need to lock it first (pop_receipt required).")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
# This class is used to model a Message inside Windows Azure Queues the usage
|
4
|
+
# it's pretty simple, here you can see a couple of samples. Messages consist on an UTF-8 string up-to 8KB and some metadata
|
5
|
+
# regarding its status and visibility. Here are all the things that you can do with a message:
|
6
|
+
#
|
7
|
+
# message.message_id #=> returns message id
|
8
|
+
#
|
9
|
+
# # this is the most important method regarding messages
|
10
|
+
# message.message_text #=> returns message contents
|
11
|
+
#
|
12
|
+
# message.pop_receipt #=> used for correlating your dequeue request + a delete operation
|
13
|
+
#
|
14
|
+
# message.expiration_time #=> returns when the message will be removed from the queue
|
15
|
+
#
|
16
|
+
# message.time_next_visible #=> when the message will be visible to other users
|
17
|
+
#
|
18
|
+
# message.insertion_time #=> when the message will be visible to other users
|
19
|
+
#
|
20
|
+
# message.queue_name #=> returns the queue name where the message belongs
|
21
|
+
#
|
22
|
+
# # remove the message from the queue
|
23
|
+
# message.destroy!
|
24
|
+
#
|
25
|
+
class Message
|
26
|
+
class << self
|
27
|
+
# This method is internally used by this class. It's the way we keep a single instance of the
|
28
|
+
# service that wraps the calls the Windows Azure Queues API. It's initialized with the values
|
29
|
+
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
30
|
+
def service_instance
|
31
|
+
@service_instance ||= Service.new(WAZ::Storage::Base.default_connection.merge(:type_of_service => 'queue'))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :message_id, :message_text, :pop_receipt, :expiration_time, :insertion_time, :time_next_visible
|
36
|
+
|
37
|
+
# Creates an instance of Message class, this method is intended to be used internally from the
|
38
|
+
# Queue.
|
39
|
+
def initialize(params = {})
|
40
|
+
self.message_id = params[:message_id]
|
41
|
+
self.message_text = params[:message_text]
|
42
|
+
self.pop_receipt = params[:pop_receipt]
|
43
|
+
self.expiration_time = params[:expiration_time]
|
44
|
+
self.insertion_time = params[:insertion_time]
|
45
|
+
self.time_next_visible = params[:time_next_visible]
|
46
|
+
@queue_name = params[:queue_name]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the Queue name where the Message belongs to
|
50
|
+
def queue_name
|
51
|
+
return @queue_name
|
52
|
+
end
|
53
|
+
|
54
|
+
# Marks the message for deletion (to later be removed from the queue by the garbage collector). If the message
|
55
|
+
# where the message is being actually called was peeked from the queue instead of locked it will raise the
|
56
|
+
# WAZ::Queues:InvalidOperation exception since it's not a permited operation.
|
57
|
+
def destroy!
|
58
|
+
raise WAZ::Queues::InvalidOperation if pop_receipt.nil?
|
59
|
+
self.class.service_instance.delete_message(queue_name, message_id, pop_receipt)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
# This class represents a Queue on Windows Azure Queues API. These are the methods implemented from Microsoft's API description
|
4
|
+
# available on MSDN at http://msdn.microsoft.com/en-us/library/dd179363.aspx
|
5
|
+
#
|
6
|
+
# # list available queues
|
7
|
+
# WAZ::Queues::Queue.list
|
8
|
+
#
|
9
|
+
# # create a queue (here you can also send hashed metadata)
|
10
|
+
# WAZ::Queues::Queue.create('my-container')
|
11
|
+
#
|
12
|
+
# # get a specific queue
|
13
|
+
# queue = WAZ::Queues::Container.find('my-container')
|
14
|
+
#
|
15
|
+
# # get queue properties (including default headers)
|
16
|
+
# queue.metadata #=> hash containing beautified metadata (:x_ms_meta_name)
|
17
|
+
#
|
18
|
+
# # set container properties (should follow x-ms-meta to be persisted)
|
19
|
+
# # if you specify the optional parameter overwrite, existing metadata
|
20
|
+
# # will be deleted else merged with new one.
|
21
|
+
# queue.put_properties!(:x_ms_meta_MyProperty => "my value")
|
22
|
+
#
|
23
|
+
# # delete queue
|
24
|
+
# queue.destroy!
|
25
|
+
#
|
26
|
+
# # clear queue contents
|
27
|
+
# queue.clear
|
28
|
+
#
|
29
|
+
# # enqueue a message
|
30
|
+
# queue.enqueue!("payload of the message")
|
31
|
+
#
|
32
|
+
# # peek a message/s (do not alter visibility, it can't be deleted neither)
|
33
|
+
# # num_of_messages (1 to 32) to be peeked (default 1)
|
34
|
+
# message = queue.peek
|
35
|
+
#
|
36
|
+
# # lock a message/s.
|
37
|
+
# # num_of_messages (1 to 32) to be peeked (default 1)
|
38
|
+
# # visiblity_timeout (default 60 sec. max 7200 [2hrs])
|
39
|
+
# message = queue.lock
|
40
|
+
#
|
41
|
+
class Queue
|
42
|
+
class << self
|
43
|
+
# Returns an array of the queues (WAZ::Queues::Queue) existing on the current
|
44
|
+
# Windows Azure Storage account.
|
45
|
+
def list
|
46
|
+
service_instance.list_queues.map do |queue|
|
47
|
+
WAZ::Queues::Queue.new(queue)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Creates a queue on the current account. If provided the metadata hash will specify additional
|
52
|
+
# metadata to be stored on the queue. (Remember that metadata on the storage account must start with
|
53
|
+
# :x_ms_metadata_{yourCustomPropertyName}, if not it will not be persisted).
|
54
|
+
def create(queue_name, metadata = {})
|
55
|
+
service_instance.create_queue(queue_name, metadata)
|
56
|
+
WAZ::Queues::Queue.new(:name => queue_name, :url => service_instance.generate_request_uri(queue_name))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Finds a queue by it's name, in case that it isn't found on the current storage account it will
|
60
|
+
# return nil shilding the user from a ResourceNotFound exception.
|
61
|
+
def find(queue_name)
|
62
|
+
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))
|
65
|
+
rescue RestClient::ResourceNotFound
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# This method is internally used by this class. It's the way we keep a single instance of the
|
71
|
+
# service that wraps the calls the Windows Azure Queues API. It's initialized with the values
|
72
|
+
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
73
|
+
def service_instance
|
74
|
+
@service_instance ||= Service.new(WAZ::Storage::Base.default_connection.merge(:type_of_service => 'queue'))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_accessor :name, :url
|
79
|
+
|
80
|
+
def initialize(options = {})
|
81
|
+
raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name)
|
82
|
+
raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url)
|
83
|
+
self.name = options[:name]
|
84
|
+
self.url = options[:url]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Deletes the queue from the current storage account.
|
88
|
+
def destroy!
|
89
|
+
self.class.service_instance.delete_queue(self.name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Retrieves the metadata headers associated with the quere.
|
93
|
+
def metadata
|
94
|
+
self.class.service_instance.get_queue_metadata(self.name)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Sets the metadata given on the new_metadata, when overwrite passed different
|
98
|
+
# than true it overrides the metadata for the queue (removing all existing metadata)
|
99
|
+
def put_properties!(new_metadata = {}, overwrite = false)
|
100
|
+
new_metadata.merge!(metadata.reject { |k, v| !k.to_s.start_with? "x_ms_meta"} ) unless overwrite
|
101
|
+
self.class.service_instance.set_queue_metadata(new_metadata)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Enqueues a message on current queue. message is just a string that should be
|
105
|
+
# UTF-8 serializable and ttl specifies the time-to-live of the message in the queue
|
106
|
+
# (in seconds).
|
107
|
+
def enqueue!(message, ttl = 604800)
|
108
|
+
self.class.service_instance.enqueue(self.name, message, ttl)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the approximated queue size.
|
112
|
+
def size
|
113
|
+
metadata[:x_ms_approximate_messages_count].to_i
|
114
|
+
end
|
115
|
+
|
116
|
+
# Since Windows Azure Queues implement a Peek-Lock pattern
|
117
|
+
# the method lock will lock a message preventing other users from
|
118
|
+
# picking/locking the current message from the queue.
|
119
|
+
#
|
120
|
+
# The API supports multiple message processing by specifiying num_of_messages (up to 32)
|
121
|
+
#
|
122
|
+
# The visibility_timeout parameter (optional) specifies for how long the message will be
|
123
|
+
# hidden from other users.
|
124
|
+
def lock(num_of_messages = 1, visibility_timeout = nil)
|
125
|
+
options = {}
|
126
|
+
options[:num_of_messages] = num_of_messages
|
127
|
+
options[:visiblity_timeout] = visibility_timeout unless visibility_timeout.nil?
|
128
|
+
messages = self.class.service_instance.get_messages(self.name, options).map do |raw_message|
|
129
|
+
WAZ::Queues::Message.new(raw_message.merge(:queue_name => self.name))
|
130
|
+
end
|
131
|
+
return messages.first() if num_of_messages == 1
|
132
|
+
return messages
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns top N (default 1, up to 32) message from the queue without performing
|
136
|
+
# any modification on the message. Since the message it's retrieved read-only
|
137
|
+
# users cannot delete the peeked message.
|
138
|
+
def peek(num_of_messages = 1)
|
139
|
+
options = {}
|
140
|
+
options[:num_of_messages] = num_of_messages
|
141
|
+
messages = self.class.service_instance.peek(self.name, options).map do |raw_message|
|
142
|
+
WAZ::Queues::Message.new(raw_message.merge(:queue_name => self.name))
|
143
|
+
end
|
144
|
+
return messages.first() if num_of_messages == 1
|
145
|
+
return messages
|
146
|
+
end
|
147
|
+
|
148
|
+
# Marks every message on the queue for deletion (to be later garbage collected).
|
149
|
+
def clear
|
150
|
+
self.class.service_instance.clear_queue(self.name)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
# This is internally used by the waz-queues part of the gem and it exposes the Windows Azure Queue API REST methods
|
4
|
+
# implementation. You can use this class to perform an specific operation that aren't provided by the current API.
|
5
|
+
class Service
|
6
|
+
include WAZ::Storage::SharedKeyCoreService
|
7
|
+
|
8
|
+
# Lists the queues on the given storage account.
|
9
|
+
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
|
+
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
|
18
|
+
return queues
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates a queue on the current storage account. Throws WAZ::Queues::QueueAlreadyExists when
|
22
|
+
# existing metadata and given metadata differ.
|
23
|
+
def create_queue(queue_name, metadata = {})
|
24
|
+
begin
|
25
|
+
url = generate_request_uri(queue_name)
|
26
|
+
request = generate_request("PUT", url, metadata)
|
27
|
+
request.execute()
|
28
|
+
rescue RestClient::RequestFailed
|
29
|
+
raise WAZ::Queues::QueueAlreadyExists, queue_name if $!.http_code == 409
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Deletes the given queue from the current storage account.
|
34
|
+
def delete_queue(queue_name)
|
35
|
+
url = generate_request_uri(queue_name)
|
36
|
+
request = generate_request("DELETE", url)
|
37
|
+
request.execute()
|
38
|
+
end
|
39
|
+
|
40
|
+
# Gets the given queue metadata.
|
41
|
+
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
|
45
|
+
end
|
46
|
+
|
47
|
+
# Sets the given queue metadata.
|
48
|
+
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()
|
52
|
+
end
|
53
|
+
|
54
|
+
# Enqueues a message on the current queue.
|
55
|
+
#
|
56
|
+
# 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
|
+
# is omitted, the default time-to-live is 7 days.
|
58
|
+
def enqueue(queue_name, message_payload, ttl = 604800)
|
59
|
+
url = generate_request_uri("#{queue_name}/messages", "messagettl" => ttl)
|
60
|
+
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()
|
63
|
+
end
|
64
|
+
|
65
|
+
# Locks N messages (1 default) from the given queue.
|
66
|
+
#
|
67
|
+
# :num_of_messages option specifies the max number of messages to get (maximum 32)
|
68
|
+
#
|
69
|
+
# :visibility_timeout option specifies the timeout of the message locking in seconds (max two hours)
|
70
|
+
def get_messages(queue_name, options = {})
|
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
|
+
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())
|
76
|
+
messages = []
|
77
|
+
REXML::XPath.each(doc, '//QueueMessage/') do |item|
|
78
|
+
message = { :message_id => REXML::XPath.first(item, "MessageId").text,
|
79
|
+
:message_text => REXML::XPath.first(item, "MessageText").text,
|
80
|
+
:expiration_time => Time.httpdate(REXML::XPath.first(item, "ExpirationTime").text),
|
81
|
+
:insertion_time => Time.httpdate(REXML::XPath.first(item, "InsertionTime").text) }
|
82
|
+
|
83
|
+
# This are only valid when peek-locking messages
|
84
|
+
message[:pop_receipt] = REXML::XPath.first(item, "PopReceipt").text unless REXML::XPath.first(item, "PopReceipt").nil?
|
85
|
+
message[:time_next_visible] = Time.httpdate(REXML::XPath.first(item, "TimeNextVisible").text) unless REXML::XPath.first(item, "TimeNextVisible").nil?
|
86
|
+
messages << message
|
87
|
+
end
|
88
|
+
return messages
|
89
|
+
end
|
90
|
+
|
91
|
+
# Peeks N messages (default 1) from the given queue.
|
92
|
+
#
|
93
|
+
# Implementation is the same of get_messages but differs on an additional parameter called :peek_only.
|
94
|
+
def peek(queue_name, options = {})
|
95
|
+
return get_messages(queue_name, {:peek_only => true}.merge(options))
|
96
|
+
end
|
97
|
+
|
98
|
+
# Deletes the given message from the queue, correlating the operation with the pop_receipt
|
99
|
+
# in order to avoid eventually inconsistent scenarios.
|
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()
|
104
|
+
end
|
105
|
+
|
106
|
+
# Marks every message on the given queue for deletion.
|
107
|
+
def clear_queue(queue_name)
|
108
|
+
url = generate_request_uri("#{queue_name}/messages")
|
109
|
+
request = generate_request("DELETE", url)
|
110
|
+
request.execute()
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
# This class is used to handle a general connection with Windows Azure Storage Account and it
|
4
|
+
# should be used at least once on the application bootstrap or configuration file.
|
5
|
+
#
|
6
|
+
# The usage is pretty simple as it's depicted on the following sample
|
7
|
+
# WAZ::Storage::establish_connection!(:account_name => "my_account_name",
|
8
|
+
# :access_key => "your_base64_key",
|
9
|
+
# :use_ssl => false)
|
10
|
+
#
|
11
|
+
class Base
|
12
|
+
class << self
|
13
|
+
# Sets the basic configuration parameters to use the API on the current context
|
14
|
+
# required parameters are :account_name, :access_key.
|
15
|
+
#
|
16
|
+
# All other parameters are optional.
|
17
|
+
def establish_connection!(options = {})
|
18
|
+
raise InvalidOption, :account_name unless options.keys.include? :account_name
|
19
|
+
raise InvalidOption, :access_key unless options.keys.include? :access_key
|
20
|
+
options[:use_ssl] = false unless options.keys.include? :use_ssl
|
21
|
+
@connection = options
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the default connection (last set) parameters. It will raise an exception WAZ::Storage::NotConnected
|
25
|
+
# when there's no connection information registered.
|
26
|
+
def default_connection
|
27
|
+
raise NotConnected unless connected?
|
28
|
+
return @connection
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a value indicating whether the current connection information has been set or not.
|
32
|
+
def connected?
|
33
|
+
return !@connection.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
# This module is imported by the specific services that use Shared Key authentication profile. On the current implementation
|
4
|
+
# this module is imported from WAZ::Queues::Service and WAZ::Blobs::Service.
|
5
|
+
module SharedKeyCoreService
|
6
|
+
attr_accessor :account_name, :access_key, :use_ssl, :base_url
|
7
|
+
|
8
|
+
# Creates an instance of the implementor service (internally used by the API).
|
9
|
+
def initialize(options = {})
|
10
|
+
self.account_name = options[:account_name]
|
11
|
+
self.access_key = options[:access_key]
|
12
|
+
self.use_ssl = options[:use_ssl] or false
|
13
|
+
self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Generates a request based on Adam Wiggings' rest-client, including all the required headers
|
17
|
+
# for interacting with Windows Azure Storage API (except for Tables). This methods embeds the
|
18
|
+
# authorization key signature on the request based on the given access_key.
|
19
|
+
def generate_request(verb, url, headers = {}, payload = nil)
|
20
|
+
http_headers = {}
|
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)
|
23
|
+
request.headers["x-ms-Date"] = Time.new.httpdate
|
24
|
+
request.headers["Content-Length"] = (request.payload or "").length
|
25
|
+
request.headers["Authorization"] = "SharedKey #{account_name}:#{generate_signature(request)}"
|
26
|
+
return request
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generates the request uri based on the resource path, the protocol, the account name and the parameters passed
|
30
|
+
# on the options hash.
|
31
|
+
def generate_request_uri(path = nil, options = {})
|
32
|
+
protocol = use_ssl ? "https" : "http"
|
33
|
+
query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{options[k]}"}.join("&") unless options.nil? or options.empty?
|
34
|
+
uri = "#{protocol}://#{account_name}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}"
|
35
|
+
uri << "?#{query_params}" if query_params
|
36
|
+
return uri
|
37
|
+
end
|
38
|
+
|
39
|
+
# Canonicalizes the request headers by following Microsoft's specification on how those headers have to be sorted
|
40
|
+
# and which of the given headers apply to be canonicalized.
|
41
|
+
def canonicalize_headers(headers)
|
42
|
+
cannonicalized_headers = headers.keys.select {|h| h.to_s.start_with? 'x-ms'}.map{ |h| "#{h.downcase.strip}:#{headers[h].strip}" }.sort{ |a, b| a <=> b }.join("\x0A")
|
43
|
+
return cannonicalized_headers
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a canonical representation of the message by combining account_name/resource_path.
|
47
|
+
def canonicalize_message(url)
|
48
|
+
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
|
49
|
+
comp_component = url.scan(/(comp=[^&]+)/i).first()
|
50
|
+
uri_component << "?#{comp_component}" if comp_component
|
51
|
+
canonicalized_message = "/#{self.account_name}/#{uri_component}"
|
52
|
+
return canonicalized_message
|
53
|
+
end
|
54
|
+
|
55
|
+
# Generates the signature based on Micosoft specs for the REST API. It includes some special headers,
|
56
|
+
# the canonicalized header line and the canonical form of the message, all of the joined by \n character. Encoded with
|
57
|
+
# Base64 and encrypted with SHA256 using the access_key as the seed.
|
58
|
+
def generate_signature(request)
|
59
|
+
signature = request.method.to_s.upcase + "\x0A" +
|
60
|
+
(request.headers["Content-MD5"] or "") + "\x0A" +
|
61
|
+
(request.headers["Content-Type"] or "") + "\x0A" +
|
62
|
+
(request.headers["Date"] or "")+ "\x0A" +
|
63
|
+
canonicalize_headers(request.headers) + "\x0A" +
|
64
|
+
canonicalize_message(request.url)
|
65
|
+
|
66
|
+
return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
# This class is the base exception from where all the exceptions raised from this API
|
4
|
+
# inherit from. If you want to handle an exception that your code may throw and you don't
|
5
|
+
# know which specific type you should handle, handle this type of exception.
|
6
|
+
class StorageException < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# This exception raises whenever a required parameter for initializing any class isn't provided. From
|
10
|
+
# WAZ::Storage::Base up to WAZ::Queues::Queue all of them use this exception.
|
11
|
+
class InvalidOption < StorageException
|
12
|
+
def initialize(missing_option)
|
13
|
+
super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# This exception is raised when the user tries to perform an operation on any Storage API class
|
18
|
+
# without previously specificying the connection options.
|
19
|
+
class NotConnected < StorageException
|
20
|
+
def initialize
|
21
|
+
super("You should establish connection before using the services, the connection configuration is required.")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'lib/waz-storage'
|
7
|
+
|
8
|
+
|
9
|
+
namespace :test do
|
10
|
+
Spec::Rake::SpecTask.new('run_with_rcov') do |t|
|
11
|
+
puts "running tests and code coverage"
|
12
|
+
t.spec_files = FileList['tests/waz/queues/*.rb', 'tests/waz/blobs/*.rb', 'tests/waz/storage/*.rb']
|
13
|
+
t.rcov = true
|
14
|
+
t.rcov_opts = ['--text-report', '--exclude', "exclude.*/.gem,test,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
|
15
|
+
t.spec_opts = ['-cfn']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :dist do
|
20
|
+
spec = Gem::Specification.new do |s|
|
21
|
+
s.name = 'waz-storage'
|
22
|
+
s.version = Gem::Version.new(WAZ::Storage::Version)
|
23
|
+
s.summary = "Client library for Windows Azure's Storage Service REST API"
|
24
|
+
s.description = "A simple implementation of Windows Azure Storage API for Ruby, inspired by the S3 gems and self experience of dealing with queues. The major goal of the whole gem is to enable ruby developers [like me =)] to leverage Windows Azure Storage features and have another option for cloud storage."
|
25
|
+
s.email = 'johnny.halife@me.com'
|
26
|
+
s.author = 'Johnny G. Halife'
|
27
|
+
s.homepage = 'http://waz-storage.heroku.com'
|
28
|
+
s.require_paths = ["lib"]
|
29
|
+
s.files = FileList['rakefile', 'lib/**/*.rb']
|
30
|
+
s.test_files = Dir['test/**/*']
|
31
|
+
s.has_rdoc = true
|
32
|
+
s.rdoc_options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
33
|
+
|
34
|
+
# Dependencies
|
35
|
+
s.add_runtime_dependency 'rest-client', '>= 1.0.3'
|
36
|
+
s.add_dependency 'ruby-hmac'
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
40
|
+
pkg.need_tar = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
namespace :docs do
|
45
|
+
Rake::RDocTask.new do |t|
|
46
|
+
t.rdoc_dir = 'rdoc'
|
47
|
+
t.title = "Windows Azure Storage library — simple gem for accessing WAZ‘s Storage REST API"
|
48
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
49
|
+
t.options << '--charset' << 'utf-8'
|
50
|
+
t.rdoc_files.include('README.rdoc')
|
51
|
+
t.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waz-storage
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Johnny G. Halife
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-15 00:00:00 -03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-hmac
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: A simple implementation of Windows Azure Storage API for Ruby, inspired by the S3 gems and self experience of dealing with queues. The major goal of the whole gem is to enable ruby developers [like me =)] to leverage Windows Azure Storage features and have another option for cloud storage.
|
36
|
+
email: johnny.halife@me.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- rakefile
|
45
|
+
- lib/waz/blobs/blob_object.rb
|
46
|
+
- lib/waz/blobs/container.rb
|
47
|
+
- lib/waz/blobs/service.rb
|
48
|
+
- lib/waz/queues/exceptions.rb
|
49
|
+
- lib/waz/queues/message.rb
|
50
|
+
- lib/waz/queues/queue.rb
|
51
|
+
- lib/waz/queues/service.rb
|
52
|
+
- lib/waz/storage/base.rb
|
53
|
+
- lib/waz/storage/core_service.rb
|
54
|
+
- lib/waz/storage/exceptions.rb
|
55
|
+
- lib/waz/storage/version.rb
|
56
|
+
- lib/waz-blobs.rb
|
57
|
+
- lib/waz-queues.rb
|
58
|
+
- lib/waz-storage.rb
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://waz-storage.heroku.com
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --line-numbers
|
66
|
+
- --inline-source
|
67
|
+
- -A cattr_accessor=object
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.5
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: Client library for Windows Azure's Storage Service REST API
|
89
|
+
test_files: []
|
90
|
+
|