photo-cook 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30713968f97de16d9fa2a2d0c3b88bcb5435ac43
4
- data.tar.gz: 6deed34ded832a9041bb6adf6bd5595acb2ae170
3
+ metadata.gz: 134a21bfc6697b25c4b682afee2ec145c0e29f38
4
+ data.tar.gz: e090d4eb4009cac709d78e50e2e5caa72f1dd5f7
5
5
  SHA512:
6
- metadata.gz: 5c76ef21599b1cb9d4c5e870b77afc2748aa7ba65613b7b46246a6850c9e3aee716182c45c4fdc100951746af0feb2a9ed910df90fd12cc68d3ad47e33cfd081
7
- data.tar.gz: a87c0fdaf2d015ac0b9aafd5b551d64c18c864f21f23d514c6ddd9989759df01cc108cbeb9a6f447435f5fdb001889479cf329c29d07e46c84502716abd89ed2
6
+ metadata.gz: 378835a2a6cf41cf6160be4c8e7b607fd343cf08b9dbf972f20e19c3242fc842ec83c276e01c82d156c87ff359f4c39e0177c51073e45b5c91b17dcc0279ae2c
7
+ data.tar.gz: 4c7274814608e4250c4a400cf9b109c44abb7f6b62ab39c14ee3d2dbc00a6dd70935af1f7c2471a994632bac5df06fb9110cc17eddf5db74456085ab6edad493
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  .bundle/
2
2
  log/*.log
3
3
  pkg/
4
- .idea
4
+ .idea
5
+ .Gemfile.lock
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ ```ruby
2
+ # application
3
+ # public
4
+ # uploads
5
+ # photos
6
+ # 1
7
+ # car.png
8
+ # resize-cache -> ../../../resize-cache/uploads/photos/1
9
+ # resize-cache
10
+ # uploads
11
+ # photos
12
+ # 1
13
+ # width=auto&height=640&pixel_ratio=1&crop=yes
14
+ # car.png
15
+ ```
@@ -1,13 +1,15 @@
1
1
  (function(root) {
2
2
  var PhotoCook = {
3
- resizeDir: <%= PhotoCook.cache_dir.to_json %>,
3
+ cacheDir: <%= PhotoCook.cache_dir.to_json %>,
4
4
 
5
- commandRegex: /\-(?:(?:\d+x\d+)|(?:\d+x)|(?:x\d+))(?:crop)?$/,
5
+ commandRegex: /^$width=auto|\d{1,5}&height=auto|\d{1,5}&pixel_ratio=[1234]&crop=yes|no$/,
6
6
 
7
7
  initialize: function() {
8
8
  PhotoCook.persistPixelRatio();
9
9
  },
10
10
 
11
+ // Returns device pixel ratio (float)
12
+ // If no ratio could be determined will return normal ratio (1.0)
11
13
  pixelRatio: (function () {
12
14
  // https://gist.github.com/marcedwards/3446599
13
15
  var mediaQuery = [
@@ -22,11 +24,11 @@
22
24
  // and if so return 2x ratio
23
25
  if (ratio == null && typeof window.matchMedia === 'function') {
24
26
  if (window.matchMedia(mediaQuery).matches) {
25
- ratio = 2;
27
+ ratio = 2.0;
26
28
  }
27
29
  }
28
30
 
29
- return parseFloat(ratio == null ? 1 : ratio);
31
+ return parseFloat(ratio) || 1.0;
30
32
  })(),
31
33
 
32
34
  persistPixelRatio: function() {
@@ -35,66 +37,35 @@
35
37
  // Expires in 1 year
36
38
  date.setTime(date.getTime() + 365 * 24 * 60 * 60 * 1000);
37
39
  var expires = 'expires=' + date.toUTCString();
38
- document.cookie = "PhotoCookPixelRatio=" + PhotoCook.pixelRatio + '; ' + expires;
40
+ document.cookie = 'PhotoCookPixelRatio=' + PhotoCook.pixelRatio + '; ' + expires;
39
41
  },
40
42
 
41
43
  resize: function (path, width, height, options) {
42
- var crop = typeof options === 'boolean' ? !!options : !!(options && options.crop);
43
- var ratio = Math.max(0, options && options.pixelRatio) || PhotoCook.pixelRatio;
44
-
45
- var command = '-' + (width && width !== 'auto' ? Math.round(width * ratio) : '')
46
- + 'x'
47
- + (height && height !== 'auto' ? Math.round(height * ratio) : '')
48
- + (crop ? 'crop' : '');
49
-
50
- var index; index = path.lastIndexOf('.');
51
- if (index < 0) { index = path.lastIndexOf('?'); }
52
- if (index < 0) { index = path.lastIndexOf('#'); }
53
-
54
- var leftPart = path.slice(0, Math.max(path.lastIndexOf('/'), 0));
55
- var rightPart = index >= 0 ? path.slice(index) : '';
56
-
57
- var basename = path.slice(
58
- leftPart ? leftPart.length + 1 : 0,
59
- rightPart ? 0 - rightPart.length : path.length
60
- );
61
-
62
- return (leftPart ? leftPart + '/' : leftPart)
63
- + PhotoCook.resizeDir
64
- + '/'
65
- + basename
66
- + command
67
- + rightPart;
44
+ var crop = typeof options === 'boolean' ? !!options : !!(options && options.crop);
45
+ var ratio = Math.ceil(Math.max(0, options && options.pixelRatio) || PhotoCook.pixelRatio);
46
+ var command = 'width=' + (+width || 'auto') +
47
+ '&height=' + (+height || 'auto') +
48
+ '&pixel_ratio=' + (+ratio || 1) +
49
+ '&crop=' + (crop ? 'yes' : 'no');
50
+ var els = path.split('/');
51
+ els.splice(-1, 0, PhotoCook.cacheDir, command);
52
+ return els.join('/');
68
53
  },
69
54
 
70
- strip: function (path) {
71
- var token = PhotoCook.resizeDir + '/';
72
- var leftIndex = path.lastIndexOf(token);
73
- if (leftIndex < 0 || (leftIndex > 0 && path[leftIndex - 1] !== '/')) {
74
- return path;
75
- }
76
-
77
- var rightIndex; rightIndex = path.lastIndexOf('.');
78
- if (rightIndex < 0) { rightIndex = path.lastIndexOf('?'); }
79
- if (rightIndex < 0) { rightIndex = path.lastIndexOf('#'); }
80
-
81
- var basename = path.slice(
82
- leftIndex + token.length,
83
- rightIndex >= 0 ? 0 - path.length + rightIndex : path.length
84
- );
55
+ strip: function (uri) {
56
+ var sections = uri.split('/');
57
+ var length = sections.length;
58
+ if (length < 3) { return uri; }
85
59
 
86
- var oldLength = basename.length;
60
+ var cacheDir = sections[length - 3];
61
+ var command = sections[length - 2];
87
62
 
88
- basename = basename.replace(PhotoCook.commandRegex, '');
89
-
90
- if (oldLength === basename.length) {
91
- return path;
63
+ if (PhotoCook.cacheDir !== cacheDir || PhotoCook.commandRegex.test(command) == false) {
64
+ return uri;
92
65
  }
93
66
 
94
- var leftPart = path.slice(0, leftIndex);
95
- var rightPart = rightIndex >= 0 ? path.slice(rightIndex) : '';
96
-
97
- return leftPart + basename + rightPart;
67
+ sections.splice(length - 3, 2)
68
+ return sections.join('/');
98
69
  },
99
70
 
100
71
  uriRegex: /^[-a-z]+:\/\/|^(?:cid|data):|^\/\//i,
@@ -1,26 +1,96 @@
1
1
  module PhotoCook
2
2
  module Assemble
3
3
 
4
- # Edit URI so it will point to PhotoCook::Middleware
4
+ # Returns URI which points to PhotoCook::Middleware
5
+ #
6
+ # Arguments:
7
+ # source_uri => /uploads/photos/1/car.png
8
+ # width => :auto
9
+ # height => 640
10
+ # pixel_ratio => 1
11
+ # crop => true
12
+ #
13
+ # Returns /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
14
+ #
5
15
  # NOTE: This method performs no validation
6
- def assemble_uri(uri, width, height, pixel_ratio, crop)
7
- result = File.join cache_dir, dirname_or_blank(uri),
8
- assemble_command(width, height, pixel_ratio, crop), File.basename(uri)
9
- uri.start_with?('/') ? '/' + result : result
16
+ def assemble_resize_uri(source_uri, width, height, pixel_ratio, crop)
17
+ source_uri.split('/').insert(-2, cache_dir, assemble_command(width, height, pixel_ratio, crop)).join('/')
10
18
  end
11
19
 
12
- # Edit path so it will point to place where resized photo stored
20
+ # Strips resize command from URI. Inverse of +assemble_resize_uri+
21
+ #
22
+ # Arguments:
23
+ # resize_uri => /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
24
+ #
25
+ # Returns /uploads/photos/1/car.png
26
+ #
13
27
  # NOTE: This method performs no validation
14
- def assemble_store_path(path, width, height, pixel_ratio, crop)
15
- rootless = path.split(File.join(root, public_dir)).second
16
- File.join root, public_dir,
17
- cache_dir, dirname_or_blank(rootless),
18
- assemble_command(width, height, pixel_ratio, crop), File.basename(path)
28
+ def disassemble_resize_uri(resize_uri)
29
+ # Take URI:
30
+ # /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
31
+ #
32
+ # Split by separator:
33
+ # ["", "uploads", "photos", "1", "resized", "width=auto&height=640&pixel_ratio=1&crop=yes", "car.png"]
34
+ #
35
+ sections = resize_uri.split('/')
36
+
37
+ # Delete PhotoCook directory:
38
+ # ["", "uploads", "photos", "1", "width=auto&height=640&pixel_ratio=1&crop=yes", "car.png"]
39
+ sections.delete_at(-3)
40
+
41
+ # Delete command string:
42
+ # ["", "uploads", "photos", "1", "car.png"]
43
+ sections.delete_at(-2)
44
+
45
+ sections.join('/')
46
+ end
47
+
48
+ # Path where source photo is stored
49
+ #
50
+ # Arguments:
51
+ # root => /application
52
+ # resize_uri => /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
53
+ #
54
+ # Returns /application/public/uploads/photos/1/car.png
55
+ #
56
+ # NOTE: This method performs no validation
57
+ def assemble_source_path(root, resize_uri)
58
+ uri = disassemble_resize_uri(resize_uri)
59
+ uri.gsub!('/', '\\') if File::SEPARATOR != '/'
60
+ File.join(assemble_public_path(root), uri)
61
+ end
62
+
63
+ # Path where resized photo is stored
64
+ #
65
+ # Arguments:
66
+ # root => /application
67
+ # source_path => /application/public/uploads/photos/1/car.png
68
+ # assembled_command => width=auto&height=640&pixel_ratio=1&crop=yes
69
+ #
70
+ # Returns /application/public/resized/uploads/photos/1/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
71
+ #
72
+ # NOTE: This method performs no validation
73
+ def assemble_store_path(root, source_path, assembled_command)
74
+ public = assemble_public_path(root)
75
+ photo_location = dirname_or_blank(source_path.split(public).last)
76
+ File.join public, cache_dir, photo_location, assembled_command, File.basename(source_path)
77
+ end
78
+
79
+ # Path to public directory
80
+ #
81
+ # Arguments:
82
+ # root => /application
83
+ #
84
+ # Returns /application/public
85
+ #
86
+ # NOTE: This method performs no validation
87
+ def assemble_public_path(root)
88
+ File.join(root, public_dir)
19
89
  end
20
90
 
21
91
  private
22
92
  def dirname_or_blank(path)
23
- (dirname = File.dirname(path)) == '.' ? '' : dirname
93
+ File.dirname(path).sub(/\A\.\z/, '')
24
94
  end
25
95
  end
26
96
  extend Assemble
@@ -8,28 +8,42 @@ module PhotoCook
8
8
  # http://www.canbike.org/CSSpixels/
9
9
  def command_regex
10
10
  @command_regex ||= %r{
11
+ \A
11
12
  width= (?<width> auto|\d{1,4}) &
12
13
  height= (?<height> auto|\d{1,4}) &
13
14
  pixel_ratio= (?<pixel_ratio>[1234]) &
14
15
  crop= (?<crop> yes|no)
16
+ \z
15
17
  }x
16
18
  end
17
19
 
18
20
  # NOTE: This method performs no validation
19
21
  def assemble_command(width, height, pixel_ratio, crop)
20
- "width=#{ width == 0 ? 'auto' : width}&" +
21
- "height=#{ height == 0 ? 'auto' : height}&" +
22
- "pixel_ratio=#{pixel_ratio.ceil}&" +
23
- "crop=#{ bool_to_crop(crop)}&"
22
+ 'width=' + encode_dimension(width) +
23
+ '&height=' + encode_dimension(height) +
24
+ '&pixel_ratio=' + encode_pixel_ratio(pixel_ratio) +
25
+ '&crop=' + encode_crop_option(crop)
24
26
  end
25
27
 
26
- def crop_to_bool(crop)
28
+ def extract_command(resize_uri)
29
+ resize_uri.split('/')[-2].match(command_regex)
30
+ end
31
+
32
+ def decode_crop_option(crop)
27
33
  crop == 'yes'
28
34
  end
29
35
 
30
- def bool_to_crop(crop)
36
+ def encode_crop_option(crop)
31
37
  crop ? 'yes' : 'no'
32
38
  end
39
+
40
+ def encode_dimension(val)
41
+ val == 0 ? 'auto' : val.to_s
42
+ end
43
+
44
+ def encode_pixel_ratio(ratio)
45
+ ratio.ceil.to_s
46
+ end
33
47
  end
34
48
  extend Command
35
49
  end
@@ -9,27 +9,27 @@ module PhotoCook
9
9
  end
10
10
 
11
11
  def check_dimensions!(width, height)
12
- raise WidthOutOfBoundsError if width < 0 || width > 9999
13
- raise HeightOutOfBoundsError if height < 0 || height > 9999
14
- raise NoConcreteDimensionsError if width + height == 0
12
+ raise WidthOutOfBounds if width < 0 || width > 9999
13
+ raise HeightOutOfBounds if height < 0 || height > 9999
14
+ raise NoConcreteDimensions if width + height == 0
15
15
  end
16
16
  end
17
17
 
18
- class WidthOutOfBoundsError < ArgumentError
18
+ class WidthOutOfBounds < ArgumentError
19
19
  def initialize
20
20
  super 'Width must be positive integer number (0...9999)'
21
21
  end
22
22
  end
23
23
 
24
- class HeightOutOfBoundsError < ArgumentError
24
+ class HeightOutOfBounds < ArgumentError
25
25
  def initialize
26
26
  super 'Height must be positive integer number (0...9999)'
27
27
  end
28
28
  end
29
29
 
30
- class NoConcreteDimensionsError < ArgumentError
30
+ class NoConcreteDimensions < ArgumentError
31
31
  def initialize
32
- super "Both width and height specified as 'auto'"
32
+ super 'Both width and height specified as :auto'
33
33
  end
34
34
  end
35
35
  extend Dimensions
@@ -0,0 +1,8 @@
1
+ module PhotoCook
2
+ class << self
3
+ attr_accessor :public_dir, :cache_dir
4
+ end
5
+
6
+ self.public_dir = 'public'
7
+ self.cache_dir = 'resize-cache'
8
+ end
@@ -1,9 +1,5 @@
1
1
  module PhotoCook
2
2
  class Engine < ::Rails::Engine
3
- config.before_initialize do
4
- PhotoCook.root = Rails.root
5
- end
6
-
7
3
  initializer :photo_cook_javascripts do |app|
8
4
  app.config.assets.paths << File.join(PhotoCook::Engine.root, 'app/assets/javascripts')
9
5
  end
@@ -1,8 +1,23 @@
1
1
  module PhotoCook
2
- module Logging
3
- def log_resize(photo, w, h, px_ratio, crop, msec)
4
- msg = %{
5
- [PhotoCook] Resized photo.
2
+ class Logger
3
+ def initialize(logger)
4
+ @logger = logger
5
+ end
6
+
7
+ def info(msg)
8
+ @logger.info(msg)
9
+ end
10
+
11
+ def matched_resize_uri(uri)
12
+ info %{
13
+ [PhotoCook] Matched resize URI.
14
+ #{uri}
15
+ }
16
+ end
17
+
18
+ def performed_resize(photo, w, h, px_ratio, crop, msec)
19
+ info %{
20
+ [PhotoCook] Performed resize.
6
21
  Source file: #{photo.source_path}
7
22
  Resized file: #{photo.resized_path}
8
23
  Width: #{w == 0 ? 'auto': "#{w}px"}
@@ -11,8 +26,35 @@ module PhotoCook
11
26
  Pixel ratio: #{px_ratio}
12
27
  Completed in: #{msec.round(1)}ms
13
28
  }
14
- rails_env? ? Rails.logger.info(msg) : print(msg)
15
29
  end
30
+
31
+ def will_symlink_cache_dir(cmd)
32
+ info %{
33
+ [PhotoCook] Will symlink cache directory.
34
+ Command: #{cmd}
35
+ }
36
+ end
37
+
38
+ def symlink_cache_dir_success
39
+ info %{
40
+ [PhotoCook] Successfully symlink cache directory.
41
+ }
42
+ end
43
+
44
+ def symlink_cache_dir_failure
45
+ info %{
46
+ [PhotoCook] Failed to symlink cache directory.
47
+ }
48
+ end
49
+ end
50
+
51
+ class STDOut
52
+ def info(msg)
53
+ print(msg)
54
+ end
55
+ end
56
+
57
+ def self.logger
58
+ @logger ||= Logger.new(rails_env? ? Rails.logger : STDOut.new)
16
59
  end
17
- extend Logging
18
60
  end
@@ -8,49 +8,55 @@ module PhotoCook
8
8
  @app, @root = app, root
9
9
  end
10
10
 
11
- # Consider we have car.png in /uploads/photos/1
11
+ # Consider we have car.png in /uploads/photos/1/car.png
12
12
  # We want to resize it with the following params:
13
- # Width: choose automatically
14
- # Height: exactly 640px
15
- # Crop: yes
16
- # Pixel ratio: 1
13
+ # Width: choose automatically
14
+ # Height: exactly 640px
15
+ # Pixel ratio: 1
16
+ # Crop: yes
17
17
  #
18
18
  # Middleware will handle this URI:
19
- # /resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
19
+ # /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
20
20
  #
21
21
  def call(env)
22
22
  uri = extract_uri(env)
23
23
 
24
24
  return default_actions(env) if
25
25
 
26
- # Check if uri starts with '/resized'
27
- false == uri.start_with?(PhotoCook.resize_uri_indicator) ||
28
-
29
- # Check if uri has valid resize command.
30
- # uri.split('/')[-2] => width:auto&height:640&crop:true&pixel_ratio:1
31
- nil == (uri.split('/')[-2] =~ PhotoCook.command_regex) ||
26
+ # Check if URI contains PhotoCook resize indicators
27
+ PhotoCook.resize_uri?(uri) == false ||
32
28
 
33
29
  # If for some reasons file exists but request went to Ruby app
34
- true == requested_file_exists?(uri)
30
+ requested_file_exists?(uri)
35
31
 
36
32
  # At this point we are sure that this request is targeting to resize photo
37
33
 
34
+ PhotoCook.logger.matched_resize_uri(uri)
35
+
36
+ # Matched data: width=auto&height=640&pixel_ratio=1&crop=yes
37
+ command = PhotoCook.extract_command(uri)
38
+
38
39
  # Assemble path of the source photo:
39
- # => /uploads/photos/1/car.png
40
- source_path = assemble_source_path(uri)
40
+ # => /application/public/uploads/photos/1/car.png
41
+ source_path = PhotoCook.assemble_source_path(@root, uri)
41
42
 
42
- # Matched data: width:auto&height:640&crop:true&pixel_ratio:1
43
- command = Regexp.last_match
43
+ # Assemble path of the resized photo:
44
+ # => /application/public/resized/uploads/photos/1/COMMAND/car.png
45
+ store_path = PhotoCook.assemble_store_path(@root, source_path, command.to_s)
44
46
 
45
47
  # Map crop option values: 'yes' => true, 'no' => false
46
- crop = PhotoCook.crop_to_bool(command[:crop])
48
+ crop = PhotoCook.decode_crop_option(command[:crop])
47
49
 
48
50
  # Finally resize photo
49
51
  # Resized photo will appear in resize directory
50
- photo = PhotoCook.resize_photo(source_path,
51
- command[:width], command[:height], pixel_ratio: command[:pixel_ratio], crop: crop)
52
+ photo = PhotoCook.perform_resize(
53
+ source_path, store_path,
54
+ command[:width], command[:height],
55
+ pixel_ratio: command[:pixel_ratio], crop: crop
56
+ )
52
57
 
53
58
  if photo
59
+ symlink_cache_dir(source_path, store_path)
54
60
  respond_with_file(env)
55
61
  else
56
62
  default_actions(env)
@@ -58,31 +64,14 @@ module PhotoCook
58
64
  end
59
65
 
60
66
  private
61
-
62
- def assemble_source_path(resize_uri)
63
- # Take URI:
64
- # /resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
65
- #
66
- # Split by file separator:
67
- # ["", "resized", "uploads", "photos", "1", "width:auto&height:640&crop:true&pixel_ratio:1", "car.png"]
68
- #
69
- els = resize_uri.split('/')
70
-
71
- # Delete PhotoCook directory:
72
- # ["", "uploads", "photos", "1", "width:auto&height:640&crop:true&pixel_ratio:1", "car.png"]
73
- els.delete_at(1)
74
-
75
- # Delete command string:
76
- # ["", "uploads", "photos", "1", "car.png"]
77
- els.delete_at(-2)
78
-
79
- URI.decode File.join(@root, PhotoCook.public_dir, els)
67
+ def public
68
+ @public ||= PhotoCook.assemble_public_path(@root)
80
69
  end
81
70
 
82
71
  def requested_file_exists?(uri)
83
72
  # Check if file exists:
84
- # /application/public/resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
85
- File.exists? File.join(@root, PhotoCook.public_dir, uri)
73
+ # /application/public/uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
74
+ File.exists? File.join(public, uri)
86
75
  end
87
76
 
88
77
  def extract_uri(env)
@@ -97,9 +86,37 @@ module PhotoCook
97
86
  def respond_with_file(env)
98
87
  # http://rubylogs.com/writing-rails-middleware/
99
88
  # https://viget.com/extend/refactoring-patterns-the-rails-middleware-response-handler
100
- status, headers, body = Rack::File.new(File.join(@root, PhotoCook.public_dir)).call(env)
89
+ status, headers, body = Rack::File.new(public).call(env)
101
90
  response = Rack::Response.new(body, status, headers)
102
91
  response.finish
103
92
  end
93
+
94
+ def symlink_cache_dir(source_path, store_path)
95
+ # /application/public/uploads/photos/1
96
+ p1 = Pathname.new(File.dirname(source_path))
97
+
98
+ # /application/public/resized/uploads/photos/1
99
+ p2 = Pathname.new(File.dirname(File.dirname(store_path)))
100
+
101
+ # ../../../resized/uploads/photos/1
102
+ relative = p2.relative_path_from(p1)
103
+
104
+ # Guess cache directory (must be same as PhotoCook.cache_dir)
105
+ cache_dir = relative.to_s.split(File::SEPARATOR).find { |el| !(el =~ /\A\.\.?\z/) }
106
+
107
+ unless Dir.exists?(p1.join(cache_dir))
108
+ cmd = "cd #{p1} && rm -rf #{cache_dir} && ln -rs #{relative} #{cache_dir}"
109
+
110
+ PhotoCook.logger.will_symlink_cache_dir(cmd)
111
+
112
+ %x{ #{cmd} }
113
+
114
+ if $?.success?
115
+ PhotoCook.logger.symlink_cache_dir_success
116
+ else
117
+ PhotoCook.logger.symlink_cache_dir_failure
118
+ end
119
+ end
120
+ end
104
121
  end
105
122
  end
@@ -3,8 +3,8 @@ module PhotoCook
3
3
  def parse_and_check_pixel_ratio(unsafe_ratio)
4
4
  pixel_ratio = unsafe_ratio.to_f
5
5
 
6
- raise PixelRatioInvalidOrInfiniteError if pixel_ratio.nan? || pixel_ratio.infinite?
7
- raise PixelRatioOutOfBoundsError if pixel_ratio < 1 || pixel_ratio > 4
6
+ raise PixelRatioInvalidOrInfinite if pixel_ratio.nan? || pixel_ratio.infinite?
7
+ raise PixelRatioOutOfBounds if pixel_ratio < 1 || pixel_ratio > 4
8
8
 
9
9
  pixel_ratio
10
10
  end
@@ -12,15 +12,22 @@ module PhotoCook
12
12
  def valid_pixel_ratio?(ratio)
13
13
  !ratio.nan? && !ratio.infinite? && ratio >= 1 && ratio <= 4
14
14
  end
15
+
16
+ # Do not produce various number of pixel ratios:
17
+ # 2.5 => 3
18
+ # 2.1 => 3
19
+ def unify_pixel_ratio(px_ratio)
20
+ px_ratio.ceil
21
+ end
15
22
  end
16
23
 
17
- class PixelRatioInvalidOrInfiniteError < ArgumentError
24
+ class PixelRatioInvalidOrInfinite < ArgumentError
18
25
  def initialize
19
26
  super 'Given pixel ratio is invalid number or infinite'
20
27
  end
21
28
  end
22
29
 
23
- class PixelRatioOutOfBoundsError < ArgumentError
30
+ class PixelRatioOutOfBounds < ArgumentError
24
31
  def initialize
25
32
  super 'Pixel ratio must be positive number: 1 <= pixel_ratio <= 4'
26
33
  end
@@ -0,0 +1,92 @@
1
+ module PhotoCook
2
+ module API
3
+ module Magick
4
+ # Performs photo resizing with ImageMagick:
5
+ # perform_resize('/application/public/uploads/car.png', '/application/public/resized_car.png', 280, 280)
6
+ #
7
+ # Source file: /application/public/uploads/car.png
8
+ # Result file: /application/public/resized_car.png
9
+ #
10
+ # NOTE: This method will perform validation
11
+ def perform_resize(source_path, store_path, width, height, options = {})
12
+ started = Time.now
13
+
14
+ width, height = parse_and_check_dimensions(width, height)
15
+ pixel_ratio, crop = open_options(options)
16
+ # Explicit # Default
17
+ pixel_ratio = unify_pixel_ratio(parse_and_check_pixel_ratio(pixel_ratio || 1))
18
+ photo = Resizer.instance.resize(source_path, store_path, width, height, pixel_ratio, !!crop)
19
+
20
+ finished = Time.now
21
+ logger.performed_resize(photo, width, height, pixel_ratio, !!crop, (finished - started) * 1000.0)
22
+ photo
23
+ end
24
+ end
25
+
26
+ module URI
27
+ # Builds URI which points to PhotoCook::Middleware:
28
+ # resize('/uploads/car.png', 280, 280, pixel_ratio: 2.5)
29
+ # => /uploads/resized/width=280&height=280&pixel_ratio=3&crop=no/car.png
30
+ #
31
+ # NOTE: This method will perform validation
32
+ def resize(uri, width, height, options = {})
33
+ width, height = parse_and_check_dimensions(width, height)
34
+ pixel_ratio, crop = open_options(options)
35
+ # Explicit # From cookies # Default
36
+ pixel_ratio = parse_and_check_pixel_ratio(pixel_ratio || PhotoCook.client_pixel_ratio || 1)
37
+ assemble_resize_uri(uri, width, height, unify_pixel_ratio(pixel_ratio), !!crop)
38
+ end
39
+
40
+ # Inverse of PhotoCook#resize (see ^):
41
+ # strip('/uploads/resized/width=280&height=280&pixel_ratio=3&crop=no/car.png')
42
+ # => /uploads/car.png
43
+ #
44
+ # NOTE: This method will perform validation
45
+ def strip(uri, check = false)
46
+ # TODO Implement check
47
+ disassemble_resize_uri(uri)
48
+ end
49
+
50
+ # Shorthand: hresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', 280, nil)
51
+ #
52
+ # NOTE: This method will perform validation
53
+ def hresize(uri, width, options = {})
54
+ resize(uri, width, :auto, options)
55
+ end
56
+
57
+ # Shorthand: vresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', nil, 280)
58
+ def vresize(uri, height, options = {})
59
+ resize(uri, :auto, height, options)
60
+ end
61
+
62
+ def resize_uri?(uri)
63
+ sections = uri.split('/')
64
+
65
+ # Check if PhotoCook cache directory exists:
66
+ # sections[-3] => resized
67
+ sections[-3] == cache_dir &&
68
+
69
+ # Check if valid resize command exists:
70
+ # sections[-2] => width=auto&height=640&pixel_ratio=1&crop=yes
71
+ (sections[-2] =~ command_regex) == 0
72
+ end
73
+ end
74
+
75
+ module Tools
76
+ def open_options(options)
77
+ case options
78
+ when Hash
79
+ [options[:pixel_ratio], options.fetch(:crop, false)]
80
+ when Symbol
81
+ [nil, options == :crop]
82
+ end
83
+ end
84
+
85
+ private :open_options
86
+ end
87
+ end
88
+
89
+ extend API::Magick
90
+ extend API::URI
91
+ extend API::Tools
92
+ end
@@ -5,11 +5,11 @@ module PhotoCook
5
5
  CENTER_GRAVITY = 'Center'.freeze
6
6
  TRANSPARENT_BACKGROUND = 'rgba(255,255,255,0.0)'.freeze
7
7
 
8
- def resize(photo_path, width, height, pixel_ratio = 1.0, crop = false)
8
+ def resize(src_path, store_path, w, h, px_ratio = 1, crop = false)
9
9
  if crop
10
- resize_to_fill(photo_path, width, height, pixel_ratio)
10
+ resize_to_fill(src_path, store_path, w, h, px_ratio)
11
11
  else
12
- resize_to_fit(photo_path, width, height, pixel_ratio)
12
+ resize_to_fit(src_path, store_path, w, h, px_ratio)
13
13
  end
14
14
  end
15
15
 
@@ -18,12 +18,11 @@ module PhotoCook
18
18
  # - new dimensions will be not larger then the specified
19
19
  #
20
20
  # https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/mini_magick.rb#L131
21
- def resize_to_fit(photo_path, width, height, pixel_ratio)
21
+ def resize_to_fit(source_path, store_path, width, height, pixel_ratio)
22
22
 
23
23
  # Do nothing if photo is not valid so exceptions will be not thrown
24
- return unless (photo = open(photo_path)) && photo.valid?
24
+ return unless (photo = open(source_path)) && photo.valid?
25
25
 
26
- store_path = assemble_store_path(photo_path, width, height, pixel_ratio, false)
27
26
  width, height = multiply_dimensions(width, height, pixel_ratio)
28
27
 
29
28
  photo.combine_options { |cmd| cmd.resize "#{literal_dimensions(width, height)}>" }
@@ -36,12 +35,11 @@ module PhotoCook
36
35
  # - the photo will be cropped if necessary
37
36
  #
38
37
  # https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/mini_magick.rb#L176
39
- def resize_to_fill(photo_path, width, height, pixel_ratio)
38
+ def resize_to_fill(source_path, store_path, width, height, pixel_ratio)
40
39
 
41
40
  # Do nothing if photo is not valid so exceptions will be not thrown
42
- return unless (photo = open(photo_path)) && photo.valid?
41
+ return unless (photo = open(source_path)) && photo.valid?
43
42
 
44
- store_path = assemble_store_path(photo_path, width, height, pixel_ratio, true)
45
43
  cols, rows = photo[:dimensions]
46
44
  mwidth, mheight = multiply_dimensions(width, height, pixel_ratio)
47
45
 
@@ -78,32 +76,26 @@ module PhotoCook
78
76
 
79
77
  protected
80
78
 
81
- def open(photo_path)
79
+ def open(source_path)
82
80
  begin
83
81
  # MiniMagick::Image.open creates a temporary file for us and protects original
84
- photo = MagickPhoto.open(photo_path)
85
- photo.source_path = photo_path
82
+ photo = MagickPhoto.open(source_path)
83
+ photo.source_path = source_path
86
84
  photo
87
85
  rescue
88
86
  nil
89
87
  end
90
88
  end
91
89
 
92
- def store(resized_photo, path_to_store_at)
93
- dir = File.dirname(path_to_store_at)
94
- FileUtils.mkdir_p(dir) unless File.exists?(dir)
95
-
96
- resized_photo.write(path_to_store_at)
97
- resized_photo.resized_path = path_to_store_at
90
+ def store(resized_photo, store_path)
91
+ FileUtils.mkdir_p(File.dirname(store_path))
92
+ resized_photo.write(store_path)
93
+ resized_photo.resized_path = store_path
98
94
  resized_photo
99
95
  end
100
96
 
101
97
  def literal_dimensions(width, height)
102
- "#{width == 0 ? nil : width}x#{height == 0 ? nil : height}"
103
- end
104
-
105
- def assemble_store_path(path, width, height, pixel_ratio, crop)
106
- PhotoCook.assemble_store_path(path, width, height, pixel_ratio, crop)
98
+ "#{width if width != 0}x#{height if height != 0}"
107
99
  end
108
100
 
109
101
  def multiply_dimensions(width, height, ratio)
@@ -1,3 +1,3 @@
1
1
  module PhotoCook
2
- VERSION = '1.1.0'
2
+ VERSION = '1.1.1'
3
3
  end
data/lib/photo-cook.rb CHANGED
@@ -1,25 +1,25 @@
1
1
  require 'fileutils'
2
+ require 'pathname'
2
3
  require 'rake'
3
4
  require 'mini_magick'
4
5
 
5
6
  module PhotoCook
6
7
  def self.rails_env?
7
- defined?(Rails)
8
+ @rails.nil? ? @rails = !!defined?(Rails) : @rails
8
9
  end
9
10
  end
10
11
 
11
12
  require 'photo-cook/dimensions'
12
13
  require 'photo-cook/pixel-ratio'
13
- require 'photo-cook/paths'
14
+ require 'photo-cook/dirs'
14
15
  require 'photo-cook/command'
15
16
  require 'photo-cook/assemble'
16
- require 'photo-cook/callbacks'
17
17
  require 'photo-cook/logging'
18
18
  require 'photo-cook/resizer'
19
19
  require 'photo-cook/middleware'
20
20
  require 'photo-cook/magick-photo'
21
21
  require 'photo-cook/carrierwave'
22
- require 'photo-cook/resizing'
22
+ require 'photo-cook/resize-api'
23
23
 
24
24
  if PhotoCook.rails_env?
25
25
  require 'photo-cook/engine'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: photo-cook
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Konoplov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-26 00:00:00.000000000 Z
11
+ date: 2015-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -47,25 +47,24 @@ extra_rdoc_files: []
47
47
  files:
48
48
  - ".gitignore"
49
49
  - Gemfile
50
- - Gemfile.lock
50
+ - README.md
51
51
  - Rakefile
52
52
  - app/assets/javascripts/photo-cook.js
53
53
  - app/assets/javascripts/photo-cook/photo-cook.js.erb
54
54
  - lib/photo-cook.rb
55
55
  - lib/photo-cook/assemble.rb
56
- - lib/photo-cook/callbacks.rb
57
56
  - lib/photo-cook/carrierwave.rb
58
57
  - lib/photo-cook/command.rb
59
58
  - lib/photo-cook/dimensions.rb
59
+ - lib/photo-cook/dirs.rb
60
60
  - lib/photo-cook/engine.rb
61
61
  - lib/photo-cook/logging.rb
62
62
  - lib/photo-cook/magick-photo.rb
63
63
  - lib/photo-cook/middleware.rb
64
- - lib/photo-cook/paths.rb
65
64
  - lib/photo-cook/pixel-ratio-spy.rb
66
65
  - lib/photo-cook/pixel-ratio.rb
66
+ - lib/photo-cook/resize-api.rb
67
67
  - lib/photo-cook/resizer.rb
68
- - lib/photo-cook/resizing.rb
69
68
  - lib/photo-cook/version.rb
70
69
  - photo-cook.gemspec
71
70
  homepage: http://github.com/yivo/photo-cook
data/Gemfile.lock DELETED
@@ -1,19 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- photo-cook (1.0.2)
5
- mini_magick (~> 4.0)
6
- rack (~> 1.5)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- mini_magick (4.1.0)
12
- rack (1.6.4)
13
-
14
- PLATFORMS
15
- ruby
16
-
17
- DEPENDENCIES
18
- mini_magick
19
- photo-cook!
@@ -1,8 +0,0 @@
1
- module PhotoCook
2
- module Callbacks
3
- def on_resize(photo, command)
4
- log_resize(photo, command) if rails?
5
- end
6
- end
7
- extend Callbacks
8
- end
@@ -1,12 +0,0 @@
1
- module PhotoCook
2
- class << self
3
- attr_accessor :public_dir, :cache_dir, :root
4
-
5
- def resize_uri_indicator
6
- '/' + cache_dir
7
- end
8
- end
9
-
10
- self.public_dir = 'public'
11
- self.cache_dir = 'resized'
12
- end
@@ -1,68 +0,0 @@
1
- module PhotoCook
2
- module Resizing
3
-
4
- # Performs photo resizing:
5
- # resize_photo('/application/public/uploads/car.png', 280, 280)
6
- #
7
- # NOTE: This method will perform validation
8
- def resize_photo(path, width, height, options = {})
9
- started = Time.now
10
-
11
- width, height = parse_and_check_dimensions(width, height)
12
- pixel_ratio, crop = open_options(options)
13
- # Explicit # Default
14
- pixel_ratio = unify_pixel_ratio(parse_and_check_pixel_ratio(pixel_ratio || 1))
15
- photo = Resizer.instance.resize(path, width, height, pixel_ratio, !!crop)
16
-
17
- finished = Time.now
18
- log_resize(photo, width, height, pixel_ratio, !!crop, (finished - started) * 1000.0)
19
- photo
20
- end
21
-
22
- # Builds URI for resizing:
23
- # resize('/uploads/car.png', 280, 280, pixel_ratio: 2.5)
24
- # => /resized/uploads/width:280&height:280&crop:0&pixel_ratio:3
25
- #
26
- # NOTE: This method will perform validation
27
- def build_resize_uri(uri, width, height, options = {})
28
- width, height = parse_and_check_dimensions(width, height)
29
- pixel_ratio, crop = open_options(options)
30
- # Explicit # From cookies # Default
31
- pixel_ratio = parse_and_check_pixel_ratio(pixel_ratio || PhotoCook.client_pixel_ratio || 1)
32
- assemble_uri(uri, width, height, unify_pixel_ratio(pixel_ratio), !!crop)
33
- end
34
-
35
- alias resize build_resize_uri
36
-
37
- # Shorthand: hresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', 280, nil)
38
- def hresize(uri, width, options = {})
39
- resize(uri, width, :auto, options)
40
- end
41
-
42
- # Shorthand: vresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', nil, 280)
43
- def vresize(uri, height, options = {})
44
- resize(uri, :auto, height, options)
45
- end
46
-
47
- private
48
- def open_options(options)
49
- case options
50
- when Hash
51
- [options[:pixel_ratio], options.fetch(:crop, false)]
52
- when Symbol
53
- [nil, options == :crop]
54
- else
55
- [nil, nil]
56
- end
57
- end
58
-
59
- # Do not produce various number of pixel ratios:
60
- # 2.5 => 3
61
- # 2.1 => 3
62
- def unify_pixel_ratio(px_ratio)
63
- px_ratio.ceil
64
- end
65
- end
66
-
67
- extend Resizing
68
- end