s3-upnow 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 731e98ac15afe2b0944cb3b6669f03a4a7771538
4
+ data.tar.gz: 56b0c073f0b216026c4fe532d8febd26639fea5a
5
+ SHA512:
6
+ metadata.gz: b9f9d5f7769adbec1d986daa9d3713ef6addd2d217791a63b449ecd6e2898f36849d60415d5b306eaabafb2367b8169577859687c29116be26d1ab4b510fc206
7
+ data.tar.gz: ed9c53695746ca26102c18185583d201ff9c00bb93ffe6a7c9802e50c20f82d53fc42a670b870dc7552690df611cb30170cdf5b9f24ba2e994531b684da58d55
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Wayne
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,304 @@
1
+ # S3UpNow
2
+
3
+ [![Build Status](https://travis-ci.org/cerdiogenes/s3-upnow.svg)](https://travis-ci.org/cerdiogenes/s3-upnow)
4
+
5
+ Easily generate a form that allows you to upload directly to Amazon S3.
6
+ Multi file uploading supported by jquery-fileupload.
7
+
8
+ Code extracted from Ryan Bates' [gallery-jquery-fileupload](https://github.com/railscasts/383-uploading-to-amazon-s3/tree/master/gallery-jquery-fileupload).
9
+
10
+ ## Installation
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 's3-upnow'
14
+
15
+ Then add a new initalizer with your AWS credentials:
16
+
17
+ **config/initializers/s3-upnow.rb**
18
+ ```ruby
19
+ S3UpNow.config do |c|
20
+ c.access_key_id = "" # your access key id
21
+ c.secret_access_key = "" # your secret access key
22
+ c.bucket = "" # your bucket name
23
+ c.region = nil # region prefix of your bucket url. This is _required_ for the non-default AWS region, eg. "s3-eu-west-1"
24
+ c.url = nil # S3 API endpoint (optional), eg. "https://#{c.bucket}.s3.amazonaws.com/"
25
+ end
26
+ ```
27
+
28
+ Make sure your AWS S3 CORS settings for your bucket look something like this:
29
+ ```xml
30
+ <CORSConfiguration>
31
+ <CORSRule>
32
+ <AllowedOrigin>http://0.0.0.0:3000</AllowedOrigin>
33
+ <AllowedMethod>GET</AllowedMethod>
34
+ <AllowedMethod>POST</AllowedMethod>
35
+ <AllowedMethod>PUT</AllowedMethod>
36
+ <MaxAgeSeconds>3000</MaxAgeSeconds>
37
+ <AllowedHeader>*</AllowedHeader>
38
+ </CORSRule>
39
+ </CORSConfiguration>
40
+ ```
41
+ In production the AllowedOrigin key should be your domain.
42
+
43
+ Add the following js and css to your asset pipeline:
44
+
45
+ **application.js.coffee**
46
+ ```coffeescript
47
+ #= require s3-upnow
48
+ ```
49
+
50
+ **application.css**
51
+ ```css
52
+ //= require s3-upnow_progress_bars
53
+ ```
54
+
55
+ ## Usage
56
+ Create a new view that uses the form helper `s3_uploader_form`:
57
+ ```ruby
58
+ <%= s3_uploader_form callback_url: model_url, callback_param: "model[image_url]", id: "s3-uploader" do %>
59
+ <%= file_field_tag :file, multiple: true %>
60
+ <% end %>
61
+ ```
62
+
63
+ * It is required that the file_field_tag is named 'file'.
64
+ * A unique :id should be added to file_field_tag if there is many 's3_uploader_form' in the page
65
+
66
+
67
+ Then in your application.js.coffee, call the S3Uploader jQuery plugin on the element you created above:
68
+ ```coffeescript
69
+ jQuery ->
70
+ $("#s3-uploader").S3Uploader()
71
+ ```
72
+
73
+ Optionally, you can also place this template in the same view for the progress bars:
74
+ ```js+erb
75
+ <script id="template-upload" type="text/x-tmpl">
76
+ <div id="file-{%=o.unique_id%}" class="upload">
77
+ {%=o.name%}
78
+ <div class="progress"><div class="bar" style="width: 0%"></div></div>
79
+ </div>
80
+ </script>
81
+ ```
82
+
83
+ ## Options for form helper
84
+ * `callback_url:` No default. The url that is POST'd to after file is uploaded to S3. If you don't specify this option, no callback to the server will be made after the file has uploaded to S3.
85
+ * `callback_method:` Defaults to `POST`. Use PUT and remove the multiple option from your file field to update a model.
86
+ * `callback_param:` Defaults to `file`. Parameter key for the POST to `callback_url` the value will be the full s3 url of the file. If for example this is set to "model[image_url]" then the data posted would be `model[image_url] : http://bucketname.s3.amazonws.com/filename.ext`
87
+ * `key:` Defaults to `uploads/{timestamp}-{unique_id}-#{SecureRandom.hex}/${filename}`. It is the key, or filename used on s3. `{timestamp}` and `{unique_id}` are special substitution strings that will be populated by javascript with values for the current upload. `${filename}` is a special s3 string that will be populated with the original uploaded file name. Needs to be at least `"${filename}"`. It is highly recommended to use both `{unique_id}`, which will prevent collisions when uploading files with the same name (such as from a mobile device, where every photo is named image.jpg), and a server-generated random value such as `#{SecureRandom.hex}`, which adds further collision protection with other uploaders.
88
+ * `key_starts_with:` Defaults to `uploads/`. Constraint on the key on s3. if you change the `key` option, make sure this starts with what you put there. If you set this as a blank string the upload path to s3 can be anything - not recommended!
89
+ * `acl:` Defaults to `public-read`. The AWS acl for files uploaded to s3.
90
+ * `max_file_size:` Defaults to `500.megabytes`. Maximum file size allowed.
91
+ * `id:` Optional html id for the form, its recommended that you give the form an id so you can reference with the jQuery plugin.
92
+ * `class:` Optional html class for the form.
93
+ * `data:` Optional html data attribute hash.
94
+ * `bucket:` Optional (defaults to bucket used in config).
95
+
96
+ ### Example with all options
97
+ ```ruby
98
+ <%= s3_uploader_form callback_url: model_url,
99
+ callback_method: "POST",
100
+ callback_param: "model[image_url]",
101
+ key: "files/{timestamp}-{unique_id}-#{SecureRandom.hex}/${filename}",
102
+ key_starts_with: "files/",
103
+ acl: "public-read",
104
+ max_file_size: 50.megabytes,
105
+ id: "s3-uploader",
106
+ class: "upload-form",
107
+ data: {:key => :val} do %>
108
+ <%= file_field_tag :file, multiple: true %>
109
+ <% end %>
110
+ ```
111
+
112
+ ### Example to persist the S3 url in your rails app
113
+ It is recommended that you persist the url that is sent via the POST request (to the url given to the `callback_url` option and as the key given in the `callback_param` option).
114
+
115
+ One way to do this is to make sure you have `resources model` in your routes file, and add a `s3_url` (or something similar) attribute to your model. Then make sure you have the create action in your controller for that model that saves the url from the callback_param.
116
+
117
+ You could then have your create action render a javascript file like this:
118
+ **create.js.erb**
119
+ ```ruby
120
+ <% if @model.new_record? %>
121
+ alert("Failed to upload model: <%= j @model.errors.full_messages.join(', ').html_safe %>");
122
+ <% else %>
123
+ $("#container").append("<%= j render(@model) %>");
124
+ <% end %>
125
+ ```
126
+ So that javascript code would be executed after the model instance is created, without a page refresh. See [@rbates's gallery-jquery-fileupload](https://github.com/railscasts/383-uploading-to-amazon-s3/tree/master/gallery-jquery-fileupload)) for an example of that method.
127
+
128
+ Note: the POST request to the rails app also includes the following parameters `filesize`, `filetype`, `filename` and `filepath`.
129
+
130
+ ### Advanced Customizations
131
+ Feel free to override the styling for the progress bars in s3-upnow_progress_bars.css, look at the source for inspiration.
132
+
133
+ Also feel free to write your own js to interface with jquery-file-upload. You might want to do this to do custom validations on the files before it is sent to S3 for example.
134
+ To do this remove `s3-upnow` from your application.js and include the necessary jquery-file-upload scripts in your asset pipeline (they are included in this gem automatically):
135
+ ```cofeescript
136
+ #= require jquery-fileupload/basic
137
+ #= require jquery-fileupload/vendor/tmpl
138
+ ```
139
+ Use the javascript in `s3-upnow` as a guide.
140
+
141
+
142
+ ## Options for S3Upload jQuery Plugin
143
+
144
+ * `path:` manual path for the files on your s3 bucket. Example: `path/to/my/files/on/s3`
145
+ Note: Your path MUST start with the option you put in your form builder for `key_starts_with`, or else you will get S3 permission errors. The file path in your s3 bucket will be `path + key`.
146
+ * `additional_data:` You can send additional data to your rails app in the persistence POST request. This would be accessible in your params hash as `params[:key][:value]`
147
+ Example: `{key: value}`
148
+ * `remove_completed_progress_bar:` By default, the progress bar will be removed once the file has been successfully uploaded. You can set this to `false` if you want to keep the progress bar.
149
+ * `remove_failed_progress_bar:` By default, the progress bar will not be removed when uploads fail. You can set this to `true` if you want to remove the progress bar.
150
+ * `before_add:` Callback function that executes before a file is added to the queue. It is passed file object and expects `true` or `false` to be returned. This could be useful if you would like to validate the filenames of files to be uploaded for example. If true is returned file will be uploaded as normal, false will cancel the upload.
151
+ * `progress_bar_target:` The jQuery selector for the element where you want the progress bars to be appended to. Default is the form element.
152
+ * `click_submit_target:` The jQuery selector for the element you wish to add a click handler to do the submitting instead of submiting on file open.
153
+
154
+ ### Example with all options
155
+ ```coffeescript
156
+ jQuery ->
157
+ $("#myS3Uploader").S3Uploader
158
+ path: 'path/to/my/files/on/s3'
159
+ additional_data: {key: 'value'}
160
+ remove_completed_progress_bar: false
161
+ before_add: myCallBackFunction # must return true or false if set
162
+ progress_bar_target: $('.js-progress-bars')
163
+ click_submit_target: $('.submit-target')
164
+ ```
165
+ ### Example with single file upload bar without script template
166
+
167
+ This demonstrates how to use progress_bar_target and allow_multiple_files (only works with false option - single file) to show only one progress bar without script template.
168
+
169
+ ```coffeescript
170
+ jQuery ->
171
+ $("#myS3Uploader").S3Uploader
172
+ progress_bar_target: $('.js-progress-bars')
173
+ allow_multiple_files: false
174
+ ```
175
+
176
+ Target for progress bar
177
+
178
+ ```html
179
+ <div class="upload js-progress-bars">
180
+ <div class="progress">
181
+ <div class="bar"> </div>
182
+ </div>
183
+ </div>
184
+ ```
185
+
186
+
187
+
188
+
189
+ ### Public methods
190
+ You can change the settings on your form later on by accessing the jQuery instance:
191
+
192
+ ```coffeescript
193
+ jQuery ->
194
+ v = $("#myS3Uploader").S3Uploader()
195
+ ...
196
+ v.path("new/path/") #only works when the key_starts_with option is blank. Not recommended.
197
+ v.additional_data("newdata")
198
+ ```
199
+
200
+ ### Javascript Events Hooks
201
+
202
+ #### First upload started
203
+ `s3_uploads_start` is fired once when any batch of uploads is starting.
204
+ ```coffeescript
205
+ $('#myS3Uploader').bind 's3_uploads_start', (e) ->
206
+ alert("Uploads have started")
207
+ ```
208
+
209
+ #### Successfull upload
210
+ When a file has been successfully uploaded to S3, the `s3_upload_complete` is triggered on the form. A `content` object is passed along with the following attributes :
211
+
212
+ * `url` The full URL to the uploaded file on S3.
213
+ * `filename` The original name of the uploaded file.
214
+ * `filepath` The path to the file (without the filename or domain)
215
+ * `filesize` The size of the uploaded file.
216
+ * `filetype` The type of the uploaded file.
217
+
218
+ This hook could be used for example to fill a form hidden field with the returned S3 url :
219
+ ```coffeescript
220
+ $('#myS3Uploader').bind "s3_upload_complete", (e, content) ->
221
+ $('#someHiddenField').val(content.url)
222
+ ```
223
+
224
+ #### Failed upload
225
+ When an error occured during the transferm the `s3_upload_failed` is triggered on the form with the same `content` object is passed for the successful upload with the addition of the `error_thrown` attribute. The most basic way to handle this error would be to display an alert message to the user in case the upload fails :
226
+ ```coffeescript
227
+ $('#myS3Uploader').bind "s3_upload_failed", (e, content) ->
228
+ alert("#{content.filename} failed to upload : #{content.error_thrown}")
229
+ ```
230
+
231
+ #### All uploads completed
232
+ When all uploads finish in a batch an `s3_uploads_complete` event will be triggered on `document`, so you could do something like:
233
+ ```coffeescript
234
+ $(document).bind 's3_uploads_complete', ->
235
+ alert("All Uploads completed")
236
+ ```
237
+
238
+ #### Rails AJAX Callbacks
239
+
240
+ In addition, the regular rails ajax callbacks will trigger on the form with regards to the POST to the server.
241
+
242
+ ```coffeescript
243
+ $('#myS3Uploader').bind "ajax:success", (e, data) ->
244
+ alert("server was notified of new file on S3; responded with '#{data}")
245
+ ```
246
+
247
+ ## Cleaning old uploads on S3
248
+ You may be processing the files upon upload and reuploading them to another
249
+ bucket or directory. If so you can remove the originali files by running a
250
+ rake task.
251
+
252
+ First, add the fog gem to your `Gemfile` and run `bundle`:
253
+ ```ruby
254
+ gem 'fog'
255
+ ```
256
+
257
+ Then, run the rake task to delete uploads older than 2 days:
258
+ ```
259
+ $ rake s3-upnow:clean_remote_uploads
260
+ Deleted file with key: "uploads/20121210T2139Z_03846cb0329b6a8eba481ec689135701/06 - PCR_RYA014-25.jpg"
261
+ Deleted file with key: "uploads/20121210T2139Z_03846cb0329b6a8eba481ec689135701/05 - PCR_RYA014-24.jpg"
262
+ $
263
+ ```
264
+
265
+ Optionally customize the prefix used for cleaning (default is `uploads/#{2.days.ago.strftime('%Y%m%d')}`):
266
+ **config/initalizers/s3-upnow.rb**
267
+ ```ruby
268
+ S3UpNow.config do |c|
269
+ # ...
270
+ c.prefix_to_clean = "my_path/#{1.week.ago.strftime('%y%m%d')}"
271
+ end
272
+ ```
273
+
274
+ Alternately, if you'd prefer for S3 to delete your old uploads automatically, you can do
275
+ so by setting your bucket's
276
+ [Lifecycle Configuration](http://docs.aws.amazon.com/AmazonS3/latest/UG/LifecycleConfiguration.html).
277
+
278
+ ## A note on IE support
279
+ IE file uploads are working but with a couple caveats.
280
+
281
+ * The before_add callback doesn't work.
282
+ * The progress bar doesn't work on IE.
283
+
284
+ But IE should still upload your files fine.
285
+
286
+
287
+ ## Contributing / TODO
288
+ This is just a simple gem that only really provides some javascript and a form helper.
289
+ This gem could go all sorts of ways based on what people want and how people contribute.
290
+ Ideas:
291
+ * More specs!
292
+ * More options to control file types, ability to batch upload.
293
+ * More convention over configuration on rails side
294
+ * Create generators.
295
+ * Model methods.
296
+ * Model method to delete files from s3
297
+
298
+
299
+ ## Credit
300
+ This gem is basically a small wrapper around code that [Ryan Bates](http://github.com/rbates) wrote for [Railscast#383](http://railscasts.com/episodes/383-uploading-to-amazon-s3). Most of the code in this gem was extracted from [gallery-jquery-fileupload](https://github.com/railscasts/383-uploading-to-amazon-s3/tree/master/gallery-jquery-fileupload).
301
+
302
+ Thank you Ryan Bates!
303
+
304
+ This code also uses the excellecnt [jQuery-File-Upload](https://github.com/blueimp/jQuery-File-Upload), which is included in this gem by its rails counterpart [jquery-fileupload-rails](https://github.com/tors/jquery-fileupload-rails)
@@ -0,0 +1,173 @@
1
+ #= require jquery-fileupload/basic
2
+ #= require jquery-fileupload/vendor/tmpl
3
+
4
+ $ = jQuery
5
+
6
+ $.fn.S3Uploader = (options) ->
7
+
8
+ # support multiple elements
9
+ if @length > 1
10
+ @each ->
11
+ $(this).S3Uploader options
12
+
13
+ return this
14
+
15
+ $uploadForm = this
16
+
17
+ settings =
18
+ path: ''
19
+ additional_data: null
20
+ before_add: null
21
+ remove_completed_progress_bar: true
22
+ remove_failed_progress_bar: false
23
+ progress_bar_target: null
24
+ click_submit_target: null
25
+ allow_multiple_files: true
26
+
27
+ $.extend settings, options
28
+
29
+ current_files = []
30
+ forms_for_submit = []
31
+ if settings.click_submit_target
32
+ settings.click_submit_target.click ->
33
+ form.submit() for form in forms_for_submit
34
+ false
35
+
36
+ setUploadForm = ->
37
+ $uploadForm.fileupload
38
+
39
+ add: (e, data) ->
40
+ file = data.files[0]
41
+ file.unique_id = Math.random().toString(36).substr(2,16)
42
+
43
+ unless settings.before_add and not settings.before_add(file)
44
+ current_files.push data
45
+ if $('#template-upload').length > 0
46
+ data.context = $($.trim(tmpl("template-upload", file)))
47
+ $(data.context).appendTo(settings.progress_bar_target || $uploadForm)
48
+ else if !settings.allow_multiple_files
49
+ data.context = settings.progress_bar_target
50
+ if settings.click_submit_target
51
+ if settings.allow_multiple_files
52
+ forms_for_submit.push data
53
+ else
54
+ forms_for_submit = [data]
55
+ else
56
+ data.submit()
57
+
58
+ start: (e) ->
59
+ $uploadForm.trigger("s3_uploads_start", [e])
60
+
61
+ progress: (e, data) ->
62
+ if data.context
63
+ progress = parseInt(data.loaded / data.total * 100, 10)
64
+ data.context.find('.bar').css('width', progress + '%')
65
+
66
+ done: (e, data) ->
67
+ content = build_content_object $uploadForm, data.files[0], data.result
68
+
69
+ callback_url = $uploadForm.data('callback-url')
70
+ if callback_url
71
+ content[$uploadForm.data('callback-param')] = content.url
72
+
73
+ $.ajax
74
+ type: $uploadForm.data('callback-method')
75
+ url: callback_url
76
+ data: content
77
+ beforeSend: ( xhr, settings ) ->
78
+ event = $.Event('ajax:beforeSend')
79
+ $uploadForm.trigger(event, [xhr, settings])
80
+ return event.result
81
+ complete: ( xhr, status ) ->
82
+ event = $.Event('ajax:complete')
83
+ $uploadForm.trigger(event, [xhr, status])
84
+ return event.result
85
+ success: ( data, status, xhr ) ->
86
+ event = $.Event('ajax:success')
87
+ $uploadForm.trigger(event, [data, status, xhr])
88
+ return event.result
89
+ error: ( xhr, status, error ) ->
90
+ event = $.Event('ajax:error')
91
+ $uploadForm.trigger(event, [xhr, status, error])
92
+ return event.result
93
+
94
+ data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar
95
+ $uploadForm.trigger("s3_upload_complete", [content])
96
+
97
+ current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
98
+ $uploadForm.trigger("s3_uploads_complete", [content]) unless current_files.length
99
+
100
+ fail: (e, data) ->
101
+ content = build_content_object $uploadForm, data.files[0], data.result
102
+ content.error_thrown = data.errorThrown
103
+
104
+ data.context.remove() if data.context && settings.remove_failed_progress_bar # remove progress bar
105
+ $uploadForm.trigger("s3_upload_failed", [content])
106
+
107
+ formData: (form) ->
108
+ data = form.serializeArray()
109
+ fileType = ""
110
+ if "type" of @files[0]
111
+ fileType = @files[0].type
112
+ data.push
113
+ name: "content-type"
114
+ value: fileType
115
+
116
+ key = $uploadForm.data("key")
117
+ .replace('{timestamp}', new Date().getTime())
118
+ .replace('{unique_id}', @files[0].unique_id)
119
+ .replace('{extension}', @files[0].name.split('.').pop())
120
+
121
+ # substitute upload timestamp and unique_id into key
122
+ key_field = $.grep data, (n) ->
123
+ n if n.name == "key"
124
+
125
+ if key_field.length > 0
126
+ key_field[0].value = settings.path + key
127
+
128
+ # IE <= 9 doesn't have XHR2 hence it can't use formData
129
+ # replace 'key' field to submit form
130
+ unless 'FormData' of window
131
+ $uploadForm.find("input[name='key']").val(settings.path + key)
132
+ data
133
+
134
+ build_content_object = ($uploadForm, file, result) ->
135
+ content = {}
136
+ if result # Use the S3 response to set the URL to avoid character encodings bugs
137
+ content.url = $(result).find("Location").text()
138
+ content.filepath = $('<a />').attr('href', content.url)[0].pathname
139
+ else # IE <= 9 retu rn a null result object so we use the file object instead
140
+ domain = $uploadForm.attr('action')
141
+ content.filepath = $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
142
+ content.url = domain + content.filepath + '/' + encodeURIComponent(file.name)
143
+
144
+ content.filename = file.name
145
+ content.filesize = file.size if 'size' of file
146
+ content.lastModifiedDate = file.lastModifiedDate if 'lastModifiedDate' of file
147
+ content.filetype = file.type if 'type' of file
148
+ content.unique_id = file.unique_id if 'unique_id' of file
149
+ content.relativePath = build_relativePath(file) if has_relativePath(file)
150
+ content = $.extend content, settings.additional_data if settings.additional_data
151
+ content
152
+
153
+ has_relativePath = (file) ->
154
+ file.relativePath || file.webkitRelativePath
155
+
156
+ build_relativePath = (file) ->
157
+ file.relativePath || (file.webkitRelativePath.split("/")[0..-2].join("/") + "/" if file.webkitRelativePath)
158
+
159
+ #public methods
160
+ @initialize = ->
161
+ # Save key for IE9 Fix
162
+ $uploadForm.data("key", $uploadForm.find("input[name='key']").val())
163
+
164
+ setUploadForm()
165
+ this
166
+
167
+ @path = (new_path) ->
168
+ settings.path = new_path
169
+
170
+ @additional_data = (new_data) ->
171
+ settings.additional_data = new_data
172
+
173
+ @initialize()
@@ -0,0 +1,17 @@
1
+ .upload {
2
+ border-top: solid 1px #CCC;
3
+ width: 400px;
4
+ padding-top: 10px;
5
+ margin-top: 10px;
6
+
7
+ .progress {
8
+ margin-top: 8px;
9
+ border: solid 1px #555;
10
+ border-radius: 3px;
11
+ -moz-border-radius: 3px;
12
+ .bar {
13
+ height: 10px;
14
+ background: #3EC144;
15
+ }
16
+ }
17
+ }
data/lib/s3-upnow.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 's3-upnow/version'
2
+ require 'jquery-fileupload-rails' if defined?(Rails)
3
+
4
+ require 'base64'
5
+ require 'openssl'
6
+ require 'digest/sha1'
7
+
8
+ require 's3-upnow/config_aws'
9
+ require 's3-upnow/form_helper'
10
+ require 's3-upnow/engine' if defined?(Rails)
11
+ require 's3-upnow/railtie' if defined?(Rails)
12
+
13
+ ActionView::Base.send(:include, S3UpNow::UploadHelper) if defined?(ActionView::Base)
@@ -0,0 +1,18 @@
1
+ require "singleton"
2
+
3
+ module S3UpNow
4
+ class Config
5
+ include Singleton
6
+
7
+ ATTRIBUTES = [:access_key_id, :secret_access_key, :bucket, :prefix_to_clean, :region, :url]
8
+
9
+ attr_accessor *ATTRIBUTES
10
+ end
11
+
12
+ def self.config
13
+ if block_given?
14
+ yield Config.instance
15
+ end
16
+ Config.instance
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module S3UpNow
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,97 @@
1
+ module S3UpNow
2
+ module UploadHelper
3
+ def s3_uploader_form(options = {}, &block)
4
+ uploader = S3Uploader.new(options)
5
+ form_tag(uploader.url, uploader.form_options) do
6
+ uploader.fields.map do |name, value|
7
+ hidden_field_tag(name, value)
8
+ end.join.html_safe + capture(&block)
9
+ end
10
+ end
11
+
12
+ class S3Uploader
13
+ def initialize(options)
14
+ @key_starts_with = options[:key_starts_with] || "uploads/"
15
+ @options = options.reverse_merge(
16
+ aws_access_key_id: S3UpNow.config.access_key_id,
17
+ aws_secret_access_key: S3UpNow.config.secret_access_key,
18
+ bucket: options[:bucket] || S3UpNow.config.bucket,
19
+ region: S3UpNow.config.region || "s3",
20
+ url: S3UpNow.config.url,
21
+ ssl: true,
22
+ acl: "public-read",
23
+ expiration: 10.hours.from_now.utc.iso8601,
24
+ max_file_size: 500.megabytes,
25
+ callback_method: "POST",
26
+ callback_param: "file",
27
+ key_starts_with: @key_starts_with,
28
+ key: key
29
+ )
30
+ end
31
+
32
+ def form_options
33
+ {
34
+ id: @options[:id],
35
+ class: @options[:class],
36
+ method: "post",
37
+ authenticity_token: false,
38
+ multipart: true,
39
+ data: {
40
+ callback_url: @options[:callback_url],
41
+ callback_method: @options[:callback_method],
42
+ callback_param: @options[:callback_param]
43
+ }.reverse_merge(@options[:data] || {})
44
+ }
45
+ end
46
+
47
+ def fields
48
+ {
49
+ :key => @options[:key] || key,
50
+ :acl => @options[:acl],
51
+ "AWSAccessKeyId" => @options[:aws_access_key_id],
52
+ :policy => policy,
53
+ :signature => signature,
54
+ :success_action_status => "201",
55
+ 'X-Requested-With' => 'xhr'
56
+ }
57
+ end
58
+
59
+ def key
60
+ @key ||= "#{@key_starts_with}{timestamp}-{unique_id}-#{SecureRandom.hex}/${filename}"
61
+ end
62
+
63
+ def url
64
+ @options[:url] || "http#{@options[:ssl] ? 's' : ''}://#{@options[:region]}.amazonaws.com/#{@options[:bucket]}/"
65
+ end
66
+
67
+ def policy
68
+ Base64.encode64(policy_data.to_json).gsub("\n", "")
69
+ end
70
+
71
+ def policy_data
72
+ {
73
+ expiration: @options[:expiration],
74
+ conditions: [
75
+ ["starts-with", "$utf8", ""],
76
+ ["starts-with", "$key", @options[:key_starts_with]],
77
+ ["starts-with", "$x-requested-with", ""],
78
+ ["content-length-range", 0, @options[:max_file_size]],
79
+ ["starts-with","$content-type", @options[:content_type_starts_with] ||""],
80
+ {bucket: @options[:bucket]},
81
+ {acl: @options[:acl]},
82
+ {success_action_status: "201"}
83
+ ] + (@options[:conditions] || [])
84
+ }
85
+ end
86
+
87
+ def signature
88
+ Base64.encode64(
89
+ OpenSSL::HMAC.digest(
90
+ OpenSSL::Digest.new('sha1'),
91
+ @options[:aws_secret_access_key], policy
92
+ )
93
+ ).gsub("\n", "")
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,7 @@
1
+ module S3UpNow
2
+ class Railtie < Rails::Railtie
3
+ initializer "railtie.configure_rails_initialization" do |app|
4
+ app.middleware.use JQuery::FileUpload::Rails::Middleware
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module S3UpNow
2
+ VERSION = "0.1.7"
3
+ end
@@ -0,0 +1,57 @@
1
+ namespace :'s3-upnow' do
2
+ desc "Removes old uploads from specified s3 bucket/directory -- Useful when uploads are processed into another directory"
3
+ task :clean_remote_uploads => :environment do
4
+ require 'thread'
5
+ require 'fog'
6
+
7
+ s3 = Fog::Storage.new(provider: "AWS", aws_access_key_id: S3UpNow.config.access_key_id, aws_secret_access_key: S3UpNow.config.secret_access_key)
8
+ bucket = S3UpNow.config.bucket
9
+ prefix = S3UpNow.config.prefix_to_clean || "uploads/#{2.days.ago.strftime('%Y%m%d')}"
10
+
11
+ queue = Queue.new
12
+ semaphore = Mutex.new
13
+ threads = []
14
+ thread_count = 20
15
+ total_listed = 0
16
+ total_deleted = 0
17
+
18
+ threads << Thread.new do
19
+ Thread.current[:name] = "get files"
20
+ # Get all the files from this bucket. Fog handles pagination internally.
21
+ s3.directories.get("#{bucket}").files.all({prefix: prefix}).each do |file|
22
+ queue.enq(file)
23
+ total_listed += 1
24
+ end
25
+ # Add a final EOF message to signal the deletion threads to stop.
26
+ thread_count.times { queue.enq(:EOF) }
27
+ end
28
+
29
+ # Delete all the files in the queue until EOF with N threads.
30
+ thread_count.times do |count|
31
+ threads << Thread.new(count) do |number|
32
+ Thread.current[:name] = "delete files(#{number})"
33
+ # Dequeue until EOF.
34
+ file = nil
35
+ while file != :EOF
36
+ # Dequeue the latest file and delete it. (Will block until it gets a new file.)
37
+ file = queue.deq
38
+ unless file == :EOF
39
+ file.destroy
40
+ puts %Q{Deleted file with key: "#{file.key}"}
41
+ end
42
+ # Increment the global synchronized counter.
43
+ semaphore.synchronize {total_deleted += 1}
44
+ end
45
+ end
46
+ end
47
+
48
+ # Wait for the threads to finish.
49
+ threads.each do |t|
50
+ begin
51
+ t.join
52
+ rescue RuntimeError => e
53
+ puts "Failure on thread #{t[:name]}: #{e.message}"
54
+ end
55
+ end
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3-upnow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.7
5
+ platform: ruby
6
+ authors:
7
+ - Carlos Eduardo Rodrigues Diógenes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: coffee-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sass-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jquery-fileupload-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.4.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.4.1
69
+ description: Direct Upload to Amazon S3 With CORS and jquery-file-upload
70
+ email:
71
+ - carlos.diogenes@fnix.com.br
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - app/assets/javascripts/s3_direct_upload.js.coffee
79
+ - app/assets/stylesheets/s3_direct_upload_progress_bars.css.scss
80
+ - lib/s3-upnow.rb
81
+ - lib/s3-upnow/config_aws.rb
82
+ - lib/s3-upnow/engine.rb
83
+ - lib/s3-upnow/form_helper.rb
84
+ - lib/s3-upnow/railtie.rb
85
+ - lib/s3-upnow/version.rb
86
+ - lib/tasks/s3_upnow.rake
87
+ homepage: ''
88
+ licenses: []
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.2.2
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Gives a form helper for Rails which allows direct uploads to s3. Based on
110
+ RailsCast#383
111
+ test_files: []