httpthumbnailer-client 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -7
- data/Gemfile.lock +56 -49
- data/README.md +39 -15
- data/VERSION +1 -1
- data/bin/httpthumbnailer-client +73 -0
- data/httpthumbnailer-client.gemspec +38 -26
- data/lib/httpthumbnailer-client.rb +122 -48
- data/spec/httpthumbnailer-client_spec.rb +9 -138
- data/spec/spec_helper.rb +11 -4
- data/spec/{test-large.jpg → support/test-large.jpg} +0 -0
- data/spec/{test.jpg → support/test.jpg} +0 -0
- data/spec/{test.txt → support/test.txt} +0 -0
- data/spec/thumbnail_spec.rb +66 -0
- data/spec/thumbnails_spec.rb +110 -0
- data/spec/uri_builder_spec.rb +16 -0
- metadata +83 -32
- data/lib/httpthumbnailer-client/multipart_response.rb +0 -45
- data/spec/multipart_response_spec.rb +0 -95
data/Gemfile
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
+
ruby "1.9.3"
|
2
3
|
|
3
|
-
gem "httpclient", ">= 2.
|
4
|
+
gem "httpclient", ">= 2.3"
|
5
|
+
gem "cli", "~> 1.1.0"
|
6
|
+
gem "multipart-parser", "~> 0.1.1"
|
4
7
|
|
5
8
|
# Add dependencies to develop your gem here.
|
6
9
|
# Include everything needed to run rake, tests, features, etc.
|
7
10
|
group :development do
|
8
|
-
gem "rspec", "~> 2.
|
11
|
+
gem "rspec", "~> 2.13"
|
12
|
+
gem "rspec-mocks", "~> 2.13"
|
9
13
|
gem "cucumber", ">= 0"
|
10
|
-
gem "
|
11
|
-
gem "jeweler", "~> 1.6"
|
12
|
-
gem "simplecov", ">= 0"
|
14
|
+
gem "jeweler", "~> 1.8.4"
|
13
15
|
gem "rdoc", "~> 3.9"
|
14
16
|
gem "daemon", "~> 1"
|
15
|
-
gem "
|
17
|
+
gem "rmagick", "~> 2"
|
18
|
+
gem "httpthumbnailer", path: '../httpthumbnailer'
|
19
|
+
gem "unicorn-cuba-base", path: '../unicorn-cuba-base'
|
16
20
|
end
|
17
|
-
|
data/Gemfile.lock
CHANGED
@@ -1,75 +1,82 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../httpthumbnailer
|
3
|
+
specs:
|
4
|
+
httpthumbnailer (1.0.0)
|
5
|
+
rmagick (~> 2)
|
6
|
+
unicorn-cuba-base (~> 1.0)
|
7
|
+
|
8
|
+
PATH
|
9
|
+
remote: ../unicorn-cuba-base
|
10
|
+
specs:
|
11
|
+
unicorn-cuba-base (1.0.0)
|
12
|
+
cli (~> 1.1.0)
|
13
|
+
cuba (~> 3.0)
|
14
|
+
facter (~> 1.6.11)
|
15
|
+
raindrops (~> 0.11)
|
16
|
+
ruby-ip (~> 0.9)
|
17
|
+
unicorn (>= 4.6.2)
|
18
|
+
|
1
19
|
GEM
|
2
20
|
remote: http://rubygems.org/
|
3
21
|
specs:
|
4
22
|
builder (3.0.0)
|
5
23
|
cli (1.1.1)
|
6
|
-
|
24
|
+
cuba (3.1.0)
|
25
|
+
rack
|
26
|
+
cucumber (1.1.3)
|
7
27
|
builder (>= 2.1.2)
|
8
|
-
diff-lcs (>= 1.1.
|
9
|
-
gherkin (~> 2.
|
28
|
+
diff-lcs (>= 1.1.2)
|
29
|
+
gherkin (~> 2.6.7)
|
10
30
|
json (>= 1.4.6)
|
31
|
+
term-ansicolor (>= 1.0.6)
|
11
32
|
daemon (1.1.0)
|
12
|
-
daemons (1.0.10)
|
13
33
|
diff-lcs (1.1.3)
|
14
|
-
|
15
|
-
gherkin (2.
|
34
|
+
facter (1.6.18)
|
35
|
+
gherkin (2.6.7)
|
16
36
|
json (>= 1.4.6)
|
17
37
|
git (1.2.5)
|
18
|
-
|
19
|
-
httpclient (2.2.7)
|
20
|
-
httpthumbnailer (0.3.1)
|
21
|
-
cli (~> 1.1.0)
|
22
|
-
haml (~> 3)
|
23
|
-
mongrel (>= 1.2.0.pre2)
|
24
|
-
rmagick (~> 2)
|
25
|
-
ruby-ip (~> 0.9)
|
26
|
-
sinatra (>= 1.2.6)
|
38
|
+
httpclient (2.3.3)
|
27
39
|
jeweler (1.8.4)
|
28
40
|
bundler (~> 1.0)
|
29
41
|
git (>= 1.2.5)
|
30
42
|
rake
|
31
43
|
rdoc
|
32
|
-
json (1.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
rack
|
40
|
-
rake (0.9.2.2)
|
41
|
-
rdoc (3.12)
|
42
|
-
json (~> 1.4)
|
44
|
+
json (1.6.1)
|
45
|
+
kgio (2.8.0)
|
46
|
+
multipart-parser (0.1.1)
|
47
|
+
rack (1.5.2)
|
48
|
+
raindrops (0.11.0)
|
49
|
+
rake (10.0.4)
|
50
|
+
rdoc (3.9.4)
|
43
51
|
rmagick (2.13.1)
|
44
|
-
rspec (2.
|
45
|
-
rspec-core (~> 2.
|
46
|
-
rspec-expectations (~> 2.
|
47
|
-
rspec-mocks (~> 2.
|
48
|
-
rspec-core (2.
|
49
|
-
rspec-expectations (2.
|
50
|
-
diff-lcs (
|
51
|
-
rspec-mocks (2.
|
52
|
+
rspec (2.13.0)
|
53
|
+
rspec-core (~> 2.13.0)
|
54
|
+
rspec-expectations (~> 2.13.0)
|
55
|
+
rspec-mocks (~> 2.13.0)
|
56
|
+
rspec-core (2.13.1)
|
57
|
+
rspec-expectations (2.13.0)
|
58
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
59
|
+
rspec-mocks (2.13.1)
|
52
60
|
ruby-ip (0.9.1)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
rack (~> 1.3, >= 1.3.6)
|
59
|
-
rack-protection (~> 1.2)
|
60
|
-
tilt (~> 1.3, >= 1.3.3)
|
61
|
-
tilt (1.3.3)
|
61
|
+
term-ansicolor (1.0.7)
|
62
|
+
unicorn (4.6.3)
|
63
|
+
kgio (~> 2.6)
|
64
|
+
rack
|
65
|
+
raindrops (~> 0.7)
|
62
66
|
|
63
67
|
PLATFORMS
|
64
68
|
ruby
|
65
69
|
|
66
70
|
DEPENDENCIES
|
67
|
-
|
71
|
+
cli (~> 1.1.0)
|
68
72
|
cucumber
|
69
73
|
daemon (~> 1)
|
70
|
-
httpclient (>= 2.
|
71
|
-
httpthumbnailer
|
72
|
-
jeweler (~> 1.
|
74
|
+
httpclient (>= 2.3)
|
75
|
+
httpthumbnailer!
|
76
|
+
jeweler (~> 1.8.4)
|
77
|
+
multipart-parser (~> 0.1.1)
|
73
78
|
rdoc (~> 3.9)
|
74
|
-
|
75
|
-
|
79
|
+
rmagick (~> 2)
|
80
|
+
rspec (~> 2.13)
|
81
|
+
rspec-mocks (~> 2.13)
|
82
|
+
unicorn-cuba-base!
|
data/README.md
CHANGED
@@ -1,44 +1,68 @@
|
|
1
|
-
#
|
1
|
+
# HTTP Thubnailer Client
|
2
2
|
|
3
3
|
Ruby client to [httpthumbnailer](http://github.com/jpastuszek/httpthumbnailer) image scaling and conversion HTTP API server.
|
4
4
|
|
5
5
|
## Installing
|
6
6
|
|
7
|
-
|
7
|
+
```bash
|
8
|
+
gem install httpthumbnailer-client
|
9
|
+
```
|
8
10
|
|
9
11
|
## Usage
|
10
12
|
|
11
|
-
|
13
|
+
### Ruby API
|
12
14
|
|
13
15
|
```ruby
|
14
16
|
require 'httpthumbnailer-client'
|
15
17
|
|
16
|
-
# read
|
18
|
+
# read original image data (may be any format supported by ImageMagick/GraphicsMagick installation on the server)
|
17
19
|
data = File.read('image_file.jpg')
|
18
20
|
|
19
|
-
#
|
21
|
+
# with API server listening on localhost port 3100
|
22
|
+
# see the API server documentation for available operations, formats and options
|
23
|
+
|
24
|
+
# generate single thumbnail from image data (single thumbnail API)
|
25
|
+
thumbnail = HTTPThumbnailerClient.new('http://localhost:3100').thumbnail(data, 'crop', 60, 30, 'jpeg')
|
26
|
+
thumbnail.mime_type # => 'image/jpeg'
|
27
|
+
thumbnail.data # => 60x30 thumbnail JPEG data String
|
28
|
+
|
29
|
+
# generate set of thumbnails from image data (multipart API)
|
20
30
|
thumbnails = HTTPThumbnailerClient.new('http://localhost:3100').thumbnail(data) do
|
21
|
-
|
22
|
-
thumbnail 'crop',
|
23
|
-
thumbnail '
|
24
|
-
thumbnail 'pad', 4, 4, 'PNG'
|
31
|
+
thumbnail 'crop', 60, 30, 'jpeg'
|
32
|
+
thumbnail 'crop', 80, 80, 'png'
|
33
|
+
thumbnail 'pad', 40, 40, 'png'
|
25
34
|
end
|
26
35
|
|
27
36
|
thumbnails[0].mime_type # => 'image/jpeg'
|
28
|
-
thumbnails[0].data # =>
|
37
|
+
thumbnails[0].data # => 60x30 thumbnail JPEG data String
|
29
38
|
|
30
39
|
thumbnails[1].mime_type # => 'image/png'
|
31
|
-
thumbnails[1].data # =>
|
40
|
+
thumbnails[1].data # => 80x80 thumbnail PNG data String
|
32
41
|
|
33
42
|
thumbnails[2].mime_type # => 'image/png'
|
34
|
-
thumbnails[2].data # =>
|
43
|
+
thumbnails[2].data # => 40x40 thumbnail PNG data String
|
35
44
|
|
36
45
|
thumbnails.input_mime_type # => 'image/jpeg' - detected input image format by API server (content based)
|
37
46
|
```
|
38
47
|
|
39
|
-
For more details see
|
48
|
+
For more details see RSpec for [single thumbnail API](http://github.com/jpastuszek/httpthumbnailer-client/blob/master/spec/thumbnail_spec.rb) and [multipart API](http://github.com/jpastuszek/httpthumbnailer-client/blob/master/spec/thumbnails_spec.rb).
|
49
|
+
|
50
|
+
### CLI tool
|
51
|
+
|
52
|
+
This gem provides `httpthumbnailer-client` command line tool that can be used to thumbnail images via [httpthumbnailer](http://github.com/jpastuszek/httpthumbnailer).
|
53
|
+
|
54
|
+
```bash
|
55
|
+
# start thumbnailing server (to stop: kill `cat httpthumbnailer.pid`)
|
56
|
+
httpthumbnailer
|
57
|
+
|
58
|
+
# thumbnail to standard output
|
59
|
+
cat ~/Pictures/compute.jpg | httpthumbnailer-client -t crop,100,200,png > thumbnail.png
|
60
|
+
|
61
|
+
# generate multiple thumbnails
|
62
|
+
cat ~/Pictures/compute.jpg | httpthumbnailer-client -t crop,100,200,jpeg,quality:100 -t pad,200,200,png thumbnail1.jpg thumbnail2.png
|
63
|
+
```
|
40
64
|
|
41
|
-
## Contributing to
|
65
|
+
## Contributing to HTTP Thubnailer Client
|
42
66
|
|
43
67
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
44
68
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
@@ -50,6 +74,6 @@ For more details see [RSpec tests](http://github.com/jpastuszek/httpthumbnailer-
|
|
50
74
|
|
51
75
|
## Copyright
|
52
76
|
|
53
|
-
Copyright (c)
|
77
|
+
Copyright (c) 2013 Jakub Pastuszek. See LICENSE.txt for
|
54
78
|
further details.
|
55
79
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'cli'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
require 'httpthumbnailer-client'
|
7
|
+
|
8
|
+
def parse_spec(spec)
|
9
|
+
method, width, height, format, *options = *spec.split(',')
|
10
|
+
fail "missing thumbnail specification argument in: #{spec}" unless method and width and height and format
|
11
|
+
|
12
|
+
opts = {}
|
13
|
+
options.each do |option|
|
14
|
+
key, value = option.split(':')
|
15
|
+
fail "missing option key or value in specifiaction: #{spec}" unless key and value
|
16
|
+
opts[key] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
[method, width, height, format, opts]
|
20
|
+
end
|
21
|
+
|
22
|
+
settings = CLI.new do
|
23
|
+
description 'Client to httpthumbnailer image scaling and conversion HTTP API server'
|
24
|
+
stdin :image_data,
|
25
|
+
description: 'image to be thumbnailed'
|
26
|
+
option :host,
|
27
|
+
short: :H,
|
28
|
+
description: 'host on which the httpthumbnailer is running',
|
29
|
+
default: 'localhost'
|
30
|
+
option :port,
|
31
|
+
short: :p,
|
32
|
+
description: 'TCP port on which the httpthumbnailer is listening',
|
33
|
+
cast: Integer,
|
34
|
+
default: 3100
|
35
|
+
options :thumbnail,
|
36
|
+
short: :t,
|
37
|
+
description: 'thumbnail specification in format <operation type>,<width>,<height>,<format>[,<option key>:<option value>]* - see httpthumbnailer documenation for more information',
|
38
|
+
required: true
|
39
|
+
arguments :output_file_name,
|
40
|
+
short: :f,
|
41
|
+
description: 'file name to store resulting thumbnail; when multiple --thumbail options are used the same number of output file names should be specified; use "-" to write to STDOUT',
|
42
|
+
default: '-'
|
43
|
+
version (Pathname.new(__FILE__).dirname + '..' + 'VERSION').read
|
44
|
+
end.parse! do |settings|
|
45
|
+
STDERR.puts "Warning: there is more thumbnail specifications given than output file names; some thumbnails will not be saved" if settings.thumbnail.length > settings.output_file_name.length
|
46
|
+
fail 'only one thumbnail can be written to STDOUT' if settings.output_file_name.count('-') > 1
|
47
|
+
settings.thumbnail = settings.thumbnail.map{|spec| parse_spec(spec)}
|
48
|
+
end
|
49
|
+
|
50
|
+
thumbnailer = HTTPThumbnailerClient.new("http://#{settings.host}:#{settings.port}")
|
51
|
+
|
52
|
+
thumbnails = if settings.thumbnail.length == 1
|
53
|
+
[thumbnailer.thumbnail(settings.stdin.read, *settings.thumbnail.first)]
|
54
|
+
else
|
55
|
+
thumbnailer.thumbnail(settings.stdin.read) do
|
56
|
+
settings.thumbnail.each do |spec|
|
57
|
+
thumbnail *spec
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
thumbnails.zip(settings.output_file_name).select do |thumbnail, out_file|
|
63
|
+
out_file
|
64
|
+
end.each do |thumbnail, out_file|
|
65
|
+
if out_file == '-'
|
66
|
+
STDOUT.write thumbnail.data
|
67
|
+
else
|
68
|
+
File.open(out_file, 'w') do |file|
|
69
|
+
file.write thumbnail.data
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -5,13 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "httpthumbnailer-client"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "1.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jakub Pastuszek"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2013-07-16"
|
13
13
|
s.description = "Thumbnails images using httpthumbniler server"
|
14
14
|
s.email = "jpastuszek@gmail.com"
|
15
|
+
s.executables = ["httpthumbnailer-client"]
|
15
16
|
s.extra_rdoc_files = [
|
16
17
|
"LICENSE.txt",
|
17
18
|
"README.md"
|
@@ -25,58 +26,69 @@ Gem::Specification.new do |s|
|
|
25
26
|
"README.md",
|
26
27
|
"Rakefile",
|
27
28
|
"VERSION",
|
29
|
+
"bin/httpthumbnailer-client",
|
28
30
|
"features/step_definitions/httpthumbnailer-client_steps.rb",
|
29
31
|
"features/support/env.rb",
|
30
32
|
"httpthumbnailer-client.gemspec",
|
31
33
|
"lib/httpthumbnailer-client.rb",
|
32
|
-
"lib/httpthumbnailer-client/multipart_response.rb",
|
33
34
|
"spec/httpthumbnailer-client_spec.rb",
|
34
|
-
"spec/multipart_response_spec.rb",
|
35
35
|
"spec/spec_helper.rb",
|
36
|
-
"spec/test-large.jpg",
|
37
|
-
"spec/test.jpg",
|
38
|
-
"spec/test.txt"
|
36
|
+
"spec/support/test-large.jpg",
|
37
|
+
"spec/support/test.jpg",
|
38
|
+
"spec/support/test.txt",
|
39
|
+
"spec/thumbnail_spec.rb",
|
40
|
+
"spec/thumbnails_spec.rb",
|
41
|
+
"spec/uri_builder_spec.rb"
|
39
42
|
]
|
40
43
|
s.homepage = "http://github.com/jpastuszek/httpthumbnailer-client"
|
41
44
|
s.licenses = ["MIT"]
|
42
45
|
s.require_paths = ["lib"]
|
43
|
-
s.rubygems_version = "1.8.
|
46
|
+
s.rubygems_version = "1.8.25"
|
44
47
|
s.summary = "API client for httpthumbniler server"
|
45
48
|
|
46
49
|
if s.respond_to? :specification_version then
|
47
50
|
s.specification_version = 3
|
48
51
|
|
49
52
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
-
s.add_runtime_dependency(%q<httpclient>, [">= 2.
|
51
|
-
s.
|
53
|
+
s.add_runtime_dependency(%q<httpclient>, [">= 2.3"])
|
54
|
+
s.add_runtime_dependency(%q<cli>, ["~> 1.1.0"])
|
55
|
+
s.add_runtime_dependency(%q<multipart-parser>, ["~> 0.1.1"])
|
56
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.13"])
|
57
|
+
s.add_development_dependency(%q<rspec-mocks>, ["~> 2.13"])
|
52
58
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
53
|
-
s.add_development_dependency(%q<
|
54
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.6"])
|
55
|
-
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
56
60
|
s.add_development_dependency(%q<rdoc>, ["~> 3.9"])
|
57
61
|
s.add_development_dependency(%q<daemon>, ["~> 1"])
|
58
|
-
s.add_development_dependency(%q<
|
62
|
+
s.add_development_dependency(%q<rmagick>, ["~> 2"])
|
63
|
+
s.add_development_dependency(%q<httpthumbnailer>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<unicorn-cuba-base>, [">= 0"])
|
59
65
|
else
|
60
|
-
s.add_dependency(%q<httpclient>, [">= 2.
|
61
|
-
s.add_dependency(%q<
|
66
|
+
s.add_dependency(%q<httpclient>, [">= 2.3"])
|
67
|
+
s.add_dependency(%q<cli>, ["~> 1.1.0"])
|
68
|
+
s.add_dependency(%q<multipart-parser>, ["~> 0.1.1"])
|
69
|
+
s.add_dependency(%q<rspec>, ["~> 2.13"])
|
70
|
+
s.add_dependency(%q<rspec-mocks>, ["~> 2.13"])
|
62
71
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
63
|
-
s.add_dependency(%q<
|
64
|
-
s.add_dependency(%q<jeweler>, ["~> 1.6"])
|
65
|
-
s.add_dependency(%q<simplecov>, [">= 0"])
|
72
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
66
73
|
s.add_dependency(%q<rdoc>, ["~> 3.9"])
|
67
74
|
s.add_dependency(%q<daemon>, ["~> 1"])
|
68
|
-
s.add_dependency(%q<
|
75
|
+
s.add_dependency(%q<rmagick>, ["~> 2"])
|
76
|
+
s.add_dependency(%q<httpthumbnailer>, [">= 0"])
|
77
|
+
s.add_dependency(%q<unicorn-cuba-base>, [">= 0"])
|
69
78
|
end
|
70
79
|
else
|
71
|
-
s.add_dependency(%q<httpclient>, [">= 2.
|
72
|
-
s.add_dependency(%q<
|
80
|
+
s.add_dependency(%q<httpclient>, [">= 2.3"])
|
81
|
+
s.add_dependency(%q<cli>, ["~> 1.1.0"])
|
82
|
+
s.add_dependency(%q<multipart-parser>, ["~> 0.1.1"])
|
83
|
+
s.add_dependency(%q<rspec>, ["~> 2.13"])
|
84
|
+
s.add_dependency(%q<rspec-mocks>, ["~> 2.13"])
|
73
85
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
74
|
-
s.add_dependency(%q<
|
75
|
-
s.add_dependency(%q<jeweler>, ["~> 1.6"])
|
76
|
-
s.add_dependency(%q<simplecov>, [">= 0"])
|
86
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
77
87
|
s.add_dependency(%q<rdoc>, ["~> 3.9"])
|
78
88
|
s.add_dependency(%q<daemon>, ["~> 1"])
|
79
|
-
s.add_dependency(%q<
|
89
|
+
s.add_dependency(%q<rmagick>, ["~> 2"])
|
90
|
+
s.add_dependency(%q<httpthumbnailer>, [">= 0"])
|
91
|
+
s.add_dependency(%q<unicorn-cuba-base>, [">= 0"])
|
80
92
|
end
|
81
93
|
end
|
82
94
|
|
@@ -1,47 +1,98 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
1
|
require 'httpclient'
|
3
|
-
require '
|
2
|
+
require 'multipart_parser/reader'
|
4
3
|
|
5
4
|
class HTTPThumbnailerClient
|
6
|
-
|
7
|
-
|
5
|
+
HTTPThumbnailerClientError = Class.new(ArgumentError)
|
6
|
+
|
7
|
+
InvalidThumbnailSpecificationError = Class.new(HTTPThumbnailerClientError)
|
8
|
+
ServerResourceNotFoundError = Class.new(HTTPThumbnailerClientError)
|
9
|
+
UnsupportedMediaTypeError = Class.new(HTTPThumbnailerClientError)
|
10
|
+
ImageTooLargeError = Class.new(HTTPThumbnailerClientError)
|
11
|
+
RemoteServerError = Class.new(HTTPThumbnailerClientError)
|
12
|
+
|
13
|
+
@@errors = {}
|
14
|
+
|
15
|
+
module Status
|
16
|
+
def status=(st)
|
17
|
+
@status = st
|
18
|
+
end
|
8
19
|
|
9
|
-
|
20
|
+
def status
|
21
|
+
@status
|
22
|
+
end
|
23
|
+
|
24
|
+
module Instance
|
25
|
+
def status
|
26
|
+
defined?(self.class.status) ? self.class.status : 500
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.extended(klass)
|
31
|
+
klass.instance_eval do
|
32
|
+
include Instance
|
33
|
+
end
|
34
|
+
end
|
10
35
|
end
|
11
36
|
|
12
|
-
|
37
|
+
def self.assign_status_code(error, status)
|
38
|
+
error.extend Status
|
39
|
+
error.status = status
|
40
|
+
@@errors[status] = error
|
41
|
+
@@errors[error] = status
|
13
42
|
end
|
14
43
|
|
15
|
-
|
44
|
+
assign_status_code(InvalidThumbnailSpecificationError, 400)
|
45
|
+
assign_status_code(ServerResourceNotFoundError, 404)
|
46
|
+
assign_status_code(UnsupportedMediaTypeError, 415)
|
47
|
+
assign_status_code(ImageTooLargeError, 413)
|
48
|
+
assign_status_code(RemoteServerError, 500)
|
49
|
+
|
50
|
+
def error_for_status(status, body)
|
51
|
+
@@errors.fetch(status){|err| RemoteServerError}.new(body)
|
16
52
|
end
|
17
53
|
|
54
|
+
UnknownResponseType = Class.new HTTPThumbnailerClientError
|
55
|
+
InvalidMultipartResponseError = Class.new HTTPThumbnailerClientError
|
56
|
+
|
18
57
|
class URIBuilder
|
19
58
|
def initialize(service_uri, &block)
|
20
59
|
@specs = []
|
21
60
|
@service_uri = service_uri
|
22
|
-
instance_eval &block
|
61
|
+
instance_eval &block if block
|
23
62
|
end
|
24
63
|
|
25
64
|
def get
|
26
65
|
"#{@service_uri}/#{@specs.join('/')}"
|
27
66
|
end
|
28
67
|
|
29
|
-
def self.thumbnail(
|
30
|
-
self.new('/thumbnail'
|
68
|
+
def self.thumbnail(*spec)
|
69
|
+
self.new('/thumbnail').thumbnail(*spec).get
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.thumbnails(&block)
|
73
|
+
self.new('/thumbnails', &block).get
|
31
74
|
end
|
32
75
|
|
33
|
-
def thumbnail(method, width, height, format = '
|
76
|
+
def thumbnail(method, width, height, format = 'jpeg', options = {})
|
77
|
+
width = width.to_s
|
78
|
+
height = height.to_s
|
79
|
+
|
34
80
|
args = []
|
35
81
|
args << method.to_s
|
36
|
-
|
37
|
-
args <<
|
82
|
+
width !~ /^([0-9]+|input)$/ and raise InvalidThumbnailSpecificationError.new("bad dimension value: #{width}")
|
83
|
+
args << width
|
84
|
+
height !~ /^([0-9]+|input)$/ and raise InvalidThumbnailSpecificationError.new("bad dimension value: #{height}")
|
85
|
+
args << height
|
38
86
|
args << format.to_s
|
39
87
|
|
40
88
|
options.keys.sort{|a, b| a.to_s <=> b.to_s}.each do |key|
|
89
|
+
raise InvalidThumbnailSpecificationError.new("empty option key for value '#{options[key]}'") if key.nil? || key.to_s.empty?
|
90
|
+
raise InvalidThumbnailSpecificationError.new("missing option value for key '#{key}'") if options[key].nil? || options[key].to_s.empty?
|
41
91
|
args << "#{key}:#{options[key]}"
|
42
92
|
end
|
43
93
|
|
44
94
|
@specs << args.join(',')
|
95
|
+
self
|
45
96
|
end
|
46
97
|
end
|
47
98
|
|
@@ -58,64 +109,87 @@ class HTTPThumbnailerClient
|
|
58
109
|
attr_accessor :input_mime_type
|
59
110
|
end
|
60
111
|
|
61
|
-
class ThumbnailingError
|
62
|
-
def initialize(msg)
|
63
|
-
@message = msg
|
64
|
-
@type = msg.match(/Error: (.*?): /)[1] rescue NoMethodError
|
65
|
-
end
|
66
|
-
|
67
|
-
attr_reader :type, :message
|
68
|
-
end
|
69
|
-
|
70
112
|
def initialize(server_url, options = {})
|
71
113
|
@server_url = server_url
|
72
114
|
@client = HTTPClient.new
|
115
|
+
|
116
|
+
# long timeouts for big image data
|
117
|
+
@client.send_timeout = options[:send_timeout] || 300
|
118
|
+
@client.receive_timeout = options[:receive_timeout] || 300
|
119
|
+
|
120
|
+
# don't use keep alive by default since backend won't support it any way unless fronted with nginx or similar
|
73
121
|
@keep_alive = options[:keep_alive] || false
|
74
122
|
end
|
75
123
|
|
76
|
-
|
77
|
-
|
124
|
+
attr_reader :server_url
|
125
|
+
attr_reader :keep_alive
|
126
|
+
|
127
|
+
def thumbnail(data, *spec, &block)
|
128
|
+
uri = if block
|
129
|
+
URIBuilder.thumbnails(&block)
|
130
|
+
else
|
131
|
+
URIBuilder.thumbnail(*spec)
|
132
|
+
end
|
78
133
|
|
79
134
|
response = @client.request('PUT', "#{@server_url}#{uri}", nil, data, {'Content-Type' => 'image/autodetect'})
|
80
135
|
@client.reset_all unless @keep_alive
|
81
136
|
|
82
137
|
content_type = response.header['Content-Type'].last
|
83
138
|
|
84
|
-
case content_type
|
139
|
+
thumbnails = case content_type
|
85
140
|
when 'text/plain'
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
when 413
|
90
|
-
raise ImageTooLargeError, response.body.delete("\r")
|
91
|
-
else
|
92
|
-
raise RemoteServerError, response.body.delete("\r")
|
93
|
-
end
|
141
|
+
raise error_for_status(response.status, response.body)
|
142
|
+
when /^image\//
|
143
|
+
Thumbnail.new(content_type, response.body)
|
94
144
|
when /^multipart\/mixed/
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
raise UnknownResponseType, part_content_type
|
145
|
+
parts = []
|
146
|
+
parser = MultipartParser::Reader.new(MultipartParser::Reader.extract_boundary_value(content_type))
|
147
|
+
parser.on_part do |part|
|
148
|
+
part_content_type = part.headers['content-type'] or raise InvalidMultipartResponseError, 'missing Content-Type header in multipart part'
|
149
|
+
part_status = part.headers['status']
|
150
|
+
data = ''
|
151
|
+
|
152
|
+
part.on_data do |partial_data|
|
153
|
+
data << partial_data
|
105
154
|
end
|
106
|
-
end
|
107
155
|
|
108
|
-
|
109
|
-
|
110
|
-
|
156
|
+
part.on_end do
|
157
|
+
case part_content_type
|
158
|
+
when 'text/plain'
|
159
|
+
part_status or raise InvalidMultipartResponseError, 'missing Status header in error part (text/plain)'
|
160
|
+
|
161
|
+
begin
|
162
|
+
raise error_for_status(part_status.to_i, data.strip)
|
163
|
+
rescue HTTPThumbnailerClientError => error # raise for stack trace
|
164
|
+
parts << error
|
165
|
+
end
|
166
|
+
when /^image\//
|
167
|
+
parts << Thumbnail.new(part_content_type, data)
|
168
|
+
else
|
169
|
+
raise UnknownResponseType, part_content_type
|
170
|
+
end
|
171
|
+
end
|
111
172
|
end
|
112
173
|
|
113
|
-
|
174
|
+
parser.write response.body
|
175
|
+
parser.ended? or raise InvalidMultipartResponseError, 'truncated multipart message'
|
176
|
+
parts
|
114
177
|
else
|
115
178
|
raise UnknownResponseType, content_type
|
116
179
|
end
|
180
|
+
|
181
|
+
unless response.header['X-Input-Image-Content-Type'].empty?
|
182
|
+
thumbnails.extend(ThumbnailsInputMimeTypeMeta)
|
183
|
+
thumbnails.input_mime_type = response.header['X-Input-Image-Content-Type'].first
|
184
|
+
end
|
185
|
+
|
186
|
+
return thumbnails
|
117
187
|
rescue HTTPClient::KeepAliveDisconnected
|
118
188
|
raise RemoteServerError, 'empty response'
|
119
189
|
end
|
190
|
+
|
191
|
+
def inspect
|
192
|
+
"#<#{self.class.name} server_url=#{server_url.inspect} keep_alive=#{keep_alive.inspect}>"
|
193
|
+
end
|
120
194
|
end
|
121
195
|
|