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.
Files changed (127) hide show
  1. data/.gitignore +11 -0
  2. data/Gemfile +2 -2
  3. data/Gemfile.lock +8 -1
  4. data/README.md +150 -42
  5. data/Rakefile +10 -0
  6. data/app/controllers/s3_multipart/application_controller.rb +6 -3
  7. data/app/controllers/s3_multipart/uploads_controller.rb +30 -29
  8. data/app/models/s3_multipart/upload.rb +13 -5
  9. data/grunt.js +44 -0
  10. data/javascripts/footer.js +5 -0
  11. data/javascripts/header.js +14 -0
  12. data/javascripts/libs/underscore.js +1 -0
  13. data/javascripts/s3mp.js +329 -0
  14. data/javascripts/upload.js +76 -0
  15. data/javascripts/uploadpart.js +32 -0
  16. data/lib/generators/s3_multipart/install_generator.rb +30 -0
  17. data/lib/generators/s3_multipart/templates/add_uploader_column_to_model.rb +7 -0
  18. data/lib/generators/s3_multipart/templates/aws.yml +4 -0
  19. data/lib/generators/s3_multipart/templates/configuration_initializer.rb +8 -0
  20. data/lib/generators/s3_multipart/templates/uploader.rb +29 -0
  21. data/{db/migrate/20110727184726_create_s3_multipart_uploads.rb → lib/generators/s3_multipart/templates/uploads_table_migration.rb} +2 -0
  22. data/lib/generators/s3_multipart/uploader_generator.rb +33 -0
  23. data/lib/s3_multipart/action_view_helpers/form_helper.rb +2 -1
  24. data/lib/s3_multipart/config.rb +12 -0
  25. data/lib/s3_multipart/http/net_http.rb +6 -6
  26. data/lib/s3_multipart/railtie.rb +12 -0
  27. data/lib/s3_multipart/transfer_helpers.rb +92 -0
  28. data/lib/s3_multipart/uploader/callbacks.rb +17 -0
  29. data/lib/s3_multipart/uploader/validations.rb +9 -0
  30. data/lib/s3_multipart/uploader.rb +27 -68
  31. data/lib/s3_multipart/version.rb +1 -1
  32. data/lib/s3_multipart.rb +4 -42
  33. data/package.json +9 -0
  34. data/s3_multipart.gemspec +6 -3
  35. data/spec/integration/uploads_controller_spec.rb +2 -1
  36. data/spec/internal/app/assets/javascripts/application.js +148 -63
  37. data/spec/internal/app/assets/stylesheets/application.css.scss +384 -0
  38. data/spec/internal/app/assets/stylesheets/font-awesome.scss +499 -0
  39. data/spec/internal/app/controllers/application_controller.rb +4 -1
  40. data/spec/internal/app/controllers/pages_controller.rb +3 -7
  41. data/spec/internal/app/models/user.rb +4 -0
  42. data/spec/internal/app/models/video.rb +5 -0
  43. data/spec/internal/app/uploaders/multipart/video_uploader.rb +32 -0
  44. data/spec/internal/app/views/pages/upload.html.erb +38 -4
  45. data/spec/internal/config/routes.rb +1 -1
  46. data/spec/internal/db/schema.rb +22 -14
  47. data/spec/internal/public/fonts/FontAwesome.otf +0 -0
  48. data/spec/internal/public/fonts/fontawesome-webfont.eot +0 -0
  49. data/spec/internal/public/fonts/fontawesome-webfont.ttf +0 -0
  50. data/spec/internal/public/fonts/fontawesome-webfont.woff +0 -0
  51. data/spec/internal/tmp/cache/sass/077f66d4d9153cfd737b81ab3f4c5d5858b0db3b/_grid-background.scssc +0 -0
  52. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_appearance.scssc +0 -0
  53. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-clip.scssc +0 -0
  54. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-origin.scssc +0 -0
  55. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-size.scssc +0 -0
  56. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_border-radius.scssc +0 -0
  57. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box-shadow.scssc +0 -0
  58. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box-sizing.scssc +0 -0
  59. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box.scssc +0 -0
  60. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_columns.scssc +0 -0
  61. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_filter.scssc +0 -0
  62. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_font-face.scssc +0 -0
  63. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_hyphenation.scssc +0 -0
  64. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_images.scssc +0 -0
  65. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_inline-block.scssc +0 -0
  66. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_opacity.scssc +0 -0
  67. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_regions.scssc +0 -0
  68. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_shared.scssc +0 -0
  69. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_text-shadow.scssc +0 -0
  70. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_transform.scssc +0 -0
  71. data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_transition.scssc +0 -0
  72. data/spec/internal/tmp/cache/sass/351072894b53bf1a2d2af4bd57c177c805cf063e/_contrast.scssc +0 -0
  73. data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_css3.scssc +0 -0
  74. data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_support.scssc +0 -0
  75. data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_typography.scssc +0 -0
  76. data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_utilities.scssc +0 -0
  77. data/spec/internal/tmp/cache/sass/633bc5f017268b81a60f921876c980c4adcebb74/_base.scssc +0 -0
  78. data/spec/internal/tmp/cache/sass/633bc5f017268b81a60f921876c980c4adcebb74/_sprite-img.scssc +0 -0
  79. data/spec/internal/tmp/cache/sass/68dbce8ac3037e0cd797c9b5c6aa131096361144/_utilities.scssc +0 -0
  80. data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_clearfix.scssc +0 -0
  81. data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_float.scssc +0 -0
  82. data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_hacks.scssc +0 -0
  83. data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_min.scssc +0 -0
  84. data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_reset.scssc +0 -0
  85. data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_tag-cloud.scssc +0 -0
  86. data/spec/internal/tmp/cache/sass/775792c99f07f64e98296a8dc8c9d2c74a28fd8a/_reset.scssc +0 -0
  87. data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_ellipsis.scssc +0 -0
  88. data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_force-wrap.scssc +0 -0
  89. data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_nowrap.scssc +0 -0
  90. data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_replacement.scssc +0 -0
  91. data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_bullets.scssc +0 -0
  92. data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_horizontal-list.scssc +0 -0
  93. data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_inline-block-list.scssc +0 -0
  94. data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_inline-list.scssc +0 -0
  95. data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_alternating-rows-and-columns.scssc +0 -0
  96. data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_borders.scssc +0 -0
  97. data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_scaffolding.scssc +0 -0
  98. data/spec/internal/tmp/cache/sass/bc7f11d6e8f07a22f545deb35760cfc7830f3369/application.css.scssc +0 -0
  99. data/spec/internal/tmp/cache/sass/bc7f11d6e8f07a22f545deb35760cfc7830f3369/font-awesome.scssc +0 -0
  100. data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_color.scssc +0 -0
  101. data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_general.scssc +0 -0
  102. data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_sprites.scssc +0 -0
  103. data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_tables.scssc +0 -0
  104. data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_hover-link.scssc +0 -0
  105. data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_link-colors.scssc +0 -0
  106. data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_unstyled-link.scssc +0 -0
  107. data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_links.scssc +0 -0
  108. data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_lists.scssc +0 -0
  109. data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_text.scssc +0 -0
  110. data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_vertical_rhythm.scssc +0 -0
  111. data/spec/internal/tmp/cache/sass/e2e5b9bb57a9d1b018b9f546bc8b16f6b2a627a9/_compass.scssc +0 -0
  112. data/spec/internal/tmp/cache/sass/e654955d4502954a409bf21071c0d98eb4ac2e9f/_utilities.scssc +0 -0
  113. data/spec/javascripts/UploadSpec.js +75 -0
  114. data/spec/javascripts/helpers/SpecHelper.js +9 -0
  115. data/spec/javascripts/support/jasmine.yml +84 -0
  116. data/spec/unit/upload_controller_spec.rb +31 -0
  117. data/spec/{requests/uploader_spec.rb → unit/upload_spec.rb} +9 -10
  118. data/spec/unit/uploader_module_spec.rb +31 -0
  119. data/vendor/assets/javascripts/s3_multipart/lib.js +456 -0
  120. data/vendor/assets/javascripts/s3_multipart/lib.min.js +1 -0
  121. metadata +103 -12
  122. data/.rspec +0 -1
  123. data/lib/s3_multipart/uploader/config.rb +0 -15
  124. data/spec/internal/app/assets/stylesheets/application.css +0 -0
  125. data/spec/internal/db/combustion_test.sqlite3 +0 -0
  126. data/vendor/assets/javascripts/s3_multipart/index.js +0 -1
  127. data/vendor/assets/javascripts/s3_multipart/s3_multipart.js +0 -478
data/.gitignore CHANGED
@@ -4,3 +4,14 @@
4
4
  # ignore files containing sensitive info
5
5
  config.ru
6
6
  /spec/setup_credentials.rb
7
+
8
+ *.gem
9
+
10
+ .rvmrc
11
+ .rspec
12
+
13
+ _SpecRunner.html
14
+
15
+ /node_modules
16
+
17
+ /spec/internal/db/combustion_test.sqlite3
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.2)
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 overview [here](http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html).
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
- Install the gem
10
-
11
- ```bash
12
- gem install s3_multipart
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
- Install the included migrations. This will create a table in your database to track initiated and completed uploads
30
+ Next, install the gem, and add it to your gemfile.
16
31
 
17
32
  ```bash
18
- rake s3_multipart:install:migrations
33
+ gem install s3_multipart
19
34
  ```
20
35
 
21
- Add the following to your routes file:
36
+ Run the included generator to create the required migrations and configuration files. Make sure to migrate after performing this step.
22
37
 
23
- ```ruby
24
- mount S3Multipart::Engine => "/s3_multipart"
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 jquery libraries have been required before this line.
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
- ```ruby
30
- //= require s3_multipart/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
- fileSelector: "#uploader", // The input name passed into multipart_uploader_form (see "Getting Started")
41
- onComplete: function(num) {
42
- console.log("File "+num+" successfully uploaded")
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(num) {
45
- console.log("File "+num+" has been paused")
63
+ onPause: function(key) {
64
+ console.log("File %d has been paused", key)
46
65
  },
47
- onCancel: function(num) {
48
- console.log("File upload "+num+" was canceled")
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, create an initializer in config/initializers with the following, adding in your credentials.
82
+ Finally, edit the aws.yml that was created in your config folder with the correct credentials for each environment.
64
83
 
65
- ```ruby
66
- S3Multipart.configure do |config|
67
- config.bucket_name = '#########'
68
- config.s3_access_key = '#########'
69
- config.s3_secret_key = '#########'
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 two helper functions required in integrating uploads into your application.
93
+ S3_Multipart comes with a generator to set up your upload controllers. Running
76
94
 
77
- The `attach_uploader` function is available in your controllers. Call it from within a routed method, and pass in a block of code to be executed when the upload has completed successfully. The completed upload object has `location`, `upload_id`, `name`, and `key` attributes that can be accessed and manipulated.
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
- def your_controller_method
81
- attach_uploader do |upload|
82
- # your code here
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 `multipart_uploader_form` function is a view helper, and generates the necessary input elements. It takes in a hash of allowed MIME types and a string of html to be interpolated between the generated file input element and submit button.
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
- button_class: 'submit-button',
142
+ uploader: 'VideoUploader'
143
+ button_class: 'submit-button',
93
144
  button_text: 'Upload selected videos',
94
- html: %Q{<button class="upload-button">Select a Video</button>}) %>
145
+ html: %Q{<button class="upload-button">Select videos</button>}) %>
95
146
  ```
96
147
 
97
- puts out this:
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/mpeg" id="uploader" name="uploader" type="file">
101
- <button class="submit-button">Upload selected videos</button>
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 enhancemenets, or write more tests, please submit a pull request.
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
@@ -0,0 +1,10 @@
1
+
2
+ # begin
3
+ # require 'jasmine'
4
+ # require 'yaml'
5
+ # load 'jasmine/tasks/jasmine.rake'
6
+ # rescue LoadError
7
+ # task :jasmine do
8
+ # abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
9
+ # end
10
+ # end
@@ -1,4 +1,7 @@
1
- module S3Multipart
2
- class ApplicationController < ActionController::Base
3
- end
1
+ # module S3Multipart
2
+ # class ApplicationController < ActionController::Base
3
+ # end
4
+ # end
5
+
6
+ class S3Multipart::ApplicationController < ApplicationController
4
7
  end
@@ -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.on_begin if upload.respond_to?(:on_begin)
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
- 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
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
- 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
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
- 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.on_complete if upload.respond_to?(:on_complete)
50
- rescue
51
- response = {error: 'There was an error completing the upload'}
52
- ensure
53
- render :json => response
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
- end
56
+
56
57
  end
57
58
  end
@@ -1,11 +1,19 @@
1
1
  module S3Multipart
2
2
  class Upload < ::ActiveRecord::Base
3
- class << self
4
- include S3Multipart::Uploader
5
- attr_accessor :on_complete_callback
6
- attr_accessor :on_begin_callback
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,5 @@
1
+ return S3MP;
2
+
3
+ }());
4
+
5
+ }(this));
@@ -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:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};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);