balloon 1.0.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 +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')
|