s3_multipart 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +8 -1
- data/README.md +150 -42
- data/Rakefile +10 -0
- data/app/controllers/s3_multipart/application_controller.rb +6 -3
- data/app/controllers/s3_multipart/uploads_controller.rb +30 -29
- data/app/models/s3_multipart/upload.rb +13 -5
- data/grunt.js +44 -0
- data/javascripts/footer.js +5 -0
- data/javascripts/header.js +14 -0
- data/javascripts/libs/underscore.js +1 -0
- data/javascripts/s3mp.js +329 -0
- data/javascripts/upload.js +76 -0
- data/javascripts/uploadpart.js +32 -0
- data/lib/generators/s3_multipart/install_generator.rb +30 -0
- data/lib/generators/s3_multipart/templates/add_uploader_column_to_model.rb +7 -0
- data/lib/generators/s3_multipart/templates/aws.yml +4 -0
- data/lib/generators/s3_multipart/templates/configuration_initializer.rb +8 -0
- data/lib/generators/s3_multipart/templates/uploader.rb +29 -0
- data/{db/migrate/20110727184726_create_s3_multipart_uploads.rb → lib/generators/s3_multipart/templates/uploads_table_migration.rb} +2 -0
- data/lib/generators/s3_multipart/uploader_generator.rb +33 -0
- data/lib/s3_multipart/action_view_helpers/form_helper.rb +2 -1
- data/lib/s3_multipart/config.rb +12 -0
- data/lib/s3_multipart/http/net_http.rb +6 -6
- data/lib/s3_multipart/railtie.rb +12 -0
- data/lib/s3_multipart/transfer_helpers.rb +92 -0
- data/lib/s3_multipart/uploader/callbacks.rb +17 -0
- data/lib/s3_multipart/uploader/validations.rb +9 -0
- data/lib/s3_multipart/uploader.rb +27 -68
- data/lib/s3_multipart/version.rb +1 -1
- data/lib/s3_multipart.rb +4 -42
- data/package.json +9 -0
- data/s3_multipart.gemspec +6 -3
- data/spec/integration/uploads_controller_spec.rb +2 -1
- data/spec/internal/app/assets/javascripts/application.js +148 -63
- data/spec/internal/app/assets/stylesheets/application.css.scss +384 -0
- data/spec/internal/app/assets/stylesheets/font-awesome.scss +499 -0
- data/spec/internal/app/controllers/application_controller.rb +4 -1
- data/spec/internal/app/controllers/pages_controller.rb +3 -7
- data/spec/internal/app/models/user.rb +4 -0
- data/spec/internal/app/models/video.rb +5 -0
- data/spec/internal/app/uploaders/multipart/video_uploader.rb +32 -0
- data/spec/internal/app/views/pages/upload.html.erb +38 -4
- data/spec/internal/config/routes.rb +1 -1
- data/spec/internal/db/schema.rb +22 -14
- data/spec/internal/public/fonts/FontAwesome.otf +0 -0
- data/spec/internal/public/fonts/fontawesome-webfont.eot +0 -0
- data/spec/internal/public/fonts/fontawesome-webfont.ttf +0 -0
- data/spec/internal/public/fonts/fontawesome-webfont.woff +0 -0
- data/spec/internal/tmp/cache/sass/077f66d4d9153cfd737b81ab3f4c5d5858b0db3b/_grid-background.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_appearance.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-clip.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-origin.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-size.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_border-radius.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box-shadow.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box-sizing.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_columns.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_filter.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_font-face.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_hyphenation.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_images.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_inline-block.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_opacity.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_regions.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_shared.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_text-shadow.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_transform.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_transition.scssc +0 -0
- data/spec/internal/tmp/cache/sass/351072894b53bf1a2d2af4bd57c177c805cf063e/_contrast.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_css3.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_support.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_typography.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_utilities.scssc +0 -0
- data/spec/internal/tmp/cache/sass/633bc5f017268b81a60f921876c980c4adcebb74/_base.scssc +0 -0
- data/spec/internal/tmp/cache/sass/633bc5f017268b81a60f921876c980c4adcebb74/_sprite-img.scssc +0 -0
- data/spec/internal/tmp/cache/sass/68dbce8ac3037e0cd797c9b5c6aa131096361144/_utilities.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_clearfix.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_float.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_hacks.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_min.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_reset.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_tag-cloud.scssc +0 -0
- data/spec/internal/tmp/cache/sass/775792c99f07f64e98296a8dc8c9d2c74a28fd8a/_reset.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_ellipsis.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_force-wrap.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_nowrap.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_replacement.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_bullets.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_horizontal-list.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_inline-block-list.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_inline-list.scssc +0 -0
- data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_alternating-rows-and-columns.scssc +0 -0
- data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_borders.scssc +0 -0
- data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_scaffolding.scssc +0 -0
- data/spec/internal/tmp/cache/sass/bc7f11d6e8f07a22f545deb35760cfc7830f3369/application.css.scssc +0 -0
- data/spec/internal/tmp/cache/sass/bc7f11d6e8f07a22f545deb35760cfc7830f3369/font-awesome.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_color.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_general.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_sprites.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_tables.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_hover-link.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_link-colors.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_unstyled-link.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_links.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_lists.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_text.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_vertical_rhythm.scssc +0 -0
- data/spec/internal/tmp/cache/sass/e2e5b9bb57a9d1b018b9f546bc8b16f6b2a627a9/_compass.scssc +0 -0
- data/spec/internal/tmp/cache/sass/e654955d4502954a409bf21071c0d98eb4ac2e9f/_utilities.scssc +0 -0
- data/spec/javascripts/UploadSpec.js +75 -0
- data/spec/javascripts/helpers/SpecHelper.js +9 -0
- data/spec/javascripts/support/jasmine.yml +84 -0
- data/spec/unit/upload_controller_spec.rb +31 -0
- data/spec/{requests/uploader_spec.rb → unit/upload_spec.rb} +9 -10
- data/spec/unit/uploader_module_spec.rb +31 -0
- data/vendor/assets/javascripts/s3_multipart/lib.js +456 -0
- data/vendor/assets/javascripts/s3_multipart/lib.min.js +1 -0
- metadata +103 -12
- data/.rspec +0 -1
- data/lib/s3_multipart/uploader/config.rb +0 -15
- data/spec/internal/app/assets/stylesheets/application.css +0 -0
- data/spec/internal/db/combustion_test.sqlite3 +0 -0
- data/vendor/assets/javascripts/s3_multipart/index.js +0 -1
- data/vendor/assets/javascripts/s3_multipart/s3_multipart.js +0 -478
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
3
|
group :development do
|
6
4
|
gem 'activerecord'
|
7
5
|
gem 'sqlite3'
|
@@ -15,11 +13,13 @@ group :development do
|
|
15
13
|
gem 'rspec'
|
16
14
|
gem 'rspec-rails'
|
17
15
|
gem 'capybara'
|
16
|
+
# gem 'jasmine'
|
18
17
|
|
19
18
|
# assets
|
20
19
|
gem 'sass-rails', '~> 3.2.3'
|
21
20
|
gem 'coffee-rails', '~> 3.2.1'
|
22
21
|
gem 'compass-rails'
|
22
|
+
gem 'jquery-ui-rails'
|
23
23
|
|
24
24
|
gemspec
|
25
25
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
s3_multipart (0.0.
|
4
|
+
s3_multipart (0.0.6)
|
5
5
|
uuid (>= 2.3.6)
|
6
6
|
xml-simple (>= 1.1.2)
|
7
7
|
|
@@ -73,6 +73,12 @@ GEM
|
|
73
73
|
hike (1.2.1)
|
74
74
|
i18n (0.6.1)
|
75
75
|
journey (1.0.4)
|
76
|
+
jquery-rails (2.1.4)
|
77
|
+
railties (>= 3.0, < 5.0)
|
78
|
+
thor (>= 0.14, < 2.0)
|
79
|
+
jquery-ui-rails (2.0.0)
|
80
|
+
jquery-rails
|
81
|
+
railties (>= 3.1.0)
|
76
82
|
json (1.7.5)
|
77
83
|
libwebsocket (0.1.7.1)
|
78
84
|
addressable
|
@@ -168,6 +174,7 @@ DEPENDENCIES
|
|
168
174
|
coffee-rails (~> 3.2.1)
|
169
175
|
combustion (~> 0.3.3)
|
170
176
|
compass-rails
|
177
|
+
jquery-ui-rails
|
171
178
|
rails
|
172
179
|
rspec
|
173
180
|
rspec-rails
|
data/README.md
CHANGED
@@ -1,33 +1,48 @@
|
|
1
1
|
# S3 Multipart
|
2
2
|
|
3
|
-
The S3 Multipart gem brings direct multipart uploading to S3 to Rails. Data is piped from the client straight to Amazon S3 and a callback is run when the upload is complete.
|
3
|
+
The S3 Multipart gem brings direct multipart uploading to S3 to Rails. Data is piped from the client straight to Amazon S3 and a server-side callback is run when the upload is complete.
|
4
4
|
|
5
|
-
Multipart uploading allows files to be split into many chunks and uploaded in parallel or succession (or both). This can result in dramatically increased upload speeds for the client and allows for the pausing and resuming of uploads. For a more complete overview of multipart uploading as it applies to S3, see the
|
5
|
+
Multipart uploading allows files to be split into many chunks and uploaded in parallel or succession (or both). This can result in dramatically increased upload speeds for the client and allows for the pausing and resuming of uploads. For a more complete overview of multipart uploading as it applies to S3, see the documentation [here](http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html).
|
6
6
|
|
7
7
|
## Setup
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
```
|
12
|
-
|
9
|
+
First, assuming that you already have an S3 bucket set up, you will need to paste the following into your CORS configuration file, located under the permissions tab in your S3 console.
|
10
|
+
|
11
|
+
```xml
|
12
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
13
|
+
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
14
|
+
<CORSRule>
|
15
|
+
<AllowedOrigin>*</AllowedOrigin>
|
16
|
+
<AllowedMethod>PUT</AllowedMethod>
|
17
|
+
<AllowedMethod>GET</AllowedMethod>
|
18
|
+
<MaxAgeSeconds>3000</MaxAgeSeconds>
|
19
|
+
<ExposeHeader>ETag</ExposeHeader>
|
20
|
+
<AllowedHeader>Authorization</AllowedHeader>
|
21
|
+
<AllowedHeader>Content-Type</AllowedHeader>
|
22
|
+
<AllowedHeader>Content-Length</AllowedHeader>
|
23
|
+
<AllowedHeader>x-amz-date</AllowedHeader>
|
24
|
+
<AllowedHeader>origin</AllowedHeader>
|
25
|
+
<AllowedHeader>Access-Control-Expose-Headers</AllowedHeader>
|
26
|
+
</CORSRule>
|
27
|
+
</CORSConfiguration>
|
13
28
|
```
|
14
29
|
|
15
|
-
|
30
|
+
Next, install the gem, and add it to your gemfile.
|
16
31
|
|
17
32
|
```bash
|
18
|
-
|
33
|
+
gem install s3_multipart
|
19
34
|
```
|
20
35
|
|
21
|
-
|
36
|
+
Run the included generator to create the required migrations and configuration files. Make sure to migrate after performing this step.
|
22
37
|
|
23
|
-
```
|
24
|
-
|
38
|
+
```bash
|
39
|
+
rails g s3_multipart:install
|
25
40
|
```
|
26
41
|
|
27
|
-
If you are using sprockets, add the following to your application.js file. Make sure that the underscore and
|
42
|
+
If you are using sprockets, add the following to your application.js file. Make sure that the latest underscore and jQuery libraries have been required before this line. Lodash is not supported at this time.
|
28
43
|
|
29
|
-
```
|
30
|
-
//= require s3_multipart/
|
44
|
+
```js
|
45
|
+
//= require s3_multipart/lib
|
31
46
|
```
|
32
47
|
|
33
48
|
Also in your application.js file you will need to include the following:
|
@@ -36,16 +51,20 @@ Also in your application.js file you will need to include the following:
|
|
36
51
|
$(function() {
|
37
52
|
$(".submit-button").click(function() { // The button class passed into multipart_uploader_form (see "Getting Started")
|
38
53
|
new window.S3MP({
|
39
|
-
bucket: "YOUR_S3_BUCKET"
|
40
|
-
|
41
|
-
|
42
|
-
|
54
|
+
bucket: "YOUR_S3_BUCKET",
|
55
|
+
fileInputElement: "#uploader",
|
56
|
+
fileList: [], // An array of files to be uploaded (see "Getting Started")
|
57
|
+
onStart: function(upload) {
|
58
|
+
console.log("File %d has started uploading", upload.key)
|
59
|
+
},
|
60
|
+
onComplete: function(upload) {
|
61
|
+
console.log("File %d successfully uploaded", upload.key)
|
43
62
|
},
|
44
|
-
onPause: function(
|
45
|
-
console.log("File
|
63
|
+
onPause: function(key) {
|
64
|
+
console.log("File %d has been paused", key)
|
46
65
|
},
|
47
|
-
onCancel: function(
|
48
|
-
console.log("File upload
|
66
|
+
onCancel: function(key) {
|
67
|
+
console.log("File upload %d was canceled", key)
|
49
68
|
},
|
50
69
|
onError: function(err) {
|
51
70
|
console.log("There was an error")
|
@@ -58,49 +77,138 @@ $(function() {
|
|
58
77
|
});
|
59
78
|
```
|
60
79
|
|
61
|
-
This piece of code does some configuration and provides various callbacks that you can hook into.
|
80
|
+
This piece of code does some configuration and provides various callbacks that you can hook into. It will be discussed further at the end of the Getting Started guide below.
|
62
81
|
|
63
|
-
Finally,
|
82
|
+
Finally, edit the aws.yml that was created in your config folder with the correct credentials for each environment.
|
64
83
|
|
65
|
-
```
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
84
|
+
```yaml
|
85
|
+
development:
|
86
|
+
access_key_id: ""
|
87
|
+
secret_access_key: ""
|
88
|
+
bucket: ""
|
71
89
|
```
|
72
90
|
|
73
91
|
## Getting Started
|
74
92
|
|
75
|
-
S3_Multipart comes with
|
93
|
+
S3_Multipart comes with a generator to set up your upload controllers. Running
|
76
94
|
|
77
|
-
|
95
|
+
```bash
|
96
|
+
rails g s3_multipart:uploader video
|
97
|
+
```
|
98
|
+
|
99
|
+
creates a video upload controller (video_uploader.rb) which resides in "app/uploaders/multipart" and looks like this:
|
78
100
|
|
79
101
|
```ruby
|
80
|
-
|
81
|
-
|
82
|
-
|
102
|
+
class VideoUploader < ApplicationController
|
103
|
+
extend S3Multipart::Uploader::Core
|
104
|
+
|
105
|
+
# Attaches the specified model to the uploader, creating a "has_one"
|
106
|
+
# relationship between the internal upload model and the given model.
|
107
|
+
attach :video
|
108
|
+
|
109
|
+
# Takes in a block that will be evaluated when the upload has been
|
110
|
+
# successfully initiated. The block will be passed an instance of
|
111
|
+
# the upload object as well as the session hash when the callback is made.
|
112
|
+
#
|
113
|
+
# The following attributes are available on the upload object:
|
114
|
+
# - key: A randomly generated unique key to replace the file
|
115
|
+
# name provided by the client
|
116
|
+
# - upload_id: A hash generated by Amazon to identify the multipart upload
|
117
|
+
# - name: The name of the file (including extensions)
|
118
|
+
# - location: The location of the file on S3. Available only to the
|
119
|
+
# upload object passed into the on_complete callback
|
120
|
+
#
|
121
|
+
on_begin do |upload, session|
|
122
|
+
# Code to be evaluated when upload completes
|
123
|
+
end
|
124
|
+
|
125
|
+
# See above comment. Called when the upload has successfully completed
|
126
|
+
on_complete do |upload, session|
|
127
|
+
# Code to be evaluated when upload completes
|
83
128
|
end
|
129
|
+
|
84
130
|
end
|
85
131
|
```
|
86
132
|
|
87
|
-
The
|
133
|
+
The generator requires a model to be passed in (in this case, the video model) and automatically creates a "has one" relationship between the upload and the model (the video). For example, in the block that the `on_begin` method takes, a video object could be created (`video = Video.create(name: upload.name)`) and linked with the upload (`upload.video = video`). When the block passed into the `on_complete` is run at a later point in time, the associated video is now accessible by calling `upload.video`. If instead, you want to construct the video object on completion and link the two then, that is ok.
|
134
|
+
|
135
|
+
The generator also creates the migration to add this functionality, so make sure to do a `rake db:migrate` after generating the controller.
|
136
|
+
|
137
|
+
To add the multipart uploader to a view, insert the following:
|
88
138
|
|
89
139
|
```ruby
|
90
140
|
<%= multipart_uploader_form(types: ['video/mpeg'],
|
91
141
|
input_name: 'uploader',
|
92
|
-
|
142
|
+
uploader: 'VideoUploader'
|
143
|
+
button_class: 'submit-button',
|
93
144
|
button_text: 'Upload selected videos',
|
94
|
-
html: %Q{<button class="upload-button">Select
|
145
|
+
html: %Q{<button class="upload-button">Select videos</button>}) %>
|
95
146
|
```
|
96
147
|
|
97
|
-
|
148
|
+
The `multipart_uploader_form` function is a view helper, and generates the necessary input elements. It takes in a array of allowed MIME types and a string of html to be interpolated between the generated file input element and submit button. It also expects an upload controller (as a string or constant) to be passed in with the 'uploader' option. This links the upload form with the callbacks specified in the given controller.
|
149
|
+
|
150
|
+
The code above outputs this:
|
98
151
|
|
99
152
|
```html
|
100
|
-
<input accept="video
|
101
|
-
<button class="
|
153
|
+
<input accept="video" data-uploader="7b2a340f42976e5520975b5d5668dc4c19b38f2c" id="uploader" multiple="multiple" name="uploader" type="file">
|
154
|
+
<button class="upload-button" type="submit">Select videos</button>
|
155
|
+
<button class="submit-button"><span>Upload selected videos</span></button>
|
102
156
|
```
|
103
157
|
|
158
|
+
Let's return to the javascript that you inserted into the application.js during setup. The S3MP constructor takes in a configuration object with a handful of required callback functions. It also takes in list of files (through the `fileList` property) that is an array of File objects. This could be retrieved by calling `$("#uploader").get(0).files` if the input element had an "uploader" id, or it could be manually constructed. See the internal tests for an example of this manual construction.
|
159
|
+
|
160
|
+
The S3MP constructor also returns an object that you can interact with. Although not demonstrated here, you can call cancel, pause, or resume on this object and pass in the zero-indexed key of the file in the fileList array you want to control.
|
161
|
+
|
162
|
+
## Tests
|
163
|
+
|
164
|
+
First, create a file `setup_credentials.rb` in the spec folder.
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# spec/setup_credentials.rb
|
168
|
+
S3Multipart.configure do |config|
|
169
|
+
config.bucket_name = ''
|
170
|
+
config.s3_access_key = ''
|
171
|
+
config.s3_secret_key = ''
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
You can now run all of the RSpec and Capybara tests with `rspec spec`
|
176
|
+
|
177
|
+
[Combustion](https://github.com/pat/combustion) is also used to simulate a rails application. Paste the following into a `config.ru` file in the base directory:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
require 'rubygems'
|
181
|
+
require 'bundler'
|
182
|
+
|
183
|
+
Bundler.require :development
|
184
|
+
|
185
|
+
Combustion.initialize! :active_record, :action_controller,
|
186
|
+
:action_view, :sprockets
|
187
|
+
|
188
|
+
S3Multipart.configure do |config|
|
189
|
+
config.bucket_name = ''
|
190
|
+
config.s3_access_key = ''
|
191
|
+
config.s3_secret_key = ''
|
192
|
+
end
|
193
|
+
|
194
|
+
run Combustion::Application
|
195
|
+
```
|
196
|
+
|
197
|
+
and boot up the app by running `rackup`. A fully functional uploader is now available if you visit http://localhost:9292
|
198
|
+
|
199
|
+
Jasmine tests are also available for the client-facing javascript library. After installing [Grunt](http://gruntjs.com/) and [PhantomJS](http://phantomjs.org/), and running `npm install` once, you can run the tests headlessly by running `grunt jasmine`.
|
200
|
+
|
201
|
+
To re-build the javascript library, run `grunt concat` and to minify, `grunt min`.
|
202
|
+
|
104
203
|
## Contributing
|
105
204
|
|
106
|
-
S3_Multipart is very much a work in progress. If you squash a bug, make
|
205
|
+
S3_Multipart is very much a work in progress. If you squash a bug, make enhancements, or write more tests, please submit a pull request.
|
206
|
+
|
207
|
+
## To Do
|
208
|
+
|
209
|
+
* If the FileBlob API is not supported on page load, the uploader should just send one giant chunk
|
210
|
+
* Handle network errors in the javascript client library
|
211
|
+
* Modular validations (checking file size and type)
|
212
|
+
* More and better tests
|
213
|
+
* More browser testing
|
214
|
+
* Roll file signing and initiation into one request
|
data/Rakefile
CHANGED
@@ -3,9 +3,9 @@ module S3Multipart
|
|
3
3
|
def create
|
4
4
|
begin
|
5
5
|
response = Upload.initiate(params)
|
6
|
-
upload = Upload.create(key: response["key"], upload_id: response["upload_id"], name: response["name"])
|
6
|
+
upload = Upload.create(key: response["key"], upload_id: response["upload_id"], name: response["name"], uploader: params["uploader"])
|
7
7
|
response["id"] = upload["id"]
|
8
|
-
upload.
|
8
|
+
upload.execute_callback(:begin, session)
|
9
9
|
rescue
|
10
10
|
response = {error: 'There was an error initiating the upload'}
|
11
11
|
ensure
|
@@ -21,37 +21,38 @@ module S3Multipart
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
def sign_batch
|
25
|
+
begin
|
26
|
+
response = Upload.sign_batch(params)
|
27
|
+
rescue
|
28
|
+
response = {error: 'There was an error in processing your upload'}
|
29
|
+
ensure
|
30
|
+
render :json => response
|
31
|
+
end
|
31
32
|
end
|
32
|
-
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
def sign_part
|
35
|
+
begin
|
36
|
+
response = Upload.sign_part(params)
|
37
|
+
rescue
|
38
|
+
response = {error: 'There was an error in processing your upload'}
|
39
|
+
ensure
|
40
|
+
render :json => response
|
41
|
+
end
|
41
42
|
end
|
42
|
-
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
44
|
+
def complete_upload
|
45
|
+
begin
|
46
|
+
response = Upload.complete(params)
|
47
|
+
upload = Upload.find_by_upload_id(params[:upload_id])
|
48
|
+
upload.update_attributes(location: response[:location])
|
49
|
+
upload.execute_callback(:complete, session)
|
50
|
+
rescue
|
51
|
+
response = {error: 'There was an error completing the upload'}
|
52
|
+
ensure
|
53
|
+
render :json => response
|
54
|
+
end
|
54
55
|
end
|
55
|
-
|
56
|
+
|
56
57
|
end
|
57
58
|
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
module S3Multipart
|
2
2
|
class Upload < ::ActiveRecord::Base
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
extend S3Multipart::TransferHelpers
|
4
|
+
|
5
|
+
attr_accessible :key, :upload_id, :name, :location, :uploader
|
6
|
+
|
7
|
+
def execute_callback(stage, session)
|
8
|
+
controller = S3Multipart::Uploader.deserialize(uploader)
|
9
|
+
|
10
|
+
case stage
|
11
|
+
when :begin
|
12
|
+
controller.on_begin_callback.call(self, session)
|
13
|
+
when :complete
|
14
|
+
controller.on_complete_callback.call(self, session)
|
15
|
+
end
|
7
16
|
end
|
8
17
|
|
9
|
-
attr_accessible :key, :upload_id, :name, :location
|
10
18
|
end
|
11
19
|
end
|
data/grunt.js
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module.exports = function(grunt) {
|
2
|
+
|
3
|
+
grunt.loadNpmTasks('grunt-contrib-uglify');
|
4
|
+
grunt.loadNpmTasks('grunt-jasmine-runner');
|
5
|
+
|
6
|
+
// Project configuration.
|
7
|
+
grunt.initConfig({
|
8
|
+
pkg: grunt.file.readJSON('package.json'),
|
9
|
+
|
10
|
+
concat: {
|
11
|
+
lib : {
|
12
|
+
src : [
|
13
|
+
'javascripts/header.js',
|
14
|
+
'javascripts/s3mp.js',
|
15
|
+
'javascripts/upload.js',
|
16
|
+
'javascripts/uploadpart.js',
|
17
|
+
'javascripts/footer.js'
|
18
|
+
],
|
19
|
+
dest : 'vendor/assets/javascripts/s3_multipart/lib.js'
|
20
|
+
}
|
21
|
+
},
|
22
|
+
|
23
|
+
jasmine : {
|
24
|
+
src : [
|
25
|
+
'javascripts/libs/underscore.js',
|
26
|
+
'javascripts/s3mp.js',
|
27
|
+
'javascripts/upload.js',
|
28
|
+
'javascripts/uploadpart.js'
|
29
|
+
],
|
30
|
+
helpers : 'spec/javascripts/helpers/*.js',
|
31
|
+
specs : 'spec/javascripts/*.js'
|
32
|
+
},
|
33
|
+
|
34
|
+
min: {
|
35
|
+
myPlugin: {
|
36
|
+
src: [
|
37
|
+
'<config:concat.lib.dest>'
|
38
|
+
],
|
39
|
+
dest: 'vendor/assets/javascripts/s3_multipart/lib.min.js'
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
});
|
44
|
+
};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
(function(global) {
|
2
|
+
global.S3MP = (function() {
|
3
|
+
|
4
|
+
// Wrap this into underscore library extension
|
5
|
+
_.mixin({
|
6
|
+
findIndex : function (collection, filter) {
|
7
|
+
for (var i = 0; i < collection.length; i++) {
|
8
|
+
if (filter(collection[i], i, collection)) {
|
9
|
+
return i;
|
10
|
+
}
|
11
|
+
}
|
12
|
+
return -1;
|
13
|
+
}
|
14
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.index<t.index?-1:1}),"value")};var k=function(n,t,r,e){var u={},i=F(t||w.identity);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};w.groupBy=function(n,t,r){return k(n,t,r,function(n,t,r){(w.has(n,t)?n[t]:n[t]=[]).push(r)})},w.countBy=function(n,t,r){return k(n,t,r,function(n,t){w.has(n,t)||(n[t]=0),n[t]++})},w.sortedIndex=function(n,t,r,e){r=null==r?w.identity:F(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
|