actionverb_s3_direct_upload 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +201 -0
- data/app/assets/javascripts/s3_direct_upload.js +117 -0
- data/app/assets/javascripts/s3_direct_upload.js.coffee +104 -0
- data/app/assets/javascripts/s3_direct_upload.js.js +117 -0
- data/app/assets/stylesheets/s3_direct_upload_progress_bars.css.scss +17 -0
- data/lib/s3_direct_upload.rb +12 -0
- data/lib/s3_direct_upload/config_aws.rb +18 -0
- data/lib/s3_direct_upload/engine.rb +4 -0
- data/lib/s3_direct_upload/form_helper.rb +88 -0
- data/lib/s3_direct_upload/version.rb +3 -0
- metadata +122 -0
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,201 @@
|
|
1
|
+
# S3DirectUpload
|
2
|
+
|
3
|
+
Easily generate a form that allows you to upload directly to Amazon S3.
|
4
|
+
Multi file uploading supported by jquery-fileupload.
|
5
|
+
|
6
|
+
Code extracted from Ryan Bates' [gallery-jquery-fileupload](https://github.com/railscasts/383-uploading-to-amazon-s3/tree/master/gallery-jquery-fileupload).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 's3_direct_upload'
|
12
|
+
|
13
|
+
Then add a new initalizer with your AWS credentials:
|
14
|
+
|
15
|
+
**config/initalizers/s3_direct_upload.rb**
|
16
|
+
```ruby
|
17
|
+
S3DirectUpload.config do |c|
|
18
|
+
c.access_key_id = "" # your access key id
|
19
|
+
c.secret_access_key = "" # your secret access key
|
20
|
+
c.bucket = "" # your bucket name
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
Make sure your AWS S3 CORS settings for your bucket look something like this:
|
25
|
+
```xml
|
26
|
+
<CORSConfiguration>
|
27
|
+
<CORSRule>
|
28
|
+
<AllowedOrigin>http://0.0.0.0:3000</AllowedOrigin>
|
29
|
+
<AllowedMethod>GET</AllowedMethod>
|
30
|
+
<AllowedMethod>POST</AllowedMethod>
|
31
|
+
<AllowedMethod>PUT</AllowedMethod>
|
32
|
+
<MaxAgeSeconds>3000</MaxAgeSeconds>
|
33
|
+
<AllowedHeader>*</AllowedHeader>
|
34
|
+
</CORSRule>
|
35
|
+
</CORSConfiguration>
|
36
|
+
```
|
37
|
+
In production the AllowedOrigin key should be your domain.
|
38
|
+
|
39
|
+
Add the following js and css to your asset pipeline:
|
40
|
+
|
41
|
+
**application.js.coffee**
|
42
|
+
```coffeescript
|
43
|
+
#= require s3_direct_upload
|
44
|
+
```
|
45
|
+
|
46
|
+
**application.css**
|
47
|
+
```css
|
48
|
+
//= require s3_direct_upload_progress_bars
|
49
|
+
```
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
Create a new view that uses the form helper `s3_uploader_form`:
|
53
|
+
```ruby
|
54
|
+
<%= s3_uploader_form post: model_url, as: "model[image_url]", id: "myS3Uploader" do %>
|
55
|
+
<%= file_field_tag :file, multiple: true %>
|
56
|
+
<% end %>
|
57
|
+
```
|
58
|
+
|
59
|
+
Then in your application.js.coffee, call the S3Uploader jQuery plugin on the element you created above:
|
60
|
+
```coffeescript
|
61
|
+
jQuery ->
|
62
|
+
$("#myS3Uploader").S3Uploader()
|
63
|
+
```
|
64
|
+
|
65
|
+
Optionally, you can also place this template in the same view for the progress bars:
|
66
|
+
```js+erb
|
67
|
+
<script id="template-upload" type="text/x-tmpl">
|
68
|
+
<div class="upload">
|
69
|
+
{%=o.name%}
|
70
|
+
<div class="progress"><div class="bar" style="width: 0%"></div></div>
|
71
|
+
</div>
|
72
|
+
</script>
|
73
|
+
```
|
74
|
+
|
75
|
+
## Options for form helper
|
76
|
+
* `post:` url in which 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.
|
77
|
+
* `as:` parameter value for the POST in which the key will be the URL of the file on S3. 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`
|
78
|
+
* `key:` key on s3. defaults to `"uploads/#{SecureRandom.hex}/${filename}"`. needs to be at least `"${filename}"`.
|
79
|
+
* `acl:` acl for files uploaded to s3, defaults to "public-read"
|
80
|
+
* `max_file_size:` maximum file size, defaults to 500.megabytes
|
81
|
+
* `id:` html id for the form, its recommended that you give the form an id so you can reference with the jQuery plugin.
|
82
|
+
* `class:` optional html class for the form.
|
83
|
+
* `data:` Optional html data
|
84
|
+
|
85
|
+
### Persisting the S3 url
|
86
|
+
It is recommended that you persist the image_url that is sent back from the POST request (to the url given to the `post` option and as the key given in the `as` option). So to access your files later.
|
87
|
+
|
88
|
+
One way to do this is to make sure you have `resources model` in your routes file, and add the `image_url` (or whatever you would like to name it) attribute to your model, and then make sure you have the create action in your controller for that model.
|
89
|
+
|
90
|
+
You could then have your create action render a javascript file like this:
|
91
|
+
**create.js.erb**
|
92
|
+
```ruby
|
93
|
+
<% if @model.new_record? %>
|
94
|
+
alert("Failed to upload model: <%= j @model.errors.full_messages.join(', ').html_safe %>");
|
95
|
+
<% else %>
|
96
|
+
$("#container").append("<%= j render(@model) %>");
|
97
|
+
<% end %>
|
98
|
+
```
|
99
|
+
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.
|
100
|
+
|
101
|
+
Note: the POST request to the rails app also includes the following parameters `filesize`, `filetype`, `filename` and `filepath`.
|
102
|
+
|
103
|
+
### Advanced Customizations
|
104
|
+
Feel free to override the styling for the progress bars in s3_direct_upload_progress_bars.css, look at the source for inspiration.
|
105
|
+
|
106
|
+
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.
|
107
|
+
To do this remove `s3_direct_upload` from your application.js and include the necessary jquery-file-upload scripts in your asset pipeline (they are included in this gem automatically):
|
108
|
+
```cofeescript
|
109
|
+
#= require jquery-fileupload/basic
|
110
|
+
#= require jquery-fileupload/vendor/tmpl
|
111
|
+
```
|
112
|
+
Use the javascript in `s3_direct_upload` as a guide.
|
113
|
+
|
114
|
+
|
115
|
+
## Options for S3Upload jQuery Plugin
|
116
|
+
|
117
|
+
* `path:` manual path for the files on your s3 bucket. Example: `path/to/my/files/on/s3`
|
118
|
+
Note: the file path in your s3 bucket will effectively be `path + key`.
|
119
|
+
* `additional_data:` You can send additional data to your rails app in the persistence POST request. This would be accessable in your params hash as `params[:key][:value]`
|
120
|
+
Example: `{key: value}`
|
121
|
+
* `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.
|
122
|
+
* `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.
|
123
|
+
* `progress_container`: This jQuery container will have the compiled '#template-upload' appended to it during upload.
|
124
|
+
|
125
|
+
### Example with all options.
|
126
|
+
```coffeescript
|
127
|
+
jQuery ->
|
128
|
+
$("#myS3Uploader").S3Uploader
|
129
|
+
path: 'path/to/my/files/on/s3'
|
130
|
+
additional_data: {key: 'value'}
|
131
|
+
remove_completed_progress_bar: false
|
132
|
+
before_add: myCallBackFunction() # must return true or false if set
|
133
|
+
```
|
134
|
+
|
135
|
+
### Public methods
|
136
|
+
You can change the settings on your form later on by accessing the jQuery instance:
|
137
|
+
|
138
|
+
```coffeescript
|
139
|
+
jQuery ->
|
140
|
+
v = $("#myS3Uploader").S3Uploader()
|
141
|
+
...
|
142
|
+
v.path("new/path/")
|
143
|
+
v.additional_data("newdata")
|
144
|
+
```
|
145
|
+
|
146
|
+
### Javascript Events Hooks
|
147
|
+
|
148
|
+
#### Successfull upload
|
149
|
+
When a file has been successfully to S3, the `s3_upload_complete` is triggered on the form. A `content` object is passed along with the following attributes :
|
150
|
+
|
151
|
+
* `url` The full URL to the uploaded file on S3.
|
152
|
+
* `filename` The original name of the uploaded file.
|
153
|
+
* `filepath` The path to the file (without the filename or domain)
|
154
|
+
* `filesize` The size of the uploaded file.
|
155
|
+
* `filetype` The type of the uploaded file.
|
156
|
+
|
157
|
+
This hook could be used for example to fill a form hidden field with the returned S3 url :
|
158
|
+
```coffeescript
|
159
|
+
$('#myS3Uploader').bind "s3_upload_complete", (e, content) ->
|
160
|
+
$('#someHiddenField').val(content.url)
|
161
|
+
```
|
162
|
+
|
163
|
+
#### Failed upload
|
164
|
+
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 :
|
165
|
+
```coffeescript
|
166
|
+
$('#myS3Uploader').bind "s3_upload_failed", (e, content) ->
|
167
|
+
alert("#{content.filename} failed to upload : #{content.error_thrown}")
|
168
|
+
```
|
169
|
+
|
170
|
+
#### All uploads completed
|
171
|
+
When all uploads finish in a batch an `s3_uploads_complete` event will be triggered on `document`, so you could do something like:
|
172
|
+
```coffeescript
|
173
|
+
$(document).bind 's3_uploads_complete', ->
|
174
|
+
alert("All Uploads completed")
|
175
|
+
```
|
176
|
+
|
177
|
+
#### First upload started
|
178
|
+
Fired `s3_uploads_start` once when any batch of uploads is starting.
|
179
|
+
```coffeescript
|
180
|
+
$('#myS3Uploader').bind 's3_uploads_start', (e) ->
|
181
|
+
$('uploadArea').show()
|
182
|
+
```
|
183
|
+
|
184
|
+
## Contributing / TODO
|
185
|
+
This is just a simple gem that only really provides some javascript and a form helper.
|
186
|
+
This gem could go all sorts of ways based on what people want and how people contribute.
|
187
|
+
Ideas:
|
188
|
+
* More specs!
|
189
|
+
* More options to control file types, ability to batch upload.
|
190
|
+
* More convention over configuration on rails side
|
191
|
+
* Create generators.
|
192
|
+
* Model methods.
|
193
|
+
* Model method to delete files from s3
|
194
|
+
|
195
|
+
|
196
|
+
## Credit
|
197
|
+
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).
|
198
|
+
|
199
|
+
Thank you Ryan Bates!
|
200
|
+
|
201
|
+
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,117 @@
|
|
1
|
+
// Generated by CoffeeScript 1.3.3
|
2
|
+
var $;
|
3
|
+
|
4
|
+
$ = jQuery;
|
5
|
+
|
6
|
+
$.fn.S3Uploader = function(options) {
|
7
|
+
var $uploadForm, build_content_object, current_files, setUploadForm, settings;
|
8
|
+
if (this.length > 1) {
|
9
|
+
this.each(function() {
|
10
|
+
return $(this).S3Uploader(options);
|
11
|
+
});
|
12
|
+
return this;
|
13
|
+
}
|
14
|
+
$uploadForm = this;
|
15
|
+
settings = {
|
16
|
+
path: '',
|
17
|
+
additional_data: null,
|
18
|
+
before_add: null,
|
19
|
+
remove_completed_progress_bar: true,
|
20
|
+
progress_container: $uploadForm
|
21
|
+
};
|
22
|
+
$.extend(settings, options);
|
23
|
+
current_files = [];
|
24
|
+
setUploadForm = function() {
|
25
|
+
return $uploadForm.fileupload({
|
26
|
+
start: function(e) {
|
27
|
+
return $uploadForm.trigger("s3_upload_start", [e]);
|
28
|
+
},
|
29
|
+
add: function(e, data) {
|
30
|
+
var file;
|
31
|
+
current_files.push(data);
|
32
|
+
file = data.files[0];
|
33
|
+
if (!(settings.before_add && !settings.before_add(file))) {
|
34
|
+
if ($('#template-upload').length > 0) {
|
35
|
+
data.context = $(tmpl("template-upload", file));
|
36
|
+
}
|
37
|
+
settings['progress_container'].append(data.context);
|
38
|
+
return data.submit();
|
39
|
+
}
|
40
|
+
},
|
41
|
+
progress: function(e, data) {
|
42
|
+
var progress;
|
43
|
+
if (data.context) {
|
44
|
+
progress = parseInt(data.loaded / data.total * 100, 10);
|
45
|
+
return data.context.find('.bar').css('width', progress + '%');
|
46
|
+
}
|
47
|
+
},
|
48
|
+
done: function(e, data) {
|
49
|
+
var content, to;
|
50
|
+
content = build_content_object($uploadForm, data.files[0]);
|
51
|
+
to = $uploadForm.data('post');
|
52
|
+
if (to) {
|
53
|
+
content[$uploadForm.data('as')] = content.url;
|
54
|
+
$.post(to, content);
|
55
|
+
}
|
56
|
+
if (data.context && settings.remove_completed_progress_bar) {
|
57
|
+
data.context.remove();
|
58
|
+
}
|
59
|
+
$uploadForm.trigger("s3_upload_complete", [content]);
|
60
|
+
current_files.splice($.inArray(data, current_files), 1);
|
61
|
+
if (current_files.length === 0) {
|
62
|
+
return $(document).trigger("s3_uploads_complete");
|
63
|
+
}
|
64
|
+
},
|
65
|
+
fail: function(e, data) {
|
66
|
+
var content;
|
67
|
+
content = build_content_object($uploadForm, data.files[0]);
|
68
|
+
content.error_thrown = data.errorThrown;
|
69
|
+
return $uploadForm.trigger("s3_upload_failed", [content]);
|
70
|
+
},
|
71
|
+
formData: function(form) {
|
72
|
+
var data, fileType;
|
73
|
+
data = form.serializeArray();
|
74
|
+
fileType = "";
|
75
|
+
if ("type" in this.files[0]) {
|
76
|
+
fileType = this.files[0].type;
|
77
|
+
}
|
78
|
+
data.push({
|
79
|
+
name: "Content-Type",
|
80
|
+
value: fileType
|
81
|
+
});
|
82
|
+
data[1].value = settings.path + data[1].value;
|
83
|
+
return data;
|
84
|
+
}
|
85
|
+
});
|
86
|
+
};
|
87
|
+
build_content_object = function($uploadForm, file) {
|
88
|
+
var content, domain, path;
|
89
|
+
domain = $uploadForm.attr('action');
|
90
|
+
path = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '');
|
91
|
+
content = {};
|
92
|
+
content.url = domain + path + '/' + file.name;
|
93
|
+
content.filename = file.name;
|
94
|
+
content.filepath = path;
|
95
|
+
if ('size' in file) {
|
96
|
+
content.filesize = file.size;
|
97
|
+
}
|
98
|
+
if ('type' in file) {
|
99
|
+
content.filetype = file.type;
|
100
|
+
}
|
101
|
+
if (settings.additional_data) {
|
102
|
+
content = $.extend(content, settings.additional_data);
|
103
|
+
}
|
104
|
+
return content;
|
105
|
+
};
|
106
|
+
this.initialize = function() {
|
107
|
+
setUploadForm();
|
108
|
+
return this;
|
109
|
+
};
|
110
|
+
this.path = function(new_path) {
|
111
|
+
return settings.path = new_path;
|
112
|
+
};
|
113
|
+
this.additional_data = function(new_data) {
|
114
|
+
return settings.additional_data = new_data;
|
115
|
+
};
|
116
|
+
return this.initialize();
|
117
|
+
};
|
@@ -0,0 +1,104 @@
|
|
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
|
+
progress_container: $uploadForm
|
23
|
+
|
24
|
+
|
25
|
+
$.extend settings, options
|
26
|
+
|
27
|
+
current_files = []
|
28
|
+
|
29
|
+
setUploadForm = ->
|
30
|
+
$uploadForm.fileupload
|
31
|
+
|
32
|
+
start: (e) ->
|
33
|
+
$uploadForm.trigger("s3_uploads_start", [e])
|
34
|
+
|
35
|
+
add: (e, data) ->
|
36
|
+
current_files.push data
|
37
|
+
file = data.files[0]
|
38
|
+
unless settings.before_add and not settings.before_add(file)
|
39
|
+
data.context = $(tmpl("template-upload", file)) if $('#template-upload').length > 0
|
40
|
+
settings['progress_container'].append(data.context)
|
41
|
+
data.submit()
|
42
|
+
|
43
|
+
progress: (e, data) ->
|
44
|
+
if data.context
|
45
|
+
progress = parseInt(data.loaded / data.total * 100, 10)
|
46
|
+
data.context.find('.bar').css('width', progress + '%')
|
47
|
+
|
48
|
+
done: (e, data) ->
|
49
|
+
content = build_content_object $uploadForm, data.files[0]
|
50
|
+
|
51
|
+
to = $uploadForm.data('post')
|
52
|
+
if to
|
53
|
+
content[$uploadForm.data('as')] = content.url
|
54
|
+
$.post(to, content)
|
55
|
+
|
56
|
+
data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar
|
57
|
+
$uploadForm.trigger("s3_upload_complete", [content])
|
58
|
+
|
59
|
+
current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
|
60
|
+
if current_files.length == 0
|
61
|
+
$(document).trigger("s3_uploads_complete")
|
62
|
+
|
63
|
+
fail: (e, data) ->
|
64
|
+
content = build_content_object $uploadForm, data.files[0]
|
65
|
+
content.error_thrown = data.errorThrown
|
66
|
+
$uploadForm.trigger("s3_upload_failed", [content])
|
67
|
+
|
68
|
+
formData: (form) ->
|
69
|
+
data = form.serializeArray()
|
70
|
+
fileType = ""
|
71
|
+
if "type" of @files[0]
|
72
|
+
fileType = @files[0].type
|
73
|
+
data.push
|
74
|
+
name: "Content-Type"
|
75
|
+
value: fileType
|
76
|
+
|
77
|
+
data[1].value = settings.path + data[1].value
|
78
|
+
|
79
|
+
data
|
80
|
+
|
81
|
+
build_content_object = ($uploadForm, file) ->
|
82
|
+
domain = $uploadForm.attr('action')
|
83
|
+
path = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
|
84
|
+
content = {}
|
85
|
+
content.url = domain + path + '/' + file.name
|
86
|
+
content.filename = file.name
|
87
|
+
content.filepath = path
|
88
|
+
content.filesize = file.size if 'size' of file
|
89
|
+
content.filetype = file.type if 'type' of file
|
90
|
+
content = $.extend content, settings.additional_data if settings.additional_data
|
91
|
+
content
|
92
|
+
|
93
|
+
#public methods
|
94
|
+
@initialize = ->
|
95
|
+
setUploadForm()
|
96
|
+
this
|
97
|
+
|
98
|
+
@path = (new_path) ->
|
99
|
+
settings.path = new_path
|
100
|
+
|
101
|
+
@additional_data = (new_data) ->
|
102
|
+
settings.additional_data = new_data
|
103
|
+
|
104
|
+
@initialize()
|
@@ -0,0 +1,117 @@
|
|
1
|
+
// Generated by CoffeeScript 1.3.3
|
2
|
+
var $;
|
3
|
+
|
4
|
+
$ = jQuery;
|
5
|
+
|
6
|
+
$.fn.S3Uploader = function(options) {
|
7
|
+
var $uploadForm, build_content_object, current_files, setUploadForm, settings;
|
8
|
+
if (this.length > 1) {
|
9
|
+
this.each(function() {
|
10
|
+
return $(this).S3Uploader(options);
|
11
|
+
});
|
12
|
+
return this;
|
13
|
+
}
|
14
|
+
$uploadForm = this;
|
15
|
+
settings = {
|
16
|
+
path: '',
|
17
|
+
additional_data: null,
|
18
|
+
before_add: null,
|
19
|
+
remove_completed_progress_bar: true,
|
20
|
+
progress_container: $uploadForm
|
21
|
+
};
|
22
|
+
$.extend(settings, options);
|
23
|
+
current_files = [];
|
24
|
+
setUploadForm = function() {
|
25
|
+
return $uploadForm.fileupload({
|
26
|
+
start: function(e) {
|
27
|
+
return $uploadForm.trigger("s3_uploads_start", [e]);
|
28
|
+
},
|
29
|
+
add: function(e, data) {
|
30
|
+
var file;
|
31
|
+
current_files.push(data);
|
32
|
+
file = data.files[0];
|
33
|
+
if (!(settings.before_add && !settings.before_add(file))) {
|
34
|
+
if ($('#template-upload').length > 0) {
|
35
|
+
data.context = $(tmpl("template-upload", file));
|
36
|
+
}
|
37
|
+
settings['progress_container'].append(data.context);
|
38
|
+
return data.submit();
|
39
|
+
}
|
40
|
+
},
|
41
|
+
progress: function(e, data) {
|
42
|
+
var progress;
|
43
|
+
if (data.context) {
|
44
|
+
progress = parseInt(data.loaded / data.total * 100, 10);
|
45
|
+
return data.context.find('.bar').css('width', progress + '%');
|
46
|
+
}
|
47
|
+
},
|
48
|
+
done: function(e, data) {
|
49
|
+
var content, to;
|
50
|
+
content = build_content_object($uploadForm, data.files[0]);
|
51
|
+
to = $uploadForm.data('post');
|
52
|
+
if (to) {
|
53
|
+
content[$uploadForm.data('as')] = content.url;
|
54
|
+
$.post(to, content);
|
55
|
+
}
|
56
|
+
if (data.context && settings.remove_completed_progress_bar) {
|
57
|
+
data.context.remove();
|
58
|
+
}
|
59
|
+
$uploadForm.trigger("s3_upload_complete", [content]);
|
60
|
+
current_files.splice($.inArray(data, current_files), 1);
|
61
|
+
if (current_files.length === 0) {
|
62
|
+
return $(document).trigger("s3_uploads_complete");
|
63
|
+
}
|
64
|
+
},
|
65
|
+
fail: function(e, data) {
|
66
|
+
var content;
|
67
|
+
content = build_content_object($uploadForm, data.files[0]);
|
68
|
+
content.error_thrown = data.errorThrown;
|
69
|
+
return $uploadForm.trigger("s3_upload_failed", [content]);
|
70
|
+
},
|
71
|
+
formData: function(form) {
|
72
|
+
var data, fileType;
|
73
|
+
data = form.serializeArray();
|
74
|
+
fileType = "";
|
75
|
+
if ("type" in this.files[0]) {
|
76
|
+
fileType = this.files[0].type;
|
77
|
+
}
|
78
|
+
data.push({
|
79
|
+
name: "Content-Type",
|
80
|
+
value: fileType
|
81
|
+
});
|
82
|
+
data[1].value = settings.path + data[1].value;
|
83
|
+
return data;
|
84
|
+
}
|
85
|
+
});
|
86
|
+
};
|
87
|
+
build_content_object = function($uploadForm, file) {
|
88
|
+
var content, domain, path;
|
89
|
+
domain = $uploadForm.attr('action');
|
90
|
+
path = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '');
|
91
|
+
content = {};
|
92
|
+
content.url = domain + path + '/' + file.name;
|
93
|
+
content.filename = file.name;
|
94
|
+
content.filepath = path;
|
95
|
+
if ('size' in file) {
|
96
|
+
content.filesize = file.size;
|
97
|
+
}
|
98
|
+
if ('type' in file) {
|
99
|
+
content.filetype = file.type;
|
100
|
+
}
|
101
|
+
if (settings.additional_data) {
|
102
|
+
content = $.extend(content, settings.additional_data);
|
103
|
+
}
|
104
|
+
return content;
|
105
|
+
};
|
106
|
+
this.initialize = function() {
|
107
|
+
setUploadForm();
|
108
|
+
return this;
|
109
|
+
};
|
110
|
+
this.path = function(new_path) {
|
111
|
+
return settings.path = new_path;
|
112
|
+
};
|
113
|
+
this.additional_data = function(new_data) {
|
114
|
+
return settings.additional_data = new_data;
|
115
|
+
};
|
116
|
+
return this.initialize();
|
117
|
+
};
|
@@ -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
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 's3_direct_upload/version'
|
2
|
+
require 'jquery-fileupload-rails' if defined?(Rails)
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require 'openssl'
|
6
|
+
require 'digest/sha1'
|
7
|
+
|
8
|
+
require 's3_direct_upload/config_aws'
|
9
|
+
require 's3_direct_upload/form_helper'
|
10
|
+
require 's3_direct_upload/engine' if defined?(Rails)
|
11
|
+
|
12
|
+
ActionView::Base.send(:include, S3DirectUpload::UploadHelper) if defined?(ActionView::Base)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module S3DirectUpload
|
4
|
+
class Config
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
ATTRIBUTES = [:access_key_id, :secret_access_key, :bucket]
|
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,88 @@
|
|
1
|
+
module S3DirectUpload
|
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
|
+
@options = options.reverse_merge(
|
15
|
+
aws_access_key_id: S3DirectUpload.config.access_key_id,
|
16
|
+
aws_secret_access_key: S3DirectUpload.config.secret_access_key,
|
17
|
+
bucket: S3DirectUpload.config.bucket,
|
18
|
+
acl: "public-read",
|
19
|
+
expiration: 10.hours.from_now.utc.iso8601,
|
20
|
+
max_file_size: 500.megabytes,
|
21
|
+
as: "file",
|
22
|
+
key: key
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def form_options
|
27
|
+
{
|
28
|
+
id: @options[:id],
|
29
|
+
class: @options[:class],
|
30
|
+
method: "post",
|
31
|
+
authenticity_token: false,
|
32
|
+
multipart: true,
|
33
|
+
data: {
|
34
|
+
post: @options[:post],
|
35
|
+
as: @options[:as]
|
36
|
+
}.reverse_merge(@options[:data] || {})
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def fields
|
41
|
+
{
|
42
|
+
:key => @options[:key] || key,
|
43
|
+
:acl => @options[:acl],
|
44
|
+
:policy => policy,
|
45
|
+
:signature => signature,
|
46
|
+
"AWSAccessKeyId" => @options[:aws_access_key_id],
|
47
|
+
success_action_status: "201"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def key
|
52
|
+
@key ||= "uploads/#{SecureRandom.hex}/${filename}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def url
|
56
|
+
"https://#{@options[:bucket]}.s3.amazonaws.com/"
|
57
|
+
end
|
58
|
+
|
59
|
+
def policy
|
60
|
+
Base64.encode64(policy_data.to_json).gsub("\n", "")
|
61
|
+
end
|
62
|
+
|
63
|
+
def policy_data
|
64
|
+
{
|
65
|
+
expiration: @options[:expiration],
|
66
|
+
conditions: [
|
67
|
+
["starts-with", "$utf8", ""],
|
68
|
+
["starts-with", "$key", ""],
|
69
|
+
["content-length-range", 0, @options[:max_file_size]],
|
70
|
+
["starts-with","$Content-Type",""],
|
71
|
+
{bucket: @options[:bucket]},
|
72
|
+
{acl: @options[:acl]},
|
73
|
+
{success_action_status: "201"}
|
74
|
+
]
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def signature
|
79
|
+
Base64.encode64(
|
80
|
+
OpenSSL::HMAC.digest(
|
81
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
82
|
+
@options[:aws_secret_access_key], policy
|
83
|
+
)
|
84
|
+
).gsub("\n", "")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: actionverb_s3_direct_upload
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wayne Hoover
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: coffee-rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.2.1
|
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: 3.2.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sass-rails
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.2.1
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.2.1
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: jquery-fileupload-rails
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.3.5
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.3.5
|
78
|
+
description: Direct Upload to Amazon S3 With CORS and jquery-file-upload
|
79
|
+
email:
|
80
|
+
- w@waynehoover.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- lib/s3_direct_upload/config_aws.rb
|
86
|
+
- lib/s3_direct_upload/engine.rb
|
87
|
+
- lib/s3_direct_upload/form_helper.rb
|
88
|
+
- lib/s3_direct_upload/version.rb
|
89
|
+
- lib/s3_direct_upload.rb
|
90
|
+
- app/assets/javascripts/s3_direct_upload.js
|
91
|
+
- app/assets/javascripts/s3_direct_upload.js.coffee
|
92
|
+
- app/assets/javascripts/s3_direct_upload.js.js
|
93
|
+
- app/assets/stylesheets/s3_direct_upload_progress_bars.css.scss
|
94
|
+
- LICENSE
|
95
|
+
- README.md
|
96
|
+
homepage: ''
|
97
|
+
licenses: []
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 1.8.23
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Gives a form helper for Rails which allows direct uploads to s3. Based on
|
120
|
+
RailsCast#383
|
121
|
+
test_files: []
|
122
|
+
has_rdoc:
|