thumbs 0.0.9 → 1.0.0.beta.1
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.
- data/Gemfile +5 -3
- data/README.textile +7 -39
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/thumbs +11 -0
- data/examples/config.god +54 -0
- data/examples/config.rb +4 -0
- data/lib/thumbs/evented_magick.rb +184 -0
- data/lib/thumbs/image.rb +29 -34
- data/lib/thumbs/image_temp_file.rb +9 -0
- data/lib/thumbs/middleware/content_type.rb +18 -13
- data/lib/thumbs.rb +122 -63
- data/thumbs.gemspec +27 -24
- metadata +61 -76
- data/lib/thumbs/middleware/cache_control.rb +0 -17
- data/lib/thumbs/middleware/cache_read.rb +0 -22
- data/lib/thumbs/middleware/cache_write.rb +0 -32
- data/lib/thumbs/middleware/config.rb +0 -41
- data/lib/thumbs/middleware/download.rb +0 -27
- data/lib/thumbs/middleware/logger.rb +0 -25
- data/lib/thumbs/middleware/not_found.rb +0 -24
- data/lib/thumbs/middleware/resize.rb +0 -25
- data/lib/thumbs/middleware/server_name.rb +0 -14
- data/lib/thumbs/server.rb +0 -16
data/Gemfile
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# A sample Gemfile
|
2
2
|
source "http://rubygems.org"
|
3
3
|
|
4
|
-
gem "
|
5
|
-
gem
|
6
|
-
gem
|
4
|
+
gem "goliath"
|
5
|
+
gem 'eventmachine', '>= 1.0.0.beta.1'
|
6
|
+
gem 'em-synchrony', '>= 0.3.0.beta.1'
|
7
|
+
gem 'em-http-request', '>= 1.0.0.beta.1'
|
8
|
+
gem "em-files"
|
7
9
|
gem "lumberjack"
|
8
10
|
|
9
11
|
group :development do
|
data/README.textile
CHANGED
@@ -1,53 +1,21 @@
|
|
1
1
|
h1. Thumbs
|
2
2
|
|
3
|
-
Image server
|
3
|
+
Image proxy server that can resize and cache images on the fly. Built in ruby with Goliath API.
|
4
4
|
|
5
5
|
h2. Requirements
|
6
6
|
|
7
|
-
*
|
8
|
-
*
|
9
|
-
* mini_magick
|
7
|
+
* ruby 1.9.2
|
8
|
+
* goliath
|
10
9
|
|
11
10
|
h2. Usage
|
12
11
|
|
13
|
-
|
12
|
+
$ gem install thumbs
|
13
|
+
$ thumbs -c path/to/config.rb -p 9000
|
14
14
|
|
15
|
-
config.ru
|
16
|
-
|
17
|
-
bc.. require "rubygems"
|
18
|
-
require 'thumbs'
|
19
|
-
|
20
|
-
run Thumbs.new(
|
21
|
-
:url_map => "/:size/:original_url", # url pattern, :original_url is required
|
22
|
-
:thumbs_folder => "public/system/thumbs", # default: false - do not write on disk
|
23
|
-
:cache => true, # cache resized images - has no effect unless thumbs_folder exist
|
24
|
-
:cache_original => true, # cache original images - has no effect unless thumbs_folder exist
|
25
|
-
:runtime => true, # add X-Runtime header
|
26
|
-
:cache_control => {
|
27
|
-
:ttl => 86400, # add Expires and Cache-Control header for 24h
|
28
|
-
:last_modified => true # add Last-Modfied header
|
29
|
-
},
|
30
|
-
:etag => true, # add ETag
|
31
|
-
:logfile => false # will write log file if filepath is provided
|
32
|
-
:server_name => "Thumbs/0.0.8", # Server header
|
33
|
-
:image_not_found => "image_not_found.jpg" # path to image_not_found
|
34
|
-
)
|
35
|
-
|
36
|
-
2. as a moundable rails app
|
37
|
-
|
38
|
-
YourApplication::Application.routes.draw do
|
39
|
-
|
40
|
-
mount Thumbs.new(:url_map => "/:size/:original_url") => "images/resized"
|
41
|
-
|
42
|
-
....
|
43
|
-
end
|
44
|
-
|
45
|
-
3. you can even use it on heroku for free
|
46
|
-
|
47
|
-
create your own rack app, or use this one: http://thumbs-gem.heroku.com/i/100x100/www.google.com/images/logos/ps_logo2.png
|
48
|
-
|
49
15
|
h2. TODO
|
50
16
|
|
17
|
+
* documentation
|
18
|
+
* more middleware
|
51
19
|
* Tests!
|
52
20
|
* Access control
|
53
21
|
* SSL support
|
data/Rakefile
CHANGED
@@ -15,10 +15,11 @@ Jeweler::Tasks.new do |gem|
|
|
15
15
|
gem.name = "thumbs"
|
16
16
|
gem.homepage = "http://github.com/martinciu/thumbs"
|
17
17
|
gem.license = "MIT"
|
18
|
-
gem.summary = "Image server
|
18
|
+
gem.summary = "Image proxy server that can resize and cache images on the fly. Built in ruby with Goliath API."
|
19
19
|
gem.description = ""
|
20
20
|
gem.email = "marcin.ciunelis@gmail.com"
|
21
21
|
gem.authors = ["Marcin Ciunelis"]
|
22
|
+
gem.executables = ["thumbs"]
|
22
23
|
end
|
23
24
|
Jeweler::RubygemsDotOrgTasks.new
|
24
25
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
1.0.0.beta.1
|
data/bin/thumbs
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
config_index = (ARGV.index('-c') || ARGV.index('--config')) + 1
|
5
|
+
ARGV[config_index] = File.absolute_path(ARGV[config_index])
|
6
|
+
rescue
|
7
|
+
puts "Please specify a valid mneme configuration file (ex: -c config.rb)"
|
8
|
+
exit if !(ARGV.index('-h') || ARGV.index('--config'))
|
9
|
+
end
|
10
|
+
|
11
|
+
system("/usr/bin/env ruby " + File.dirname(__FILE__) + '/../lib/thumbs.rb' + ' ' + ARGV.join(" "))
|
data/examples/config.god
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
env = "production"
|
2
|
+
app_root = File.expand_path("../..", __FILE__)
|
3
|
+
|
4
|
+
%w{9001 9002}.each do |port|
|
5
|
+
God.watch do |w|
|
6
|
+
w.name = "thumbs-#{env}-#{port}"
|
7
|
+
w.group = "thumbs-#{env}"
|
8
|
+
w.interval = 30.seconds
|
9
|
+
w.dir = app_root
|
10
|
+
w.start = "thumbs -p #{port} -e #{env}"
|
11
|
+
w.uid = 'rails'
|
12
|
+
w.gid = 'rails'
|
13
|
+
|
14
|
+
w.stop_signal = 'QUIT'
|
15
|
+
w.stop_timeout = 20.seconds
|
16
|
+
|
17
|
+
# restart if memory gets too high
|
18
|
+
w.transition(:up, :restart) do |on|
|
19
|
+
on.condition(:memory_usage) do |c|
|
20
|
+
c.above = 150.megabytes
|
21
|
+
c.times = 2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# determine the state on startup
|
26
|
+
w.transition(:init, { true => :up, false => :start }) do |on|
|
27
|
+
on.condition(:process_running) do |c|
|
28
|
+
c.running = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# determine when process has finished starting
|
33
|
+
w.transition([:start, :restart], :up) do |on|
|
34
|
+
on.condition(:process_running) do |c|
|
35
|
+
c.running = true
|
36
|
+
c.interval = 5.seconds
|
37
|
+
end
|
38
|
+
|
39
|
+
# failsafe
|
40
|
+
on.condition(:tries) do |c|
|
41
|
+
c.times = 5
|
42
|
+
c.transition = :start
|
43
|
+
c.interval = 5.seconds
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# start if process is not running
|
48
|
+
w.transition(:up, :start) do |on|
|
49
|
+
on.condition(:process_running) do |c|
|
50
|
+
c.running = false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/examples/config.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
require "stringio"
|
3
|
+
require "fileutils"
|
4
|
+
require "open3"
|
5
|
+
|
6
|
+
require File.join(File.dirname(__FILE__), '/image_temp_file')
|
7
|
+
|
8
|
+
module EventedMagick
|
9
|
+
class MiniMagickError < RuntimeError; end
|
10
|
+
|
11
|
+
class Image
|
12
|
+
attr :path
|
13
|
+
attr :tempfile
|
14
|
+
attr :output
|
15
|
+
|
16
|
+
# Class Methods
|
17
|
+
# -------------
|
18
|
+
class << self
|
19
|
+
def from_blob(blob, ext = nil)
|
20
|
+
begin
|
21
|
+
tempfile = ImageTempFile.new(ext)
|
22
|
+
tempfile.binmode
|
23
|
+
tempfile.write(blob)
|
24
|
+
ensure
|
25
|
+
tempfile.close if tempfile
|
26
|
+
end
|
27
|
+
|
28
|
+
return self.new(tempfile.path, tempfile)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Use this if you don't want to overwrite the image file
|
32
|
+
def open(image_path)
|
33
|
+
File.open(image_path, "rb") do |f|
|
34
|
+
self.from_blob(f.read, File.extname(image_path))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias_method :from_file, :open
|
38
|
+
end
|
39
|
+
|
40
|
+
# Instance Methods
|
41
|
+
# ----------------
|
42
|
+
def initialize(input_path, tempfile=nil)
|
43
|
+
@path = input_path
|
44
|
+
@tempfile = tempfile # ensures that the tempfile will stick around until this image is garbage collected.
|
45
|
+
@method = defined?(::EM) && EM.reactor_running? ? :evented_execute : :blocking_execute
|
46
|
+
|
47
|
+
# Ensure that the file is an image
|
48
|
+
output = run_command("identify", "-format", format_option("%m %w %h"), @path)
|
49
|
+
(format, width, height) = output.split
|
50
|
+
@values = { 'format' => format, 'width' => width.to_i, 'height' => height.to_i, 'dimensions' => [width.to_i, height.to_i] }
|
51
|
+
end
|
52
|
+
|
53
|
+
# For reference see http://www.imagemagick.org/script/command-line-options.php#format
|
54
|
+
def [](value)
|
55
|
+
key = value.to_s
|
56
|
+
return @values[key] if %w(format width height dimensions).include? key
|
57
|
+
if key == "size"
|
58
|
+
File.size(@path)
|
59
|
+
else
|
60
|
+
run_command('identify', '-format', "\"#{key}\"", @path).split("\n")[0]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sends raw commands to imagemagick's mogrify command. The image path is automatically appended to the command
|
65
|
+
def <<(*args)
|
66
|
+
run_command("mogrify", *args << @path)
|
67
|
+
end
|
68
|
+
|
69
|
+
# This is a 'special' command because it needs to change @path to reflect the new extension
|
70
|
+
# Formatting an animation into a non-animated type will result in ImageMagick creating multiple
|
71
|
+
# pages (starting with 0). You can choose which page you want to manipulate. We default to the
|
72
|
+
# first page.
|
73
|
+
def format(format, page=0)
|
74
|
+
run_command("mogrify", "-format", format, @path)
|
75
|
+
|
76
|
+
old_path = @path.dup
|
77
|
+
@path.sub!(/(\.\w+)?$/, ".#{format}")
|
78
|
+
File.delete(old_path) unless old_path == @path
|
79
|
+
|
80
|
+
unless File.exists?(@path)
|
81
|
+
begin
|
82
|
+
FileUtils.copy_file(@path.sub(".#{format}", "-#{page}.#{format}"), @path)
|
83
|
+
rescue e
|
84
|
+
raise MiniMagickError, "Unable to format to #{format}; #{e}" unless File.exist?(@path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
ensure
|
88
|
+
Dir[@path.sub(/(\.\w+)?$/, "-[0-9]*.#{format}")].each do |fname|
|
89
|
+
File.unlink(fname)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Writes the temporary image that we are using for processing to the output path
|
94
|
+
def write(output_path)
|
95
|
+
FileUtils.copy_file @path, output_path
|
96
|
+
run_command "identify", output_path # Verify that we have a good image
|
97
|
+
end
|
98
|
+
|
99
|
+
# Give you raw data back
|
100
|
+
def to_blob
|
101
|
+
f = File.new @path
|
102
|
+
f.binmode
|
103
|
+
f.read
|
104
|
+
ensure
|
105
|
+
f.close if f
|
106
|
+
end
|
107
|
+
|
108
|
+
# If an unknown method is called then it is sent through the morgrify program
|
109
|
+
# Look here to find all the commands (http://www.imagemagick.org/script/mogrify.php)
|
110
|
+
def method_missing(symbol, *args)
|
111
|
+
args.push(@path) # push the path onto the end
|
112
|
+
run_command("mogrify", "-#{symbol}", *args)
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# You can use multiple commands together using this method
|
117
|
+
def combine_options(&block)
|
118
|
+
c = CommandBuilder.new
|
119
|
+
block.call c
|
120
|
+
run_command("mogrify", *c.args << @path)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Check to see if we are running on win32 -- we need to escape things differently
|
124
|
+
def windows?
|
125
|
+
!(RUBY_PLATFORM =~ /win32/).nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
# Outputs a carriage-return delimited format string for Unix and Windows
|
129
|
+
def format_option(format)
|
130
|
+
windows? ? "#{format}\\n" : "#{format}\\\\n"
|
131
|
+
end
|
132
|
+
|
133
|
+
def run_command(command, *args)
|
134
|
+
full_args = args.collect do |arg|
|
135
|
+
# args can contain characters like '>' so we must escape them, but don't quote switches
|
136
|
+
if arg !~ /^[\+\-]/
|
137
|
+
"\"#{arg}\""
|
138
|
+
else
|
139
|
+
arg.to_s
|
140
|
+
end
|
141
|
+
end.join(' ')
|
142
|
+
|
143
|
+
full_cmd = "#{command} #{full_args}"
|
144
|
+
(output, status) = send(@method, full_cmd)
|
145
|
+
|
146
|
+
if status.exitstatus == 0
|
147
|
+
output
|
148
|
+
else
|
149
|
+
raise MiniMagickError, "ImageMagick command (#{full_cmd.inspect}) failed: #{{:status_code => status, :output => output}.inspect}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def evented_execute(cmd)
|
154
|
+
fiber = Fiber.current
|
155
|
+
EM::system(cmd) do |output, status|
|
156
|
+
fiber.resume([output, status])
|
157
|
+
end
|
158
|
+
|
159
|
+
Fiber.yield
|
160
|
+
end
|
161
|
+
|
162
|
+
def blocking_execute(cmd)
|
163
|
+
output = `#{cmd}`
|
164
|
+
[output, $?]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class CommandBuilder
|
169
|
+
attr :args
|
170
|
+
|
171
|
+
def initialize
|
172
|
+
@args = []
|
173
|
+
end
|
174
|
+
|
175
|
+
def method_missing(symbol, *args)
|
176
|
+
@args << "-#{symbol}"
|
177
|
+
@args += args
|
178
|
+
end
|
179
|
+
|
180
|
+
def +(value)
|
181
|
+
@args << "+#{value}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/thumbs/image.rb
CHANGED
@@ -1,41 +1,36 @@
|
|
1
|
-
|
2
|
-
module Thumbs
|
1
|
+
class Image
|
3
2
|
|
4
|
-
|
3
|
+
attr_accessor :server, :path, :size, :url
|
5
4
|
|
6
|
-
|
5
|
+
def initialize(params)
|
6
|
+
@size = params[:size] if params[:size] =~ /^\d+x\d+$/
|
7
|
+
@url = params[:original_url]
|
8
|
+
@thumbs_folder = params[:thumbs_folder]
|
9
|
+
end
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@thumbs_folder = params[:thumbs_folder]
|
12
|
-
end
|
11
|
+
def local_path(size)
|
12
|
+
File.join(@thumbs_folder, spread(sha(size+@url))) if @thumbs_folder
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
def original_path
|
19
|
-
local_path("original")
|
20
|
-
end
|
21
|
-
|
22
|
-
def resized_path
|
23
|
-
local_path(@size)
|
24
|
-
end
|
25
|
-
|
26
|
-
def remote_url
|
27
|
-
"http://#{@url}"
|
28
|
-
end
|
29
|
-
|
30
|
-
protected
|
31
|
-
def sha(path)
|
32
|
-
Digest::SHA1.hexdigest(path)
|
33
|
-
end
|
34
|
-
|
35
|
-
def spread(sha, n = 2)
|
36
|
-
sha[2, 0] = "/"
|
37
|
-
sha
|
38
|
-
end
|
15
|
+
def original_path
|
16
|
+
local_path("original")
|
39
17
|
end
|
40
18
|
|
19
|
+
def resized_path
|
20
|
+
local_path(@size)
|
21
|
+
end
|
22
|
+
|
23
|
+
def remote_url
|
24
|
+
"http://#{@url}"
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def sha(path)
|
29
|
+
Digest::SHA1.hexdigest(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def spread(sha, n = 2)
|
33
|
+
sha[2, 0] = "/"
|
34
|
+
sha
|
35
|
+
end
|
41
36
|
end
|
@@ -1,16 +1,21 @@
|
|
1
|
-
|
1
|
+
class ContentType
|
2
|
+
def initialize(app, default_content_type = "image/jpeg")
|
3
|
+
@app = app
|
4
|
+
@default_content_type = default_content_type
|
5
|
+
end
|
2
6
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@default_content_type = default_content_type
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(env)
|
11
|
-
status, headers, body = @app.call(env)
|
12
|
-
headers['Content-Type'] = Rack::Mime.mime_type(File.extname(env["thumbs.remote_url"]), @default_content_type) if headers['Content-Type'].nil?
|
13
|
-
[status, headers, body]
|
7
|
+
def call(env)
|
8
|
+
async_cb = env['async.callback']
|
9
|
+
env['async.callback'] = Proc.new do |status, headers, body|
|
10
|
+
async_cb.call(post_process(status, headers, body, env))
|
14
11
|
end
|
12
|
+
|
13
|
+
status, headers, body = @app.call(env)
|
14
|
+
post_process(status, headers, body, env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post_process(status, headers, body, env)
|
18
|
+
headers['Content-Type'] = Rack::Mime.mime_type(File.extname(env["PATH_INFO"]), @default_content_type) if headers['Content-Type'].nil?
|
19
|
+
[status, headers, body]
|
15
20
|
end
|
16
|
-
end
|
21
|
+
end
|
data/lib/thumbs.rb
CHANGED
@@ -1,74 +1,133 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
require "
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
autoload :ContentType, 'thumbs/middleware/content_type'
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require 'goliath'
|
5
|
+
require 'em-http'
|
6
|
+
require 'em-synchrony/em-http'
|
7
|
+
require 'digest/sha1'
|
8
|
+
require "em-files"
|
9
|
+
require 'rack/mime'
|
10
|
+
require 'lumberjack'
|
11
|
+
|
12
|
+
require File.join(File.dirname(__FILE__), '/thumbs/evented_magick')
|
13
|
+
require File.join(File.dirname(__FILE__), '/thumbs/image')
|
14
|
+
require File.join(File.dirname(__FILE__), '/thumbs/middleware/content_type')
|
15
|
+
|
16
|
+
class Thumbs < Goliath::API
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
use Rack::Config do |env|
|
41
|
-
env['thumbs.thumbs_folder'] = options[:thumbs_folder]
|
18
|
+
use ::Rack::Runtime
|
19
|
+
|
20
|
+
use Goliath::Rack::Heartbeat # respond to /status with 200, OK (monitoring, etc)
|
21
|
+
use ContentType
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@logger = Lumberjack::Logger.new("logs/thumbs.log", :time_format => "%Y-%m-%d %H:%M:%S", :roll => :daily, :flush_seconds => 5)
|
25
|
+
end
|
26
|
+
|
27
|
+
def response(env)
|
28
|
+
start_time = Time.now.to_f
|
29
|
+
log_message = []
|
30
|
+
url_pattern, keys = compile(env.url_map)
|
31
|
+
status, headers, boady = [-1, {}, ""]
|
32
|
+
|
33
|
+
if match = url_pattern.match(env['PATH_INFO'])
|
34
|
+
values = match.captures.to_a
|
35
|
+
params = keys.zip(values).inject({}) do |hash,(k,v)|
|
36
|
+
hash[k.to_sym] = v
|
37
|
+
hash
|
42
38
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
39
|
+
image = Image.new(params.merge(:thumbs_folder => env.thumbs_folder))
|
40
|
+
log_message << image.url
|
41
|
+
log_message << image.size
|
42
|
+
else
|
43
|
+
log_message << "invalid_url"
|
44
|
+
return not_found
|
45
|
+
end
|
46
|
+
|
47
|
+
#cache_resized_read
|
48
|
+
if env.cache
|
49
|
+
begin
|
50
|
+
status, body = 200, File.read(image.resized_path)
|
51
|
+
log_message << "resized_cache"
|
52
|
+
write_log log_message, start_time
|
53
|
+
return [status, headers, body]
|
54
|
+
rescue Errno::ENOENT, IOError => e
|
59
55
|
end
|
60
|
-
|
61
|
-
|
56
|
+
end
|
57
|
+
|
58
|
+
#cache_original_read
|
59
|
+
if env.cache_original
|
60
|
+
begin
|
61
|
+
status, body = 200, File.read(image.original_path)
|
62
|
+
log_message << "original_cache"
|
63
|
+
rescue Errno::ENOENT, IOError => e
|
64
|
+
end
|
65
|
+
end
|
62
66
|
|
63
|
-
|
67
|
+
if status < 200
|
68
|
+
#download
|
69
|
+
log_message << "download"
|
70
|
+
original_response = EM::HttpRequest.new(image.remote_url).get :connection_timeout => 5, :inactivity_timeout => 10, :redirects => 0
|
71
|
+
status = original_response.response_header.status
|
72
|
+
if status >= 200 && status < 300
|
73
|
+
status, body = 200, original_response.response
|
74
|
+
#cache_original_write
|
75
|
+
if cache_original
|
76
|
+
path = image.original_path
|
77
|
+
tries = 0
|
78
|
+
begin
|
79
|
+
EM::File.open(path, "wb") {|f| f.write(body) }
|
80
|
+
rescue Errno::ENOENT, IOError
|
81
|
+
Dir.mkdir(File.dirname(path), 0755)
|
82
|
+
retry if (tries += 1) == 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
status, heders, body = not_found
|
87
|
+
end
|
64
88
|
|
65
|
-
|
89
|
+
end
|
66
90
|
|
67
|
-
|
68
|
-
|
91
|
+
#resize
|
92
|
+
if status > 0
|
93
|
+
thumb = EventedMagick::Image.from_blob(body)
|
94
|
+
thumb.resize image.size
|
95
|
+
body = thumb.to_blob
|
96
|
+
end
|
69
97
|
|
70
|
-
|
98
|
+
#cache_resized_write
|
99
|
+
if env.cache && status >= 200 && status < 300
|
100
|
+
path = image.resized_path
|
101
|
+
tries = 0
|
102
|
+
begin
|
103
|
+
EM::File.open(path, "wb") {|f| f.write(body) }
|
104
|
+
rescue Errno::ENOENT, IOError
|
105
|
+
Dir.mkdir(File.dirname(path), 0755)
|
106
|
+
retry if (tries += 1) == 1
|
107
|
+
end
|
71
108
|
end
|
109
|
+
write_log log_message, start_time
|
110
|
+
[status, headers, body]
|
72
111
|
end
|
73
|
-
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def compile(url_map)
|
116
|
+
keys = []
|
117
|
+
pattern = url_map.gsub(/((:\w+))/) do |match|
|
118
|
+
keys << $2[1..-1]
|
119
|
+
"(.+?)"
|
120
|
+
end
|
121
|
+
[/^#{pattern}$/, keys]
|
122
|
+
end
|
123
|
+
|
124
|
+
def not_found
|
125
|
+
[404, {}, File.read(File.expand_path("thumbs/images/image_not_found.jpg", __FILE__))]
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_log(message, start_time)
|
129
|
+
message << "#{((Time.now.to_f - start_time)*1000).round}ms"
|
130
|
+
@logger.info message.join("\t")
|
131
|
+
end
|
132
|
+
|
74
133
|
end
|
data/thumbs.gemspec
CHANGED
@@ -5,13 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{thumbs}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "1.0.0.beta.1"
|
9
9
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Marcin Ciunelis"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-04-12}
|
13
13
|
s.description = %q{}
|
14
14
|
s.email = %q{marcin.ciunelis@gmail.com}
|
15
|
+
s.executables = ["thumbs"]
|
15
16
|
s.extra_rdoc_files = [
|
16
17
|
"LICENSE.txt",
|
17
18
|
"README.textile"
|
@@ -22,20 +23,15 @@ Gem::Specification.new do |s|
|
|
22
23
|
"README.textile",
|
23
24
|
"Rakefile",
|
24
25
|
"VERSION",
|
26
|
+
"bin/thumbs",
|
27
|
+
"examples/config.god",
|
28
|
+
"examples/config.rb",
|
25
29
|
"lib/thumbs.rb",
|
30
|
+
"lib/thumbs/evented_magick.rb",
|
26
31
|
"lib/thumbs/image.rb",
|
32
|
+
"lib/thumbs/image_temp_file.rb",
|
27
33
|
"lib/thumbs/images/image_not_found.jpg",
|
28
|
-
"lib/thumbs/middleware/cache_control.rb",
|
29
|
-
"lib/thumbs/middleware/cache_read.rb",
|
30
|
-
"lib/thumbs/middleware/cache_write.rb",
|
31
|
-
"lib/thumbs/middleware/config.rb",
|
32
34
|
"lib/thumbs/middleware/content_type.rb",
|
33
|
-
"lib/thumbs/middleware/download.rb",
|
34
|
-
"lib/thumbs/middleware/logger.rb",
|
35
|
-
"lib/thumbs/middleware/not_found.rb",
|
36
|
-
"lib/thumbs/middleware/resize.rb",
|
37
|
-
"lib/thumbs/middleware/server_name.rb",
|
38
|
-
"lib/thumbs/server.rb",
|
39
35
|
"spec/spec_helper.rb",
|
40
36
|
"spec/thumbs_spec.rb",
|
41
37
|
"thumbs.gemspec"
|
@@ -43,9 +39,10 @@ Gem::Specification.new do |s|
|
|
43
39
|
s.homepage = %q{http://github.com/martinciu/thumbs}
|
44
40
|
s.licenses = ["MIT"]
|
45
41
|
s.require_paths = ["lib"]
|
46
|
-
s.rubygems_version = %q{1.
|
47
|
-
s.summary = %q{Image server
|
42
|
+
s.rubygems_version = %q{1.7.2}
|
43
|
+
s.summary = %q{Image proxy server that can resize and cache images on the fly. Built in ruby with Goliath API.}
|
48
44
|
s.test_files = [
|
45
|
+
"examples/config.rb",
|
49
46
|
"spec/spec_helper.rb",
|
50
47
|
"spec/thumbs_spec.rb"
|
51
48
|
]
|
@@ -54,26 +51,32 @@ Gem::Specification.new do |s|
|
|
54
51
|
s.specification_version = 3
|
55
52
|
|
56
53
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
-
s.add_runtime_dependency(%q<
|
58
|
-
s.add_runtime_dependency(%q<
|
59
|
-
s.add_runtime_dependency(%q<
|
54
|
+
s.add_runtime_dependency(%q<goliath>, [">= 0"])
|
55
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 1.0.0.beta.1"])
|
56
|
+
s.add_runtime_dependency(%q<em-synchrony>, [">= 0.3.0.beta.1"])
|
57
|
+
s.add_runtime_dependency(%q<em-http-request>, [">= 1.0.0.beta.1"])
|
58
|
+
s.add_runtime_dependency(%q<em-files>, [">= 0"])
|
60
59
|
s.add_runtime_dependency(%q<lumberjack>, [">= 0"])
|
61
60
|
s.add_development_dependency(%q<rspec>, [">= 0"])
|
62
61
|
s.add_development_dependency(%q<bundler>, [">= 0"])
|
63
62
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
64
63
|
else
|
65
|
-
s.add_dependency(%q<
|
66
|
-
s.add_dependency(%q<
|
67
|
-
s.add_dependency(%q<
|
64
|
+
s.add_dependency(%q<goliath>, [">= 0"])
|
65
|
+
s.add_dependency(%q<eventmachine>, [">= 1.0.0.beta.1"])
|
66
|
+
s.add_dependency(%q<em-synchrony>, [">= 0.3.0.beta.1"])
|
67
|
+
s.add_dependency(%q<em-http-request>, [">= 1.0.0.beta.1"])
|
68
|
+
s.add_dependency(%q<em-files>, [">= 0"])
|
68
69
|
s.add_dependency(%q<lumberjack>, [">= 0"])
|
69
70
|
s.add_dependency(%q<rspec>, [">= 0"])
|
70
71
|
s.add_dependency(%q<bundler>, [">= 0"])
|
71
72
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
72
73
|
end
|
73
74
|
else
|
74
|
-
s.add_dependency(%q<
|
75
|
-
s.add_dependency(%q<
|
76
|
-
s.add_dependency(%q<
|
75
|
+
s.add_dependency(%q<goliath>, [">= 0"])
|
76
|
+
s.add_dependency(%q<eventmachine>, [">= 1.0.0.beta.1"])
|
77
|
+
s.add_dependency(%q<em-synchrony>, [">= 0.3.0.beta.1"])
|
78
|
+
s.add_dependency(%q<em-http-request>, [">= 1.0.0.beta.1"])
|
79
|
+
s.add_dependency(%q<em-files>, [">= 0"])
|
77
80
|
s.add_dependency(%q<lumberjack>, [">= 0"])
|
78
81
|
s.add_dependency(%q<rspec>, [">= 0"])
|
79
82
|
s.add_dependency(%q<bundler>, [">= 0"])
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thumbs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 9
|
10
|
-
version: 0.0.9
|
4
|
+
prerelease: 6
|
5
|
+
version: 1.0.0.beta.1
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Marcin Ciunelis
|
@@ -15,113 +10,111 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-
|
19
|
-
default_executable:
|
13
|
+
date: 2011-04-12 00:00:00 Z
|
20
14
|
dependencies:
|
21
15
|
- !ruby/object:Gem::Dependency
|
16
|
+
name: goliath
|
22
17
|
requirement: &id001 !ruby/object:Gem::Requirement
|
23
18
|
none: false
|
24
19
|
requirements:
|
25
20
|
- - ">="
|
26
21
|
- !ruby/object:Gem::Version
|
27
|
-
hash: 3
|
28
|
-
segments:
|
29
|
-
- 0
|
30
22
|
version: "0"
|
31
|
-
version_requirements: *id001
|
32
|
-
name: rack
|
33
|
-
prerelease: false
|
34
23
|
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
35
26
|
- !ruby/object:Gem::Dependency
|
27
|
+
name: eventmachine
|
36
28
|
requirement: &id002 !ruby/object:Gem::Requirement
|
37
29
|
none: false
|
38
30
|
requirements:
|
39
31
|
- - ">="
|
40
32
|
- !ruby/object:Gem::Version
|
41
|
-
|
42
|
-
segments:
|
43
|
-
- 0
|
44
|
-
version: "0"
|
45
|
-
version_requirements: *id002
|
46
|
-
name: rack-contrib
|
47
|
-
prerelease: false
|
33
|
+
version: 1.0.0.beta.1
|
48
34
|
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
49
37
|
- !ruby/object:Gem::Dependency
|
38
|
+
name: em-synchrony
|
50
39
|
requirement: &id003 !ruby/object:Gem::Requirement
|
51
40
|
none: false
|
52
41
|
requirements:
|
53
42
|
- - ">="
|
54
43
|
- !ruby/object:Gem::Version
|
55
|
-
|
56
|
-
segments:
|
57
|
-
- 0
|
58
|
-
version: "0"
|
59
|
-
version_requirements: *id003
|
60
|
-
name: mini_magick
|
61
|
-
prerelease: false
|
44
|
+
version: 0.3.0.beta.1
|
62
45
|
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id003
|
63
48
|
- !ruby/object:Gem::Dependency
|
49
|
+
name: em-http-request
|
64
50
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
51
|
none: false
|
66
52
|
requirements:
|
67
53
|
- - ">="
|
68
54
|
- !ruby/object:Gem::Version
|
69
|
-
|
70
|
-
segments:
|
71
|
-
- 0
|
72
|
-
version: "0"
|
73
|
-
version_requirements: *id004
|
74
|
-
name: lumberjack
|
75
|
-
prerelease: false
|
55
|
+
version: 1.0.0.beta.1
|
76
56
|
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *id004
|
77
59
|
- !ruby/object:Gem::Dependency
|
60
|
+
name: em-files
|
78
61
|
requirement: &id005 !ruby/object:Gem::Requirement
|
79
62
|
none: false
|
80
63
|
requirements:
|
81
64
|
- - ">="
|
82
65
|
- !ruby/object:Gem::Version
|
83
|
-
hash: 3
|
84
|
-
segments:
|
85
|
-
- 0
|
86
66
|
version: "0"
|
87
|
-
|
88
|
-
name: rspec
|
67
|
+
type: :runtime
|
89
68
|
prerelease: false
|
90
|
-
|
69
|
+
version_requirements: *id005
|
91
70
|
- !ruby/object:Gem::Dependency
|
71
|
+
name: lumberjack
|
92
72
|
requirement: &id006 !ruby/object:Gem::Requirement
|
93
73
|
none: false
|
94
74
|
requirements:
|
95
75
|
- - ">="
|
96
76
|
- !ruby/object:Gem::Version
|
97
|
-
hash: 3
|
98
|
-
segments:
|
99
|
-
- 0
|
100
77
|
version: "0"
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
101
80
|
version_requirements: *id006
|
102
|
-
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
type: :development
|
103
90
|
prerelease: false
|
91
|
+
version_requirements: *id007
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: bundler
|
94
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: "0"
|
104
100
|
type: :development
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: *id008
|
105
103
|
- !ruby/object:Gem::Dependency
|
106
|
-
|
104
|
+
name: jeweler
|
105
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
107
106
|
none: false
|
108
107
|
requirements:
|
109
108
|
- - ~>
|
110
109
|
- !ruby/object:Gem::Version
|
111
|
-
hash: 7
|
112
|
-
segments:
|
113
|
-
- 1
|
114
|
-
- 5
|
115
|
-
- 2
|
116
110
|
version: 1.5.2
|
117
|
-
version_requirements: *id007
|
118
|
-
name: jeweler
|
119
|
-
prerelease: false
|
120
111
|
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: *id009
|
121
114
|
description: ""
|
122
115
|
email: marcin.ciunelis@gmail.com
|
123
|
-
executables:
|
124
|
-
|
116
|
+
executables:
|
117
|
+
- thumbs
|
125
118
|
extensions: []
|
126
119
|
|
127
120
|
extra_rdoc_files:
|
@@ -133,24 +126,18 @@ files:
|
|
133
126
|
- README.textile
|
134
127
|
- Rakefile
|
135
128
|
- VERSION
|
129
|
+
- bin/thumbs
|
130
|
+
- examples/config.god
|
131
|
+
- examples/config.rb
|
136
132
|
- lib/thumbs.rb
|
133
|
+
- lib/thumbs/evented_magick.rb
|
137
134
|
- lib/thumbs/image.rb
|
135
|
+
- lib/thumbs/image_temp_file.rb
|
138
136
|
- lib/thumbs/images/image_not_found.jpg
|
139
|
-
- lib/thumbs/middleware/cache_control.rb
|
140
|
-
- lib/thumbs/middleware/cache_read.rb
|
141
|
-
- lib/thumbs/middleware/cache_write.rb
|
142
|
-
- lib/thumbs/middleware/config.rb
|
143
137
|
- lib/thumbs/middleware/content_type.rb
|
144
|
-
- lib/thumbs/middleware/download.rb
|
145
|
-
- lib/thumbs/middleware/logger.rb
|
146
|
-
- lib/thumbs/middleware/not_found.rb
|
147
|
-
- lib/thumbs/middleware/resize.rb
|
148
|
-
- lib/thumbs/middleware/server_name.rb
|
149
|
-
- lib/thumbs/server.rb
|
150
138
|
- spec/spec_helper.rb
|
151
139
|
- spec/thumbs_spec.rb
|
152
140
|
- thumbs.gemspec
|
153
|
-
has_rdoc: true
|
154
141
|
homepage: http://github.com/martinciu/thumbs
|
155
142
|
licenses:
|
156
143
|
- MIT
|
@@ -164,26 +151,24 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
151
|
requirements:
|
165
152
|
- - ">="
|
166
153
|
- !ruby/object:Gem::Version
|
167
|
-
hash:
|
154
|
+
hash: 758287189825552390
|
168
155
|
segments:
|
169
156
|
- 0
|
170
157
|
version: "0"
|
171
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
159
|
none: false
|
173
160
|
requirements:
|
174
|
-
- - "
|
161
|
+
- - ">"
|
175
162
|
- !ruby/object:Gem::Version
|
176
|
-
|
177
|
-
segments:
|
178
|
-
- 0
|
179
|
-
version: "0"
|
163
|
+
version: 1.3.1
|
180
164
|
requirements: []
|
181
165
|
|
182
166
|
rubyforge_project:
|
183
|
-
rubygems_version: 1.
|
167
|
+
rubygems_version: 1.7.2
|
184
168
|
signing_key:
|
185
169
|
specification_version: 3
|
186
|
-
summary: Image server
|
170
|
+
summary: Image proxy server that can resize and cache images on the fly. Built in ruby with Goliath API.
|
187
171
|
test_files:
|
172
|
+
- examples/config.rb
|
188
173
|
- spec/spec_helper.rb
|
189
174
|
- spec/thumbs_spec.rb
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Thumbs
|
2
|
-
class CacheControl
|
3
|
-
def initialize(app, options)
|
4
|
-
@app = app
|
5
|
-
@ttl = options[:ttl] || 86400
|
6
|
-
@last_modified = options[:last_modified].nil? ? true : options[:last_modified]
|
7
|
-
end
|
8
|
-
|
9
|
-
def call(env)
|
10
|
-
status, headers, body = @app.call(env)
|
11
|
-
headers['Cache-Control'] ||= "max-age=#{@ttl}, public"
|
12
|
-
headers['Expires'] ||= (Time.now + @ttl).httpdate
|
13
|
-
headers['Last-Modified'] ||= Time.now.httpdate if @last_modified
|
14
|
-
[status, headers, body]
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Thumbs
|
2
|
-
class CacheRead
|
3
|
-
|
4
|
-
def initialize(app, cache_type = "resized")
|
5
|
-
@app = app
|
6
|
-
@cache_type = cache_type
|
7
|
-
end
|
8
|
-
|
9
|
-
def call(env)
|
10
|
-
if env["thumbs.#{@cache_type}_path"]
|
11
|
-
begin
|
12
|
-
status, headers, body = [200, {}, File.read(env["thumbs.#{@cache_type}_path"])]
|
13
|
-
env['thumbs.logger'] << "#{@cache_type}_cache" if env['thumbs.logger']
|
14
|
-
return [status, headers, body]
|
15
|
-
rescue Errno::ENOENT, IOError => e
|
16
|
-
end
|
17
|
-
end
|
18
|
-
return @app.call(env)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module Thumbs
|
2
|
-
class CacheWrite
|
3
|
-
|
4
|
-
def initialize(app, cache_type = "resized")
|
5
|
-
@app = app
|
6
|
-
@cache_type = cache_type
|
7
|
-
end
|
8
|
-
|
9
|
-
def call(env)
|
10
|
-
status, headers, body = @app.call(env)
|
11
|
-
|
12
|
-
path = env["thumbs.#{@cache_type}_path"]
|
13
|
-
|
14
|
-
if path && status == 200
|
15
|
-
tries = 0
|
16
|
-
begin
|
17
|
-
File.open(path, "wb") {|f| f.write(body) }
|
18
|
-
rescue Errno::ENOENT, IOError
|
19
|
-
Dir.mkdir(File.dirname(path), 0755)
|
20
|
-
retry if (tries += 1) == 1
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
[status, headers, body]
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module Thumbs
|
2
|
-
class Config
|
3
|
-
|
4
|
-
def initialize(app, url_map)
|
5
|
-
@app = app
|
6
|
-
@url_pattern, @keys = compile(url_map)
|
7
|
-
end
|
8
|
-
|
9
|
-
def call(env)
|
10
|
-
env['thumbs.url_pattern'] = @url_pattern
|
11
|
-
if match = @url_pattern.match(env['PATH_INFO'])
|
12
|
-
values = match.captures.to_a
|
13
|
-
params = @keys.zip(values).inject({}) do |hash,(k,v)|
|
14
|
-
hash[k.to_sym] = v
|
15
|
-
hash
|
16
|
-
end
|
17
|
-
|
18
|
-
image = Image.new(params.merge(:thumbs_folder => env['thumbs.thumbs_folder']))
|
19
|
-
|
20
|
-
env['thumbs.remote_url'] = image.remote_url
|
21
|
-
env['thumbs.url'] = image.url
|
22
|
-
env['thumbs.resized_path'] = image.resized_path
|
23
|
-
env['thumbs.original_path'] = image.original_path
|
24
|
-
env['thumbs.size'] = image.size
|
25
|
-
end
|
26
|
-
@app.call(env)
|
27
|
-
end
|
28
|
-
|
29
|
-
protected
|
30
|
-
def compile(url_map)
|
31
|
-
keys = []
|
32
|
-
pattern = url_map.gsub(/((:\w+))/) do |match|
|
33
|
-
keys << $2[1..-1]
|
34
|
-
"(.+?)"
|
35
|
-
end
|
36
|
-
[/^#{pattern}$/, keys]
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
|
3
|
-
module Thumbs
|
4
|
-
class Download
|
5
|
-
|
6
|
-
def initialize(app)
|
7
|
-
@app = app
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(env)
|
11
|
-
if env['thumbs.remote_url']
|
12
|
-
begin
|
13
|
-
status, headers, body = [200, {}, open(env['thumbs.remote_url']).read]
|
14
|
-
env['thumbs.logger'] << "download" if env['thumbs.logger']
|
15
|
-
return [status, headers, body]
|
16
|
-
rescue StandardError, Timeout::Error => e
|
17
|
-
status, headers, body = [404, {'Content-Type' => 'text/plain'}, ["Not found"]]
|
18
|
-
env['thumbs.logger'] << "not_found" if env['thumbs.logger']
|
19
|
-
return [status, headers, body]
|
20
|
-
end
|
21
|
-
else
|
22
|
-
return @app.call(env)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'lumberjack'
|
2
|
-
require 'benchmark'
|
3
|
-
|
4
|
-
module Thumbs
|
5
|
-
class Logger
|
6
|
-
def initialize(app, logfile_path)
|
7
|
-
@logger = Lumberjack::Logger.new(logfile_path, :time_format => "%Y-%m-%d %H:%M:%S", :roll => :daily, :flush_seconds => 5)
|
8
|
-
@app = app
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(env)
|
12
|
-
env['thumbs.logger'] = []
|
13
|
-
env['thumbs.logger'] << env['thumbs.url']
|
14
|
-
env['thumbs.logger'] << env['thumbs.size']
|
15
|
-
response = []
|
16
|
-
realtime = Benchmark.realtime do
|
17
|
-
response = @app.call(env)
|
18
|
-
end
|
19
|
-
status, headers, body = response
|
20
|
-
env['thumbs.logger'] << sprintf('%.4f', realtime)
|
21
|
-
@logger.info env['thumbs.logger'].join("\t")
|
22
|
-
return [status, headers, body]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Thumbs
|
2
|
-
|
3
|
-
class NotFound
|
4
|
-
F = ::File
|
5
|
-
|
6
|
-
def initialize(app, image_not_found)
|
7
|
-
@app = app
|
8
|
-
file = F.expand_path(image_not_found)
|
9
|
-
@content = F.read(file)
|
10
|
-
end
|
11
|
-
|
12
|
-
def call(env)
|
13
|
-
status, headers, body = @app.call(env)
|
14
|
-
if status == 200
|
15
|
-
[status, headers, body]
|
16
|
-
else
|
17
|
-
env['thumbs.logger'] << "not_found" if env['thumbs.logger']
|
18
|
-
[404, {}, @content]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'mini_magick'
|
2
|
-
|
3
|
-
module Thumbs
|
4
|
-
class Resize
|
5
|
-
|
6
|
-
def initialize(app)
|
7
|
-
@app = app
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(env)
|
11
|
-
status, headers, body = @app.call(env)
|
12
|
-
|
13
|
-
if env['thumbs.size']
|
14
|
-
begin
|
15
|
-
thumb = MiniMagick::Image.read(body)
|
16
|
-
thumb.resize env['thumbs.size']
|
17
|
-
body = thumb.to_blob
|
18
|
-
rescue
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
[status, headers, body]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module Thumbs
|
2
|
-
class ServerName
|
3
|
-
def initialize(app, server_name)
|
4
|
-
@app = app
|
5
|
-
@server_name = server_name
|
6
|
-
end
|
7
|
-
|
8
|
-
def call(env)
|
9
|
-
status, headers, body = @app.call(env)
|
10
|
-
headers['Server'] = @server_name
|
11
|
-
[status, headers, body]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|