httpthumbnailer-client 0.1.1 → 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.
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