Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Namespace everything under Imagery module and lib/imagery

  • Loading branch information...
commit bddd1395892bdfad940832eada8b8d5e75709a67 1 parent 013c543
Joshua Peek josh authored committed

Showing 41 changed files with 483 additions and 471 deletions. Show diff stats Hide diff stats

  1. +11 9 config.ru
  2. +0 33 image_server.rb
  3. +0 68 lib/image.rb
  4. +0 47 lib/image_variant_generator.rb
  5. +12 0 lib/imagery.rb
  6. +69 0 lib/imagery/image.rb
  7. +47 0 lib/imagery/image_variant_generator.rb
  8. 0  lib/{ → imagery}/logger_ext.rb
  9. +41 0 lib/imagery/middleware/accel_redirect.rb
  10. +21 0 lib/imagery/middleware/cache_purge.rb
  11. +17 0 lib/imagery/middleware/favicon_filter.rb
  12. +32 0 lib/imagery/middleware/logged_request.rb
  13. +24 0 lib/imagery/middleware/remote_proxy.rb
  14. +13 0 lib/imagery/middleware/server_name.rb
  15. +32 0 lib/imagery/send_file.rb
  16. +22 0 lib/imagery/server.rb
  17. +55 0 lib/imagery/svg_generator.rb
  18. +23 0 lib/imagery/transformations.rb
  19. +35 0 lib/imagery/transformations/borders.rb
  20. +8 8 lib/{ → imagery}/transformations/sizes.rb
  21. +8 9 lib/{ → imagery}/transformations/transform.rb
  22. 0  lib/{ → imagery}/vendor/SyslogLogger-1.4.0/History.txt
  23. 0  lib/{ → imagery}/vendor/SyslogLogger-1.4.0/Manifest.txt
  24. 0  lib/{ → imagery}/vendor/SyslogLogger-1.4.0/README.txt
  25. 0  lib/{ → imagery}/vendor/SyslogLogger-1.4.0/Rakefile
  26. 0  lib/{ → imagery}/vendor/SyslogLogger-1.4.0/lib/syslog_logger.rb
  27. 0  lib/{ → imagery}/vendor/SyslogLogger-1.4.0/test/test_syslog_logger.rb
  28. +0 40 lib/middleware/accel_redirect.rb
  29. +0 20 lib/middleware/cache_purge.rb
  30. +0 16 lib/middleware/favicon_filter.rb
  31. +0 31 lib/middleware/logged_request.rb
  32. +0 23 lib/middleware/remote_proxy.rb
  33. +0 12 lib/middleware/server_name.rb
  34. +0 31 lib/send_file.rb
  35. +0 56 lib/svg_generator.rb
  36. +0 21 lib/transformations.rb
  37. +0 35 lib/transformations/borders.rb
  38. +2 1  test/helper.rb
  39. +6 6 test/test_image_variant_generator.rb
  40. +1 1  test/test_remote_proxy.rb
  41. +4 4 test/test_svg_generation.rb
20 config.ru
@@ -3,7 +3,9 @@
3 3 require 'rubygems'
4 4 require 'rack/cache'
5 5 require 'rack/contrib'
6   -require 'image_server'
  6 +
  7 +$: << File.join(File.dirname(__FILE__), 'lib')
  8 +require 'imagery'
7 9 require 'config/env'
8 10
9 11
@@ -20,8 +22,8 @@ end
20 22
21 23 if ENV['NGINX_ACCEL_REDIRECTS']
22 24 STDERR.puts 'Using accel redirect (Shopify config).'
23   - require 'lib/middleware/accel_redirect'
24   - use AccelRedirect
  25 + require 'imagery/middleware/accel_redirect'
  26 + use Imagery::AccelRedirect
25 27 else
26 28 use Rack::Sendfile
27 29 end
@@ -29,13 +31,13 @@ end
29 31 use Rack::ShowExceptions
30 32
31 33 # 1. Forget about stupid favicons
32   -use FaviconFilter
  34 +use Imagery::FaviconFilter
33 35
34 36 # 2. Log all other incoming requests
35   -use LoggedRequest
  37 +use Imagery::LoggedRequest
36 38
37 39 # 3. Override server name into something non embarrasing
38   -use ServerName
  40 +use Imagery::ServerName
39 41
40 42 # 4. Content type needs to be present, default to attachment
41 43 use Rack::ContentType, "application/octet-stream"
@@ -46,10 +48,10 @@ use Rack::Cache,
46 48 :entitystore => ENV['ENTITY_STORE']
47 49
48 50 # 6. handle PURGE requests
49   -use CachePurge
  51 +use Imagery::CachePurge
50 52
51 53 # 7. See if files already exist on remote host, if so handle them directly
52   -use RemoteProxy
  54 +use Imagery::RemoteProxy
53 55
54 56 # 8. Otherwise run the image server and produce the missing images
55   -run ImageServer.new
  57 +run Imagery::Server.new
33 image_server.rb
... ... @@ -1,33 +0,0 @@
1   -require 'lib/send_file'
2   -require 'lib/middleware/cache_purge'
3   -require 'lib/middleware/logged_request'
4   -require 'lib/middleware/remote_proxy'
5   -require 'lib/middleware/server_name'
6   -require 'lib/middleware/favicon_filter'
7   -require 'lib/logger_ext'
8   -require 'lib/transformations'
9   -require 'lib/svg_generator'
10   -require 'lib/image_variant_generator'
11   -require 'lib/image'
12   -
13   -class ImageServer
14   - include SendFile
15   -
16   - NotFound = [404, {'Content-Type' => 'text/html'}, ['<h1>File not Found</h1>']].freeze
17   -
18   - def call(env)
19   - Logger.current.info 'Attempting to generate missing file...'
20   -
21   - [SvgGenerator, ImageVariantGenerator].each do |generator|
22   - if image = generator.from_url(env['imagery.origin_host'], env['PATH_INFO'] + (env['QUERY_STRING'].empty? ? '' : "?#{env['QUERY_STRING']}"))
23   -
24   - return send_file(image)
25   - end
26   - end
27   -
28   - Logger.current.info 'No generator available'
29   -
30   - NotFound
31   - end
32   -
33   -end
68 lib/image.rb
... ... @@ -1,68 +0,0 @@
1   -require 'RMagick'
2   -require 'fileutils'
3   -require 'patron'
4   -
5   -class Image
6   - attr_accessor :content
7   - attr_reader :headers
8   - attr_reader :status
9   - attr_reader :server
10   -
11   - def initialize(server, path)
12   - @server = server
13   - @path = path
14   - download(path)
15   - end
16   -
17   - def found?
18   - @status == 200
19   - end
20   -
21   - def basename
22   - File.basename(@path)
23   - end
24   -
25   - def basename_no_ext
26   - File.basename(@path, ext)
27   - end
28   -
29   - def ext
30   - File.extname(@path)
31   - end
32   -
33   - def dirname
34   - File.dirname(@path)
35   - end
36   -
37   - private
38   -
39   - def session
40   - @@session ||= begin
41   - sess = Patron::Session.new
42   - sess.timeout = 10
43   - sess.headers['User-Agent'] = 'imagery/1.0'
44   - sess
45   - end
46   - end
47   -
48   - def download(path_info)
49   - session.base_url = "http://#{server}"
50   -
51   - response = Logger.current.info_with_time "Loading http://#{server}#{path_info}" do
52   - session.get(path_info)
53   - end
54   -
55   - @path = path_info.split('?')[0]
56   - @headers = response.headers
57   - @status = response.status
58   -
59   - if found?
60   - self.content = response.body
61   - true
62   - else
63   - Logger.current.error "Not found"
64   - false
65   - end
66   - end
67   -end
68   -
47 lib/image_variant_generator.rb
... ... @@ -1,47 +0,0 @@
1   -require 'RMagick'
2   -require 'fileutils'
3   -
4   -class ImageVariantGenerator
5   - VARIANT_DELIMITER = '_'
6   - SupportedImageTypes = ['.gif', '.jpg', '.jpeg', '.png', '.bmp']
7   -
8   - attr_accessor :content
9   - attr_accessor :content_type
10   -
11   - def self.variant_parser
12   - @variant_parser ||= /(.*)\_(#{Transformations.list.join('|')})(#{SupportedImageTypes.join('|')})/i
13   - end
14   -
15   - def self.from_url(server, path)
16   - return nil unless path =~ variant_parser
17   -
18   - remote_path = "#{$1}#{$3}"
19   -
20   - file = Image.new(server, remote_path)
21   - if file.found?
22   - transform_content(file, $2)
23   - file
24   - else
25   - nil
26   - end
27   - end
28   -
29   - def initialize(image)
30   - @image = image
31   - end
32   -
33   - def self.transform_content(image, variant)
34   - img = Magick::Image.from_blob(image.content).first
35   - transformation = Transformations[variant]
36   -
37   - Logger.current.info_with_time "Transforming image to #{variant}" do
38   - raise ArgumentError, "#{variant} is not a known transformation. (#{Transformations.list.join(', ')})" if transformation.nil?
39   - img = transformation.call(img)
40   - raise ArgumentError, "Creating variant #{variant} for #{path} produced an error. Please return a Magick::Image" if img.nil?
41   - image.content = img.to_blob
42   - end
43   - true
44   - end
45   -
46   -end
47   -
12 lib/imagery.rb
... ... @@ -0,0 +1,12 @@
  1 +require 'imagery/send_file'
  2 +require 'imagery/middleware/cache_purge'
  3 +require 'imagery/middleware/logged_request'
  4 +require 'imagery/middleware/remote_proxy'
  5 +require 'imagery/middleware/server_name'
  6 +require 'imagery/middleware/favicon_filter'
  7 +require 'imagery/logger_ext'
  8 +require 'imagery/transformations'
  9 +require 'imagery/svg_generator'
  10 +require 'imagery/image_variant_generator'
  11 +require 'imagery/image'
  12 +require 'imagery/server'
69 lib/imagery/image.rb
... ... @@ -0,0 +1,69 @@
  1 +require 'RMagick'
  2 +require 'fileutils'
  3 +require 'patron'
  4 +
  5 +module Imagery
  6 + class Image
  7 + attr_accessor :content
  8 + attr_reader :headers
  9 + attr_reader :status
  10 + attr_reader :server
  11 +
  12 + def initialize(server, path)
  13 + @server = server
  14 + @path = path
  15 + download(path)
  16 + end
  17 +
  18 + def found?
  19 + @status == 200
  20 + end
  21 +
  22 + def basename
  23 + File.basename(@path)
  24 + end
  25 +
  26 + def basename_no_ext
  27 + File.basename(@path, ext)
  28 + end
  29 +
  30 + def ext
  31 + File.extname(@path)
  32 + end
  33 +
  34 + def dirname
  35 + File.dirname(@path)
  36 + end
  37 +
  38 + private
  39 +
  40 + def session
  41 + @@session ||= begin
  42 + sess = Patron::Session.new
  43 + sess.timeout = 10
  44 + sess.headers['User-Agent'] = 'imagery/1.0'
  45 + sess
  46 + end
  47 + end
  48 +
  49 + def download(path_info)
  50 + session.base_url = "http://#{server}"
  51 +
  52 + response = Logger.current.info_with_time "Loading http://#{server}#{path_info}" do
  53 + session.get(path_info)
  54 + end
  55 +
  56 + @path = path_info.split('?')[0]
  57 + @headers = response.headers
  58 + @status = response.status
  59 +
  60 + if found?
  61 + self.content = response.body
  62 + true
  63 + else
  64 + Logger.current.error "Not found"
  65 + false
  66 + end
  67 + end
  68 + end
  69 +end
47 lib/imagery/image_variant_generator.rb
... ... @@ -0,0 +1,47 @@
  1 +require 'RMagick'
  2 +require 'fileutils'
  3 +
  4 +module Imagery
  5 + class ImageVariantGenerator
  6 + VARIANT_DELIMITER = '_'
  7 + SupportedImageTypes = ['.gif', '.jpg', '.jpeg', '.png', '.bmp']
  8 +
  9 + attr_accessor :content
  10 + attr_accessor :content_type
  11 +
  12 + def self.variant_parser
  13 + @variant_parser ||= /(.*)\_(#{Transformations.list.join('|')})(#{SupportedImageTypes.join('|')})/i
  14 + end
  15 +
  16 + def self.from_url(server, path)
  17 + return nil unless path =~ variant_parser
  18 +
  19 + remote_path = "#{$1}#{$3}"
  20 +
  21 + file = Image.new(server, remote_path)
  22 + if file.found?
  23 + transform_content(file, $2)
  24 + file
  25 + else
  26 + nil
  27 + end
  28 + end
  29 +
  30 + def initialize(image)
  31 + @image = image
  32 + end
  33 +
  34 + def self.transform_content(image, variant)
  35 + img = Magick::Image.from_blob(image.content).first
  36 + transformation = Transformations[variant]
  37 +
  38 + Logger.current.info_with_time "Transforming image to #{variant}" do
  39 + raise ArgumentError, "#{variant} is not a known transformation. (#{Transformations.list.join(', ')})" if transformation.nil?
  40 + img = transformation.call(img)
  41 + raise ArgumentError, "Creating variant #{variant} for #{path} produced an error. Please return a Magick::Image" if img.nil?
  42 + image.content = img.to_blob
  43 + end
  44 + true
  45 + end
  46 + end
  47 +end
0  lib/logger_ext.rb → lib/imagery/logger_ext.rb
File renamed without changes
41 lib/imagery/middleware/accel_redirect.rb
... ... @@ -0,0 +1,41 @@
  1 +require 'rack/file'
  2 +
  3 +class File #:nodoc:
  4 + alias :to_path :path
  5 +end
  6 +
  7 +# To make this work you have to add:
  8 +#
  9 +# location /cache/ {
  10 + # internal;
  11 + # alias /mnt/data/cache/rack/body;
  12 +# }
  13 +#
  14 +# to nginx config
  15 +
  16 +
  17 +module Imagery
  18 + class AccelRedirect
  19 + F = ::File
  20 +
  21 + def initialize(app, variation=nil)
  22 + @app = app
  23 + end
  24 +
  25 + def call(env)
  26 + status, headers, body = @app.call(env)
  27 + if body.respond_to?(:to_path)
  28 +
  29 + path = body.to_path
  30 + url = path.sub(/^#{ENV['CACHE_LOCATION']}/i, '/cache')
  31 +
  32 + Logger.current.info " => sending #{url} through nginx"
  33 +
  34 + headers['Content-Length'] = '0'
  35 + headers['X-Accel-Redirect'] = url
  36 + body = []
  37 + end
  38 + [status, headers, body]
  39 + end
  40 + end
  41 +end
21 lib/imagery/middleware/cache_purge.rb
... ... @@ -0,0 +1,21 @@
  1 +module Imagery
  2 + class CachePurge
  3 + Success = [200, {'Content-Type' => 'text/plain'}, ['OK']]
  4 +
  5 + def initialize(app)
  6 + @app = app
  7 + end
  8 +
  9 + # PURGE /s/files/1/0001/4168/files/thumbs/pic_thumb.jpg?12428536032 HTTP/1.0
  10 +
  11 + def call(env)
  12 + # Rack cache automatically invalidates resource if the verbs are not GET/POST so
  13 + # we don't actually have to do anything. Simply don't delegate those to the backend
  14 + if env['REQUEST_METHOD'] == 'PURGE'
  15 + Success
  16 + else
  17 + @app.call(env)
  18 + end
  19 + end
  20 + end
  21 +end
17 lib/imagery/middleware/favicon_filter.rb
... ... @@ -0,0 +1,17 @@
  1 +module Imagery
  2 + class FaviconFilter
  3 + Empty = [200, {'Content-Type' => 'text/plain'}, ['']].freeze
  4 +
  5 + def initialize(app)
  6 + @app = app
  7 + end
  8 +
  9 + def call(env)
  10 + if env['REQUEST_URI'] == '/favicon.ico'
  11 + return Empty
  12 + else
  13 + @app.call(env)
  14 + end
  15 + end
  16 + end
  17 +end
32 lib/imagery/middleware/logged_request.rb
... ... @@ -0,0 +1,32 @@
  1 +module Imagery
  2 + class LoggedRequest
  3 +
  4 + def initialize(app)
  5 + @app = app
  6 + end
  7 +
  8 + def call(env)
  9 +
  10 + request = Rack::Cache::Request.new(env)
  11 +
  12 + resp = nil
  13 +
  14 + Logger.current.buffer do
  15 +
  16 + Logger.current.info "#{request.request_method} #{request.path} [#{request.ip}]"
  17 +
  18 + secs = Benchmark.realtime do
  19 + Logger.current.intend do
  20 + resp = @app.call(env)
  21 + end
  22 + end
  23 +
  24 + Logger.current.info((resp[0] < 399 ? 'Success' : "Error [#{resp[0]}]") + " after %.3fs Cache: %s" % [secs, resp[1]['X-Rack-Cache']])
  25 + Logger.current.info ''
  26 +
  27 + end
  28 +
  29 + resp
  30 + end
  31 + end
  32 +end
24 lib/imagery/middleware/remote_proxy.rb
... ... @@ -0,0 +1,24 @@
  1 +module Imagery
  2 + class RemoteProxy
  3 + include SendFile
  4 +
  5 + def initialize(app)
  6 + @app = app
  7 + end
  8 +
  9 + def call(env)
  10 + request = Rack::Request.new(env)
  11 +
  12 + requested_file = Image.new(env['imagery.origin_host'], env['PATH_INFO'] + (env['QUERY_STRING'].empty? ? '' : "?#{env['QUERY_STRING']}"))
  13 +
  14 + # If file exists we simply sent it to the client.
  15 + if requested_file.found?
  16 + Logger.current.info "Requested file exists upstream."
  17 +
  18 + send_file(requested_file)
  19 + else
  20 + @app.call(env)
  21 + end
  22 + end
  23 + end
  24 +end
13 lib/imagery/middleware/server_name.rb
... ... @@ -0,0 +1,13 @@
  1 +module Imagery
  2 + class ServerName
  3 + def initialize(app)
  4 + @app = app
  5 + end
  6 +
  7 + def call(env)
  8 + hash = @app.call(env)
  9 + hash[1]['Server'] = 'Shopify Imagery'
  10 + hash
  11 + end
  12 + end
  13 +end
32 lib/imagery/send_file.rb
... ... @@ -0,0 +1,32 @@
  1 +require 'time'
  2 +
  3 +module Imagery
  4 + module SendFile
  5 + CopyHeaders = ['Content-Type', 'Cache-Control', 'Last-Modified', 'ETag']
  6 +
  7 + ContentTypes = {
  8 + '.gif' => 'image/gif',
  9 + '.jpg' => 'image/jpeg',
  10 + '.jpeg' => 'image/jpeg',
  11 + '.png' => 'image/png',
  12 + '.bmp' => 'image/x-bitmap',
  13 + '.svg' => 'image/svg+xml'
  14 + }
  15 +
  16 + def send_file(file)
  17 + headers = {'Content-Length' => file.content.length.to_s}
  18 +
  19 + if file.respond_to?(:headers)
  20 + CopyHeaders.each do |key|
  21 + headers[key] = file.headers[key] if file.headers.has_key?(key)
  22 + end
  23 + end
  24 +
  25 + headers['ETag'] ||= Digest::MD5.hexdigest(file.content)
  26 + headers['Cache-Control'] ||= 'public, max-age=31557600'
  27 + headers['Last-Modified'] ||= Time.new.httpdate
  28 +
  29 + [200, headers, [file.content]]
  30 + end
  31 + end
  32 +end
22 lib/imagery/server.rb
... ... @@ -0,0 +1,22 @@
  1 +module Imagery
  2 + class Server
  3 + include SendFile
  4 +
  5 + NotFound = [404, {'Content-Type' => 'text/html'}, ['<h1>File not Found</h1>']].freeze
  6 +
  7 + def call(env)
  8 + Logger.current.info 'Attempting to generate missing file...'
  9 +
  10 + [SvgGenerator, ImageVariantGenerator].each do |generator|
  11 + if image = generator.from_url(env['imagery.origin_host'], env['PATH_INFO'] + (env['QUERY_STRING'].empty? ? '' : "?#{env['QUERY_STRING']}"))
  12 +
  13 + return send_file(image)
  14 + end
  15 + end
  16 +
  17 + Logger.current.info 'No generator available'
  18 +
  19 + NotFound
  20 + end
  21 + end
  22 +end
55 lib/imagery/svg_generator.rb
... ... @@ -0,0 +1,55 @@
  1 +require 'RMagick'
  2 +require 'fileutils'
  3 +require 'net/http'
  4 +
  5 +module Imagery
  6 + # http://localhost:9292/s/files/1/0001/8392/assets/fish.svg
  7 + #
  8 + class SvgGenerator
  9 + SvgFileTest = /\.svg\.png/i
  10 +
  11 + def self.from_url(server, path)
  12 + return nil unless path =~ SvgFileTest
  13 +
  14 + file = Image.new(server, original_path_for(path) )
  15 + if file.found?
  16 + file.headers['Content-Type'] = 'image/png'
  17 + file.content = Converter.new(file.content).svg_to_png
  18 + file
  19 + else
  20 + nil
  21 + end
  22 + end
  23 +
  24 + def self.original_path_for(path)
  25 + path.gsub(/\.png/, '')
  26 + end
  27 +
  28 + class Converter
  29 + def initialize(blob)
  30 + @blob = blob
  31 + end
  32 +
  33 + def svg_to_png
  34 + logger.info "** rasterize svg to png"
  35 + result = popen("rsvg-convert")
  36 + raise TransformationError, "Data was not a valid SVG image." unless $? == 0
  37 + result
  38 + end
  39 +
  40 + private
  41 +
  42 + def logger
  43 + Logger.current
  44 + end
  45 +
  46 + def popen(cmd)
  47 + IO.popen(cmd, 'r+') do |io|
  48 + io.write @blob
  49 + io.close_write
  50 + io.read
  51 + end
  52 + end
  53 + end
  54 + end
  55 +end
23 lib/imagery/transformations.rb
... ... @@ -0,0 +1,23 @@
  1 +require 'RMagick'
  2 +
  3 +module Imagery
  4 + module Transformations
  5 + @transformations = Hash.new
  6 +
  7 + def self.list
  8 + @transformations.keys
  9 + end
  10 +
  11 + def self.[](name)
  12 + @transformations[name.to_s]
  13 + end
  14 +
  15 + def self.register(name, &block)
  16 + @transformations[name.to_s] = Proc.new(&block)
  17 + end
  18 + end
  19 +end
  20 +
  21 +
  22 +
  23 +Dir[ File.dirname(__FILE__) + '/transformations/*.rb'].each { |f| require f }
35 lib/imagery/transformations/borders.rb
... ... @@ -0,0 +1,35 @@
  1 +# Creates a
  2 +
  3 +Imagery::Transformations.register :border do |image|
  4 + # Add Polaroid border
  5 + image.border!(5, 5, "white")
  6 +end
  7 +
  8 +Imagery::Transformations.register :shadow do |image|
  9 + shadow = image.flip
  10 + shadow = shadow.colorize(1, 1, 1, "#ccc")
  11 + shadow.background_color = "white"
  12 + shadow.border!(10, 10, "white")
  13 + shadow = shadow.blur_image(0, 7)
  14 +
  15 + x = (shadow.columns - image.columns) / 2
  16 + y = (shadow.rows - image.rows) / 2
  17 +
  18 + ## Composite original image on top of shadow and save
  19 + shadow.composite(image, x, y-3, Magick::OverCompositeOp)
  20 +end
  21 +
  22 +Imagery::Transformations.register :polaroid do |image|
  23 + image.border!(10, 10, "white")
  24 +
  25 + shadow = image.colorize(1, 1, 1, "#ccc")
  26 + shadow.background_color = "white"
  27 + shadow.border!(10, 10, "white")
  28 + shadow = shadow.blur_image(0, 7)
  29 +
  30 + x = (shadow.columns - image.columns) / 2
  31 + y = (shadow.rows - image.rows) / 2
  32 +
  33 + ## Composite original image on top of shadow and save
  34 + shadow.composite(image, x, y-3, Magick::OverCompositeOp)
  35 +end
16 lib/transformations/sizes.rb → lib/imagery/transformations/sizes.rb
... ... @@ -1,31 +1,31 @@
1   -Transformations.register :pico do |image|
  1 +Imagery::Transformations.register :pico do |image|
2 2 image.change_geometry("16x16>") { |x, y, image| image.resize!(x,y) }
3 3 end
4 4
5   -Transformations.register :icon do |image|
  5 +Imagery::Transformations.register :icon do |image|
6 6 image.change_geometry("32x32>") { |x, y, image| image.resize!(x,y) }
7 7 end
8 8
9   -Transformations.register :thumb do |image|
  9 +Imagery::Transformations.register :thumb do |image|
10 10 image.change_geometry("50x50>") { |x, y, image| image.resize!(x,y) }
11 11 end
12 12
13   -Transformations.register :small do |image|
  13 +Imagery::Transformations.register :small do |image|
14 14 image.change_geometry("100x100>") { |x, y, image| image.resize!(x,y) }
15 15 end
16 16
17   -Transformations.register :compact do |image|
  17 +Imagery::Transformations.register :compact do |image|
18 18 image.change_geometry("160x160>") { |x, y, image| image.resize!(x,y) }
19 19 end
20 20
21   -Transformations.register :medium do |image|
  21 +Imagery::Transformations.register :medium do |image|
22 22 image.change_geometry("240x240>") { |x, y, image| image.resize!(x,y) }
23 23 end
24 24
25   -Transformations.register :large do |image|
  25 +Imagery::Transformations.register :large do |image|
26 26 image.change_geometry("480x480>") { |x, y, image| image.resize!(x,y) }
27 27 end
28 28
29   -Transformations.register :grande do |image|
  29 +Imagery::Transformations.register :grande do |image|
30 30 image.change_geometry("600x600>") { |x, y, image| image.resize!(x,y) }
31 31 end
17 lib/transformations/transform.rb → lib/imagery/transformations/transform.rb
... ... @@ -1,34 +1,33 @@
1   -Transformations.register :square do |image|
  1 +Imagery::Transformations.register :square do |image|
2 2 min = [image.columns, image.rows].min
3 3 image.crop_resized(min, min, Magick::CenterGravity)
4 4 end
5 5
6   -Transformations.register 'max-square' do |image|
  6 +Imagery::Transformations.register 'max-square' do |image|
7 7 max = [image.columns, image.rows].max
8 8 image.crop_resized(max, max, Magick::CenterGravity)
9 9 end
10 10
11   -Transformations.register 'pico-square' do |image|
  11 +Imagery::Transformations.register 'pico-square' do |image|
12 12 image.crop_resized(16,16, Magick::CenterGravity)
13 13 end
14 14
15   -Transformations.register 'icon-square' do |image|
  15 +Imagery::Transformations.register 'icon-square' do |image|
16 16 image.crop_resized(32,32, Magick::CenterGravity)
17 17 end
18 18
19   -Transformations.register 'thumb-square' do |image|
  19 +Imagery::Transformations.register 'thumb-square' do |image|
20 20 image.crop_resized(50,50, Magick::CenterGravity)
21 21 end
22 22
23   -Transformations.register 'medium-square' do |image|
  23 +Imagery::Transformations.register 'medium-square' do |image|
24 24 image.crop_resized(240,240, Magick::CenterGravity)
25 25 end
26 26
27   -Transformations.register 'small-square' do |image|
  27 +Imagery::Transformations.register 'small-square' do |image|
28 28 image.crop_resized(100,100, Magick::CenterGravity)
29 29 end
30 30
31   -Transformations.register 'large-square' do |image|
  31 +Imagery::Transformations.register 'large-square' do |image|
32 32 image.crop_resized(480,480, Magick::CenterGravity)
33 33 end
34   -
0  lib/vendor/SyslogLogger-1.4.0/History.txt → lib/imagery/vendor/SyslogLogger-1.4.0/History.txt
File renamed without changes
0  lib/vendor/SyslogLogger-1.4.0/Manifest.txt → lib/imagery/vendor/SyslogLogger-1.4.0/Manifest.txt
File renamed without changes
0  lib/vendor/SyslogLogger-1.4.0/README.txt → lib/imagery/vendor/SyslogLogger-1.4.0/README.txt
File renamed without changes
0  lib/vendor/SyslogLogger-1.4.0/Rakefile → lib/imagery/vendor/SyslogLogger-1.4.0/Rakefile
File renamed without changes
0  lib/vendor/SyslogLogger-1.4.0/lib/syslog_logger.rb → ...ry/vendor/SyslogLogger-1.4.0/lib/syslog_logger.rb
File renamed without changes
0  ...dor/SyslogLogger-1.4.0/test/test_syslog_logger.rb → ...dor/SyslogLogger-1.4.0/test/test_syslog_logger.rb
File renamed without changes
40 lib/middleware/accel_redirect.rb
... ... @@ -1,40 +0,0 @@
1   -require 'rack/file'
2   -
3   -class File #:nodoc:
4   - alias :to_path :path
5   -end
6   -
7   -# To make this work you have to add:
8   -#
9   -# location /cache/ {
10   - # internal;
11   - # alias /mnt/data/cache/rack/body;
12   -# }
13   -#
14   -# to nginx config
15   -
16   -
17   -class AccelRedirect
18   - F = ::File
19   -
20   - def initialize(app, variation=nil)
21   - @app = app
22   - end
23   -
24   - def call(env)
25   - status, headers, body = @app.call(env)
26   - if body.respond_to?(:to_path)
27   -
28   - path = body.to_path
29   - url = path.sub(/^#{ENV['CACHE_LOCATION']}/i, '/cache')
30   -
31   - Logger.current.info " => sending #{url} through nginx"
32   -
33   - headers['Content-Length'] = '0'
34   - headers['X-Accel-Redirect'] = url
35   - body = []
36   - end
37   - [status, headers, body]
38   - end
39   -
40   -end
20 lib/middleware/cache_purge.rb
... ... @@ -1,20 +0,0 @@
1   -
2   -class CachePurge
3   - Success = [200, {'Content-Type' => 'text/plain'}, ['OK']]
4   -
5   - def initialize(app)
6   - @app = app
7   - end
8   -
9   - # PURGE /s/files/1/0001/4168/files/thumbs/pic_thumb.jpg?12428536032 HTTP/1.0
10   -
11   - def call(env)
12   - # Rack cache automatically invalidates resource if the verbs are not GET/POST so
13   - # we don't actually have to do anything. Simply don't delegate those to the backend
14   - if env['REQUEST_METHOD'] == 'PURGE'
15   - Success
16   - else
17   - @app.call(env)
18   - end
19   - end
20   -end
16 lib/middleware/favicon_filter.rb
... ... @@ -1,16 +0,0 @@
1   -
2   -class FaviconFilter
3   - Empty = [200, {'Content-Type' => 'text/plain'}, ['']].freeze
4   -
5   - def initialize(app)
6   - @app = app
7   - end
8   -
9   - def call(env)
10   - if env['REQUEST_URI'] == '/favicon.ico'
11   - return Empty
12   - else
13   - @app.call(env)
14   - end
15   - end
16   -end
31 lib/middleware/logged_request.rb
... ... @@ -1,31 +0,0 @@
1   -
2   -class LoggedRequest
3   -
4   - def initialize(app)
5   - @app = app
6   - end
7   -
8   - def call(env)
9   -
10   - request = Rack::Cache::Request.new(env)
11   -
12   - resp = nil
13   -
14   - Logger.current.buffer do
15   -
16   - Logger.current.info "#{request.request_method} #{request.path} [#{request.ip}]"
17   -
18   - secs = Benchmark.realtime do
19   - Logger.current.intend do
20   - resp = @app.call(env)
21   - end
22   - end
23   -
24   - Logger.current.info((resp[0] < 399 ? 'Success' : "Error [#{resp[0]}]") + " after %.3fs Cache: %s" % [secs, resp[1]['X-Rack-Cache']])
25   - Logger.current.info ''
26   -
27   - end
28   -
29   - resp
30   - end
31   -end
23 lib/middleware/remote_proxy.rb
... ... @@ -1,23 +0,0 @@
1   -
2   -class RemoteProxy
3   - include SendFile
4   -
5   - def initialize(app)
6   - @app = app
7   - end
8   -
9   - def call(env)
10   - request = Rack::Request.new(env)
11   -
12   - requested_file = Image.new(env['imagery.origin_host'], env['PATH_INFO'] + (env['QUERY_STRING'].empty? ? '' : "?#{env['QUERY_STRING']}"))
13   -
14   - # If file exists we simply sent it to the client.
15   - if requested_file.found?
16   - Logger.current.info "Requested file exists upstream."
17   -
18   - send_file(requested_file)
19   - else
20   - @app.call(env)
21   - end
22   - end
23   -end
12 lib/middleware/server_name.rb
... ... @@ -1,12 +0,0 @@
1   -
2   -class ServerName
3   - def initialize(app)
4   - @app = app
5   - end
6   -
7   - def call(env)
8   - hash = @app.call(env)
9   - hash[1]['Server'] = 'Shopify Imagery'
10   - hash
11   - end
12   -end
31 lib/send_file.rb
... ... @@ -1,31 +0,0 @@
1   -require 'time'
2   -
3   -module SendFile
4   -
5   - CopyHeaders = ['Content-Type', 'Cache-Control', 'Last-Modified', 'ETag']
6   -
7   - ContentTypes = {
8   - '.gif' => 'image/gif',
9   - '.jpg' => 'image/jpeg',
10   - '.jpeg' => 'image/jpeg',
11   - '.png' => 'image/png',
12   - '.bmp' => 'image/x-bitmap',
13   - '.svg' => 'image/svg+xml'
14   - }
15   -
16   - def send_file(file)
17   - headers = {'Content-Length' => file.content.length.to_s}
18   -
19   - if file.respond_to?(:headers)
20   - CopyHeaders.each do |key|
21   - headers[key] = file.headers[key] if file.headers.has_key?(key)
22   - end
23   - end
24   -
25   - headers['ETag'] ||= Digest::MD5.hexdigest(file.content)
26   - headers['Cache-Control'] ||= 'public, max-age=31557600'
27   - headers['Last-Modified'] ||= Time.new.httpdate
28   -
29   - [200, headers, [file.content]]
30   - end
31   -end
56 lib/svg_generator.rb
... ... @@ -1,56 +0,0 @@
1   -require 'RMagick'
2   -require 'fileutils'
3   -require 'net/http'
4   -
5   -# http://localhost:9292/s/files/1/0001/8392/assets/fish.svg
6   -#
7   -class SvgGenerator
8   - SvgFileTest = /\.svg\.png/i
9   -
10   - def self.from_url(server, path)
11   - return nil unless path =~ SvgFileTest
12   -
13   - file = Image.new(server, original_path_for(path) )
14   - if file.found?
15   - file.headers['Content-Type'] = 'image/png'
16   - file.content = Converter.new(file.content).svg_to_png
17   - file
18   - else
19   - nil
20   - end
21   - end
22   -
23   - def self.original_path_for(path)
24   - path.gsub(/\.png/, '')
25   - end
26   -
27   - class Converter
28   - def initialize(blob)
29   - @blob = blob
30   - end
31   -
32   - def svg_to_png
33   - logger.info "** rasterize svg to png"
34   - result = popen("rsvg-convert")
35   - raise TransformationError, "Data was not a valid SVG image." unless $? == 0
36   - result
37   - end
38   -
39   - private
40   -
41   - def logger
42   - Logger.current
43   - end
44   -
45   - def popen(cmd)
46   - IO.popen(cmd, 'r+') do |io|
47   - io.write @blob
48   - io.close_write
49   - io.read
50   - end
51   - end
52   - end
53   -
54   -
55   -end
56   -
21 lib/transformations.rb
... ... @@ -1,21 +0,0 @@
1   -require 'RMagick'
2   -
3   -module Transformations
4   - @transformations = Hash.new
5   -
6   - def self.list
7   - @transformations.keys
8   - end
9   -
10   - def self.[](name)
11   - @transformations[name.to_s]
12   - end
13   -
14   - def self.register(name, &block)
15   - @transformations[name.to_s] = Proc.new(&block)
16   - end
17   -end
18   -
19   -
20   -
21   -Dir[ File.dirname(__FILE__) + '/transformations/*.rb'].each { |f| require f }
35 lib/transformations/borders.rb
... ... @@ -1,35 +0,0 @@
1   -# Creates a
2   -
3   -Transformations.register :border do |image|
4   - # Add Polaroid border
5   - image.border!(5, 5, "white")
6   -end
7   -
8   -Transformations.register :shadow do |image|
9   - shadow = image.flip
10   - shadow = shadow.colorize(1, 1, 1, "#ccc")
11   - shadow.background_color = "white"
12   - shadow.border!(10, 10, "white")
13   - shadow = shadow.blur_image(0, 7)
14   -
15   - x = (shadow.columns - image.columns) / 2
16   - y = (shadow.rows - image.rows) / 2
17   -
18   - ## Composite original image on top of shadow and save
19   - shadow.composite(image, x, y-3, Magick::OverCompositeOp)
20   -end
21   -
22   -Transformations.register :polaroid do |image|
23   - image.border!(10, 10, "white")
24   -
25   - shadow = image.colorize(1, 1, 1, "#ccc")
26   - shadow.background_color = "white"
27   - shadow.border!(10, 10, "white")
28   - shadow = shadow.blur_image(0, 7)
29   -
30   - x = (shadow.columns - image.columns) / 2
31   - y = (shadow.rows - image.rows) / 2
32   -
33   - ## Composite original image on top of shadow and save
34   - shadow.composite(image, x, y-3, Magick::OverCompositeOp)
35   -end
3  test/helper.rb
... ... @@ -1,4 +1,5 @@
1 1 $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..')
  2 +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib')
2 3
3 4 require 'rubygems'
4 5 require 'test/unit'
@@ -7,5 +8,5 @@
7 8 require 'mocha'
8 9
9 10 require 'rack'
10   -require 'image_server'
  11 +require 'imagery'
11 12 require 'config/env'
12 test/test_image_variant_generator.rb
@@ -19,18 +19,18 @@ def setup
19 19 end
20 20
21 21 def test_successfull_call
22   - assert ImageVariantGenerator.from_url('static.shopify.com', '/image_pico.png')
23   - assert ImageVariantGenerator.from_url('static.shopify.com', '/image_small.png')
  22 + assert Imagery::ImageVariantGenerator.from_url('static.shopify.com', '/image_pico.png')
  23 + assert Imagery::ImageVariantGenerator.from_url('static.shopify.com', '/image_small.png')
24 24 end
25 25
26 26 def test_return_nil_on_404
27   - assert_equal nil, ImageVariantGenerator.from_url('static.shopify.com', '/failed_image_pico.png')
  27 + assert_equal nil, Imagery::ImageVariantGenerator.from_url('static.shopify.com', '/failed_image_pico.png')
28 28 end
29 29
30 30 def test_wrong_filename
31   - assert_equal nil, ImageVariantGenerator.from_url('static.shopify.com', '/image.png')
32   - assert_equal nil, ImageVariantGenerator.from_url('static.shopify.com', '/image_whatever.png')
33   - assert_equal nil, ImageVariantGenerator.from_url('static.shopify.com', '/image.tga')
  31 + assert_equal nil, Imagery::ImageVariantGenerator.from_url('static.shopify.com', '/image.png')
  32 + assert_equal nil, Imagery::ImageVariantGenerator.from_url('static.shopify.com', '/image_whatever.png')
  33 + assert_equal nil, Imagery::ImageVariantGenerator.from_url('static.shopify.com', '/image.tga')
34 34 end
35 35
36 36 end
2  test/test_remote_proxy.rb
@@ -27,7 +27,7 @@ def setup
27 27 )
28 28
29 29
30   - @app = RemoteProxy.new lambda { StandardResponse }
  30 + @app = Imagery::RemoteProxy.new lambda { StandardResponse }
31 31 end
32 32
33 33 def test_successfull_call
8 test/test_svg_generation.rb
@@ -10,14 +10,14 @@ def setup
10 10 def test_successfull_call
11 11 Patron::Session.any_instance.expects(:get).with('/image.svg').returns( stub(:headers => {}, :body => File.read( File.dirname(__FILE__) + '/assets/fish.svg'), :status => 200))
12 12
13   - assert SvgGenerator.from_url('static.shopify.com', '/image.svg.png')
  13 + assert Imagery::SvgGenerator.from_url('static.shopify.com', '/image.svg.png')
14 14 end
15 15
16 16 def test_wrong_filename
17 17
18   - assert_equal nil, SvgGenerator.from_url('static.shopify.com', '/image.svg')
19   - assert_equal nil, SvgGenerator.from_url('static.shopify.com', '/image.png')
20   - assert_equal nil, SvgGenerator.from_url('static.shopify.com', '/image.svg.bmp')
  18 + assert_equal nil, Imagery::SvgGenerator.from_url('static.shopify.com', '/image.svg')
  19 + assert_equal nil, Imagery::SvgGenerator.from_url('static.shopify.com', '/image.png')
  20 + assert_equal nil, Imagery::SvgGenerator.from_url('static.shopify.com', '/image.svg.bmp')
21 21 end
22 22
23 23 end

0 comments on commit bddd139

Please sign in to comment.
Something went wrong with that request. Please try again.