s3_cors_fileupload 0.2.1 → 0.3.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/CHANGELOG.md +10 -3
  4. data/Gemfile +11 -9
  5. data/Gemfile.lock +86 -65
  6. data/README.md +8 -4
  7. data/lib/generators/s3_cors_fileupload/install/install_generator.rb +5 -2
  8. data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads_controller.rb +3 -1
  9. data/lib/generators/s3_cors_fileupload/install/templates/source_file.rb +4 -3
  10. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/_template_download.html.erb +18 -7
  11. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/_template_upload.html.erb +4 -4
  12. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/_template_uploaded.html.erb +17 -6
  13. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/index.html.erb +1 -1
  14. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/_template_download.html.haml +14 -4
  15. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/_template_upload.html.haml +5 -5
  16. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/_template_uploaded.html.haml +12 -1
  17. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/index.html.haml +1 -1
  18. data/lib/s3_cors_fileupload.rb +6 -0
  19. data/lib/s3_cors_fileupload/rails.rb +1 -1
  20. data/lib/s3_cors_fileupload/rails/form_helper.rb +15 -12
  21. data/lib/s3_cors_fileupload/version.rb +3 -3
  22. data/s3_cors_fileupload.gemspec +3 -4
  23. data/spec/dummy/Rakefile +0 -1
  24. data/spec/dummy/app/assets/javascripts/application.js +4 -3
  25. data/spec/dummy/app/assets/javascripts/s3_uploads.js +92 -0
  26. data/spec/dummy/app/assets/stylesheets/application.css +1 -0
  27. data/spec/dummy/app/controllers/application_controller.rb +3 -1
  28. data/spec/dummy/app/controllers/s3_uploads_controller.rb +56 -0
  29. data/spec/dummy/app/models/source_file.rb +54 -0
  30. data/spec/dummy/app/views/layouts/application.html.erb +2 -2
  31. data/spec/dummy/app/views/s3_uploads/_template_download.html.erb +43 -0
  32. data/spec/dummy/app/views/s3_uploads/_template_upload.html.erb +36 -0
  33. data/spec/dummy/app/views/s3_uploads/_template_uploaded.html.erb +41 -0
  34. data/spec/dummy/app/views/s3_uploads/index.html.erb +41 -0
  35. data/spec/dummy/bin/bundle +3 -0
  36. data/spec/dummy/bin/rails +4 -0
  37. data/spec/dummy/bin/rake +4 -0
  38. data/spec/dummy/config.ru +1 -1
  39. data/spec/dummy/config/application.rb +2 -33
  40. data/spec/dummy/config/boot.rb +4 -9
  41. data/spec/dummy/config/environment.rb +2 -2
  42. data/spec/dummy/config/environments/development.rb +14 -19
  43. data/spec/dummy/config/environments/production.rb +40 -27
  44. data/spec/dummy/config/environments/test.rb +14 -12
  45. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  46. data/spec/dummy/config/initializers/inflections.rb +6 -5
  47. data/spec/dummy/config/initializers/secret_token.rb +7 -2
  48. data/spec/dummy/config/initializers/session_store.rb +0 -5
  49. data/spec/dummy/config/initializers/wrap_parameters.rb +6 -6
  50. data/spec/dummy/config/locales/en.yml +20 -2
  51. data/spec/dummy/config/routes.rb +27 -24
  52. data/spec/dummy/db/migrate/20131001201535_create_source_files.rb +14 -0
  53. data/spec/dummy/db/schema.rb +27 -0
  54. data/spec/dummy/public/404.html +43 -11
  55. data/spec/dummy/public/422.html +43 -11
  56. data/spec/dummy/public/500.html +43 -11
  57. data/spec/features/uploads_spec.rb +37 -0
  58. data/spec/lib/s3_cors_fileupload/rails/engine_spec.rb +8 -0
  59. data/spec/lib/s3_cors_fileupload/rails/form_helper_spec.rb +30 -0
  60. data/spec/lib/s3_cors_fileupload/rails/policy_helper_spec.rb +95 -0
  61. data/spec/s3_cors_fileupload_spec.rb +8 -4
  62. data/spec/spec_helper.rb +8 -8
  63. data/spec/support/dummy.pdf +0 -0
  64. data/vendor/assets/javascripts/s3_cors_fileupload/index.js +1 -0
  65. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload-image.js +111 -32
  66. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload-ui.js +24 -18
  67. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload-validate.js +7 -6
  68. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload.js +22 -15
  69. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/load-image-meta.js +137 -0
  70. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/tmpl.js +8 -8
  71. data/vendor/assets/stylesheets/jquery.fileupload-ui.css.erb +2 -1
  72. metadata +56 -28
  73. data/spec/dummy/script/rails +0 -6
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'Uploads' do
4
+ background { visit source_files_path }
5
+
6
+ describe 'GET #index' do
7
+ scenario { page.should have_selector('h2', :text => 'Upload file(s)') }
8
+
9
+ scenario :s3_cors_fileupload_form_tag do
10
+ within('form#fileupload') do
11
+ page.should have_selector('div.fileupload-buttonbar')
12
+ page.should have_selector('table#upload_files')
13
+ page.should have_selector('input[type=file]#file')
14
+ end
15
+ end
16
+
17
+ scenario "Attaching files", :js => true do
18
+ within('form#fileupload') do
19
+ within('table#upload_files tbody.files') do
20
+ page.should_not have_selector('tr')
21
+ page.text.should be_blank
22
+ end
23
+ attach_file('file', File.expand_path('../../support/dummy.pdf', __FILE__))
24
+ # After a file is attached, it should get added to the table#upload_files to be uploaded
25
+ within('table#upload_files tbody.files') do
26
+ page.should have_selector('tr.template-upload')
27
+ within('tr.template-upload') do
28
+ page.should have_selector('td', :text => 'dummy.pdf')
29
+ page.should have_selector('td', :text => '7.84 KB')
30
+ page.should have_selector('td', :text => 'Start')
31
+ page.should have_selector('td', :text => 'Cancel')
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3CorsFileupload::Rails::Engine do
4
+ subject { S3CorsFileupload::Rails::Engine }
5
+
6
+ it { should be_instance_of(Class) }
7
+ its(:superclass) { should == ::Rails::Engine }
8
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3CorsFileupload::FormHelper do
4
+ it { S3CorsFileupload::FormHelper.should be_instance_of(Module) }
5
+ it { ActionView::Base.should include(S3CorsFileupload::FormHelper) }
6
+
7
+ describe "Instance Methods" do
8
+ subject { ActionView::Base.new }
9
+
10
+ describe :s3_cors_fileupload_form_tag do
11
+ it { should respond_to(:s3_cors_fileupload_form_tag) }
12
+
13
+ it "should return HTML for the jQuery-File-Upload form" do
14
+ value = subject.s3_cors_fileupload_form_tag
15
+ value.should be_a(String)
16
+ value.should be_instance_of(ActiveSupport::SafeBuffer)
17
+ value.should be_html_safe
18
+ value.match(/<form/).should_not be_nil
19
+ end
20
+ end
21
+
22
+ describe :s3_cors_fileupload_form do
23
+ it { should respond_to(:s3_cors_fileupload_form) }
24
+
25
+ it "should be an alias for the `s3_cors_fileupload_form_tag` method" do
26
+ subject.method(:s3_cors_fileupload_form).should == subject.method(:s3_cors_fileupload_form_tag)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3CorsFileupload::PolicyHelper do
4
+ it { S3CorsFileupload::PolicyHelper.should be_instance_of(Class) }
5
+
6
+ let(:options) { {} }
7
+ let(:policy_helper) { S3CorsFileupload::PolicyHelper.new(options) }
8
+
9
+ describe "Constructor" do
10
+ context "no arguments" do
11
+ it "should build without error" do
12
+ policy_helper
13
+ end
14
+
15
+ it "should use default values" do
16
+ policy_helper.options[:acl].should == 'public-read'
17
+ policy_helper.options[:max_file_size].should == S3CorsFileupload::Config.max_file_size || 524288000
18
+ policy_helper.options[:bucket].should == S3CorsFileupload::Config.bucket
19
+ policy_helper.options[:secret_access_key].should == S3CorsFileupload::Config.secret_access_key
20
+ end
21
+ end
22
+
23
+ context "arguments" do
24
+ context :acl do
25
+ let(:options) { {:acl => 'private'} }
26
+ it { policy_helper.options[:acl].should == 'private' }
27
+ end
28
+
29
+ context :max_file_size do
30
+ let(:options) { {:max_file_size => 10} }
31
+ it { policy_helper.options[:max_file_size].should == 10 }
32
+ end
33
+
34
+ context :bucket do
35
+ let(:options) { {:bucket => 'foobar'} }
36
+ it { policy_helper.options[:bucket].should == 'foobar' }
37
+ end
38
+
39
+ context :bucket do
40
+ let(:options) { {:bucket => 'foobar'} }
41
+ it { policy_helper.options[:bucket].should == 'foobar' }
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "Instance Methods" do
47
+ describe :policy_document do
48
+ it { should respond_to(:policy_document) }
49
+
50
+ it "should generate a policy document for AWS S3 and use `Base64` to encode it" do
51
+ value = policy_helper.policy_document
52
+ value.should be_instance_of(String)
53
+ # decode and then deserialize the value returned from the policy document
54
+ value = MultiJson.load Base64.decode64(value), symbolize_keys: true
55
+ value.should be_instance_of(Hash)
56
+ value.should have_key(:expiration)
57
+ value.should have_key(:conditions)
58
+ value[:conditions].should == [
59
+ { bucket: policy_helper.options[:bucket] },
60
+ { acl: policy_helper.options[:acl] },
61
+ { success_action_status: '201' },
62
+ ["content-length-range", 0, policy_helper.options[:max_file_size]],
63
+ ["starts-with", "$utf8", ""],
64
+ ["starts-with", "$key", ""],
65
+ ["starts-with", "$Content-Type", ""]
66
+ ]
67
+ end
68
+
69
+ it "should cache the return value in an instance variable" do
70
+ policy_helper.instance_variable_get(:@policy_document).should be_nil
71
+ value = policy_helper.policy_document
72
+ policy_helper.instance_variable_get(:@policy_document).should_not be_nil
73
+ policy_helper.instance_variable_get(:@policy_document).should == value
74
+ end
75
+ end
76
+
77
+ describe :upload_signature do
78
+ it { should respond_to(:upload_signature) }
79
+
80
+ it "should create an upload signature based off our secret_access_key, and convert it to a string" do
81
+ policy_helper.upload_signature.should == Base64.encode64(
82
+ OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, policy_helper.options[:secret_access_key], policy_helper.policy_document)
83
+ ).gsub(/\n/, '')
84
+ end
85
+
86
+ it "should cache the return value in an instance variable" do
87
+ policy_helper.instance_variable_get(:@upload_signature).should be_nil
88
+ value = policy_helper.upload_signature
89
+ policy_helper.instance_variable_get(:@upload_signature).should_not be_nil
90
+ policy_helper.instance_variable_get(:@upload_signature).should == value
91
+ end
92
+ end
93
+ end
94
+
95
+ end
@@ -1,9 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe S3CorsFileupload do
4
-
5
4
  it { should be_instance_of(Module) }
6
-
7
- # we will need to write some additional specs for the module within the context of a Rails app,
8
- # since much of the functionality doesn't get required unless Rails is defined.
5
+
6
+ describe "Methods" do
7
+ describe :active_record_protected_attributes? do
8
+ it { should respond_to(:active_record_protected_attributes?) }
9
+
10
+ its(:active_record_protected_attributes?) { should be_false } # Since the dummy app isn't using `ProtectedAttributes`
11
+ end
12
+ end
9
13
  end
@@ -3,6 +3,14 @@ require File.expand_path('../dummy/config/environment', __FILE__)
3
3
  # the gem's library gets included through a require statement on 'dummy/config/application.rb'
4
4
  require 'rspec/rails'
5
5
  require 'rspec/autorun'
6
+ require 'capybara/rspec'
7
+ require 'capybara/rails'
8
+ require 'capybara/poltergeist'
9
+
10
+ Capybara.javascript_driver = :poltergeist
11
+
12
+ # Run any available migration in the dummy app
13
+ ActiveRecord::Migrator.migrate("#{::Rails.root}/db/migrate")
6
14
 
7
15
  # Requires supporting ruby files with custom matchers and macros, etc,
8
16
  # in spec/support/ and its subdirectories.
@@ -38,12 +46,4 @@ RSpec.configure do |config|
38
46
  # the seed, which is printed after each run.
39
47
  # --seed 1234
40
48
  config.order = 'random'
41
-
42
- # Prep for running the test suite
43
- config.before(:all) do
44
- # Invoke the generator for the gem.
45
- # ::Rails::Generators.invoke('s3_cors_fileupload:install')
46
- # Run any migrations for the dummy app prior to running the spec suite.
47
- # ActiveRecord::Migrator.migrate "#{::Rails.root}/db/migrate"
48
- end
49
49
  end
Binary file
@@ -1,6 +1,7 @@
1
1
  //= require s3_cors_fileupload/vendor/jquery.ui.widget
2
2
  //= require s3_cors_fileupload/vendor/tmpl
3
3
  //= require s3_cors_fileupload/vendor/load-image
4
+ //= require s3_cors_fileupload/vendor/load-image-meta
4
5
  //= require s3_cors_fileupload/jquery.iframe-transport
5
6
  //= require s3_cors_fileupload/jquery.fileupload
6
7
  //= require s3_cors_fileupload/jquery.fileupload-process
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload Image Preview & Resize Plugin 1.0
2
+ * jQuery File Upload Image Preview & Resize Plugin 1.2.3
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2013, Sebastian Tschan
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  /*jslint nomen: true, unparam: true, regexp: true */
13
- /*global define, window */
13
+ /*global define, window, document, DataView, Blob, Uint8Array */
14
14
 
15
15
  (function (factory) {
16
16
  'use strict';
@@ -19,6 +19,9 @@
19
19
  define([
20
20
  'jquery',
21
21
  'load-image',
22
+ 'load-image-meta',
23
+ 'load-image-exif',
24
+ 'load-image-ios',
22
25
  'canvas-to-blob',
23
26
  './jquery.fileupload-process'
24
27
  ], factory);
@@ -34,37 +37,55 @@
34
37
 
35
38
  // Prepend to the default processQueue:
36
39
  $.blueimp.fileupload.prototype.options.processQueue.unshift(
40
+ {
41
+ action: 'loadImageMetaData',
42
+ disableImageHead: '@',
43
+ disableExif: '@',
44
+ disableExifThumbnail: '@',
45
+ disableExifSub: '@',
46
+ disableExifGps: '@',
47
+ disabled: '@disableImageMetaDataLoad'
48
+ },
37
49
  {
38
50
  action: 'loadImage',
39
- // Always trigger this action,
40
- // even if the previous action was rejected:
41
- always: true,
42
- fileTypes: '@loadImageFileTypes',
43
- maxFileSize: '@loadImageMaxFileSize',
44
- noRevoke: '@loadImageNoRevoke',
51
+ // Use the action as prefix for the "@" options:
52
+ prefix: true,
53
+ fileTypes: '@',
54
+ maxFileSize: '@',
55
+ noRevoke: '@',
45
56
  disabled: '@disableImageLoad'
46
57
  },
47
58
  {
48
59
  action: 'resizeImage',
49
- maxWidth: '@imageMaxWidth',
50
- maxHeight: '@imageMaxHeight',
51
- minWidth: '@imageMinWidth',
52
- minHeight: '@imageMinHeight',
53
- crop: '@imageCrop',
60
+ // Use "image" as prefix for the "@" options:
61
+ prefix: 'image',
62
+ maxWidth: '@',
63
+ maxHeight: '@',
64
+ minWidth: '@',
65
+ minHeight: '@',
66
+ crop: '@',
54
67
  disabled: '@disableImageResize'
55
68
  },
56
69
  {
57
70
  action: 'saveImage',
58
71
  disabled: '@disableImageResize'
59
72
  },
73
+ {
74
+ action: 'saveImageMetaData',
75
+ disabled: '@disableImageMetaDataSave'
76
+ },
60
77
  {
61
78
  action: 'resizeImage',
62
- maxWidth: '@previewMaxWidth',
63
- maxHeight: '@previewMaxHeight',
64
- minWidth: '@previewMinWidth',
65
- minHeight: '@previewMinHeight',
66
- crop: '@previewCrop',
67
- canvas: '@previewAsCanvas',
79
+ // Use "preview" as prefix for the "@" options:
80
+ prefix: 'preview',
81
+ maxWidth: '@',
82
+ maxHeight: '@',
83
+ minWidth: '@',
84
+ minHeight: '@',
85
+ crop: '@',
86
+ orientation: '@',
87
+ thumbnail: '@',
88
+ canvas: '@',
68
89
  disabled: '@disableImagePreview'
69
90
  },
70
91
  {
@@ -83,7 +104,7 @@
83
104
  // matched against the file type:
84
105
  loadImageFileTypes: /^image\/(gif|jpeg|png)$/,
85
106
  // The maximum file size of images to load:
86
- loadImageMaxFileSize: 5000000, // 5MB
107
+ loadImageMaxFileSize: 10000000, // 10MB
87
108
  // The maximum width of resized images:
88
109
  imageMaxWidth: 1920,
89
110
  // The maximum height of resized images:
@@ -96,10 +117,15 @@
96
117
  previewMaxWidth: 80,
97
118
  // The maximum height of the preview images:
98
119
  previewMaxHeight: 80,
120
+ // Defines the preview orientation (1-8) or takes the orientation
121
+ // value from Exif data if set to true:
122
+ previewOrientation: true,
123
+ // Create the preview using the Exif data thumbnail:
124
+ previewThumbnail: true,
99
125
  // Define if preview images should be cropped or only scaled:
100
126
  previewCrop: false,
101
127
  // Define if preview images should be resized as canvas elements:
102
- previewAsCanvas: true
128
+ previewCanvas: true
103
129
  },
104
130
 
105
131
  processActions: {
@@ -122,15 +148,14 @@
122
148
  !loadImage(
123
149
  file,
124
150
  function (img) {
125
- if (!img.src) {
126
- return dfd.rejectWith(that, [data]);
151
+ if (img.src) {
152
+ data.img = img;
127
153
  }
128
- data.img = img;
129
154
  dfd.resolveWith(that, [data]);
130
155
  },
131
156
  options
132
157
  )) {
133
- dfd.rejectWith(that, [data]);
158
+ return data;
134
159
  }
135
160
  return dfd.promise();
136
161
  },
@@ -140,14 +165,38 @@
140
165
  // Accepts the options maxWidth, maxHeight, minWidth,
141
166
  // minHeight, canvas and crop:
142
167
  resizeImage: function (data, options) {
168
+ if (options.disabled) {
169
+ return data;
170
+ }
171
+ var that = this,
172
+ dfd = $.Deferred(),
173
+ resolve = function (newImg) {
174
+ data[newImg.getContext ? 'canvas' : 'img'] = newImg;
175
+ dfd.resolveWith(that, [data]);
176
+ },
177
+ thumbnail,
178
+ img,
179
+ newImg;
143
180
  options = $.extend({canvas: true}, options);
144
- var img = (options.canvas && data.canvas) || data.img,
145
- canvas;
146
- if (img && !options.disabled) {
147
- canvas = loadImage.scale(img, options);
148
- if (canvas && (canvas.width !== img.width ||
149
- canvas.height !== img.height)) {
150
- data[canvas.getContext ? 'canvas' : 'img'] = canvas;
181
+ if (data.exif) {
182
+ if (options.orientation === true) {
183
+ options.orientation = data.exif.get('Orientation');
184
+ }
185
+ if (options.thumbnail) {
186
+ thumbnail = data.exif.get('Thumbnail');
187
+ if (thumbnail) {
188
+ loadImage(thumbnail, resolve, options);
189
+ return dfd.promise();
190
+ }
191
+ }
192
+ }
193
+ img = (options.canvas && data.canvas) || data.img;
194
+ if (img) {
195
+ newImg = loadImage.scale(img, options);
196
+ if (newImg.width !== img.width ||
197
+ newImg.height !== img.height) {
198
+ resolve(newImg);
199
+ return dfd.promise();
151
200
  }
152
201
  }
153
202
  return data;
@@ -196,6 +245,36 @@
196
245
  return dfd.promise();
197
246
  },
198
247
 
248
+ loadImageMetaData: function (data, options) {
249
+ if (options.disabled) {
250
+ return data;
251
+ }
252
+ var that = this,
253
+ dfd = $.Deferred();
254
+ loadImage.parseMetaData(data.files[data.index], function (result) {
255
+ $.extend(data, result);
256
+ dfd.resolveWith(that, [data]);
257
+ }, options);
258
+ return dfd.promise();
259
+ },
260
+
261
+ saveImageMetaData: function (data, options) {
262
+ if (!(data.imageHead && data.canvas &&
263
+ data.canvas.toBlob && !options.disabled)) {
264
+ return data;
265
+ }
266
+ var file = data.files[data.index],
267
+ blob = new Blob([
268
+ data.imageHead,
269
+ // Resized images always have a head size of 20 bytes,
270
+ // including the JPEG marker and a minimal JFIF header:
271
+ this._blobSlice.call(file, 20)
272
+ ], {type: file.type});
273
+ blob.name = file.name;
274
+ data.files[data.index] = blob;
275
+ return data;
276
+ },
277
+
199
278
  // Sets the resized version of the image as a property of the
200
279
  // file object, must be called after "saveImage":
201
280
  setImage: function (data, options) {