snapimage 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/README.md +2 -2
  2. data/bin/snapimage_generate_config +40 -7
  3. data/lib/snapimage.rb +5 -13
  4. data/lib/snapimage/adapters/cloudinary/config.rb +13 -0
  5. data/lib/snapimage/adapters/cloudinary/storage.rb +22 -0
  6. data/lib/snapimage/adapters/local/config.rb +14 -0
  7. data/lib/snapimage/adapters/local/storage.rb +48 -0
  8. data/lib/snapimage/config.rb +3 -1
  9. data/lib/snapimage/exceptions.rb +0 -6
  10. data/lib/snapimage/middleware.rb +2 -1
  11. data/lib/snapimage/rack/request.rb +1 -1
  12. data/lib/snapimage/rack/response.rb +2 -10
  13. data/lib/snapimage/server.rb +12 -17
  14. data/lib/snapimage/storage.rb +10 -0
  15. data/lib/snapimage/version.rb +1 -1
  16. data/snapimage.gemspec +0 -1
  17. data/spec/acceptance/upload_spec.rb +45 -3
  18. data/spec/snapimage/adapters/local/storage_spec.rb +44 -0
  19. data/spec/snapimage/middleware_spec.rb +2 -0
  20. data/spec/snapimage/rack/request_spec.rb +3 -9
  21. data/spec/snapimage/server_spec.rb +27 -11
  22. data/spec/support/assets/config.json +2 -0
  23. data/spec/support/assets/config.yml +2 -0
  24. metadata +9 -34
  25. data/lib/snapimage/image/image.rb +0 -103
  26. data/lib/snapimage/image/image_name_utils.rb +0 -131
  27. data/lib/snapimage/server_actions/server_actions.authorize.rb +0 -69
  28. data/lib/snapimage/server_actions/server_actions.delete_resource_images.rb +0 -23
  29. data/lib/snapimage/server_actions/server_actions.generate_image.rb +0 -169
  30. data/lib/snapimage/server_actions/server_actions.list_resource_images.rb +0 -23
  31. data/lib/snapimage/server_actions/server_actions.sync_resource.rb +0 -78
  32. data/lib/snapimage/storage/storage.rb +0 -120
  33. data/lib/snapimage/storage/storage_server.local.rb +0 -120
  34. data/lib/snapimage/storage/storage_server.rb +0 -110
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe SnapImage::Local::Storage do
4
+ before do
5
+ @config = { "directory" => "/directory", "public_url" => "http://snapimage.com/public" }
6
+ @storage = SnapImage::Local::Storage.new(@config)
7
+ end
8
+
9
+ describe "#store" do
10
+ before do
11
+ @file = double("file")
12
+ FileUtils.stub(:mkdir_p)
13
+ end
14
+
15
+ it "stores the file" do
16
+ File.should_receive(:open).with("/directory/abc/123/file.png", "wb").once
17
+ @storage.store("file.png", @file, directory: "abc/123")
18
+ end
19
+
20
+ it "returns the public URL" do
21
+ File.stub(:open)
22
+ @storage.store("file.png", @file, directory: "abc/123").should eq "http://snapimage.com/public/abc/123/file.png"
23
+ end
24
+ end
25
+
26
+ describe "#get_file_path" do
27
+ it "returns a simple file path when the file doesn't exist" do
28
+ File.stub(:exists?).and_return(false)
29
+ @storage.send(:get_file_path, "abc/123", "pic.png").should eq "abc/123/pic.png"
30
+ end
31
+
32
+ it "returns the file path appended with (1) when only the file exists" do
33
+ File.stub(:exists?).and_return(true)
34
+ Dir.stub(:glob).and_return([])
35
+ @storage.send(:get_file_path, "abc/123", "pic.png").should eq "abc/123/pic(1).png"
36
+ end
37
+
38
+ it "returns the file path appended with the next number when multiple files exist" do
39
+ File.stub(:exists?).and_return(true)
40
+ Dir.stub(:glob).and_return(["abc/123/pic(10).png", "abc/123/pic(2).png"])
41
+ @storage.send(:get_file_path, "abc/123", "pic.png").should eq "abc/123/pic(11).png"
42
+ end
43
+ end
44
+ end
@@ -9,6 +9,7 @@ describe SnapImage::Middleware do
9
9
  describe "#call" do
10
10
  it "passes the call through to the rack app when the path does not match" do
11
11
  @app.should_receive(:call).with(@env).and_return("app")
12
+ SnapImage::Storage.stub(:new)
12
13
  middleware = SnapImage::Middleware.new(@app, config: {})
13
14
  middleware.call(@env).should eq "app"
14
15
  end
@@ -16,6 +17,7 @@ describe SnapImage::Middleware do
16
17
  it "passes the call through to the SnapImage Server" do
17
18
  server = double("server")
18
19
  server.should_receive(:call).and_return("server")
20
+ SnapImage::Storage.stub(:new)
19
21
  SnapImage::Server.stub(:new).and_return(server)
20
22
 
21
23
  @app.should_not_receive(:call)
@@ -8,25 +8,19 @@ describe SnapImage::Request do
8
8
  describe "#bad_request?" do
9
9
  it "returns true if the request is not a post" do
10
10
  @request.stub(:post?).and_return(false)
11
- @request.stub(:POST).and_return({ "file" => "abc", "directory" => "123" })
11
+ @request.stub(:POST).and_return({ "file" => "abc" })
12
12
  @request.bad_request?.should be_true
13
13
  end
14
14
 
15
15
  it "returns true if the request does not include file" do
16
16
  @request.stub(:post?).and_return(true)
17
- @request.stub(:POST).and_return({ "directory" => "123" })
18
- @request.bad_request?.should be_true
19
- end
20
-
21
- it "returns true if the request does not include directory" do
22
- @request.stub(:post?).and_return(true)
23
- @request.stub(:POST).and_return({ "file" => "abc" })
17
+ @request.stub(:POST).and_return({})
24
18
  @request.bad_request?.should be_true
25
19
  end
26
20
 
27
21
  it "returns false if the request is valid" do
28
22
  @request.stub(:post?).and_return(true)
29
- @request.stub(:POST).and_return({ "file" => "abc", "directory" => "123" })
23
+ @request.stub(:POST).and_return({ "file" => "abc" })
30
24
  @request.bad_request?.should be_false
31
25
  end
32
26
  end
@@ -3,8 +3,10 @@ require "spec_helper"
3
3
  describe SnapImage::Server do
4
4
  before do
5
5
  @request = double("request")
6
- @config = { "directory" => "/directory", "max_file_size" => 100 }
7
- @server = SnapImage::Server.new(@request, @config)
6
+ @request.stub(:xhr?).and_return(true)
7
+ @config = { "directory" => "/directory", "public_url" => "http://snapimage.com/public", "max_file_size" => 100 }
8
+ @storage = double("storage")
9
+ @server = SnapImage::Server.new(@request, @config, @storage)
8
10
  end
9
11
 
10
12
  describe "#call" do
@@ -54,7 +56,7 @@ describe SnapImage::Server do
54
56
  response[2].body.should eq ['{"status_code":405,"message":"File Too Large"}']
55
57
  end
56
58
 
57
- it "returns success when the file already exists" do
59
+ it "returns success when the file is saved" do
58
60
  @request.stub(:bad_request?).and_return(false)
59
61
  tempfile = double("tempfile")
60
62
  tempfile.stub(:size).and_return(50)
@@ -63,15 +65,15 @@ describe SnapImage::Server do
63
65
  file.stub(:tempfile).and_return(tempfile)
64
66
  @request.stub(:file).and_return(file)
65
67
  @request.stub(:[]).with("directory").and_return("abc/123")
66
- File.stub(:exists?).and_return(true)
67
- FileUtils.stub(:mkdir_p)
68
+ @storage.should_receive(:store).with("abc123.png", tempfile, directory: "abc/123").and_return("http://snapimage.com/public/abc/123/abc123.png")
68
69
  response = @server.call
69
70
  response[0].should eq 200
70
71
  response[1]["Content-Type"].should eq "text/json"
71
- response[2].body.should eq ['{"status_code":200,"message":"Success"}']
72
+ response[2].body.should eq ['{"status_code":200,"url":"http://snapimage.com/public/abc/123/abc123.png","message":"Success"}']
72
73
  end
73
74
 
74
- it "returns success when the file is saved" do
75
+ it "returns content type of text/html when non-XHR" do
76
+ @request.stub(:xhr?).and_return(false)
75
77
  @request.stub(:bad_request?).and_return(false)
76
78
  tempfile = double("tempfile")
77
79
  tempfile.stub(:size).and_return(50)
@@ -80,13 +82,27 @@ describe SnapImage::Server do
80
82
  file.stub(:tempfile).and_return(tempfile)
81
83
  @request.stub(:file).and_return(file)
82
84
  @request.stub(:[]).with("directory").and_return("abc/123")
83
- File.stub(:exists?).and_return(false)
84
- FileUtils.stub(:mkdir_p)
85
- File.should_receive(:open).with("/directory/abc/123/abc123.png", "wb").once
85
+ @storage.should_receive(:store).with("abc123.png", tempfile, directory: "abc/123").and_return("http://snapimage.com/public/abc/123/abc123.png")
86
+ response = @server.call
87
+ response[0].should eq 200
88
+ response[1]["Content-Type"].should eq "text/html"
89
+ response[2].body.should eq ['{"status_code":200,"url":"http://snapimage.com/public/abc/123/abc123.png","message":"Success"}']
90
+ end
91
+
92
+ it "uses the default directory when none is given" do
93
+ @request.stub(:bad_request?).and_return(false)
94
+ tempfile = double("tempfile")
95
+ tempfile.stub(:size).and_return(50)
96
+ file = double("file")
97
+ file.stub(:filename).and_return("abc123.png")
98
+ file.stub(:tempfile).and_return(tempfile)
99
+ @request.stub(:file).and_return(file)
100
+ @request.stub(:[]).with("directory").and_return(nil)
101
+ @storage.should_receive(:store).with("abc123.png", tempfile, directory: "uncategorized").and_return("http://snapimage.com/public/uncategorized/abc123.png")
86
102
  response = @server.call
87
103
  response[0].should eq 200
88
104
  response[1]["Content-Type"].should eq "text/json"
89
- response[2].body.should eq ['{"status_code":200,"message":"Success"}']
105
+ response[2].body.should eq ['{"status_code":200,"url":"http://snapimage.com/public/uncategorized/abc123.png","message":"Success"}']
90
106
  end
91
107
  end
92
108
  end
@@ -1,4 +1,6 @@
1
1
  {
2
+ "adapter": "local",
2
3
  "directory": "/path/to/directory",
4
+ "public_url": "http://snapimage.com/public/url",
3
5
  "max_file_size": 100
4
6
  }
@@ -1,2 +1,4 @@
1
+ adapter: "local"
1
2
  directory: "/path/to/directory"
3
+ public_url: "http://snapimage.com/public/url"
2
4
  max_file_size: 100
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snapimage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-13 00:00:00.000000000 Z
12
+ date: 2013-09-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -27,22 +27,6 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: rmagick
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: '0'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
46
30
  - !ruby/object:Gem::Dependency
47
31
  name: sinatra
48
32
  requirement: !ruby/object:Gem::Requirement
@@ -158,23 +142,18 @@ files:
158
142
  - bin/snapimage_generate_config
159
143
  - bin/snapimage_server
160
144
  - lib/snapimage.rb
145
+ - lib/snapimage/adapters/cloudinary/config.rb
146
+ - lib/snapimage/adapters/cloudinary/storage.rb
147
+ - lib/snapimage/adapters/local/config.rb
148
+ - lib/snapimage/adapters/local/storage.rb
161
149
  - lib/snapimage/config.rb
162
150
  - lib/snapimage/exceptions.rb
163
- - lib/snapimage/image/image.rb
164
- - lib/snapimage/image/image_name_utils.rb
165
151
  - lib/snapimage/middleware.rb
166
152
  - lib/snapimage/rack/request.rb
167
153
  - lib/snapimage/rack/request_file.rb
168
154
  - lib/snapimage/rack/response.rb
169
155
  - lib/snapimage/server.rb
170
- - lib/snapimage/server_actions/server_actions.authorize.rb
171
- - lib/snapimage/server_actions/server_actions.delete_resource_images.rb
172
- - lib/snapimage/server_actions/server_actions.generate_image.rb
173
- - lib/snapimage/server_actions/server_actions.list_resource_images.rb
174
- - lib/snapimage/server_actions/server_actions.sync_resource.rb
175
- - lib/snapimage/storage/storage.rb
176
- - lib/snapimage/storage/storage_server.local.rb
177
- - lib/snapimage/storage/storage_server.rb
156
+ - lib/snapimage/storage.rb
178
157
  - lib/snapimage/version.rb
179
158
  - snapimage.gemspec
180
159
  - spec/acceptance/delete_resource_images_spec.rb
@@ -182,6 +161,7 @@ files:
182
161
  - spec/acceptance/modify_spec.rb
183
162
  - spec/acceptance/sync_spec.rb
184
163
  - spec/acceptance/upload_spec.rb
164
+ - spec/snapimage/adapters/local/storage_spec.rb
185
165
  - spec/snapimage/config_spec.rb
186
166
  - spec/snapimage/image/image_name_utils_spec.rb
187
167
  - spec/snapimage/image/image_spec.rb
@@ -220,18 +200,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
220
200
  - - ! '>='
221
201
  - !ruby/object:Gem::Version
222
202
  version: '0'
223
- segments:
224
- - 0
225
- hash: 2769920436023059955
226
203
  required_rubygems_version: !ruby/object:Gem::Requirement
227
204
  none: false
228
205
  requirements:
229
206
  - - ! '>='
230
207
  - !ruby/object:Gem::Version
231
208
  version: '0'
232
- segments:
233
- - 0
234
- hash: 2769920436023059955
235
209
  requirements: []
236
210
  rubyforge_project:
237
211
  rubygems_version: 1.8.23
@@ -244,6 +218,7 @@ test_files:
244
218
  - spec/acceptance/modify_spec.rb
245
219
  - spec/acceptance/sync_spec.rb
246
220
  - spec/acceptance/upload_spec.rb
221
+ - spec/snapimage/adapters/local/storage_spec.rb
247
222
  - spec/snapimage/config_spec.rb
248
223
  - spec/snapimage/image/image_name_utils_spec.rb
249
224
  - spec/snapimage/image/image_spec.rb
@@ -1,103 +0,0 @@
1
- module SnapImage
2
- class Image
3
- def self.from_path(path, public_url)
4
- image = SnapImage::Image.new(public_url)
5
- image.set_image_from_path(path)
6
- image
7
- end
8
-
9
- def self.from_blob(blob)
10
- image = SnapImage::Image.new
11
- image.set_image_from_blob(blob)
12
- image
13
- end
14
-
15
- def self.from_image(img)
16
- image = SnapImage::Image.new
17
- image.set_image_from_image(img)
18
- image
19
- end
20
-
21
- attr_accessor :public_url
22
-
23
- # Arguments:
24
- # * public_url:: Public URL associated with the image
25
- def initialize(public_url = nil)
26
- @public_url = public_url
27
- end
28
-
29
- # Arguments:
30
- # * path:: Local path or URL
31
- def set_image_from_path(path)
32
- destroy!
33
- @image = Magick::ImageList.new(path)
34
- end
35
-
36
- # Arguments:
37
- # * blob:: Image blob
38
- def set_image_from_blob(blob)
39
- destroy!
40
- @image = Magick::ImageList.new
41
- @image.from_blob(blob)
42
- end
43
-
44
- # Arguments:
45
- # * image:: RMagick Image
46
- def set_image_from_image(image)
47
- destroy!
48
- @image = image
49
- end
50
-
51
- def width
52
- @image.columns
53
- end
54
-
55
- def height
56
- @image.rows
57
- end
58
-
59
- def blob
60
- @image.to_blob
61
- end
62
-
63
- def destroy!
64
- @image.destroy! if @image && !@image.destroyed?
65
- end
66
-
67
- # Crops the image with the given parameters.
68
- #
69
- # Arguments:
70
- # * x:: x coordinate of the top left corner
71
- # * y:: y coordinate of the top left corner
72
- # * width:: width of the crop rectangle
73
- # * height:: height of the crop rectangle
74
- def crop(x, y, width, height)
75
- @image.crop!(x, y, width, height)
76
- end
77
-
78
- # Resizes the image with the given paramaters.
79
- #
80
- # Arguments:
81
- # * width:: Width to resize to
82
- # * height:: Height to resize to (optional)
83
- # * maintain_aspect_ratio:: If true, the image will be resized to fit within the width/height specified while maintaining the aspect ratio. If false, the image is allowed to be stretched.
84
- def resize(width, height = nil, maintain_aspect_ratio = true)
85
- raise "Height must be specified when not maintaining aspect ratio." if !maintain_aspect_ratio && !height
86
- # If no height is given, make sure it does not interfere with the
87
- # resizing.
88
- height ||= [width, @image.rows].max
89
- if maintain_aspect_ratio
90
- @image.resize_to_fit!(width, height)
91
- else
92
- @image.resize!(width, height)
93
- end
94
- end
95
-
96
- # Sharpens the image.
97
- def sharpen
98
- image = @image.sharpen
99
- destroy!
100
- @image = image
101
- end
102
- end
103
- end
@@ -1,131 +0,0 @@
1
- module SnapImage
2
- class ImageNameUtils
3
- # Base Image Format:
4
- # {basename}-{original dimensions}.{extname}
5
- #
6
- # Modified Image Format:
7
- # {basename}-{original dimensions}-{crop}-{dimensions}-{sharpen}.{extname}
8
- #
9
- # Notes
10
- # * crop:: {x}x{y}x{width}x{height}
11
- # * dimensions:: {width}x{height}
12
- # * sharpen:: 0 or 1
13
- IMAGE_PATH_REGEXP = /^(.*\/|)(([a-z0-9]{8})-(\d+)x(\d+)(-(\d+)x(\d+)x(\d+)x(\d+)-(\d+)x(\d+)-([01])|).(png|jpg|gif))$/
14
-
15
- class << self
16
- # Returns true if the path is for a base image. False otherwise.
17
- #
18
- # The path points to a base image if the dimensions are the same as the
19
- # original dimensions, there is no cropping, and there is no sharpening.
20
- def base_image?(path)
21
- get_image_name_parts(path)[:is_base]
22
- end
23
-
24
- # Returns the extension name of the path. Normalizes jpeg to jpg.
25
- def get_image_type(path)
26
- type = File.extname(path).sub(/^\./, "")
27
- type = "jpg" if type == "jpeg"
28
- type
29
- end
30
-
31
- # Returns true if the name is valid. False otherwise.
32
- def valid?(path)
33
- get_image_name_parts(path)
34
- return true
35
- rescue
36
- return false
37
- end
38
-
39
- # Parses the following format:
40
- # {basename}-{original dimensions}-{crop}-{dimensions}-{sharpen}.{extname}
41
- # Notes about the format:
42
- # * crop:: {x}x{y}x{width}x{height}
43
- # * dimensions:: {width}x{height}
44
- # * sharpen:: 0 or 1
45
- #
46
- # Returns the information in a hash.
47
- def get_image_name_parts(path)
48
- matches = path.match(SnapImage::ImageNameUtils::IMAGE_PATH_REGEXP)
49
- raise SnapImage::InvalidImageIdentifier, "The image identifier is invalid: #{path}" unless matches
50
- parts = {
51
- is_base: matches[6].size == 0,
52
- full: matches[0],
53
- path: matches[1].sub(/\/$/, ""),
54
- filename: matches[2],
55
- basename: matches[3],
56
- original_dimensions: [matches[4].to_i, matches[5].to_i],
57
- extname: matches[14]
58
- }
59
- unless parts[:is_base]
60
- parts.merge!({
61
- crop: {
62
- x: matches[7].to_i,
63
- y: matches[8].to_i,
64
- width: matches[9].to_i,
65
- height: matches[10].to_i
66
- },
67
- dimensions: [matches[11].to_i, matches[12].to_i],
68
- sharpen: !!matches[13]
69
- })
70
- end
71
- parts
72
- end
73
-
74
- # Returns the base image name from the path.
75
- def get_base_image_path(path)
76
- parts = get_image_name_parts(path)
77
- "#{parts[:path]}/#{parts[:basename]}-#{parts[:original_dimensions][0]}x#{parts[:original_dimensions][1]}.#{parts[:extname]}"
78
- end
79
-
80
- # Returns the name with the new width and height.
81
- def get_resized_image_name(name, width, height)
82
- parts = SnapImage::ImageNameUtils.get_image_name_parts(name)
83
- if parts[:is_base]
84
- resized_name = SnapImage::ImageNameUtils.generate_image_name(width, height, parts[:extname], basename: parts[:basename])
85
- else
86
- options = {
87
- basename: parts[:basename],
88
- crop: parts[:crop],
89
- width: width,
90
- height: height,
91
- sharpen: parts[:sharpend]
92
- }
93
- resized_name = SnapImage::ImageNameUtils.generate_image_name(parts[:original_dimensions][0], parts[:original_dimensions][1], parts[:extname], options)
94
- end
95
- resized_name
96
- end
97
-
98
- # Generates a random alphanumeric string of 8 characters.
99
- def generate_basename
100
- (0...8).map { rand(36).to_s(36) }.join
101
- end
102
-
103
- # When no options besides :basename are given, generates a base image
104
- # name in the format:
105
- # {basename}-{original dimensions}.{extname}
106
- #
107
- # Otherwise, generates a modified image name in the format:
108
- # {basename}-{original dimensions}-{crop}-{dimensions}-{sharpen}.{extname}
109
- #
110
- # Notes about the format:
111
- # * crop:: {x}x{y}x{width}x{height}
112
- # * dimensions:: {width}x{height}
113
- # * sharpen:: 0 or 1
114
- #
115
- # Options:
116
- # * basename:: Defaults to a randomly generated basename of 8 characters
117
- # * crop:: Crop values {x: <int>, y: <int>, width: <int>, height: <int>}
118
- # * width:: New width
119
- # * height:: New height
120
- # * sharpen:: True or false
121
- def generate_image_name(original_width, original_height, extname, options = {})
122
- basename = options.delete(:basename) || self.generate_basename
123
- if options.empty?
124
- "#{basename}-#{original_width}x#{original_height}.#{extname}"
125
- else
126
- "#{basename}-#{original_width}x#{original_height}-#{options[:crop][:x]}x#{options[:crop][:y]}x#{options[:crop][:width]}x#{options[:crop][:height]}-#{options[:width]}x#{options[:height]}-#{options[:sharpen] ? 1 : 0}.#{extname}"
127
- end
128
- end
129
- end
130
- end
131
- end