paperclip-azure 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/azure/blob/blob_service.rb +12 -0
- data/lib/azure/core/auth/shared_access_signature.rb +84 -0
- data/lib/paperclip-azure.rb +3 -0
- data/lib/paperclip/storage/azure.rb +255 -0
- data/paperclip-azure.gemspec +77 -0
- data/spec/spec_helper.rb +29 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 71d1e2806cadeb22414cdd7bacba0f7158a1e1c4
|
4
|
+
data.tar.gz: c9999f06aff5f07d0df7abfd232ccad3e2a38ab1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27395952b0f12f5a64ed397f56dbec348a9671081737d4519bc474d8bd0932ac7017b59b67dda19dd0c84add00c48fd2822844c3efa49fec79c54fc7fd2bbbfd
|
7
|
+
data.tar.gz: acc6d1af9f93acc8fcb89e7424b0c113d67dbe71ea78951fa61aa91150c4f3651bb630f0f172bc6cb5e92c7d0fbabd3f9b477a933e2a9caa18a1a7c55c0acb6c
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem 'azure'
|
4
|
+
gem 'hashie', require: false
|
5
|
+
gem 'addressable', require: false
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem "rspec", "~> 2.8.0"
|
9
|
+
gem "yard", "~> 0.7"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.0"
|
12
|
+
gem "jeweler", "~> 2.0.1"
|
13
|
+
gem "simplecov", ">= 0"
|
14
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.3.6)
|
5
|
+
azure (0.6.4)
|
6
|
+
json (~> 1.8)
|
7
|
+
mime-types (~> 1.0)
|
8
|
+
nokogiri (~> 1.5)
|
9
|
+
systemu (~> 2.6)
|
10
|
+
uuid (~> 2.0)
|
11
|
+
builder (3.2.2)
|
12
|
+
descendants_tracker (0.0.4)
|
13
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
14
|
+
diff-lcs (1.1.3)
|
15
|
+
docile (1.1.5)
|
16
|
+
faraday (0.9.0)
|
17
|
+
multipart-post (>= 1.2, < 3)
|
18
|
+
git (1.2.8)
|
19
|
+
github_api (0.12.1)
|
20
|
+
addressable (~> 2.3)
|
21
|
+
descendants_tracker (~> 0.0.4)
|
22
|
+
faraday (~> 0.8, < 0.10)
|
23
|
+
hashie (>= 3.2)
|
24
|
+
multi_json (>= 1.7.5, < 2.0)
|
25
|
+
nokogiri (~> 1.6.3)
|
26
|
+
oauth2
|
27
|
+
hashie (3.3.1)
|
28
|
+
highline (1.6.21)
|
29
|
+
jeweler (2.0.1)
|
30
|
+
builder
|
31
|
+
bundler (>= 1.0)
|
32
|
+
git (>= 1.2.5)
|
33
|
+
github_api
|
34
|
+
highline (>= 1.6.15)
|
35
|
+
nokogiri (>= 1.5.10)
|
36
|
+
rake
|
37
|
+
rdoc
|
38
|
+
json (1.8.1)
|
39
|
+
jwt (1.0.0)
|
40
|
+
macaddr (1.7.1)
|
41
|
+
systemu (~> 2.6.2)
|
42
|
+
mime-types (1.25.1)
|
43
|
+
mini_portile (0.6.0)
|
44
|
+
multi_json (1.10.1)
|
45
|
+
multi_xml (0.5.5)
|
46
|
+
multipart-post (2.0.0)
|
47
|
+
nokogiri (1.6.3.1)
|
48
|
+
mini_portile (= 0.6.0)
|
49
|
+
oauth2 (1.0.0)
|
50
|
+
faraday (>= 0.8, < 0.10)
|
51
|
+
jwt (~> 1.0)
|
52
|
+
multi_json (~> 1.3)
|
53
|
+
multi_xml (~> 0.5)
|
54
|
+
rack (~> 1.2)
|
55
|
+
rack (1.5.2)
|
56
|
+
rake (10.3.2)
|
57
|
+
rdoc (3.12.2)
|
58
|
+
json (~> 1.4)
|
59
|
+
rspec (2.8.0)
|
60
|
+
rspec-core (~> 2.8.0)
|
61
|
+
rspec-expectations (~> 2.8.0)
|
62
|
+
rspec-mocks (~> 2.8.0)
|
63
|
+
rspec-core (2.8.0)
|
64
|
+
rspec-expectations (2.8.0)
|
65
|
+
diff-lcs (~> 1.1.2)
|
66
|
+
rspec-mocks (2.8.0)
|
67
|
+
simplecov (0.9.0)
|
68
|
+
docile (~> 1.1.0)
|
69
|
+
multi_json
|
70
|
+
simplecov-html (~> 0.8.0)
|
71
|
+
simplecov-html (0.8.0)
|
72
|
+
systemu (2.6.4)
|
73
|
+
thread_safe (0.3.4)
|
74
|
+
uuid (2.3.7)
|
75
|
+
macaddr (~> 1.0)
|
76
|
+
yard (0.8.7.4)
|
77
|
+
|
78
|
+
PLATFORMS
|
79
|
+
ruby
|
80
|
+
|
81
|
+
DEPENDENCIES
|
82
|
+
addressable
|
83
|
+
azure
|
84
|
+
bundler (~> 1.0)
|
85
|
+
hashie
|
86
|
+
jeweler (~> 2.0.1)
|
87
|
+
rdoc (~> 3.12)
|
88
|
+
rspec (~> 2.8.0)
|
89
|
+
simplecov
|
90
|
+
yard (~> 0.7)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Jordan Yaker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= paperclip-azure
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to paperclip-azure
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2015 Jordan. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
+
gem.name = "paperclip-azure"
|
18
|
+
gem.homepage = "http://github.com/jordanyaker/paperclip-azure"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{An Azure Blob Storage implementation for Paperclip.}
|
21
|
+
gem.description = %Q{An Azure Blob Storage implementation for Paperclip.}
|
22
|
+
gem.email = "help@supportify.io"
|
23
|
+
gem.authors = ["Jordan Yaker"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Code coverage detail"
|
35
|
+
task :simplecov do
|
36
|
+
ENV['COVERAGE'] = "true"
|
37
|
+
Rake::Task['spec'].execute
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'yard'
|
43
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,12 @@
|
|
1
|
+
if defined? Azure::Blob::BlobService
|
2
|
+
module Azure
|
3
|
+
module Blob
|
4
|
+
BlobService.class_eval do
|
5
|
+
def initialize(signer=Core::Auth::SharedKey.new, account_name=Azure.config.storage_account_name)
|
6
|
+
super(signer, account_name)
|
7
|
+
@host = "http://#{account_name}.blob.core.windows.net"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Adapted from the SharedAccessSignature class found in the Azure
|
2
|
+
# Extensions project by David Michael located at https://github.com/dmichael/azure-contrib
|
3
|
+
|
4
|
+
require 'hashie/dash'
|
5
|
+
require 'addressable/uri'
|
6
|
+
|
7
|
+
module Azure
|
8
|
+
module Core
|
9
|
+
module Auth
|
10
|
+
class SharedAccessSignature
|
11
|
+
class Version20130815 < Hashie::Dash
|
12
|
+
property :resource, default: 'c', required: true
|
13
|
+
property :permissions, default: 'r', required: true
|
14
|
+
property :start, default: ''
|
15
|
+
property :identifier, default: ''
|
16
|
+
property :expiry, required: true
|
17
|
+
property :canonicalized_resource, required: true
|
18
|
+
property :access_key, required: true
|
19
|
+
# Do not change this
|
20
|
+
property :version, default: '2103-08-15'
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :uri, :options
|
24
|
+
|
25
|
+
def initialize(uri, options = {}, account = ENV['AZURE_STORAGE_ACCOUNT'])
|
26
|
+
# This is the uri that we are signing
|
27
|
+
@uri = Addressable::URI.parse(uri)
|
28
|
+
|
29
|
+
is_blob = options[:resource] == 'b'
|
30
|
+
|
31
|
+
# Create the options hash that will be turned into a query string
|
32
|
+
@options = Version20130815.new(options.merge(canonicalized_resource: canonicalized_resource(@uri, account, is_blob)))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a "canonicalized resource" from the full uri to be signed
|
36
|
+
def canonicalized_resource(uri, account = ENV['AZURE_STORAGE_ACCOUNT'], is_blob = false)
|
37
|
+
path = uri.path # Addressable::URI
|
38
|
+
# There is only really one level deep for containers, the remainder is the BLOB key (that looks like a path)
|
39
|
+
path_array = path.split('/').reject {|p| p == ''}
|
40
|
+
container = path_array.shift
|
41
|
+
|
42
|
+
string = if is_blob
|
43
|
+
File.join('/', account, container, path_array.join('/'))
|
44
|
+
else
|
45
|
+
File.join('/', account, container)
|
46
|
+
end
|
47
|
+
|
48
|
+
string
|
49
|
+
end
|
50
|
+
|
51
|
+
# When creating the query string from the options, we only include the no-empty fields
|
52
|
+
# - this is opposed to the string that gets signed which includes them as blank.
|
53
|
+
def create_query_values(options = Version20130815.new)
|
54
|
+
# Parts
|
55
|
+
parts = {}
|
56
|
+
parts[:st] = URI.unescape(options[:start]) unless options[:start] == ''
|
57
|
+
parts[:se] = URI.unescape(options[:expiry])
|
58
|
+
parts[:sr] = URI.unescape(options[:resource])
|
59
|
+
parts[:sp] = URI.unescape(options[:permissions])
|
60
|
+
parts[:si] = URI.unescape(options[:identifier]) unless options[:identifier] == ''
|
61
|
+
parts[:sig] = URI.unescape( create_signature(options) )
|
62
|
+
|
63
|
+
parts
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_signature(options = Version20130815)
|
67
|
+
string_to_sign = []
|
68
|
+
string_to_sign << options[:permissions]
|
69
|
+
string_to_sign << options[:start]
|
70
|
+
string_to_sign << options[:expiry]
|
71
|
+
string_to_sign << options[:canonicalized_resource]
|
72
|
+
string_to_sign << options[:identifier]
|
73
|
+
|
74
|
+
Azure::Core::Auth::Signer.new(options[:access_key]).sign(string_to_sign.join("\n").force_encoding("UTF-8"))
|
75
|
+
end
|
76
|
+
|
77
|
+
def sign
|
78
|
+
@uri.query_values = create_query_values(@options)
|
79
|
+
@uri.to_s
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Storage
|
3
|
+
# Azure's container file hosting service is a scalable, easy place to store files for
|
4
|
+
# distribution. You can find out more about it at http://azure.microsoft.com/en-us/services/storage/
|
5
|
+
#
|
6
|
+
# To use Paperclip with Azure, include the +azure+ gem in your Gemfile:
|
7
|
+
# gem 'azure'
|
8
|
+
# There are a few Azure-specific options for has_attached_file:
|
9
|
+
# * +azure_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
|
10
|
+
# to a YAML file containing the +access_key+ and +storage_account+ that azure
|
11
|
+
# gives you. You can 'environment-space' this just like you do to your
|
12
|
+
# database.yml file, so different environments can use different accounts:
|
13
|
+
# development:
|
14
|
+
# storage_account_name: foo
|
15
|
+
# access_key: 123...
|
16
|
+
# test:
|
17
|
+
# storage_account_name: foo
|
18
|
+
# access_key: abc...
|
19
|
+
# production:
|
20
|
+
# storage_account_name: foo
|
21
|
+
# access_key: 456...
|
22
|
+
# This is not required, however, and the file may simply look like this:
|
23
|
+
# storage_account_name: foo
|
24
|
+
# access_key: 456...
|
25
|
+
# In which case, those access keys will be used in all environments. You can also
|
26
|
+
# put your container name in this file, instead of adding it to the code directly.
|
27
|
+
# This is useful when you want the same account but a different container for
|
28
|
+
# development versus production.
|
29
|
+
# When using a Proc it provides a single parameter which is the attachment itself. A
|
30
|
+
# method #instance is available on the attachment which will take you back to your
|
31
|
+
# code. eg.
|
32
|
+
# class User
|
33
|
+
# has_attached_file :download,
|
34
|
+
# :storage => :azure,
|
35
|
+
# :azure_credentials => Proc.new{|a| a.instance.azure_credentials }
|
36
|
+
#
|
37
|
+
# def azure_credentials
|
38
|
+
# { :container => "xxx", :storage_account_name => "xxx", :access_key => "xxx" }
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# * +container+: This is the name of the Azure container that will store your files. Remember
|
43
|
+
# that the container must be unique across the storage account. If the container does not exist
|
44
|
+
# Paperclip will attempt to create it. The container name will not be interpolated.
|
45
|
+
# You can define the container as a Proc if you want to determine it's name at runtime.
|
46
|
+
# Paperclip will call that Proc with attachment as the only argument.
|
47
|
+
# * +path+: This is the key under the container in which the file will be stored. The
|
48
|
+
# URL will be constructed from the container and the path. This is what you will want
|
49
|
+
# to interpolate. Keys should be unique, like filenames, and despite the fact that
|
50
|
+
# Azure (strictly speaking) does not support directories, you can still use a / to
|
51
|
+
# separate parts of your file name.
|
52
|
+
|
53
|
+
module Azure
|
54
|
+
def self.extended base
|
55
|
+
begin
|
56
|
+
require 'azure'
|
57
|
+
rescue LoadError => e
|
58
|
+
e.message << " (You may need to install the azure SDK gem)"
|
59
|
+
raise e
|
60
|
+
end unless defined?(::Azure::Core)
|
61
|
+
|
62
|
+
base.instance_eval do
|
63
|
+
@azure_options = @options[:azure_options] || {}
|
64
|
+
end
|
65
|
+
|
66
|
+
Paperclip.interpolates(:azure_path_url) do |attachment, style|
|
67
|
+
attachment.azure_uri(style)
|
68
|
+
end unless Paperclip::Interpolations.respond_to? :azure_path_url
|
69
|
+
end
|
70
|
+
|
71
|
+
def expiring_url(time = 3600, style_name = default_style)
|
72
|
+
if path(style_name)
|
73
|
+
uri = azure_uri(style_name)
|
74
|
+
signer = ::Azure::Core::Auth::SharedAccessSignature.new(uri, {
|
75
|
+
resource: 'b',
|
76
|
+
permissions: 'r',
|
77
|
+
start: 5.minutes.ago.utc.iso8601,
|
78
|
+
expiry: time.since.utc.iso8601,
|
79
|
+
access_key: azure_credentials[:access_key]
|
80
|
+
},
|
81
|
+
azure_account_name
|
82
|
+
)
|
83
|
+
signer.sign
|
84
|
+
else
|
85
|
+
url(style_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def azure_credentials
|
90
|
+
@azure_credentials ||= parse_credentials(@options[:azure_credentials])
|
91
|
+
end
|
92
|
+
|
93
|
+
def azure_account_name
|
94
|
+
account_name = @options[:azure_storage_account_name] || azure_credentials[:storage_account_name]
|
95
|
+
account_name = account_name.call(self) if account_name.is_a?(Proc)
|
96
|
+
|
97
|
+
account_name
|
98
|
+
end
|
99
|
+
|
100
|
+
def container_name
|
101
|
+
@container ||= @options[:container] || azure_credentials[:container]
|
102
|
+
@container = @container.call(self) if @container.respond_to?(:call)
|
103
|
+
@container or raise ArgumentError, "missing required :container option"
|
104
|
+
end
|
105
|
+
|
106
|
+
def azure_interface
|
107
|
+
@azure_interface ||= begin
|
108
|
+
config = {}
|
109
|
+
|
110
|
+
[:storage_account_name, :access_key, :container].each do |opt|
|
111
|
+
config[opt] = azure_credentials[opt] if azure_credentials[opt]
|
112
|
+
end
|
113
|
+
|
114
|
+
obtain_azure_instance_for(config.merge(@azure_options))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def obtain_azure_instance_for(options)
|
119
|
+
instances = (Thread.current[:paperclip_azure_instances] ||= {})
|
120
|
+
|
121
|
+
unless instances[options]
|
122
|
+
signer = ::Azure::Core::Auth::SharedKey.new options[:storage_account_name], options[:access_key]
|
123
|
+
instances[options] = ::Azure::BlobService.new(signer, options[:storage_account_name])
|
124
|
+
end
|
125
|
+
|
126
|
+
instances[options]
|
127
|
+
end
|
128
|
+
|
129
|
+
def azure_uri(style_name = default_style)
|
130
|
+
"#{azure_base_url}/#{container_name}/#{path(style_name).gsub(%r{\A/}, '')}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def azure_base_url
|
134
|
+
"https://#{azure_account_name}.blob.core.windows.net"
|
135
|
+
end
|
136
|
+
|
137
|
+
def azure_container
|
138
|
+
@azure_container ||= azure_interface.get_container_properties container_name
|
139
|
+
end
|
140
|
+
|
141
|
+
def azure_object(style_name = default_style)
|
142
|
+
azure_interface.get_blob_properties container_name, path(style_name).sub(%r{\A/},'')
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_credentials(creds)
|
146
|
+
creds = creds.respond_to?('call') ? creds.call(self) : creds
|
147
|
+
creds = find_credentials(creds).stringify_keys
|
148
|
+
env = Object.const_defined?(:Rails) ? Rails.env : nil
|
149
|
+
(creds[env] || creds).symbolize_keys
|
150
|
+
end
|
151
|
+
|
152
|
+
def exists?(style = default_style)
|
153
|
+
if original_filename
|
154
|
+
azure_object(style).nil?
|
155
|
+
else
|
156
|
+
false
|
157
|
+
end
|
158
|
+
rescue ::Azure::Core::Http::HTTPError => e
|
159
|
+
raise unless e.status_code == 404
|
160
|
+
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_container
|
165
|
+
azure_interface.create_container container_name
|
166
|
+
end
|
167
|
+
|
168
|
+
def flush_writes #:nodoc:
|
169
|
+
@queued_for_write.each do |style, file|
|
170
|
+
retries = 0
|
171
|
+
begin
|
172
|
+
log("saving #{path(style)}")
|
173
|
+
if azure_container
|
174
|
+
save_blob container_name, path(style).sub(%r{\A/},''), file
|
175
|
+
end
|
176
|
+
rescue ::Azure::Core::Http::HTTPError => e
|
177
|
+
if e.status_code == 404
|
178
|
+
create_container
|
179
|
+
retry
|
180
|
+
else
|
181
|
+
raise
|
182
|
+
end
|
183
|
+
ensure
|
184
|
+
file.rewind
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
after_flush_writes # allows attachment to clean up temp files
|
189
|
+
|
190
|
+
@queued_for_write = {}
|
191
|
+
end
|
192
|
+
|
193
|
+
def save_blob(container_name, storage_path, file)
|
194
|
+
if file.size < 64.megabytes
|
195
|
+
azure_interface.create_block_blob container_name, storage_path, file.read
|
196
|
+
else
|
197
|
+
blocks = []; count = 0
|
198
|
+
while data = file.read(4.megabytes)
|
199
|
+
block_id = "block_#{(count += 1).to_s.rjust(5, '0')}"
|
200
|
+
|
201
|
+
azure_interface.create_blob_block container_name, storage_path, block_id, data
|
202
|
+
|
203
|
+
blocks << [block_id]
|
204
|
+
end
|
205
|
+
|
206
|
+
azure_interface.commit_blob_blocks container_name, storage_path, blocks
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def flush_deletes #:nodoc:
|
211
|
+
@queued_for_delete.each do |path|
|
212
|
+
begin
|
213
|
+
log("deleting #{path}")
|
214
|
+
azure_interface.delete_blob container_name, path
|
215
|
+
rescue ::Azure::Core::Http::HTTPError => e
|
216
|
+
raise unless e.status_code == 404
|
217
|
+
end
|
218
|
+
end
|
219
|
+
@queued_for_delete = []
|
220
|
+
end
|
221
|
+
|
222
|
+
def copy_to_local_file(style, local_dest_path)
|
223
|
+
log("copying #{path(style)} to local file #{local_dest_path}")
|
224
|
+
|
225
|
+
blob, content = azure_interface.get_blob(container_name, path(style).sub(%r{\A/},''))
|
226
|
+
|
227
|
+
::File.open(local_dest_path, 'wb') do |local_file|
|
228
|
+
local_file.write(content)
|
229
|
+
end
|
230
|
+
rescue ::Azure::Core::Http::HTTPError => e
|
231
|
+
raise unless e.status_code == 404
|
232
|
+
|
233
|
+
warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
|
234
|
+
false
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def find_credentials creds
|
240
|
+
case creds
|
241
|
+
when File
|
242
|
+
YAML::load(ERB.new(File.read(creds.path)).result)
|
243
|
+
when String, Pathname
|
244
|
+
YAML::load(ERB.new(File.read(creds)).result)
|
245
|
+
when Hash
|
246
|
+
creds
|
247
|
+
when NilClass
|
248
|
+
{}
|
249
|
+
else
|
250
|
+
raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "paperclip-azure"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jordan Yaker"]
|
12
|
+
s.date = "2015-01-19"
|
13
|
+
s.description = "An Azure Blob Storage implementation for Paperclip."
|
14
|
+
s.email = "help@supportify.io"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/azure/blob/blob_service.rb",
|
29
|
+
"lib/azure/core/auth/shared_access_signature.rb",
|
30
|
+
"lib/paperclip-azure.rb",
|
31
|
+
"lib/paperclip/storage/azure.rb",
|
32
|
+
"paperclip-azure.gemspec",
|
33
|
+
"spec/spec_helper.rb"
|
34
|
+
]
|
35
|
+
s.homepage = "http://github.com/jordanyaker/paperclip-azure"
|
36
|
+
s.licenses = ["MIT"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = "2.0.6"
|
39
|
+
s.summary = "An Azure Blob Storage implementation for Paperclip."
|
40
|
+
|
41
|
+
if s.respond_to? :specification_version then
|
42
|
+
s.specification_version = 4
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_runtime_dependency(%q<azure>, [">= 0"])
|
46
|
+
s.add_runtime_dependency(%q<hashie>, [">= 0"])
|
47
|
+
s.add_runtime_dependency(%q<addressable>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
49
|
+
s.add_development_dependency(%q<yard>, ["~> 0.7"])
|
50
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
51
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
52
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
53
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<azure>, [">= 0"])
|
56
|
+
s.add_dependency(%q<hashie>, [">= 0"])
|
57
|
+
s.add_dependency(%q<addressable>, [">= 0"])
|
58
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
59
|
+
s.add_dependency(%q<yard>, ["~> 0.7"])
|
60
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
61
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
62
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
63
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
64
|
+
end
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<azure>, [">= 0"])
|
67
|
+
s.add_dependency(%q<hashie>, [">= 0"])
|
68
|
+
s.add_dependency(%q<addressable>, [">= 0"])
|
69
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
70
|
+
s.add_dependency(%q<yard>, ["~> 0.7"])
|
71
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
72
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
73
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
74
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
module SimpleCov::Configuration
|
4
|
+
def clean_filters
|
5
|
+
@filters = []
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
SimpleCov.configure do
|
10
|
+
clean_filters
|
11
|
+
load_adapter 'test_frameworks'
|
12
|
+
end
|
13
|
+
|
14
|
+
ENV["COVERAGE"] && SimpleCov.start do
|
15
|
+
add_filter "/.rvm/"
|
16
|
+
end
|
17
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
18
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
19
|
+
|
20
|
+
require 'rspec'
|
21
|
+
require 'paperclip-azure'
|
22
|
+
|
23
|
+
# Requires supporting files with custom matchers and macros, etc,
|
24
|
+
# in ./support/ and its subdirectories.
|
25
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: paperclip-azure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordan Yaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: azure
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hashie
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: addressable
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.8.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.8.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.7'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rdoc
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.12'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.12'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: jeweler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.0.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.0.1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: An Azure Blob Storage implementation for Paperclip.
|
140
|
+
email: help@supportify.io
|
141
|
+
executables: []
|
142
|
+
extensions: []
|
143
|
+
extra_rdoc_files:
|
144
|
+
- LICENSE.txt
|
145
|
+
- README.rdoc
|
146
|
+
files:
|
147
|
+
- .document
|
148
|
+
- .rspec
|
149
|
+
- Gemfile
|
150
|
+
- Gemfile.lock
|
151
|
+
- LICENSE.txt
|
152
|
+
- README.rdoc
|
153
|
+
- Rakefile
|
154
|
+
- VERSION
|
155
|
+
- lib/azure/blob/blob_service.rb
|
156
|
+
- lib/azure/core/auth/shared_access_signature.rb
|
157
|
+
- lib/paperclip-azure.rb
|
158
|
+
- lib/paperclip/storage/azure.rb
|
159
|
+
- paperclip-azure.gemspec
|
160
|
+
- spec/spec_helper.rb
|
161
|
+
homepage: http://github.com/jordanyaker/paperclip-azure
|
162
|
+
licenses:
|
163
|
+
- MIT
|
164
|
+
metadata: {}
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - '>='
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
requirements: []
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.0.6
|
182
|
+
signing_key:
|
183
|
+
specification_version: 4
|
184
|
+
summary: An Azure Blob Storage implementation for Paperclip.
|
185
|
+
test_files: []
|