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 CHANGED
@@ -1,17 +1,20 @@
1
1
  source "http://rubygems.org"
2
+ ruby "1.9.3"
2
3
 
3
- gem "httpclient", ">= 2.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.3"
11
+ gem "rspec", "~> 2.13"
12
+ gem "rspec-mocks", "~> 2.13"
9
13
  gem "cucumber", ">= 0"
10
- gem "bundler", "~> 1.1"
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 "httpthumbnailer", "~> 0.3"
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
- cucumber (1.2.1)
24
+ cuba (3.1.0)
25
+ rack
26
+ cucumber (1.1.3)
7
27
  builder (>= 2.1.2)
8
- diff-lcs (>= 1.1.3)
9
- gherkin (~> 2.11.0)
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
- gem_plugin (0.2.3)
15
- gherkin (2.11.2)
34
+ facter (1.6.18)
35
+ gherkin (2.6.7)
16
36
  json (>= 1.4.6)
17
37
  git (1.2.5)
18
- haml (3.1.7)
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.7.5)
33
- mongrel (1.2.0.pre2)
34
- daemons (~> 1.0.10)
35
- gem_plugin (~> 0.2.3)
36
- multi_json (1.3.6)
37
- rack (1.4.1)
38
- rack-protection (1.2.0)
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.11.0)
45
- rspec-core (~> 2.11.0)
46
- rspec-expectations (~> 2.11.0)
47
- rspec-mocks (~> 2.11.0)
48
- rspec-core (2.11.1)
49
- rspec-expectations (2.11.3)
50
- diff-lcs (~> 1.1.3)
51
- rspec-mocks (2.11.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
- simplecov (0.6.4)
54
- multi_json (~> 1.0)
55
- simplecov-html (~> 0.5.3)
56
- simplecov-html (0.5.3)
57
- sinatra (1.3.3)
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
- bundler (~> 1.1)
71
+ cli (~> 1.1.0)
68
72
  cucumber
69
73
  daemon (~> 1)
70
- httpclient (>= 2.2)
71
- httpthumbnailer (~> 0.3)
72
- jeweler (~> 1.6)
74
+ httpclient (>= 2.3)
75
+ httpthumbnailer!
76
+ jeweler (~> 1.8.4)
77
+ multipart-parser (~> 0.1.1)
73
78
  rdoc (~> 3.9)
74
- rspec (~> 2.3)
75
- simplecov
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
- # httpthumbnailer-client
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
- gem install httpthumbnailer-client
7
+ ```bash
8
+ gem install httpthumbnailer-client
9
+ ```
8
10
 
9
11
  ## Usage
10
12
 
11
- Basic usage:
13
+ ### Ruby API
12
14
 
13
15
  ```ruby
14
16
  require 'httpthumbnailer-client'
15
17
 
16
- # read orginal image data (may be any format supported by ImageMagick installation on the server)
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
- # thumbnail image with API server listening on localhost port 3100
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
- # definition of thumbnail formats - see the API server documentation for available operations, formats and options
22
- thumbnail 'crop', 6, 3, 'JPEG'
23
- thumbnail 'crop', 8, 8, 'PNG'
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 # => 6x3 thumbnail JPEG data Strine
37
+ thumbnails[0].data # => 60x30 thumbnail JPEG data String
29
38
 
30
39
  thumbnails[1].mime_type # => 'image/png'
31
- thumbnails[1].data # => 8x8 thumbnail PNG data String
40
+ thumbnails[1].data # => 80x80 thumbnail PNG data String
32
41
 
33
42
  thumbnails[2].mime_type # => 'image/png'
34
- thumbnails[2].data # => 4x4 thumbnail PNG data String
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 [RSpec tests](http://github.com/jpastuszek/httpthumbnailer-client/blob/master/spec/httpthumbnailer-client_spec.rb)
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 httpthumbnailer-client
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) 2012 Jakub Pastuszek. See LICENSE.txt for
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
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.1.1"
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 = "2012-09-05"
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.23"
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.2"])
51
- s.add_development_dependency(%q<rspec>, ["~> 2.3"])
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<bundler>, ["~> 1.1"])
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<httpthumbnailer>, ["~> 0.3"])
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.2"])
61
- s.add_dependency(%q<rspec>, ["~> 2.3"])
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<bundler>, ["~> 1.1"])
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<httpthumbnailer>, ["~> 0.3"])
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.2"])
72
- s.add_dependency(%q<rspec>, ["~> 2.3"])
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<bundler>, ["~> 1.1"])
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<httpthumbnailer>, ["~> 0.3"])
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 'httpthumbnailer-client/multipart_response'
2
+ require 'multipart_parser/reader'
4
3
 
5
4
  class HTTPThumbnailerClient
6
- class UnsupportedMediaTypeError < ArgumentError
7
- end
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
- class ImageTooLargeError < ArgumentError
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
- class UnknownResponseType < ArgumentError
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
- class RemoteServerError < ArgumentError
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(&block)
30
- self.new('/thumbnail', &block).get
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 = 'JPEG', options = {})
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
- args << width.to_s
37
- args << height.to_s
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
- def thumbnail(data, &block)
77
- uri = URIBuilder.thumbnail(&block)
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
- case response.status
87
- when 415
88
- raise UnsupportedMediaTypeError, response.body.delete("\r")
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
- thumbnails = MultipartResponse.new(content_type, response.body).parts.map do |part|
96
- part_content_type = part.header['Content-Type']
97
-
98
- case part_content_type
99
- when 'text/plain'
100
- ThumbnailingError.new(part.body.delete("\r"))
101
- when /^image\//
102
- Thumbnail.new(part_content_type, part.body)
103
- else
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
- unless response.header['X-Input-Image-Content-Type'].empty?
109
- thumbnails.extend(ThumbnailsInputMimeTypeMeta)
110
- thumbnails.input_mime_type = response.header['X-Input-Image-Content-Type'].first
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
- return thumbnails
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