balloon 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +185 -0
- data/README_EN.md +4 -0
- data/Rakefile +1 -0
- data/balloon.gemspec +35 -0
- data/lib/balloon.rb +42 -0
- data/lib/balloon/base.rb +43 -0
- data/lib/balloon/cache.rb +44 -0
- data/lib/balloon/configuration.rb +85 -0
- data/lib/balloon/download.rb +50 -0
- data/lib/balloon/error.rb +5 -0
- data/lib/balloon/file_extension.rb +187 -0
- data/lib/balloon/http/client.rb +91 -0
- data/lib/balloon/http/response.rb +71 -0
- data/lib/balloon/load.rb +33 -0
- data/lib/balloon/locale/en.yml +7 -0
- data/lib/balloon/processing.rb +98 -0
- data/lib/balloon/storage/file.rb +62 -0
- data/lib/balloon/storage/store.rb +84 -0
- data/lib/balloon/storage/upyun.rb +91 -0
- data/lib/balloon/up.rb +100 -0
- data/lib/balloon/uploader.rb +90 -0
- data/lib/balloon/validate.rb +41 -0
- data/lib/balloon/version.rb +3 -0
- data/lib/rails/generators/balloon/config/config_generator.rb +15 -0
- data/lib/rails/generators/balloon/config/templates/balloon.yml +11 -0
- data/spec/fixtures/UPCASE.JPG +0 -0
- data/spec/fixtures/egg +0 -0
- data/spec/fixtures/invalid.jpg +1 -0
- data/spec/fixtures/kid.gif +0 -0
- data/spec/fixtures/kids.gif +0 -0
- data/spec/fixtures/word.txt +1 -0
- metadata +237 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module Balloon
|
5
|
+
module Download
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def down_url(uri)
|
9
|
+
connection = ::Faraday.new
|
10
|
+
response = connection.get(uri)
|
11
|
+
generate_down_cache_directory
|
12
|
+
path = File.join down_cache_path, generate_down_id
|
13
|
+
|
14
|
+
if response.status != 200
|
15
|
+
raise Balloon::DownloadError, I18n.translate(:"errors.messages.response_error")
|
16
|
+
end
|
17
|
+
|
18
|
+
if content_type = MIME::Types[response.headers["content-type"]][0]
|
19
|
+
mime_type = content_type.content_type
|
20
|
+
if self.respond_to?(:uploader_mimetype_white)
|
21
|
+
if !uploader_mimetype_white.include?(mime_type)
|
22
|
+
raise Balloon::DownloadError, I18n.translate(:"errors.messages.down_mime_error")
|
23
|
+
end
|
24
|
+
elsif self.respond_to?(:uploader_mimetype_black)
|
25
|
+
if !uploader_mimetype_black.include?(mime_type)
|
26
|
+
raise Balloon::DownloadError, I18n.translate(:"errors.messages.down_mime_error")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
File.open(path, "wb") do |f|
|
32
|
+
f.write(response.body)
|
33
|
+
end
|
34
|
+
|
35
|
+
return path
|
36
|
+
rescue Faraday::Error::ConnectionFailed => e
|
37
|
+
raise Balloon::DownloadError, I18n.translate(:"errors.messages.connection_failed")
|
38
|
+
rescue Faraday::Error::TimeoutError => e
|
39
|
+
raise Ballloon::DownloadError, I18n.translate(:"errors.messages.timeout_error")
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def generate_down_id
|
45
|
+
now = Time.now
|
46
|
+
"#{now.to_i}#{now.usec.to_s[0, 5]}".to_i.to_s(16)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Balloon
|
5
|
+
# This class is file Extension for ruby class
|
6
|
+
#
|
7
|
+
# @param[File, UploadFile, Hash, String, StringIO] file
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# file = FileExtension.new(file)
|
12
|
+
# file.path
|
13
|
+
# file.filename
|
14
|
+
# file.basename
|
15
|
+
# file.mime_type
|
16
|
+
# file.extension
|
17
|
+
# file.read
|
18
|
+
#
|
19
|
+
|
20
|
+
class FileExtension
|
21
|
+
SANITIZE_REGEX = /[^a-zA-Z0-9\.]|[\^]/
|
22
|
+
|
23
|
+
FILENAME_REGEX = [ /\A(.+)\.(tar\.([glxb]?z|bz2))\z/, /\A(.+)\.([^\.]+)\z/ ]
|
24
|
+
|
25
|
+
IMAGE_REGEX = [
|
26
|
+
["GIF8", "image/gif"],
|
27
|
+
["\x89PNG", "image/png"],
|
28
|
+
["\xff\xd8\xff\xe0\x00\x10JFIF", "image/jpeg"],
|
29
|
+
["\xff\xd8\xff\xe1(.*){2}Exif", "image/jpeg"]
|
30
|
+
]
|
31
|
+
|
32
|
+
IMAGE_EXT_LIST = {
|
33
|
+
"image/gif" => "gif",
|
34
|
+
"image/jpeg" => "jpg",
|
35
|
+
"image/png" => "png"
|
36
|
+
}
|
37
|
+
|
38
|
+
def initialize(file, mime_type = nil)
|
39
|
+
if file.is_a?(Hash)
|
40
|
+
@file = file["tempfile"] || file[:tempfile]
|
41
|
+
@original_filename = file["filename"] || file[:filename]
|
42
|
+
@mime_type = file["content_type"] || file[:content_type]
|
43
|
+
else
|
44
|
+
@file = file
|
45
|
+
@original_filename = nil
|
46
|
+
@mime_type = mime_type
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get real path with uploaded file
|
51
|
+
#
|
52
|
+
# @return [String] file path
|
53
|
+
def path
|
54
|
+
return "" if @file.blank?
|
55
|
+
if @file.is_a?(String) || @file.is_a?(Pathname)
|
56
|
+
File.expand_path(@file)
|
57
|
+
elsif @file.respond_to?(:path)
|
58
|
+
File.expand_path(@file.path)
|
59
|
+
else
|
60
|
+
""
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get original filename with uploaded file
|
65
|
+
#
|
66
|
+
# @return [String] file original filename
|
67
|
+
def original_filename
|
68
|
+
return @original_filename if @original_filename
|
69
|
+
if @file && @file.respond_to?(:original_filename)
|
70
|
+
@file.original_filename
|
71
|
+
elsif !path.blank?
|
72
|
+
File.basename(path)
|
73
|
+
else
|
74
|
+
""
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get filename with uploaded file
|
79
|
+
#
|
80
|
+
# @return [string] the real filename
|
81
|
+
def filename
|
82
|
+
return "" if original_filename.blank?
|
83
|
+
sanitize(original_filename)
|
84
|
+
end
|
85
|
+
|
86
|
+
def basename
|
87
|
+
return "" if filename.blank?
|
88
|
+
split_extension(filename)[0]
|
89
|
+
end
|
90
|
+
|
91
|
+
def extension
|
92
|
+
ext_name = split_extension(filename)[1] if !filename.blank?
|
93
|
+
return ext_name if !ext_name.blank?
|
94
|
+
return IMAGE_EXT_LIST[mime_type]
|
95
|
+
end
|
96
|
+
|
97
|
+
def mime_type
|
98
|
+
return get_mime_type(MIME::Types[@mime_type]) if @mime_type
|
99
|
+
ext_name = split_extension(filename)[1] if !filename.blank?
|
100
|
+
return get_mime_type(MIME::Types.type_for(ext_name)) if !ext_name.blank?
|
101
|
+
if type = read_mime_type then return type end
|
102
|
+
if type = command_mime_type then return type end
|
103
|
+
end
|
104
|
+
|
105
|
+
def size
|
106
|
+
return 0 if @file.blank?
|
107
|
+
if @file.is_a?(String)
|
108
|
+
exists? ? File.size(path) : 0
|
109
|
+
elsif @file.respond_to?(:size)
|
110
|
+
@file.size
|
111
|
+
else
|
112
|
+
0
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def empty?
|
117
|
+
@file.nil? || self.size.nil? || ( self.size.zero? && !self.exists? )
|
118
|
+
end
|
119
|
+
|
120
|
+
def exists?
|
121
|
+
return File.exists?(self.path) if !path.empty?
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
|
125
|
+
def read(count = nil)
|
126
|
+
return "" if empty?
|
127
|
+
if exists? && @file.is_a?(String)
|
128
|
+
File.open(@file, "rb") {|file| file.read(count) }
|
129
|
+
elsif @file.respond_to?(:read)
|
130
|
+
@file.read(count)
|
131
|
+
else
|
132
|
+
""
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# @todo What Change basename add extension
|
138
|
+
def save_to(new_path, permissions = nil, directory_permission = nil)
|
139
|
+
new_path = File.expand_path new_path
|
140
|
+
new_path = File.join new_path, basename + "." + extension
|
141
|
+
|
142
|
+
if exists?
|
143
|
+
FileUtils.cp(path, new_path) unless new_path == path
|
144
|
+
else
|
145
|
+
File.open(new_path, "wb"){ |f| f.write(read) }
|
146
|
+
end
|
147
|
+
File.chmod(permissions, new_path) if permissions
|
148
|
+
self.class.new(new_path)
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def sanitize(name)
|
154
|
+
name = name.gsub("\\", "/")
|
155
|
+
name = name.gsub(SANITIZE_REGEX, "_")
|
156
|
+
name = "_#{name}" if name =~ /\A\.+\z/
|
157
|
+
name = "unnamed" if name.size == 0
|
158
|
+
#name = name.downcase
|
159
|
+
return name.mb_chars.to_s
|
160
|
+
end
|
161
|
+
|
162
|
+
def split_extension(filename)
|
163
|
+
FILENAME_REGEX.each do |regexp|
|
164
|
+
return $1, $2 if filename =~ regexp
|
165
|
+
end
|
166
|
+
return filename, ""
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_mime_type(mime_type)
|
170
|
+
mime_type.first.content_type if !mime_type.empty?
|
171
|
+
end
|
172
|
+
|
173
|
+
def read_mime_type
|
174
|
+
content = read(10)
|
175
|
+
return if content.blank?
|
176
|
+
IMAGE_REGEX.each do |regexp|
|
177
|
+
return regexp[1] if content =~ %r"^#{regexp[0].force_encoding("binary")}"
|
178
|
+
end
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def command_mime_type
|
183
|
+
content = `file #{path} --mime-type`.gsub("\n", '')
|
184
|
+
content.split(':')[1].strip if content
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Balloon
|
4
|
+
module Http
|
5
|
+
class Client
|
6
|
+
|
7
|
+
attr_reader :url
|
8
|
+
|
9
|
+
attr_reader :login, :pass
|
10
|
+
|
11
|
+
attr_reader :klass, :token
|
12
|
+
|
13
|
+
attr_accessor :headers
|
14
|
+
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
attr_accessor :connection
|
18
|
+
|
19
|
+
attr_accessor :conn_build
|
20
|
+
|
21
|
+
def initialize(uri = nil, options = nil, &block)
|
22
|
+
@url = uri
|
23
|
+
yield self if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def builder(&block)
|
27
|
+
@conn_build = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def url=(uri)
|
31
|
+
@conn = nil
|
32
|
+
@url = uri
|
33
|
+
end
|
34
|
+
|
35
|
+
def basic_auth(login, pass)
|
36
|
+
@login = login
|
37
|
+
@pass = pass
|
38
|
+
end
|
39
|
+
|
40
|
+
def token_auth(klass, token)
|
41
|
+
@klass = klass
|
42
|
+
@token = token
|
43
|
+
end
|
44
|
+
|
45
|
+
def connection
|
46
|
+
@connection ||= begin
|
47
|
+
conn = Faraday.new(url: url)
|
48
|
+
conn.basic_auth(login, pass) if login
|
49
|
+
conn.build do |b|
|
50
|
+
conn_build.call(b)
|
51
|
+
end if conn_build
|
52
|
+
conn
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def request(verb, uri, query={}, size = nil)
|
57
|
+
headers['Authorization'] = klass.instance_eval(token.call(verb.to_s.upcase, uri, size, headers['Date'])) if token
|
58
|
+
verb == :get ? query_get = query : query_post = query
|
59
|
+
uri = connection.build_url(uri, query_get)
|
60
|
+
response = connection.run_request(verb, uri, query_post, headers) do |request|
|
61
|
+
yield request if block_given?
|
62
|
+
end
|
63
|
+
response = Response.new(response)
|
64
|
+
case response.status
|
65
|
+
when 301, 302, 303, 307
|
66
|
+
request(verb, response.headers['location'], query)
|
67
|
+
when 200..299, 300..399
|
68
|
+
response
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def get(uri, query = {}, &block)
|
73
|
+
request(:get, uri, query, 0, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def post(uri, query = {}, size = nil, &block)
|
77
|
+
size = size || 0
|
78
|
+
request(:post, uri, query, size,&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def put(uri, query = {}, size = nil, &block)
|
82
|
+
size = size || 0
|
83
|
+
request(:put, uri, query, size, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete(uri, &block)
|
87
|
+
request(:delete, uri, nil, 0, &block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Balloon
|
2
|
+
module Http
|
3
|
+
class Response
|
4
|
+
attr_reader :response
|
5
|
+
|
6
|
+
CONTENT_TYPE = {
|
7
|
+
'application/json' => :json,
|
8
|
+
'application/x-www-form-urlencoded' => :html,
|
9
|
+
'text/html' => :html,
|
10
|
+
'text/javascript' => :json
|
11
|
+
}
|
12
|
+
|
13
|
+
PARSERS = {
|
14
|
+
:json => lambda{ |body| MultiJson.respond_to?(:adapter) ? MultiJson.load(body) : MultiJson.decode(body) rescue body},
|
15
|
+
:html => lambda{ |body| Nokogiri::HTML(body)}
|
16
|
+
}
|
17
|
+
|
18
|
+
def initialize(response)
|
19
|
+
@response = response
|
20
|
+
end
|
21
|
+
|
22
|
+
def headers
|
23
|
+
response.headers
|
24
|
+
end
|
25
|
+
|
26
|
+
def body
|
27
|
+
decode(response.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
def decode(body)
|
31
|
+
return '' if !body
|
32
|
+
return body if json?
|
33
|
+
charset = body.match(/charset\s*=[\s|\W]*([\w-]+)/)
|
34
|
+
if charset[1].downcase != "utf-8"
|
35
|
+
begin
|
36
|
+
body.encode! "utf-8", charset[1], {:invalid => :replace}
|
37
|
+
rescue
|
38
|
+
body
|
39
|
+
end
|
40
|
+
else
|
41
|
+
body
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def status
|
46
|
+
response.status
|
47
|
+
end
|
48
|
+
|
49
|
+
# Attempts to determine the content type of the response.
|
50
|
+
def content_type
|
51
|
+
((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip
|
52
|
+
end
|
53
|
+
|
54
|
+
def json?
|
55
|
+
CONTENT_TYPE[content_type] == :json || !response.body.match(/\<html/)
|
56
|
+
end
|
57
|
+
|
58
|
+
def parser
|
59
|
+
type = CONTENT_TYPE[content_type]
|
60
|
+
type = :json if type == :html && !response.body.match(/\<html/)
|
61
|
+
return type
|
62
|
+
end
|
63
|
+
|
64
|
+
def parsed
|
65
|
+
return nil unless CONTENT_TYPE.key?(content_type)
|
66
|
+
return nil unless PARSERS.key?(parser)
|
67
|
+
@parsed ||= PARSERS[parser].call(body)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/balloon/load.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
if defined?(Rails)
|
2
|
+
module Balloon
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
initializer "balloon set path" do
|
5
|
+
Balloon.root = Rails.root.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer "Baloon.configure_rails_initializeation" do
|
9
|
+
config_file = Rails.root.join('config/balloon.yml')
|
10
|
+
if config_file.file?
|
11
|
+
config = YAML.load(ERB.new(config_file.read).result)
|
12
|
+
Balloon.configure_load(config, Rails.env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
elsif defined?(Sinatra)
|
18
|
+
if defined?(Padrino) && defined?(PADRINO_ROOT)
|
19
|
+
root = PADRINO_ROOT
|
20
|
+
env = Padrino.env
|
21
|
+
else
|
22
|
+
root = Sinatra::Application.root
|
23
|
+
env = Sinatra::Application.environment
|
24
|
+
end
|
25
|
+
Balloon.root = root
|
26
|
+
config_file = File.join(root, 'config/balloon.yml')
|
27
|
+
if File.exist?(config_file)
|
28
|
+
config = YAML.load(ERB.new(File.read(config_file)).result)
|
29
|
+
Balloon.configure_load(config, env)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
I18n.load_path << File.join(File.dirname(__FILE__), "locale", 'en.yml')
|