snapimage 0.1.1 → 0.2.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.
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