Skip to content

Commit

Permalink
Extract from Refile gem
Browse files Browse the repository at this point in the history
  • Loading branch information
jnicklas committed May 10, 2015
0 parents commit ad51adc
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.bundle
*.so
*.o
*.a
mkmf.log
s3.yml
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in refile-s3.gemspec
gemspec

gem "refile", github: "refile/refile"
22 changes: 22 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2015 Jonas Nicklas

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Refile::S3

Amazon S3 backend for the [Refile][gh] gem. See the Refile documentation for
more details.

## License

[MIT](LICENSE.txt)

[gh]: http://github.com/refile/refile
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require "bundler/gem_tasks"

149 changes: 149 additions & 0 deletions lib/refile/s3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
require "aws-sdk"
require "open-uri"
require "refile"
require "refile/s3/version"

module Refile
# A refile backend which stores files in Amazon S3
#
# @example
# backend = Refile::Backend::S3.new(
# access_key_id: "xyz",
# secret_access_key: "abcd1234",
# region: "sa-east-1",
# bucket: "my-bucket",
# prefix: "files"
# )
# file = backend.upload(StringIO.new("hello"))
# backend.read(file.id) # => "hello"
class S3
extend Refile::BackendMacros

attr_reader :access_key_id, :max_size

# Sets up an S3 backend with the given credentials.
#
# @param [String] access_key_id
# @param [String] secret_access_key
# @param [String] region The AWS region to connect to
# @param [String] bucket The name of the bucket where files will be stored
# @param [String] prefix A prefix to add to all files. Prefixes on S3 are kind of like folders.
# @param [Integer, nil] max_size The maximum size of an uploaded file
# @param [#hash] hasher A hasher which is used to generate ids from files
# @param [Hash] s3_options Additional options to initialize S3 with
# @see http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Core/Configuration.html
# @see http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3.html
def initialize(access_key_id:, secret_access_key:, region:, bucket:, max_size: nil, prefix: nil, hasher: Refile::RandomHasher.new, **s3_options)
@access_key_id = access_key_id
@secret_access_key = secret_access_key
@s3_options = { access_key_id: access_key_id, secret_access_key: secret_access_key, region: region }.merge s3_options
@s3 = Aws::S3::Resource.new @s3_options
@bucket_name = bucket
@bucket = @s3.bucket @bucket_name
@hasher = hasher
@prefix = prefix
@max_size = max_size
end

# Upload a file into this backend
#
# @param [IO] uploadable An uploadable IO-like object.
# @return [Refile::File] The uploaded file
verify_uploadable def upload(uploadable)
id = @hasher.hash(uploadable)

if uploadable.is_a?(Refile::File) and uploadable.backend.is_a?(S3) and uploadable.backend.access_key_id == access_key_id
object(id).copy_from(copy_source: [@bucket_name, uploadable.backend.object(uploadable.id).key].join("/"))
else
object(id).put(body: uploadable, content_length: uploadable.size)
end

Refile::File.new(self, id)
end

# Get a file from this backend.
#
# Note that this method will always return a {Refile::File} object, even
# if a file with the given id does not exist in this backend. Use
# {FileSystem#exists?} to check if the file actually exists.
#
# @param [Sring] id The id of the file
# @return [Refile::File] The retrieved file
verify_id def get(id)
Refile::File.new(self, id)
end

# Delete a file from this backend
#
# @param [Sring] id The id of the file
# @return [void]
verify_id def delete(id)
object(id).delete
end

# Return an IO object for the uploaded file which can be used to read its
# content.
#
# @param [Sring] id The id of the file
# @return [IO] An IO object containing the file contents
verify_id def open(id)
Kernel.open(object(id).presigned_url(:get))
end

# Return the entire contents of the uploaded file as a String.
#
# @param [String] id The id of the file
# @return [String] The file's contents
verify_id def read(id)
object(id).get.body.read
rescue Aws::S3::Errors::NoSuchKey
nil
end

# Return the size in bytes of the uploaded file.
#
# @param [Sring] id The id of the file
# @return [Integer] The file's size
verify_id def size(id)
object(id).get.content_length
rescue Aws::S3::Errors::NoSuchKey
nil
end

# Return whether the file with the given id exists in this backend.
#
# @param [Sring] id The id of the file
# @return [Boolean]
verify_id def exists?(id)
object(id).exists?
end

# Remove all files in this backend. You must confirm the deletion by
# passing the symbol `:confirm` as an argument to this method.
#
# @example
# backend.clear!(:confirm)
# @raise [Refile::Confirm] Unless the `:confirm` symbol has been passed.
# @param [:confirm] confirm Pass the symbol `:confirm` to confirm deletion.
# @return [void]
def clear!(confirm = nil)
raise Refile::Confirm unless confirm == :confirm
@bucket.objects(prefix: @prefix).delete
end

# Return a presign signature which can be used to upload a file into this
# backend directly.
#
# @return [Refile::Signature]
def presign
id = RandomHasher.new.hash
signature = @bucket.presigned_post(key: [*@prefix, id].join("/"))
signature.content_length_range(0..@max_size) if @max_size
Signature.new(as: "file", id: id, url: signature.url.to_s, fields: signature.fields)
end

verify_id def object(id)
@bucket.object([*@prefix, id].join("/"))
end
end
end
5 changes: 5 additions & 0 deletions lib/refile/s3/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Refile
class S3
VERSION = "0.0.1"
end
end
26 changes: 26 additions & 0 deletions refile-s3.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'refile/s3/version'

Gem::Specification.new do |spec|
spec.name = "refile-s3"
spec.version = Refile::S3::VERSION
spec.authors = ["Jonas Nicklas"]
spec.email = ["jonas.nicklas@gmail.com"]
spec.summary = "Amazon S3 backend for the Refile gem"
spec.homepage = ""
spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_dependency "refile", "~> 0.5.0"
spec.add_dependency "aws-sdk", "~> 2.0"
spec.add_development_dependency "bundler", "~> 1.7"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "webmock"
end
12 changes: 12 additions & 0 deletions spec/refile/s3_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require "refile/spec_helper"
require "refile/s3"

WebMock.allow_net_connect!

config = YAML.load_file("s3.yml").map { |k, v| [k.to_sym, v] }.to_h

RSpec.describe Refile::S3 do
let(:backend) { Refile::S3.new(max_size: 100, **config) }

it_behaves_like :backend
end

0 comments on commit ad51adc

Please sign in to comment.