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 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