├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── paperclip-qiniu.rb ├── paperclip-qiniu │ ├── exceptions.rb │ └── version.rb └── paperclip │ ├── qiniu.rb │ ├── qiniu │ └── action_view_extensions │ │ ├── qiniu_image_path.rb │ │ └── qiniu_image_tag.rb │ └── storage │ └── qiniu.rb ├── paperclip-qiniu.gemspec └── spec ├── lib └── paperclip │ └── storage │ └── qiniu_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | InstalledFiles 7 | _yardoc 8 | coverage 9 | doc/ 10 | lib/bundler/man 11 | pkg 12 | rdoc 13 | spec/reports 14 | test/tmp 15 | test/version_tmp 16 | tmp 17 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in paperclip-qiniu.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | paperclip-qiniu (0.2.0) 5 | paperclip 6 | qiniu (~> 6.4.1) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | activemodel (4.2.0) 12 | activesupport (= 4.2.0) 13 | builder (~> 3.1) 14 | activesupport (4.2.0) 15 | i18n (~> 0.7) 16 | json (~> 1.7, >= 1.7.7) 17 | minitest (~> 5.1) 18 | thread_safe (~> 0.3, >= 0.3.4) 19 | tzinfo (~> 1.1) 20 | builder (3.2.2) 21 | climate_control (0.0.3) 22 | activesupport (>= 3.0) 23 | cocaine (0.5.5) 24 | climate_control (>= 0.0.3, < 1.0) 25 | diff-lcs (1.2.5) 26 | i18n (0.7.0) 27 | json (1.8.2) 28 | mime-types (2.4.3) 29 | minitest (5.5.1) 30 | netrc (0.10.2) 31 | paperclip (4.2.1) 32 | activemodel (>= 3.0.0) 33 | activesupport (>= 3.0.0) 34 | cocaine (~> 0.5.3) 35 | mime-types 36 | qiniu (6.4.1) 37 | json (~> 1.7) 38 | mime-types (~> 2.4.3) 39 | rest-client (~> 1.6) 40 | ruby-hmac (~> 0.4) 41 | rest-client (1.7.2) 42 | mime-types (>= 1.16, < 3.0) 43 | netrc (~> 0.7) 44 | rspec (3.2.0) 45 | rspec-core (~> 3.2.0) 46 | rspec-expectations (~> 3.2.0) 47 | rspec-mocks (~> 3.2.0) 48 | rspec-core (3.2.0) 49 | rspec-support (~> 3.2.0) 50 | rspec-expectations (3.2.0) 51 | diff-lcs (>= 1.2.0, < 2.0) 52 | rspec-support (~> 3.2.0) 53 | rspec-mocks (3.2.0) 54 | diff-lcs (>= 1.2.0, < 2.0) 55 | rspec-support (~> 3.2.0) 56 | rspec-support (3.2.1) 57 | ruby-hmac (0.4.0) 58 | thread_safe (0.3.4) 59 | tzinfo (1.2.2) 60 | thread_safe (~> 0.1) 61 | 62 | PLATFORMS 63 | ruby 64 | 65 | DEPENDENCIES 66 | paperclip-qiniu! 67 | rspec 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 LI Daobing 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paperclip::Qiniu 2 | 3 | storage [paperclip](https://github.com/thoughtbot/paperclip/) attachments to http://qiniutek.com 4 | 5 | example project: https://github.com/lidaobing/paperclip-qiniu-example 6 | 7 | example site: http://paperclip-qiniu-example.herokuapp.com/ 8 | 9 | if you are using Rails 3, please use version 0.1.0: https://rubygems.org/gems/paperclip-qiniu/versions/0.1.0 10 | 11 | ## Usage 12 | 13 | * confirm you are working on a rails app 14 | 15 | * add following line to `Gemfile` 16 | 17 | ```ruby 18 | gem 'paperclip' 19 | gem 'paperclip-qiniu' 20 | # or get the latest version 21 | # gem 'paperclip-qiniu', :git => "git://github.com/lidaobing/paperclip-qiniu" 22 | ``` 23 | 24 | * create `config/initializers/paperclip.rb` 25 | 26 | ```ruby 27 | Paperclip::Attachment.default_options[:storage] = :qiniu 28 | Paperclip::Attachment.default_options[:qiniu_credentials] = { 29 | :access_key => ENV['QINIU_ACCESS_KEY'] || raise("set env QINIU_ACCESS_KEY"), 30 | :secret_key => ENV['QINIU_SECRET_KEY'] || raise("set env QINIU_SECRET_KEY") 31 | } 32 | Paperclip::Attachment.default_options[:bucket] = 'paperclip-qiniu-example' 33 | Paperclip::Attachment.default_options[:use_timestamp] = false 34 | Paperclip::Attachment.default_options[:qiniu_host] = 35 | 'http://cdn.example.com' 36 | ``` 37 | 38 | for more information on `qiniu_host`, read http://docs.qiniutek.com/v2/sdk/ruby/#publish 39 | 40 | * add a model like this 41 | 42 | ```ruby 43 | class Image < ActiveRecord::Base 44 | attr_accessible :file 45 | has_attached_file :file, :path => ":class/:attachment/:id/:basename.:extension" 46 | validates :file, :attachment_presence => true 47 | validates_attachment_content_type :file, :content_type => /\Aimage\/.*\Z/ 48 | end 49 | ``` 50 | 51 | * show image in your view 52 | 53 | ```erb 54 | <%= qiniu_image_tag @image.file.url, :thumbnail => '300x300>', :quality => 80 %> 55 | or 56 | <%= image_tag qiniu_image_path(@image.file.url, :thumbnail => '300x300>', :quality => 80) %> 57 | ``` 58 | 59 | support options: `thumbnail`, `gravity`, `crop`, `quality`, `rotate`, `format`, `auto_orient`. for more information on these options, check the document: http://docs.qiniutek.com/v3/api/foimg/#fo-imageMogr 60 | 61 | ## Contributing 62 | 63 | 1. Fork it 64 | 2. Create your feature branch (`git checkout -b my-new-feature`) 65 | 3. Commit your changes (`git commit -am 'Added some feature'`) 66 | 4. Push to the branch (`git push origin my-new-feature`) 67 | 5. Create new Pull Request 68 | 69 | ## CHANGELOG 70 | 71 | ### 0.1.1 (2015-01-06) 72 | 73 | * upgrade Ruby SDK from `qiniu-rs` to `6.4.1` 74 | 75 | ### 0.1.0 (2012-09-06) 76 | 77 | * add view helper: qiniu_image_tag, qiniu_image_path 78 | * update demo code, style is no longer needed. 79 | 80 | ### 0.0.3 (2012-09-06) 81 | 82 | * support qiniu api v3. 83 | 84 | ### 0.0.2 (2012-07-17) 85 | 86 | * support public host. 87 | 88 | ### 0.0.1 89 | 90 | * it works. 91 | 92 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require 'rspec/core/rake_task' 4 | 5 | RSpec::Core::RakeTask.new 6 | -------------------------------------------------------------------------------- /lib/paperclip-qiniu.rb: -------------------------------------------------------------------------------- 1 | require "paperclip-qiniu/version" 2 | require 'paperclip/storage/qiniu' 3 | require 'paperclip/qiniu/action_view_extensions/qiniu_image_path' if defined?(ActionView) 4 | require 'paperclip/qiniu/action_view_extensions/qiniu_image_tag' if defined?(ActionView) 5 | -------------------------------------------------------------------------------- /lib/paperclip-qiniu/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Paperclip 2 | module Qiniu 3 | class Error < StandardError; end 4 | class UploadFailed < Error; end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/paperclip-qiniu/version.rb: -------------------------------------------------------------------------------- 1 | module Paperclip 2 | module Qiniu 3 | VERSION = "0.2.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/paperclip/qiniu.rb: -------------------------------------------------------------------------------- 1 | require 'paperclip-qiniu' 2 | -------------------------------------------------------------------------------- /lib/paperclip/qiniu/action_view_extensions/qiniu_image_path.rb: -------------------------------------------------------------------------------- 1 | module Paperclip 2 | module Qiniu 3 | module ActionViewExtensions 4 | module QiniuImagePath 5 | def qiniu_image_path(source, options={}) 6 | options = options.clone 7 | thumbnail = options.delete(:thumbnail) 8 | gravity = options.delete(:gravity) 9 | crop = options.delete(:crop) 10 | quality = options.delete(:quality) 11 | rotate = options.delete(:rotate) 12 | format = options.delete(:format) 13 | auto_orient = options.delete(:auto_orient) 14 | res = source 15 | res += "?imageMogr" 16 | res += "/thumbnail/#{CGI.escape thumbnail}" if thumbnail 17 | res += "/gravity/#{CGI.escape gravity}" if gravity 18 | res += "/crop/#{CGI.escape crop}" if crop 19 | res += "/quality/#{CGI.escape quality.to_s}" if quality 20 | res += "/rotate/#{CGI.escape rotate.to_s}" if rotate 21 | res += "/format/#{CGI.escape format.to_s}" if format 22 | res += "/auto-orient" if auto_orient 23 | if res.end_with? '?imageMogr' 24 | source 25 | else 26 | res 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | 34 | ActionView::Base.send :include, Paperclip::Qiniu::ActionViewExtensions::QiniuImagePath 35 | -------------------------------------------------------------------------------- /lib/paperclip/qiniu/action_view_extensions/qiniu_image_tag.rb: -------------------------------------------------------------------------------- 1 | module Paperclip 2 | module Qiniu 3 | module ActionViewExtensions 4 | module QiniuImageTag 5 | def qiniu_image_tag(source, options={}) 6 | options.symbolize_keys! 7 | 8 | src = path_to_image(source) 9 | options[:src] = qiniu_image_path(src, options) 10 | 11 | unless src =~ /^(?:cid|data):/ || src.blank? 12 | options[:alt] = options.fetch(:alt){ image_alt(src) } 13 | end 14 | 15 | if size = options.delete(:size) 16 | options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} 17 | end 18 | 19 | if mouseover = options.delete(:mouseover) 20 | options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'" 21 | options[:onmouseout] = "this.src='#{options[:src]}'" 22 | end 23 | 24 | tag("img", options) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | 31 | ActionView::Base.send :include, Paperclip::Qiniu::ActionViewExtensions::QiniuImageTag 32 | -------------------------------------------------------------------------------- /lib/paperclip/storage/qiniu.rb: -------------------------------------------------------------------------------- 1 | require 'paperclip-qiniu/exceptions' 2 | 3 | module Paperclip 4 | module Storage 5 | module Qiniu 6 | def self.extended base 7 | begin 8 | require 'qiniu' 9 | rescue LoadError => e 10 | e.message << " (You may need to install the qiniu gem)" 11 | raise e 12 | end unless defined?(::Qiniu) 13 | 14 | base.instance_eval do 15 | unless @options[:url].to_s.match(/^:fog.*url$/) 16 | @options[:path] = @options[:path].gsub(/:url/, @options[:url]) 17 | @options[:url] = ':qiniu_public_url' 18 | end 19 | Paperclip.interpolates(:qiniu_public_url) do |attachment, style| 20 | attachment.public_url(style) 21 | end unless Paperclip::Interpolations.respond_to? :qiniu_public_url 22 | end 23 | 24 | end 25 | 26 | def exists?(style = default_style) 27 | init 28 | !!::Qiniu.stat(bucket, path(style)) 29 | end 30 | 31 | def flush_writes 32 | init 33 | for style, file in @queued_for_write do 34 | log("saving #{path(style)}") 35 | retried = false 36 | begin 37 | upload(file, path(style)) 38 | ensure 39 | file.rewind 40 | end 41 | end 42 | 43 | after_flush_writes # allows attachment to clean up temp files 44 | 45 | @queued_for_write = {} 46 | end 47 | 48 | def flush_deletes 49 | init 50 | for path in @queued_for_delete do 51 | ::Qiniu.delete(bucket, path) 52 | end 53 | @queued_for_delete = [] 54 | end 55 | 56 | def public_url(style = default_style) 57 | init 58 | if @options[:qiniu_host] 59 | "#{dynamic_fog_host_for_style(style)}/#{path(style)}" 60 | else 61 | res = ::Qiniu.get(bucket, path(style)) 62 | if res 63 | res["url"] 64 | else 65 | nil 66 | end 67 | end 68 | end 69 | 70 | private 71 | 72 | def init 73 | return if @inited 74 | ::Qiniu.establish_connection! @options[:qiniu_credentials] 75 | @inited = true 76 | end 77 | 78 | def upload(file, path) 79 | upload_token = ::Qiniu.generate_upload_token :scope => bucket 80 | opts = {:uptoken => upload_token, 81 | :file => file.path, 82 | :key => path, 83 | :bucket => bucket, 84 | :mime_type => file.content_type, 85 | :enable_crc32_check => true} 86 | unless ::Qiniu.upload_file(opts) 87 | raise Paperclip::Qiniu::UploadFailed 88 | end 89 | end 90 | 91 | def bucket 92 | @options[:bucket] || raise("bucket is nil") 93 | end 94 | 95 | def dynamic_fog_host_for_style(style) 96 | if @options[:qiniu_host].respond_to?(:call) 97 | @options[:qiniu_host].call(self) 98 | else 99 | @options[:qiniu_host] 100 | end 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /paperclip-qiniu.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/paperclip-qiniu/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["LI Daobing"] 6 | gem.email = ["lidaobing@gmail.com"] 7 | gem.description = %q{paperclip plugin for qiniu} 8 | gem.summary = %q{paperclip plugin for qiniu} 9 | gem.homepage = "https://github.com/lidaobing/paperclip-qiniu" 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "paperclip-qiniu" 15 | gem.require_paths = ["lib"] 16 | gem.version = Paperclip::Qiniu::VERSION 17 | gem.add_dependency 'paperclip' 18 | gem.add_dependency 'qiniu', '~> 6.4.1' 19 | gem.add_development_dependency 'rspec' 20 | end 21 | -------------------------------------------------------------------------------- /spec/lib/paperclip/storage/qiniu_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'paperclip/storage/qiniu' 3 | 4 | module Paperclip::Storage 5 | describe Qiniu do 6 | context 'exists?' do 7 | pending "it's hard to test paperclip plugin" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # Require this file using `require "spec_helper"` to ensure that it is only 4 | # loaded once. 5 | # 6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 7 | RSpec.configure do |config| 8 | config.treat_symbols_as_metadata_keys_with_true_values = true 9 | config.run_all_when_everything_filtered = true 10 | config.filter_run :focus 11 | 12 | # Run specs in random order to surface order dependencies. If you find an 13 | # order dependency and want to debug it, you can fix the order by providing 14 | # the seed, which is printed after each run. 15 | # --seed 1234 16 | config.order = 'random' 17 | end 18 | --------------------------------------------------------------------------------