diff --git a/.travis.yml b/.travis.yml index 52e1d3436..47d00e916 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,12 @@ rvm: - - 2.0 - 2.1 - 2.2 script: "bundle exec rake clean spec cucumber" gemfile: - - gemfiles/3.2.awsv2.1.gemfile - - gemfiles/4.1.awsv2.1.gemfile - gemfiles/4.2.awsv2.1.gemfile - - gemfiles/3.2.awsv2.0.gemfile - - gemfiles/4.1.awsv2.0.gemfile - gemfiles/4.2.awsv2.0.gemfile - - gemfiles/3.2.awsv1.gemfile - - gemfiles/4.1.awsv1.gemfile - - gemfiles/4.2.awsv1.gemfile sudo: false cache: bundler diff --git a/Appraisals b/Appraisals index 240977dd6..bdfd8bbc0 100644 --- a/Appraisals +++ b/Appraisals @@ -1,44 +1,17 @@ -appraise "3.2.awsv2.1" do - gem "rails", "~> 3.2.0" - gem "aws-sdk", "~> 2.1.0" -end - -appraise "4.1.awsv2.1" do - gem "rails", "~> 4.1.0" - gem "aws-sdk", "~> 2.1.0" -end - appraise "4.2.awsv2.1" do gem "rails", "~> 4.2.0" - gem "aws-sdk", "~> 2.1.0" -end - -appraise "3.2.awsv2.0" do - gem "rails", "~> 3.2.0" - gem "aws-sdk", "~> 2.0.0" -end + gem "aws-sdk-s3", "~> 1" -appraise "4.1.awsv2.0" do - gem "rails", "~> 4.1.0" - gem "aws-sdk", "~> 2.0.0" + group :development, :test do + gem 'mime-types', '>= 1.16', '< 4' + end end appraise "4.2.awsv2.0" do gem "rails", "~> 4.2.0" - gem "aws-sdk", "~> 2.0.0" -end - -appraise "3.2.awsv1" do - gem "rails", "~> 3.2.0" - gem "aws-sdk", "~> 1.5" -end + gem "aws-sdk-s3", "~> 1" -appraise "4.1.awsv1" do - gem "rails", "~> 4.1.0" - gem "aws-sdk", "~> 1.5" -end - -appraise "4.2.awsv1" do - gem "rails", "~> 4.2.0" - gem "aws-sdk", "~> 1.5" + group :development, :test do + gem 'mime-types', '>= 1.16', '< 4' + end end diff --git a/Gemfile b/Gemfile index bdeb5f71b..98ee80d44 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem 'pry' # Prevents bundler from taking a long-time to resolve group :development, :test do gem 'activerecord-import' - gem 'mime-types', '~> 1.16' + gem 'mime-types' gem 'builder' gem 'rubocop', require: false end diff --git a/LICENSE b/LICENSE index 49c7d45a3..68bcce33f 100644 --- a/LICENSE +++ b/LICENSE @@ -3,7 +3,7 @@ LICENSE The MIT License -Copyright (c) 2008-2015 Jon Yurek and thoughtbot, inc. +Copyright (c) 2008-2016 Jon Yurek and thoughtbot, inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NEWS b/NEWS index dc8ada6b3..c50a24e49 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ master: +* Drops support to end-of-life'd ruby 2.0. * Improvement: Paperclip now supports aws-sdk v2 @betesh, @davetchen, https://github.com/thoughtbot/paperclip/pull/1903 @@ -9,14 +10,10 @@ master: s3_region may be nested in s3_credentials, and (if not nested in s3_credentials) it may be a Proc. -4.3.1 (2015-09-09): +4.3 -* Backport of bugfix to `remove_column`, so it works in Rails 3 and 4 - c740fb171fe2f88c60b999d2a1c2122f2b8f43e9 -* Fix GeometryParser regex for usage of '@>' flag -* `url` on a unpersisted record returns default_url -* spec deprecation warnings and failures -* README adjustments +See patch versions in v4.3 NEWS: +https://github.com/thoughtbot/paperclip/blob/v4.3/NEWS 4.3.0 (2015-06-18): diff --git a/README.md b/README.md index 4191dd4a9..d66b6f64f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Paperclip - [Deleting an Attachment](#deleting-an-attachment) - [Usage](#usage) - [Validations](#validations) +- [Internationalization (I18n)](#internationalization-i18n) - [Security Validations](#security-validations) - [Defaults](#defaults) - [Migrations](#migrations-1) @@ -400,6 +401,12 @@ inferred content_type, regardless of the actual contents of the file. --- +Internationalization (I18n) +--------------------------- + +For using or adding locale files in different languages, check the project +https://github.com/thoughtbot/paperclip-i18n. + Security Validations ==================== @@ -554,7 +561,7 @@ Storage Paperclip ships with 3 storage adapters: * File Storage -* S3 Storage (via `aws-sdk` or `aws-sdk-v1`) +* S3 Storage (via `aws-sdk`) * Fog Storage If you would like to use Paperclip with another storage, you can install these @@ -584,10 +591,6 @@ the `aws-sdk` gem in your Gemfile: ```ruby gem 'aws-sdk', '>= 2.0.0' # If using paperclip `master` (upcoming v5.0) ``` -or -```ruby -gem 'aws-sdk-v1' # If using paperclip <= v4.3.1 -``` And then you can specify using S3 from `has_attached_file`. You can find more information about configuring and using S3 storage in @@ -967,7 +970,7 @@ Thank you to all [the contributors](https://github.com/thoughtbot/paperclip/grap License ------- -Paperclip is Copyright © 2008-2015 thoughtbot, inc. It is free software, and may be +Paperclip is Copyright © 2008-2016 thoughtbot, inc. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file. About thoughtbot diff --git a/UPGRADING b/UPGRADING index 44a2739a3..d997a83b6 100644 --- a/UPGRADING +++ b/UPGRADING @@ -12,8 +12,3 @@ changes: note that the format of the permissions changed from using an underscore to using a hyphen. For example, `:public_read` needs to be changed to `public-read`. - -If you want to continue using an earlier version of aws-sdk, replace - aws-sdk with aws-sdk-v1 in your Gemfile. - -If both are in your Gemfile, paperclip will use aws-sdk v2. diff --git a/gemfiles/3.2.awsv1.gemfile b/gemfiles/3.2.awsv1.gemfile deleted file mode 100644 index a40423302..000000000 --- a/gemfiles/3.2.awsv1.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 3.2.0" -gem "aws-sdk", "~> 1.5" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/3.2.awsv2.0.gemfile b/gemfiles/3.2.awsv2.0.gemfile deleted file mode 100644 index 8c7cd14a8..000000000 --- a/gemfiles/3.2.awsv2.0.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 3.2.0" -gem "aws-sdk", "~> 2.0.0" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/3.2.awsv2.1.gemfile b/gemfiles/3.2.awsv2.1.gemfile deleted file mode 100644 index 1b586574c..000000000 --- a/gemfiles/3.2.awsv2.1.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 3.2.0" -gem "aws-sdk", "~> 2.1.0" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/4.1.awsv1.gemfile b/gemfiles/4.1.awsv1.gemfile deleted file mode 100644 index 278659d4b..000000000 --- a/gemfiles/4.1.awsv1.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 4.1.0" -gem "aws-sdk", "~> 1.5" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/4.1.awsv2.0.gemfile b/gemfiles/4.1.awsv2.0.gemfile deleted file mode 100644 index 7295e2937..000000000 --- a/gemfiles/4.1.awsv2.0.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 4.1.0" -gem "aws-sdk", "~> 2.0.0" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/4.1.awsv2.1.gemfile b/gemfiles/4.1.awsv2.1.gemfile deleted file mode 100644 index 9ab82b0ca..000000000 --- a/gemfiles/4.1.awsv2.1.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 4.1.0" -gem "aws-sdk", "~> 2.1.0" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/4.2.awsv1.gemfile b/gemfiles/4.2.awsv1.gemfile deleted file mode 100644 index 8b423c951..000000000 --- a/gemfiles/4.2.awsv1.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sqlite3", "~> 1.3.8", platforms: :ruby -gem "pry" -gem "rails", "~> 4.2.0" -gem "aws-sdk", "~> 1.5" - -group :development, :test do - gem "activerecord-import" - gem "mime-types", "~> 1.16" - gem "builder" - gem "rubocop", :require => false -end - -gemspec path: "../" diff --git a/gemfiles/4.2.awsv2.0.gemfile b/gemfiles/4.2.awsv2.0.gemfile index 528906aec..ef705dfd6 100644 --- a/gemfiles/4.2.awsv2.0.gemfile +++ b/gemfiles/4.2.awsv2.0.gemfile @@ -9,7 +9,7 @@ gem "aws-sdk", "~> 2.0.0" group :development, :test do gem "activerecord-import" - gem "mime-types", "~> 1.16" + gem "mime-types", ">= 1.16", "< 4" gem "builder" gem "rubocop", :require => false end diff --git a/gemfiles/4.2.awsv2.1.gemfile b/gemfiles/4.2.awsv2.1.gemfile index 361cfa09c..9b52119cf 100644 --- a/gemfiles/4.2.awsv2.1.gemfile +++ b/gemfiles/4.2.awsv2.1.gemfile @@ -9,7 +9,7 @@ gem "aws-sdk", "~> 2.1.0" group :development, :test do gem "activerecord-import" - gem "mime-types", "~> 1.16" + gem "mime-types", ">= 1.16", "< 4" gem "builder" gem "rubocop", :require => false end diff --git a/lib/paperclip/attachment.rb b/lib/paperclip/attachment.rb index 87f26d5a8..9dcf72754 100644 --- a/lib/paperclip/attachment.rb +++ b/lib/paperclip/attachment.rb @@ -427,7 +427,7 @@ def initialize_storage #:nodoc: def assign_attributes @queued_for_write[:original] = @file assign_file_information - assign_fingerprint(@file.fingerprint) + assign_fingerprint { @file.fingerprint } assign_timestamps end @@ -437,9 +437,9 @@ def assign_file_information instance_write(:file_size, @file.size) end - def assign_fingerprint(fingerprint) + def assign_fingerprint if instance_respond_to?(:fingerprint) - instance_write(:fingerprint, fingerprint) + instance_write(:fingerprint, yield) end end @@ -465,7 +465,7 @@ def dirty! def reset_file_if_original_reprocessed instance_write(:file_size, @queued_for_write[:original].size) - assign_fingerprint(@queued_for_write[:original].fingerprint) + assign_fingerprint { @queued_for_write[:original].fingerprint } reset_updater end @@ -523,6 +523,10 @@ def post_process_style(name, style) #:nodoc: @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor| file = Paperclip.processor(processor).make(file, style.processor_options, self) intermediate_files << file unless file == @queued_for_write[:original] + # if we're processing the original, close + unlink the source tempfile + if name == :original + @queued_for_write[:original].close(true) + end file end diff --git a/lib/paperclip/content_type_detector.rb b/lib/paperclip/content_type_detector.rb index 782ed9e7c..6c98baada 100644 --- a/lib/paperclip/content_type_detector.rb +++ b/lib/paperclip/content_type_detector.rb @@ -50,17 +50,22 @@ def empty_file? alias :empty? :empty_file? def calculated_type_matches - possible_types.select do |content_type| - content_type == type_from_file_contents - end + possible_types & types_from_file_contents end def possible_types MIME::Types.type_for(@filepath).collect(&:content_type) end + def types_from_file_contents + [type_from_file_command, type_from_mime_magic].compact + rescue Errno::ENOENT => e + Paperclip.log("Error while determining content type: #{e}") + SENSIBLE_DEFAULT + end + def type_from_file_contents - type_from_mime_magic || type_from_file_command + type_from_file_command || type_from_mime_magic rescue Errno::ENOENT => e Paperclip.log("Error while determining content type: #{e}") SENSIBLE_DEFAULT diff --git a/lib/paperclip/glue.rb b/lib/paperclip/glue.rb index 2d14c627a..c8d9e89a3 100644 --- a/lib/paperclip/glue.rb +++ b/lib/paperclip/glue.rb @@ -8,7 +8,7 @@ def self.included(base) base.extend ClassMethods base.send :include, Callbacks base.send :include, Validators - base.send :include, Schema if defined? ActiveRecord + base.send :include, Schema if defined? ActiveRecord::Base locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}") I18n.load_path += locale_path unless I18n.load_path.include?(locale_path) diff --git a/lib/paperclip/io_adapters/abstract_adapter.rb b/lib/paperclip/io_adapters/abstract_adapter.rb index 189778e67..ada5dbe96 100644 --- a/lib/paperclip/io_adapters/abstract_adapter.rb +++ b/lib/paperclip/io_adapters/abstract_adapter.rb @@ -4,7 +4,7 @@ module Paperclip class AbstractAdapter OS_RESTRICTED_CHARACTERS = %r{[/:]} - attr_reader :content_type, :original_filename, :size + attr_reader :content_type, :original_filename, :size, :tempfile delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile alias :length :size diff --git a/lib/paperclip/io_adapters/attachment_adapter.rb b/lib/paperclip/io_adapters/attachment_adapter.rb index 8319617f6..ff7695082 100644 --- a/lib/paperclip/io_adapters/attachment_adapter.rb +++ b/lib/paperclip/io_adapters/attachment_adapter.rb @@ -24,7 +24,13 @@ def copy_to_tempfile(source) if source.staged? FileUtils.cp(source.staged_path(@style), destination.path) else - source.copy_to_local_file(@style, destination.path) + begin + source.copy_to_local_file(@style, destination.path) + rescue Errno::EACCES => e + # clean up lingering tempfile if we cannot access source file + destination.close(true) + raise + end end destination end diff --git a/lib/paperclip/schema.rb b/lib/paperclip/schema.rb index dafb715f9..311d23920 100644 --- a/lib/paperclip/schema.rb +++ b/lib/paperclip/schema.rb @@ -38,8 +38,7 @@ def remove_attachment(table_name, *attachment_names) options = attachment_names.extract_options! attachment_names.each do |attachment_name| - COLUMNS.each_pair do |column_name, column_type| - column_options = options.merge(options[column_name.to_sym] || {}) + COLUMNS.keys.each do |column_name| remove_column(table_name, "#{attachment_name}_#{column_name}") end end diff --git a/lib/paperclip/storage/s3.rb b/lib/paperclip/storage/s3.rb index 50caf1b29..bb8772233 100644 --- a/lib/paperclip/storage/s3.rb +++ b/lib/paperclip/storage/s3.rb @@ -41,7 +41,7 @@ module Storage # * +s3_permissions+: This is a String that should be one of the "canned" access # policies that S3 provides (more information can be found here: # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html) - # The default for Paperclip is :public_read (aws-sdk v1) / public-read (aws-sdk v2). + # The default for Paperclip is public-read. # # You can set permission on a per style bases by doing the following: # :s3_permissions => { @@ -112,22 +112,23 @@ module Storage # :s3_storage_class => :reduced_redundancy module S3 + SEMAPHORE = Mutex.new + def self.extended base - unless defined?(AWS_CLASS) - begin - require 'aws-sdk' - const_set('AWS_CLASS', defined?(::Aws) ? ::Aws : ::AWS) - const_set('AWS_BASE_ERROR', - defined?(::Aws) ? Aws::Errors::ServiceError : AWS::Errors::Base) - const_set('DEFAULT_PERMISSION', - defined?(::AWS) ? :public_read : :'public-read') - - rescue LoadError => e - e.message << " (You may need to install the aws-sdk gem)" - raise e - end - if Gem::Version.new(AWS_CLASS::VERSION) >= Gem::Version.new(2) && Gem::Version.new(AWS_CLASS::VERSION) <= Gem::Version.new("2.0.33") - raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version." + SEMAPHORE.synchronize do + unless defined?(AWS_CLASS) + begin + require 'aws-sdk-s3' + const_set('AWS_CLASS', defined?(::Aws) ? ::Aws : ::AWS) + const_set('AWS_BASE_ERROR', + defined?(::Aws) ? Aws::Errors::ServiceError : AWS::Errors::Base) + const_set('DEFAULT_PERMISSION', + defined?(::AWS) ? :public_read : :'public-read') + + rescue LoadError => e + e.message << " (You may need to install the aws-sdk gem)" + raise e + end end end @@ -182,10 +183,10 @@ def sanitize_hash(hash) "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}" end unless Paperclip::Interpolations.respond_to? :s3_alias_url Paperclip.interpolates(:s3_path_url) do |attachment, style| - "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}" + "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name(style)}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}" end unless Paperclip::Interpolations.respond_to? :s3_path_url Paperclip.interpolates(:s3_domain_url) do |attachment, style| - "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}" + "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name(style)}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}" end unless Paperclip::Interpolations.respond_to? :s3_domain_url Paperclip.interpolates(:asset_host) do |attachment, style| "#{attachment.path(style).sub(%r{\A/}, "".freeze)}" @@ -194,13 +195,11 @@ def sanitize_hash(hash) def expiring_url(time = 3600, style_name = default_style) if path(style_name) - if aws_v1? - base_options = { :expires => time, :secure => use_secure_protocol?(style_name) } - s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s - else - base_options = { :expires_in => time } - s3_object(style_name).presigned_url(:get, base_options.merge(s3_url_options)).to_s - end + base_options = { expires_in: time } + s3_object(style_name).presigned_url( + :get, + base_options.merge(s3_url_options), + ).to_s else url(style_name) end @@ -217,9 +216,10 @@ def s3_host_name host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze end - def s3_region + def s3_region(style = :original) region = @options[:s3_region] region = region.call(self) if region.is_a?(Proc) + region = styles[style][:region] if styles[style] && styles[style][:region] region || s3_credentials[:s3_region] end @@ -236,55 +236,42 @@ def s3_url_options s3_url_options end - def bucket_name + def bucket_name(style = default_style) @bucket = @options[:bucket] || s3_credentials[:bucket] @bucket = @bucket.call(self) if @bucket.respond_to?(:call) + @bucket = styles[style][:bucket] if styles[style] && styles[style][:bucket] @bucket or raise ArgumentError, "missing required :bucket option" end - def s3_interface - @s3_interface ||= begin - config = if aws_v1? - { :s3_endpoint => s3_host_name } - else - { :region => s3_region } - end - - if using_http_proxy? + def s3_interface(style = default_style) + config = { region: s3_region(style) } - proxy_opts = { :host => http_proxy_host } - proxy_opts[:port] = http_proxy_port if http_proxy_port - if http_proxy_user - userinfo = http_proxy_user.to_s - userinfo += ":#{http_proxy_password}" if http_proxy_password - proxy_opts[:userinfo] = userinfo - end - config[:proxy_uri] = URI::HTTP.build(proxy_opts) - end + if using_http_proxy? - [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt| - config[opt] = s3_credentials[opt] if s3_credentials[opt] + proxy_opts = { :host => http_proxy_host } + proxy_opts[:port] = http_proxy_port if http_proxy_port + if http_proxy_user + userinfo = http_proxy_user.to_s + userinfo += ":#{http_proxy_password}" if http_proxy_password + proxy_opts[:userinfo] = userinfo end + config[:proxy_uri] = URI::HTTP.build(proxy_opts) + end - obtain_s3_instance_for(config.merge(@s3_options)) + [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt| + config[opt] = s3_credentials[opt] if s3_credentials[opt] end + + obtain_s3_instance_for(config.merge(@s3_options)) end def obtain_s3_instance_for(options) instances = (Thread.current[:paperclip_s3_instances] ||= {}) - instances[options] ||= if aws_v1? - AWS_CLASS::S3.new(options) - else - AWS_CLASS::S3::Resource.new(options) - end + instances[options] ||= AWS_CLASS::S3::Resource.new(options) end - def s3_bucket - @s3_bucket ||= if aws_v1? - s3_interface.buckets[bucket_name] - else - s3_interface.bucket(bucket_name) - end + def s3_bucket(style = default_style) + s3_interface(style).bucket(bucket_name(style)) end def style_name_as_path(style_name) @@ -292,11 +279,7 @@ def style_name_as_path(style_name) end def s3_object style_name = default_style - if aws_v1? - s3_bucket.objects[style_name_as_path(style_name)] - else - s3_bucket.object(style_name_as_path(style_name)) - end + s3_bucket(style_name).object style_name_as_path(style_name) end def using_http_proxy? @@ -366,24 +349,18 @@ def s3_protocol(style = default_style, with_colon = false) end end - def create_bucket - if aws_v1? - s3_interface.buckets.create(bucket_name) - else - s3_interface.bucket(bucket_name).create - end + def create_bucket(style_name) + s3_interface(style_name).bucket(bucket_name(style_name)).create end def flush_writes #:nodoc: - @queued_for_write.each do |style, file| + @queued_for_write.peach(3) do |style, file| retries = 0 begin log("saving #{path(style)}") - acl = @s3_permissions[style] || @s3_permissions[:default] - acl = acl.call(self, style) if acl.respond_to?(:call) write_options = { :content_type => file.content_type, - :acl => acl + :acl => s3_permissions(style) } # add storage class for this style if defined @@ -404,13 +381,10 @@ def flush_writes #:nodoc: write_options[:metadata] = @s3_metadata unless @s3_metadata.empty? write_options.merge!(@s3_headers) - if aws_v1? - s3_object(style).write(file, write_options) - else - s3_object(style).upload_file(file.path, write_options) - end + puts "******* #{s3_object.inspect}" + s3_object(style).upload_file(file.path, write_options) rescue AWS_CLASS::S3::Errors::NoSuchBucket - create_bucket + create_bucket(style) retry rescue AWS_CLASS::S3::Errors::SlowDown retries += 1 @@ -431,14 +405,11 @@ def flush_writes #:nodoc: end def flush_deletes #:nodoc: + # TODO figure out style from path @queued_for_delete.each do |path| begin log("deleting #{path}") - if aws_v1? - s3_bucket.objects[path.sub(%r{\A/},'')] - else - s3_bucket.object(path.sub(%r{\A/},'')) - end.delete + s3_bucket.object(path.sub(%r{\A/}, "")).delete rescue AWS_BASE_ERROR => e # Ignore this. end @@ -449,7 +420,7 @@ def flush_deletes #:nodoc: def copy_to_local_file(style, local_dest_path) log("copying #{path(style)} to local file #{local_dest_path}") ::File.open(local_dest_path, 'wb') do |local_file| - s3_object(style).send(aws_v1? ? :read : :get) do |chunk| + s3_object(style).get do |chunk| local_file.write(chunk) end end @@ -460,10 +431,6 @@ def copy_to_local_file(style, local_dest_path) private - def aws_v1? - Gem::Version.new(AWS_CLASS::VERSION) < Gem::Version.new(2) - end - def find_credentials creds case creds when File diff --git a/lib/paperclip/url_generator.rb b/lib/paperclip/url_generator.rb index bed60f2ab..2b528a8ee 100644 --- a/lib/paperclip/url_generator.rb +++ b/lib/paperclip/url_generator.rb @@ -2,6 +2,13 @@ module Paperclip class UrlGenerator + class << self + def encoder + @encoder ||= URI::RFC2396_Parser.new + end + delegate :escape, :unescape, to: :encoder + end + def initialize(attachment, attachment_options) @attachment = attachment @attachment_options = attachment_options @@ -61,7 +68,7 @@ def escape_url(url) if url.respond_to?(:escape) url.escape else - URI.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" } + self.class.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" } end end diff --git a/lib/paperclip/validators/media_type_spoof_detection_validator.rb b/lib/paperclip/validators/media_type_spoof_detection_validator.rb index d3699acc1..4bbe9dff9 100644 --- a/lib/paperclip/validators/media_type_spoof_detection_validator.rb +++ b/lib/paperclip/validators/media_type_spoof_detection_validator.rb @@ -8,6 +8,7 @@ def validate_each(record, attribute, value) if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed? record.errors.add(attribute, :spoofed_media_type) end + adapter.tempfile.close(true) if adapter.tempfile end end diff --git a/paperclip.gemspec b/paperclip.gemspec index da32719cc..f330af1dc 100644 --- a/paperclip.gemspec +++ b/paperclip.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - if File.exists?('UPGRADING') + if File.exist?('UPGRADING') s.post_install_message = File.read("UPGRADING") end @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', '>= 3.2.0') s.add_dependency('cocaine', '~> 0.5.5') s.add_dependency('mime-types') - s.add_dependency('mimemagic', '0.3.0') + s.add_dependency('mimemagic', '~> 0.3.0') s.add_development_dependency('activerecord', '>= 3.2.0') s.add_development_dependency('shoulda') diff --git a/spec/paperclip/attachment_spec.rb b/spec/paperclip/attachment_spec.rb index 7dbf79fed..ac8015c5a 100644 --- a/spec/paperclip/attachment_spec.rb +++ b/spec/paperclip/attachment_spec.rb @@ -1374,6 +1374,12 @@ def call(filename) end it "does not calculate fingerprint" do + Digest::MD5.stubs(:file) + @dummy.avatar = @file + expect(Digest::MD5).to have_received(:file).never + end + + it "does not assign fingerprint" do @dummy.avatar = @file assert_nil @dummy.avatar.fingerprint end diff --git a/spec/paperclip/integration_spec.rb b/spec/paperclip/integration_spec.rb index e820e494d..b4118f272 100644 --- a/spec/paperclip/integration_spec.rb +++ b/spec/paperclip/integration_spec.rb @@ -3,14 +3,25 @@ require 'open-uri' describe 'Paperclip' do + around do |example| + files, files2 = nil, nil + files = ObjectSpace.each_object(Tempfile).select{|x| x.path && File.file?(x.path)} + example.run + files2 = ObjectSpace.each_object(Tempfile).select{|x| x.path && File.file?(x.path)} + diff = files2-files + expect(diff).to eq([]), "Leaked tempfiles: #{diff.inspect}" + end + context "Many models at once" do before do rebuild_model @file = File.new(fixture_file("5k.png"), 'rb') # Deals with `Too many open files` error - Dummy.import 100.times.map { Dummy.new avatar: @file } - Dummy.import 100.times.map { Dummy.new avatar: @file } - Dummy.import 100.times.map { Dummy.new avatar: @file } + dummies = 300.times.map { Dummy.new avatar: @file } + Dummy.import dummies + # save attachment instances to run after hooks including tempfile cleanup + # since activerecord-import does not use our usually hooked-in hooks (such as after_save) + dummies.each{|d| d.avatar.save } end after { @file.close } @@ -159,7 +170,11 @@ assert_not_equal File.size(@file.path), @dummy.avatar.size end - after { @file.close } + after do + @file.close + # save attachment instance to run after hooks (including tempfile cleanup) + @dummy.avatar.save + end end context "A model with attachments scoped under an id" do @@ -347,6 +362,8 @@ it "is not ok with bad files" do @dummy.avatar = @bad_file assert ! @dummy.valid? + # save attachment instance to run after hooks (including tempfile cleanup) + @dummy.avatar.save end it "knows the difference between good files, bad files, and not files when validating" do @@ -354,8 +371,13 @@ @d2 = Dummy.find(@dummy.id) @d2.avatar = @file assert @d2.valid?, @d2.errors.full_messages.inspect + # save attachment instance to run after hooks (including tempfile cleanup) + @d2.avatar.save + @d2.avatar = @bad_file assert ! @d2.valid? + # save attachment instance to run after hooks (including tempfile cleanup) + @d2.avatar.save end it "is able to reload without saving and not have the file disappear" do diff --git a/spec/paperclip/paperclip_spec.rb b/spec/paperclip/paperclip_spec.rb index c47dfdbc6..6d87b5b4f 100644 --- a/spec/paperclip/paperclip_spec.rb +++ b/spec/paperclip/paperclip_spec.rb @@ -123,33 +123,6 @@ class ::Four; end end end - if using_protected_attributes? - context "that is attr_protected" do - before do - Dummy.class_eval do - attr_protected :avatar - end - @dummy = Dummy.new - end - - it "does not assign the avatar on mass-set" do - @dummy.attributes = { other: "I'm set!", - avatar: @file } - - assert_equal "I'm set!", @dummy.other - assert ! @dummy.avatar? - end - - it "allows assigment on normal set" do - @dummy.other = "I'm set!" - @dummy.avatar = @file - - assert_equal "I'm set!", @dummy.other - assert @dummy.avatar? - end - end - end - context "with a subclass" do before do class ::SubDummy < Dummy; end diff --git a/spec/paperclip/storage/s3_spec.rb b/spec/paperclip/storage/s3_spec.rb index c361bfc35..82fbf612e 100644 --- a/spec/paperclip/storage/s3_spec.rb +++ b/spec/paperclip/storage/s3_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require 'aws-sdk' +require 'aws-sdk-s3' describe Paperclip::Storage::S3 do before do @@ -14,6 +14,19 @@ def aws2_add_region defined?(::Aws) ? { s3_region: 'us-east-1' } : {} end + context "multithreaded initialization" do + it "should not fail on missing constants" do + 10.times.map do |i| + Thread.new do + ActiveRecord::Base.connection_pool.with_connection do |_| + rebuild_model (aws2_add_region).merge storage: :s3 + Dummy.new.avatar + end + end + end.map{|t| t.join} + end + end + context "Parsing S3 credentials" do before do @proxy_settings = {host: "127.0.0.1", port: 8888, user: "foo", password: "bar"} diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 887b432ad..036770123 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -37,7 +37,6 @@ config.include ModelReconstruction config.include TestData config.extend VersionHelper - config.extend RailsHelpers::ClassMethods config.mock_framework = :mocha config.before(:all) do rebuild_model diff --git a/spec/support/rails_helpers.rb b/spec/support/rails_helpers.rb deleted file mode 100644 index fb45bff1f..000000000 --- a/spec/support/rails_helpers.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RailsHelpers - module ClassMethods - def using_protected_attributes? - ActiveRecord::VERSION::MAJOR < 4 - end - end -end