file_store 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 033d82e34fc2d7300183e67f190556b556d87d44
4
- data.tar.gz: c0fb965cfdbe67d8add8e57f43ad5a8574cad9fa
3
+ metadata.gz: 9fff72cdc5645c1080b8753bc93094675979b4f8
4
+ data.tar.gz: c43124ef49f590095bc4e15b858766e363649a67
5
5
  SHA512:
6
- metadata.gz: 37d5b82dc70d0075bc0f21e282e93230855a9d0d550b3071ea026d11b6d4a4f20fa5b7bd2c6b2ea811172bd638e76b5af516fca08ff9b1cc490cce60889c316b
7
- data.tar.gz: 81026ca72dda868b9d74bd1da7ce50cd419631c2658df81fd5494dd4720e1b5b5d65b3ea3f0d201954b807dae1e26de4678582972c506bca38b57d10b02f02a0
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,6 @@
1
+ module FileStore
2
+ module Providers
3
+ autoload(:Provider, 'file_store/providers/provider')
4
+ autoload(:S3, 'file_store/providers/s3')
5
+ end # Providers
6
+ 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
@@ -0,0 +1,3 @@
1
+ module FileStore
2
+ VERSION = '0.1.0'
3
+ end
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.1
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-08 00:00:00.000000000 Z
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