file_store 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/file_store.rb +17 -0
- data/lib/file_store/config.rb +21 -0
- data/lib/file_store/instance.rb +28 -0
- data/lib/file_store/providers.rb +6 -0
- data/lib/file_store/providers/provider.rb +93 -0
- data/lib/file_store/providers/s3.rb +89 -0
- data/lib/file_store/version.rb +3 -0
- metadata +41 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fff72cdc5645c1080b8753bc93094675979b4f8
|
4
|
+
data.tar.gz: c43124ef49f590095bc4e15b858766e363649a67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f12572c541306fd26abe99669b3f702c15df09f76d4c19e1a70e7440121790e3d9841e81e3a0c90a76552c9c5d800a88a755984dd1db4286f3ff9765917adee
|
7
|
+
data.tar.gz: 80ac8001ddba6019313e361c4050f35343b73898b0846b97053a8d61e04efa661b11f4012dd254257303953f175cdf212c11e1fadab72c3dc8993747c5744189
|
data/lib/file_store.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'file_store/version'
|
2
|
+
|
3
|
+
module FileStore
|
4
|
+
autoload(:Config, 'file_store/config')
|
5
|
+
autoload(:Instance, 'file_store/instance')
|
6
|
+
autoload(:Providers, 'file_store/providers')
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def method_missing(meth, *args, &block)
|
10
|
+
instance.public_send(meth, *args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def instance
|
14
|
+
@__instance ||= Instance.new
|
15
|
+
end
|
16
|
+
end # Class Methods
|
17
|
+
end # FileStore
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module FileStore
|
2
|
+
class Config
|
3
|
+
VALID_CONFIGS = [:aws_access_key, :aws_access_secret, :aws_s3_bucket,
|
4
|
+
:aws_region].freeze
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@_config_hash = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Set up accessors for the config fields
|
12
|
+
VALID_CONFIGS.each { |config|
|
13
|
+
define_method(config) { @_config_hash[config] }
|
14
|
+
define_method("#{config}=") { |value| @_config_hash[config] = value }
|
15
|
+
}
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
@_config_hash
|
19
|
+
end
|
20
|
+
end # Config
|
21
|
+
end # FileStore
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FileStore
|
2
|
+
class Instance
|
3
|
+
def config
|
4
|
+
(@__config ||= Config.new).tap { |config| yield config if block_given? }
|
5
|
+
end
|
6
|
+
|
7
|
+
def provider
|
8
|
+
# Default to the S3 provider. This will be our only provider in the
|
9
|
+
# forseeable future.
|
10
|
+
@__provider ||= Providers::S3.new(config.to_h)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def respond_to_missing?(meth)
|
16
|
+
provider.respond_to?(meth) || super
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(meth, *args, &block)
|
20
|
+
if provider.respond_to?(meth)
|
21
|
+
meth = provider.mocked? ? "mock_#{meth}" : meth
|
22
|
+
provider.public_send(meth, *args, &block)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end # Instance
|
28
|
+
end # FileStore
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module FileStore
|
4
|
+
module Providers
|
5
|
+
class Provider
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
if (missing_fields = required_configs - opts.keys).length > 0
|
10
|
+
fail "missing fields: #{missing_fields.join(', ')}"
|
11
|
+
end
|
12
|
+
|
13
|
+
@mock = false
|
14
|
+
@options = opts
|
15
|
+
end
|
16
|
+
|
17
|
+
def upload(prefix, file_name, data = nil)
|
18
|
+
ext = _generate_ext_if_needed(file_name)
|
19
|
+
file_path = generate_unique_path(prefix) + "/#{file_name}#{ext}"
|
20
|
+
file_id = upload!(file_path, data)
|
21
|
+
"#{_provider_name}://#{file_id}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def download_url(file_id, opts = {})
|
25
|
+
# Extract the provider from the file_id. i.e. For S3: s3://file/id/path
|
26
|
+
# Validate that the current provider matches the file_id's provider
|
27
|
+
extracted_file_id = file_id.gsub(/\A(.*):\/\//, '')
|
28
|
+
fail "invalid provider: #{$1}" unless $1 == _provider_name
|
29
|
+
download_url!(extracted_file_id, opts)
|
30
|
+
end
|
31
|
+
|
32
|
+
def mock!
|
33
|
+
@mock = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def mocked?
|
37
|
+
!!@mock
|
38
|
+
end
|
39
|
+
|
40
|
+
def mock_upload(prefix, file_name, data = nil)
|
41
|
+
ext = _generate_ext_if_needed(file_name)
|
42
|
+
prefix + _generate_random_path + "/#{file_name}#{ext}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def mock_download_url(file_id, opts = {})
|
46
|
+
"http://mocked_download_url.com/#{file_id}"
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def upload!(prefix, file_name, data = nil)
|
52
|
+
fail NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
def download_url!(file_id, opts = {})
|
56
|
+
fail NotImplementedError
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_unique_path(prefix)
|
60
|
+
20.times do
|
61
|
+
path = prefix + _generate_random_path
|
62
|
+
return path unless path_exists?(path)
|
63
|
+
end
|
64
|
+
|
65
|
+
fail "could not find unique path"
|
66
|
+
end
|
67
|
+
|
68
|
+
def path_exists?(path)
|
69
|
+
fail NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
def required_configs
|
73
|
+
fail NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
##
|
79
|
+
# Will create a random path of four 3-digit numbers i.e. /123/456/789/321
|
80
|
+
def _generate_random_path
|
81
|
+
4.times.map { '/%03d' % SecureRandom.random_number(1000) }.join
|
82
|
+
end
|
83
|
+
|
84
|
+
def _provider_name
|
85
|
+
self.class.name.split('::').last.downcase
|
86
|
+
end
|
87
|
+
|
88
|
+
def _generate_ext_if_needed(file_name)
|
89
|
+
File.extname(file_name).length > 0 ? '' : '.dat'
|
90
|
+
end
|
91
|
+
end # Provider
|
92
|
+
end # Providers
|
93
|
+
end # FileStore
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module FileStore
|
4
|
+
module Providers
|
5
|
+
class S3 < Provider
|
6
|
+
REQUIRED_CONFIGS = [:aws_access_key, :aws_access_secret, :aws_s3_bucket,
|
7
|
+
:aws_region].freeze
|
8
|
+
DEFAULT_S3_OPTIONS = {
|
9
|
+
server_side_encryption: 'AES256'.freeze,
|
10
|
+
acl: 'private'.freeze
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
super
|
15
|
+
|
16
|
+
@_synchronizer = Mutex.new
|
17
|
+
@_s3_interface = ::Aws::S3::Resource.new(
|
18
|
+
access_key_id: options[:aws_access_key],
|
19
|
+
secret_access_key: options[:aws_access_secret],
|
20
|
+
region: options[:aws_region]
|
21
|
+
)
|
22
|
+
@_s3_bucket = _s3_interface.bucket(_bucket_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
##
|
28
|
+
# Send data directly to S3 either as a String / IO object, or as a
|
29
|
+
# multi-part upload
|
30
|
+
def upload!(file_path, data = nil)
|
31
|
+
_synchronize do
|
32
|
+
_s3_object(file_path).put(DEFAULT_S3_OPTIONS.merge(body: data))
|
33
|
+
"#{_bucket_name}/#{file_path}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Generate a download url for the object on S3 referenced by the file_id.
|
39
|
+
# Pass in a ttl as an option for the link expiration time in seconds.
|
40
|
+
def download_url!(file_id, opts = {})
|
41
|
+
options = { expires_in: opts[:ttl] || 600 }
|
42
|
+
bucket_name, file_id = _extract_bucket_name(file_id)
|
43
|
+
fail "invalid bucket: #{bucket_name}" unless bucket_name == _bucket_name
|
44
|
+
fail "object: #{file_id} doesn't exist" unless _object_exists?(file_id)
|
45
|
+
|
46
|
+
_s3_object(file_id).presigned_url(:get, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def path_exists?(path)
|
50
|
+
_s3_bucket.objects(prefix: path).one?
|
51
|
+
end
|
52
|
+
|
53
|
+
def required_configs
|
54
|
+
REQUIRED_CONFIGS
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def _extract_bucket_name(file_id)
|
60
|
+
split_file_id = file_id.split('/')
|
61
|
+
[split_file_id.shift, split_file_id.join('/')]
|
62
|
+
end
|
63
|
+
|
64
|
+
def _s3_interface
|
65
|
+
@_s3_interface
|
66
|
+
end
|
67
|
+
|
68
|
+
def _s3_bucket
|
69
|
+
@_s3_bucket
|
70
|
+
end
|
71
|
+
|
72
|
+
def _bucket_name
|
73
|
+
options[:aws_s3_bucket]
|
74
|
+
end
|
75
|
+
|
76
|
+
def _s3_object(path = "")
|
77
|
+
_s3_bucket.object(path)
|
78
|
+
end
|
79
|
+
|
80
|
+
def _object_exists?(path)
|
81
|
+
_s3_object(path).exists?
|
82
|
+
end
|
83
|
+
|
84
|
+
def _synchronize(&block)
|
85
|
+
@_synchronizer.synchronize(&block)
|
86
|
+
end
|
87
|
+
end # S3
|
88
|
+
end # Providers
|
89
|
+
end # FileStore
|
metadata
CHANGED
@@ -1,24 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: file_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Robert Honer
|
8
7
|
- Kayvon Ghaffari
|
8
|
+
- Robert Honer
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-02-
|
13
|
-
dependencies:
|
12
|
+
date: 2016-02-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 2.2.14
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 2.2.14
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rspec
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
14
42
|
description: ''
|
15
43
|
email:
|
16
|
-
- robert@payout.com
|
17
44
|
- kayvon@payout.com
|
45
|
+
- robert@payout.com
|
18
46
|
executables: []
|
19
47
|
extensions: []
|
20
48
|
extra_rdoc_files: []
|
21
|
-
files:
|
49
|
+
files:
|
50
|
+
- lib/file_store.rb
|
51
|
+
- lib/file_store/config.rb
|
52
|
+
- lib/file_store/instance.rb
|
53
|
+
- lib/file_store/providers.rb
|
54
|
+
- lib/file_store/providers/provider.rb
|
55
|
+
- lib/file_store/providers/s3.rb
|
56
|
+
- lib/file_store/version.rb
|
22
57
|
homepage: http://github.com/payout/file_store
|
23
58
|
licenses:
|
24
59
|
- MIT
|