activestorage 5.2.0.rc1 → 5.2.0.rc2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activestorage might be problematic. Click here for more details.

Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -1
  3. data/README.md +1 -1
  4. data/app/assets/javascripts/activestorage.js +1 -1
  5. data/app/controllers/active_storage/{variants_controller.rb → representations_controller.rb} +3 -3
  6. data/app/javascript/activestorage/blob_upload.js +1 -1
  7. data/app/javascript/activestorage/direct_upload.js +6 -0
  8. data/app/models/active_storage/blob.rb +17 -4
  9. data/app/models/active_storage/blob/identifiable.rb +10 -1
  10. data/app/models/active_storage/blob/representable.rb +2 -2
  11. data/app/models/active_storage/filename.rb +10 -0
  12. data/app/models/active_storage/preview.rb +3 -4
  13. data/app/models/active_storage/variant.rb +1 -1
  14. data/app/models/active_storage/variation.rb +8 -0
  15. data/config/routes.rb +8 -20
  16. data/lib/active_storage/analyzer/image_analyzer.rb +11 -1
  17. data/lib/active_storage/attached/macros.rb +16 -2
  18. data/lib/active_storage/attached/many.rb +7 -11
  19. data/lib/active_storage/attached/one.rb +13 -14
  20. data/lib/active_storage/engine.rb +13 -4
  21. data/lib/active_storage/gem_version.rb +1 -1
  22. data/lib/active_storage/previewer/mupdf_previewer.rb +36 -0
  23. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +35 -0
  24. data/lib/active_storage/service.rb +5 -0
  25. data/lib/active_storage/service/azure_storage_service.rb +19 -10
  26. data/lib/active_storage/service/disk_service.rb +21 -7
  27. data/lib/active_storage/service/gcs_service.rb +17 -5
  28. data/lib/active_storage/service/mirror_service.rb +1 -1
  29. data/lib/active_storage/service/s3_service.rb +8 -2
  30. metadata +12 -27
  31. data/app/controllers/active_storage/previews_controller.rb +0 -10
  32. data/app/models/active_storage/identification.rb +0 -38
  33. data/lib/active_storage/previewer/pdf_previewer.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bc16265d7932fb2f23dfb0b71e015515203f8a149b027e404e1bf06573b93d0
4
- data.tar.gz: bb8be52b8407b7d58eb79783ef4d520d587c49af5ac7403f900c7476c46004db
3
+ metadata.gz: 33c951e662aecba7002316ed527639dd5d34fbd6ee8e9fc4761b5340a1f8921e
4
+ data.tar.gz: 2de8ce856d601c374b412f6cfe1fcfe350676cbfa82194c081a6ca4508057247
5
5
  SHA512:
6
- metadata.gz: f5acac6993a14450d216ff15f3c4bca7344044b5c108ec7c75fdb85ee6436aee209cd747f4ed8c54afd73ff6c83f2f2d36e414cdfa20336ea4bf90a47e944965
7
- data.tar.gz: e45364d1e7575d2119a54b09f5c9f943206a47701adb5508f47684fb42720f4ae53824418739643242255ed12e6ab983d20ee127ce769d01e228b9c2832c59cb
6
+ metadata.gz: d5f44e8b8a474f1cc391f3875d232c44dbd676199733f2522d9bbe3a6da65041868dca229f921fa2ecf654b12fb277fdfa3ed0ec5972cc11893ab53824fbd15f
7
+ data.tar.gz: 95126d61ada21c590de7f0cf4da8a76c202a7f500283429e60871e2a0f286b5cf1aab200c9459782f795bdb6684e22179c94d6237787a94a9749fdcf44089d22
@@ -1,3 +1,40 @@
1
+ ## Rails 5.2.0.rc2 (March 20, 2018) ##
2
+
3
+ * Allow full use of the AWS S3 SDK options for authentication. If an
4
+ explicit AWS key pair and/or region is not provided in `storage.yml`,
5
+ attempt to use environment variables, shared credentials, or IAM
6
+ (instance or task) role credentials. Order of precedence is determined
7
+ by the [AWS SDK](https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html).
8
+
9
+ *Brian Knight*
10
+
11
+ * Remove path config option from Azure service.
12
+
13
+ The Active Storage service for Azure Storage has an option called `path`
14
+ that is ambiguous in meaning. It needs to be set to the primary blob
15
+ storage endpoint but that can be determined from the blobs client anyway.
16
+
17
+ To simplify the configuration, we've removed the `path` option and
18
+ now get the endpoint from the blobs client instead.
19
+
20
+ Closes #32225.
21
+
22
+ *Andrew White*
23
+
24
+ * Generate root-relative paths in disk service URL methods.
25
+
26
+ Obviate the disk service's `:host` configuration option.
27
+
28
+ *George Claghorn*
29
+
30
+ * Add source code to published npm package.
31
+
32
+ This allows activestorage users to depend on the javascript source code
33
+ rather than the compiled code, which can produce smaller javascript bundles.
34
+
35
+ *Richard Macklin*
36
+
37
+
1
38
  ## Rails 5.2.0.rc1 (January 30, 2018) ##
2
39
 
3
40
  * Preserve display aspect ratio when extracting width and height from videos
@@ -14,7 +51,6 @@
14
51
 
15
52
  *Hiroki Zenigami*
16
53
 
17
-
18
54
  * Force `:attachment` disposition for specific, configurable content types.
19
55
  This mitigates possible security issues such as XSS or phishing when
20
56
  serving them inline. A list of such content types is included by default,
data/README.md CHANGED
@@ -8,7 +8,7 @@ Image files can furthermore be transformed using on-demand variants for quality,
8
8
 
9
9
  ## Compared to other storage solutions
10
10
 
11
- A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/rails/blob/master/activestorage/app/models/active_storage/blob.rb) and [Attachment](https://github.com/rails/rails/blob/master/activestorage/app/models/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the `Attachment` join model, which then connects to the actual `Blob`.
11
+ A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/rails/blob/5-2-stable/activestorage/app/models/active_storage/blob.rb) and [Attachment](https://github.com/rails/rails/blob/5-2-stable/activestorage/app/models/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the `Attachment` join model, which then connects to the actual `Blob`.
12
12
 
13
13
  `Blob` models store attachment metadata (filename, content-type, etc.), and their identifier key in the storage service. Blob models do not store the actual binary data. They are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing one (though of course you can delete the previous version later if you don't need it).
14
14
 
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=t.disabled,i=r.bubbles,a=r.cancelable,u=r.detail,o=document.createEvent("Event");o.initEvent(e,i||!0,a||!0),o.detail=u||{};try{t.disabled=!1,t.dispatchEvent(o)}finally{t.disabled=n}return o}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i<r;i++)n[i-2]=arguments[i];return t[e].apply(t,n)}}r.d(e,"a",function(){return c});var a=r(6),u=r(8),o=r(9),s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),f=0,c=function(){function t(e,r,i){n(this,t),this.id=++f,this.file=e,this.url=r,this.delegate=i}return s(t,[{key:"create",value:function(t){var e=this;a.a.create(this.file,function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.focus(),e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o="input[type=file][data-direct-upload-url]:not([disabled])",s=function(){function t(e){n(this,t),this.form=e,this.inputs=Object(a.c)(e,o).filter(function(t){return t.files.length})}return u(t,[{key:"start",value:function(t){var e=this,r=this.createDirectUploadControllers();this.dispatch("start"),function n(){var i=r.shift();i?i.start(function(r){r?(t(r),e.dispatch("end")):n()}):(t(),e.dispatch("end"))}()}},{key:"createDirectUploadControllers",value:function(){var t=[];return this.inputs.forEach(function(e){Object(a.e)(e.files).forEach(function(r){var n=new i.a(e,r);t.push(n)})}),t}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=function(){function t(e,r){n(this,t),this.input=e,this.file=r,this.directUpload=new i.a(this.file,this.url,this),this.dispatch("initialize")}return u(t,[{key:"start",value:function(t){var e=this,r=document.createElement("input");r.type="hidden",r.name=this.input.name,this.input.insertAdjacentElement("beforebegin",r),this.dispatch("start"),this.directUpload.create(function(n,i){n?(r.parentNode.removeChild(r),e.dispatchError(n)):r.value=i.signed_id,e.dispatch("end"),t(n)})}},{key:"uploadRequestDidProgress",value:function(t){var e=t.loaded/t.total*100;e&&this.dispatch("progress",{progress:e})}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSlice,s=function(){function t(e){n(this,t),this.file=e,this.chunkSize=2097152,this.chunkCount=Math.ceil(this.file.size/this.chunkSize),this.chunkIndex=0}return u(t,null,[{key:"create",value:function(e,r){new t(e).create(r)}}]),u(t,[{key:"create",value:function(t){var e=this;this.callback=t,this.md5Buffer=new a.a.ArrayBuffer,this.fileReader=new FileReader,this.fileReader.addEventListener("load",function(t){return e.fileReaderDidLoad(t)}),this.fileReader.addEventListener("error",function(t){return e.fileReaderDidError(t)}),this.readNextChunk()}},{key:"fileReaderDidLoad",value:function(t){if(this.md5Buffer.append(t.target.result),!this.readNextChunk()){var e=this.md5Buffer.end(!0),r=btoa(e);this.callback(null,r)}}},{key:"fileReaderDidError",value:function(t){this.callback("Error reading "+this.file.name)}},{key:"readNextChunk",value:function(){if(this.chunkIndex<this.chunkCount){var t=this.chunkIndex*this.chunkSize,e=Math.min(t+this.chunkSize,this.file.size),r=o.call(this.file,t,e);return this.fileReader.readAsArrayBuffer(r),this.chunkIndex++,!0}return!1}}]),t}()},function(t,e,r){!function(e){t.exports=e()}(function(t){"use strict";function e(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r+=(n&i|~n&a)+e[0]-680876936|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n<i;n+=1)a[n>>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64<f?t.subarray(r-64):new Uint8Array(0),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r=0;r<i;r+=1)a[r>>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e<t.length;e+=1)t[e]=u(t[e]);return t.join("")}function s(t){return/[\u0080-\uFFFF]/.test(t)&&(t=unescape(encodeURIComponent(t))),t}function f(t,e){var r,n=t.length,i=new ArrayBuffer(n),a=new Uint8Array(i);for(r=0;r<n;r+=1)a[r]=t.charCodeAt(r);return e?a:i}function c(t){return String.fromCharCode.apply(null,new Uint8Array(t))}function h(t,e,r){var n=new Uint8Array(t.byteLength+e.byteLength);return n.set(new Uint8Array(t)),n.set(new Uint8Array(e),t.byteLength),r?n:n.buffer}function l(t){var e,r=[],n=t.length;for(e=0;e<n-1;e+=2)r.push(parseInt(t.substr(e,2),16));return String.fromCharCode.apply(String,r)}function d(){this.reset()}var p=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];return"5d41402abc4b2a76b9719d911017c592"!==o(i("hello"))&&function(t,e){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64<a?new Uint8Array(i.buffer.slice(r-64)):new Uint8Array(0),this},d.ArrayBuffer.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),u=function(){function t(e,r,a){var u=this;n(this,t),this.file=e,this.attributes={filename:e.name,content_type:e.type,byte_size:e.size,checksum:r},this.xhr=new XMLHttpRequest,this.xhr.open("POST",a,!0),this.xhr.responseType="json",this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.setRequestHeader("Accept","application/json"),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.setRequestHeader("X-CSRF-Token",Object(i.d)("csrf-token")),this.xhr.addEventListener("load",function(t){return u.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return u.requestDidError(t)})}return a(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(JSON.stringify({blob:this.attributes}))}},{key:"requestDidLoad",value:function(t){if(this.status>=200&&this.status<300){var e=this.response,r=e.direct_upload;delete e.direct_upload,this.attributes=e,this.directUploadData=r,this.callback(null,this.toJSON())}else this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}},{key:"status",get:function(){return this.xhr.status}},{key:"response",get:function(){var t=this.xhr,e=t.responseType,r=t.response;return"json"==e?r:JSON.parse(r)}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=function(){function t(e){var r=this;n(this,t),this.blob=e,this.file=e.file;var i=e.directUploadData,a=i.url,u=i.headers;this.xhr=new XMLHttpRequest,this.xhr.open("PUT",a,!0),this.xhr.responseType="text";for(var o in u)this.xhr.setRequestHeader(o,u[o]);this.xhr.addEventListener("load",function(t){return r.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return r.requestDidError(t)})}return i(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(this.file)}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;r>=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])});
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=t.disabled,i=r.bubbles,a=r.cancelable,u=r.detail,o=document.createEvent("Event");o.initEvent(e,i||!0,a||!0),o.detail=u||{};try{t.disabled=!1,t.dispatchEvent(o)}finally{t.disabled=n}return o}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i<r;i++)n[i-2]=arguments[i];return t[e].apply(t,n)}}r.d(e,"a",function(){return c});var a=r(6),u=r(8),o=r(9),s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),f=0,c=function(){function t(e,r,i){n(this,t),this.id=++f,this.file=e,this.url=r,this.delegate=i}return s(t,[{key:"create",value:function(t){var e=this;a.a.create(this.file,function(r,n){if(r)return void t(r);var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.focus(),e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o="input[type=file][data-direct-upload-url]:not([disabled])",s=function(){function t(e){n(this,t),this.form=e,this.inputs=Object(a.c)(e,o).filter(function(t){return t.files.length})}return u(t,[{key:"start",value:function(t){var e=this,r=this.createDirectUploadControllers();this.dispatch("start"),function n(){var i=r.shift();i?i.start(function(r){r?(t(r),e.dispatch("end")):n()}):(t(),e.dispatch("end"))}()}},{key:"createDirectUploadControllers",value:function(){var t=[];return this.inputs.forEach(function(e){Object(a.e)(e.files).forEach(function(r){var n=new i.a(e,r);t.push(n)})}),t}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=function(){function t(e,r){n(this,t),this.input=e,this.file=r,this.directUpload=new i.a(this.file,this.url,this),this.dispatch("initialize")}return u(t,[{key:"start",value:function(t){var e=this,r=document.createElement("input");r.type="hidden",r.name=this.input.name,this.input.insertAdjacentElement("beforebegin",r),this.dispatch("start"),this.directUpload.create(function(n,i){n?(r.parentNode.removeChild(r),e.dispatchError(n)):r.value=i.signed_id,e.dispatch("end"),t(n)})}},{key:"uploadRequestDidProgress",value:function(t){var e=t.loaded/t.total*100;e&&this.dispatch("progress",{progress:e})}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSlice,s=function(){function t(e){n(this,t),this.file=e,this.chunkSize=2097152,this.chunkCount=Math.ceil(this.file.size/this.chunkSize),this.chunkIndex=0}return u(t,null,[{key:"create",value:function(e,r){new t(e).create(r)}}]),u(t,[{key:"create",value:function(t){var e=this;this.callback=t,this.md5Buffer=new a.a.ArrayBuffer,this.fileReader=new FileReader,this.fileReader.addEventListener("load",function(t){return e.fileReaderDidLoad(t)}),this.fileReader.addEventListener("error",function(t){return e.fileReaderDidError(t)}),this.readNextChunk()}},{key:"fileReaderDidLoad",value:function(t){if(this.md5Buffer.append(t.target.result),!this.readNextChunk()){var e=this.md5Buffer.end(!0),r=btoa(e);this.callback(null,r)}}},{key:"fileReaderDidError",value:function(t){this.callback("Error reading "+this.file.name)}},{key:"readNextChunk",value:function(){if(this.chunkIndex<this.chunkCount){var t=this.chunkIndex*this.chunkSize,e=Math.min(t+this.chunkSize,this.file.size),r=o.call(this.file,t,e);return this.fileReader.readAsArrayBuffer(r),this.chunkIndex++,!0}return!1}}]),t}()},function(t,e,r){!function(e){t.exports=e()}(function(t){"use strict";function e(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r+=(n&i|~n&a)+e[0]-680876936|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n<i;n+=1)a[n>>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64<f?t.subarray(r-64):new Uint8Array(0),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r=0;r<i;r+=1)a[r>>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e<t.length;e+=1)t[e]=u(t[e]);return t.join("")}function s(t){return/[\u0080-\uFFFF]/.test(t)&&(t=unescape(encodeURIComponent(t))),t}function f(t,e){var r,n=t.length,i=new ArrayBuffer(n),a=new Uint8Array(i);for(r=0;r<n;r+=1)a[r]=t.charCodeAt(r);return e?a:i}function c(t){return String.fromCharCode.apply(null,new Uint8Array(t))}function h(t,e,r){var n=new Uint8Array(t.byteLength+e.byteLength);return n.set(new Uint8Array(t)),n.set(new Uint8Array(e),t.byteLength),r?n:n.buffer}function l(t){var e,r=[],n=t.length;for(e=0;e<n-1;e+=2)r.push(parseInt(t.substr(e,2),16));return String.fromCharCode.apply(String,r)}function d(){this.reset()}var p=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];return"5d41402abc4b2a76b9719d911017c592"!==o(i("hello"))&&function(t,e){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64<a?new Uint8Array(i.buffer.slice(r-64)):new Uint8Array(0),this},d.ArrayBuffer.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),u=function(){function t(e,r,a){var u=this;n(this,t),this.file=e,this.attributes={filename:e.name,content_type:e.type,byte_size:e.size,checksum:r},this.xhr=new XMLHttpRequest,this.xhr.open("POST",a,!0),this.xhr.responseType="json",this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.setRequestHeader("Accept","application/json"),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.setRequestHeader("X-CSRF-Token",Object(i.d)("csrf-token")),this.xhr.addEventListener("load",function(t){return u.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return u.requestDidError(t)})}return a(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(JSON.stringify({blob:this.attributes}))}},{key:"requestDidLoad",value:function(t){if(this.status>=200&&this.status<300){var e=this.response,r=e.direct_upload;delete e.direct_upload,this.attributes=e,this.directUploadData=r,this.callback(null,this.toJSON())}else this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}},{key:"status",get:function(){return this.xhr.status}},{key:"response",get:function(){var t=this.xhr,e=t.responseType,r=t.response;return"json"==e?r:JSON.parse(r)}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=function(){function t(e){var r=this;n(this,t),this.blob=e,this.file=e.file;var i=e.directUploadData,a=i.url,u=i.headers;this.xhr=new XMLHttpRequest,this.xhr.open("PUT",a,!0),this.xhr.responseType="text";for(var o in u)this.xhr.setRequestHeader(o,u[o]);this.xhr.addEventListener("load",function(t){return r.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return r.requestDidError(t)})}return i(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(this.file.slice())}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;r>=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])});
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Take a signed permanent reference for a variant and turn it into an expiring service URL for download.
3
+ # Take a signed permanent reference for a blob representation and turn it into an expiring service URL for download.
4
4
  # Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
5
5
  # security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own
6
6
  # authenticated redirection controller.
7
- class ActiveStorage::VariantsController < ActionController::Base
7
+ class ActiveStorage::RepresentationsController < ActionController::Base
8
8
  include ActiveStorage::SetBlob
9
9
 
10
10
  def show
11
11
  expires_in ActiveStorage::Blob.service.url_expires_in
12
- redirect_to ActiveStorage::Variant.new(@blob, params[:variation_key]).processed.service_url(disposition: params[:disposition])
12
+ redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
13
13
  end
14
14
  end
@@ -17,7 +17,7 @@ export class BlobUpload {
17
17
 
18
18
  create(callback) {
19
19
  this.callback = callback
20
- this.xhr.send(this.file)
20
+ this.xhr.send(this.file.slice())
21
21
  }
22
22
 
23
23
  requestDidLoad(event) {
@@ -14,8 +14,14 @@ export class DirectUpload {
14
14
 
15
15
  create(callback) {
16
16
  FileChecksum.create(this.file, (error, checksum) => {
17
+ if (error) {
18
+ callback(error)
19
+ return
20
+ }
21
+
17
22
  const blob = new BlobRecord(this.file, checksum, this.url)
18
23
  notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
24
+
19
25
  blob.create(error => {
20
26
  if (error) {
21
27
  callback(error)
@@ -14,17 +14,25 @@
14
14
  # update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file.
15
15
  # If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one.
16
16
  class ActiveStorage::Blob < ActiveRecord::Base
17
- include Analyzable, Identifiable, Representable
17
+ require_dependency "active_storage/blob/analyzable"
18
+ require_dependency "active_storage/blob/identifiable"
19
+ require_dependency "active_storage/blob/representable"
20
+
21
+ include Analyzable
22
+ include Identifiable
23
+ include Representable
18
24
 
19
25
  self.table_name = "active_storage_blobs"
20
26
 
21
27
  has_secure_token :key
22
- store :metadata, accessors: [ :analyzed, :identified ], coder: JSON
28
+ store :metadata, accessors: [ :analyzed, :identified ], coder: ActiveRecord::Coders::JSON
23
29
 
24
30
  class_attribute :service
25
31
 
26
32
  has_many :attachments
27
33
 
34
+ scope :unattached, -> { left_joins(:attachments).where(ActiveStorage::Attachment.table_name => { blob_id: nil }) }
35
+
28
36
  class << self
29
37
  # You can used the signed ID of a blob to refer to it on the client side without fear of tampering.
30
38
  # This is particularly helpful for direct uploads where the client-side needs to refer to the blob
@@ -109,8 +117,11 @@ class ActiveStorage::Blob < ActiveRecord::Base
109
117
  # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
110
118
  # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
111
119
  # it allows permanent URLs that redirect to the +service_url+ to be cached in the view.
112
- def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: self.filename)
113
- service.url key, expires_in: expires_in, disposition: forcibly_serve_as_binary? ? :attachment : disposition, filename: filename, content_type: content_type
120
+ def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: nil, **options)
121
+ filename = ActiveStorage::Filename.wrap(filename || self.filename)
122
+
123
+ service.url key, expires_in: expires_in, filename: filename, content_type: content_type,
124
+ disposition: forcibly_serve_as_binary? ? :attachment : disposition, **options
114
125
  end
115
126
 
116
127
  # Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be
@@ -191,4 +202,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
191
202
  def forcibly_serve_as_binary?
192
203
  ActiveStorage.content_types_to_serve_as_binary.include?(content_type)
193
204
  end
205
+
206
+ ActiveSupport.run_load_hooks(:active_storage_blob, self)
194
207
  end
@@ -2,10 +2,19 @@
2
2
 
3
3
  module ActiveStorage::Blob::Identifiable
4
4
  def identify
5
- ActiveStorage::Identification.new(self).apply
5
+ update! content_type: identify_content_type, identified: true unless identified?
6
6
  end
7
7
 
8
8
  def identified?
9
9
  identified
10
10
  end
11
+
12
+ private
13
+ def identify_content_type
14
+ Marcel::MimeType.for download_identifiable_chunk, name: filename.to_s, declared_type: content_type
15
+ end
16
+
17
+ def download_identifiable_chunk
18
+ service.download_chunk key, 0...4.kilobytes
19
+ end
11
20
  end
@@ -27,7 +27,7 @@ module ActiveStorage::Blob::Representable
27
27
  # variable, call ActiveStorage::Blob#variable?.
28
28
  def variant(transformations)
29
29
  if variable?
30
- ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations))
30
+ ActiveStorage::Variant.new(self, transformations)
31
31
  else
32
32
  raise ActiveStorage::InvariableError
33
33
  end
@@ -55,7 +55,7 @@ module ActiveStorage::Blob::Representable
55
55
  # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
56
56
  def preview(transformations)
57
57
  if previewable?
58
- ActiveStorage::Preview.new(self, ActiveStorage::Variation.wrap(transformations))
58
+ ActiveStorage::Preview.new(self, transformations)
59
59
  else
60
60
  raise ActiveStorage::UnpreviewableError
61
61
  end
@@ -3,8 +3,18 @@
3
3
  # Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
4
4
  # A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
5
5
  class ActiveStorage::Filename
6
+ require_dependency "active_storage/filename/parameters"
7
+
6
8
  include Comparable
7
9
 
10
+ class << self
11
+ # Returns a Filename instance based on the given filename. If the filename is a Filename, it is
12
+ # returned unmodified. If it is a String, it is passed to ActiveStorage::Filename.new.
13
+ def wrap(filename)
14
+ filename.kind_of?(self) ? filename : new(filename)
15
+ end
16
+ end
17
+
8
18
  def initialize(filename)
9
19
  @filename = filename
10
20
  end
@@ -21,10 +21,9 @@
21
21
  #
22
22
  # Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
23
23
  #
24
- # The built-in previewers rely on third-party system libraries:
25
- #
26
- # * {ffmpeg}[https://www.ffmpeg.org]
27
- # * {mupdf}[https://mupdf.com]
24
+ # The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
25
+ # {ffmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
26
+ # and the other requires {mupdf}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or mupdf.
28
27
  #
29
28
  # These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
30
29
  # install and use third-party software, make sure you understand the licensing implications of doing so.
@@ -115,7 +115,7 @@ class ActiveStorage::Variant
115
115
 
116
116
  def download_image
117
117
  require "mini_magick"
118
- MiniMagick::Image.create { |file| download_blob_to(file) }
118
+ MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) }
119
119
  end
120
120
 
121
121
  def transform(image)
@@ -8,6 +8,14 @@
8
8
  #
9
9
  # ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
10
10
  #
11
+ # You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
12
+ #
13
+ # ActiveStorage::Variation.new(combine_options: {
14
+ # resize: "100x100^",
15
+ # gravity: "center",
16
+ # crop: "100x100+0+0",
17
+ # })
18
+ #
11
19
  # A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
12
20
  class ActiveStorage::Variation
13
21
  attr_reader :transformations
@@ -11,30 +11,18 @@ Rails.application.routes.draw do
11
11
  resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
12
12
 
13
13
 
14
- get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation
14
+ get "/rails/active_storage/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
15
15
 
16
- direct :rails_variant do |variant, options|
17
- signed_blob_id = variant.blob.signed_id
18
- variation_key = variant.variation.key
19
- filename = variant.blob.filename
16
+ direct :rails_representation do |representation, options|
17
+ signed_blob_id = representation.blob.signed_id
18
+ variation_key = representation.variation.key
19
+ filename = representation.blob.filename
20
20
 
21
- route_for(:rails_blob_variation, signed_blob_id, variation_key, filename, options)
21
+ route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
22
22
  end
23
23
 
24
- resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_variant, variant, options) }
25
-
26
-
27
- get "/rails/active_storage/previews/:signed_blob_id/:variation_key/*filename" => "active_storage/previews#show", as: :rails_blob_preview
28
-
29
- direct :rails_preview do |preview, options|
30
- signed_blob_id = preview.blob.signed_id
31
- variation_key = preview.variation.key
32
- filename = preview.blob.filename
33
-
34
- route_for(:rails_blob_preview, signed_blob_id, variation_key, filename, options)
35
- end
36
-
37
- resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_preview, preview, options) }
24
+ resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_representation, variant, options) }
25
+ resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
38
26
 
39
27
 
40
28
  get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
@@ -3,6 +3,8 @@
3
3
  module ActiveStorage
4
4
  # Extracts width and height in pixels from an image blob.
5
5
  #
6
+ # If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience.
7
+ #
6
8
  # Example:
7
9
  #
8
10
  # ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
@@ -17,7 +19,11 @@ module ActiveStorage
17
19
 
18
20
  def metadata
19
21
  read_image do |image|
20
- { width: image.width, height: image.height }
22
+ if rotated_image?(image)
23
+ { width: image.height, height: image.width }
24
+ else
25
+ { width: image.width, height: image.height }
26
+ end
21
27
  end
22
28
  rescue LoadError
23
29
  logger.info "Skipping image analysis because the mini_magick gem isn't installed"
@@ -31,5 +37,9 @@ module ActiveStorage
31
37
  yield MiniMagick::Image.new(file.path)
32
38
  end
33
39
  end
40
+
41
+ def rotated_image?(image)
42
+ %w[ RightTop LeftBottom ].include?(image["%[orientation]"])
43
+ end
34
44
  end
35
45
  end
@@ -38,13 +38,15 @@ module ActiveStorage
38
38
  end
39
39
  CODE
40
40
 
41
- has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record
41
+ has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false
42
42
  has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
43
43
 
44
44
  scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
45
45
 
46
46
  if dependent == :purge_later
47
47
  after_destroy_commit { public_send(name).purge_later }
48
+ else
49
+ before_destroy { public_send(name).detach }
48
50
  end
49
51
  end
50
52
 
@@ -83,13 +85,25 @@ module ActiveStorage
83
85
  end
84
86
  CODE
85
87
 
86
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record
88
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: false do
89
+ def purge
90
+ each(&:purge)
91
+ reset
92
+ end
93
+
94
+ def purge_later
95
+ each(&:purge_later)
96
+ reset
97
+ end
98
+ end
87
99
  has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
88
100
 
89
101
  scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
90
102
 
91
103
  if dependent == :purge_later
92
104
  after_destroy_commit { public_send(name).purge_later }
105
+ else
106
+ before_destroy { public_send(name).detach }
93
107
  end
94
108
  end
95
109
  end
@@ -44,20 +44,16 @@ module ActiveStorage
44
44
  attachments.destroy_all if attached?
45
45
  end
46
46
 
47
+ ##
48
+ # :method: purge
49
+ #
47
50
  # Directly purges each associated attachment (i.e. destroys the blobs and
48
51
  # attachments and deletes the files on the service).
49
- def purge
50
- if attached?
51
- attachments.each(&:purge)
52
- attachments.reload
53
- end
54
- end
55
52
 
53
+
54
+ ##
55
+ # :method: purge_later
56
+ #
56
57
  # Purges each associated attachment through the queuing system.
57
- def purge_later
58
- if attached?
59
- attachments.each(&:purge_later)
60
- end
61
- end
62
58
  end
63
59
  end
@@ -20,10 +20,16 @@ module ActiveStorage
20
20
  # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
21
21
  # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
22
22
  def attach(attachable)
23
- if attached? && dependent == :purge_later
24
- replace attachable
25
- else
26
- write_attachment build_attachment_from(attachable)
23
+ blob_was = blob if attached?
24
+ blob = create_blob_from(attachable)
25
+
26
+ unless blob == blob_was
27
+ transaction do
28
+ detach
29
+ write_attachment build_attachment(blob: blob)
30
+ end
31
+
32
+ blob_was.purge_later if blob_was && dependent == :purge_later
27
33
  end
28
34
  end
29
35
 
@@ -63,17 +69,10 @@ module ActiveStorage
63
69
  end
64
70
 
65
71
  private
66
- def replace(attachable)
67
- blob.tap do
68
- transaction do
69
- detach
70
- write_attachment build_attachment_from(attachable)
71
- end
72
- end.purge_later
73
- end
72
+ delegate :transaction, to: :record
74
73
 
75
- def build_attachment_from(attachable)
76
- ActiveStorage::Attachment.new(record: record, name: name, blob: create_blob_from(attachable))
74
+ def build_attachment(blob:)
75
+ ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
77
76
  end
78
77
 
79
78
  def write_attachment(attachment)
@@ -3,7 +3,8 @@
3
3
  require "rails"
4
4
  require "active_storage"
5
5
 
6
- require "active_storage/previewer/pdf_previewer"
6
+ require "active_storage/previewer/poppler_pdf_previewer"
7
+ require "active_storage/previewer/mupdf_previewer"
7
8
  require "active_storage/previewer/video_previewer"
8
9
 
9
10
  require "active_storage/analyzer/image_analyzer"
@@ -14,11 +15,19 @@ module ActiveStorage
14
15
  isolate_namespace ActiveStorage
15
16
 
16
17
  config.active_storage = ActiveSupport::OrderedOptions.new
17
- config.active_storage.previewers = [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
18
+ config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
18
19
  config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
19
20
  config.active_storage.paths = ActiveSupport::OrderedOptions.new
20
21
 
21
- config.active_storage.variable_content_types = %w( image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop )
22
+ config.active_storage.variable_content_types = %w(
23
+ image/png
24
+ image/gif
25
+ image/jpg
26
+ image/jpeg
27
+ image/vnd.adobe.photoshop
28
+ image/vnd.microsoft.icon
29
+ )
30
+
22
31
  config.active_storage.content_types_to_serve_as_binary = %w(
23
32
  text/html
24
33
  text/javascript
@@ -60,7 +69,7 @@ module ActiveStorage
60
69
  end
61
70
 
62
71
  initializer "active_storage.services" do
63
- config.to_prepare do
72
+ ActiveSupport.on_load(:active_storage_blob) do
64
73
  if config_choice = Rails.configuration.active_storage.service
65
74
  configs = Rails.configuration.active_storage.service_configurations ||= begin
66
75
  config_file = Pathname.new(Rails.root.join("config/storage.yml"))
@@ -10,7 +10,7 @@ module ActiveStorage
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
12
  TINY = 0
13
- PRE = "rc1"
13
+ PRE = "rc2"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Previewer::MuPDFPreviewer < Previewer
5
+ class << self
6
+ def accept?(blob)
7
+ blob.content_type == "application/pdf" && mutool_exists?
8
+ end
9
+
10
+ def mutool_path
11
+ ActiveStorage.paths[:mutool] || "mutool"
12
+ end
13
+
14
+ def mutool_exists?
15
+ return @mutool_exists unless @mutool_exists.nil?
16
+
17
+ system mutool_path, out: File::NULL, err: File::NULL
18
+
19
+ @mutool_exists = $?.exitstatus == 1
20
+ end
21
+ end
22
+
23
+ def preview
24
+ download_blob_to_tempfile do |input|
25
+ draw_first_page_from input do |output|
26
+ yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+ def draw_first_page_from(file, &block)
33
+ draw self.class.mutool_path, "draw", "-F", "png", "-o", "-", file.path, "1", &block
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Previewer::PopplerPDFPreviewer < Previewer
5
+ class << self
6
+ def accept?(blob)
7
+ blob.content_type == "application/pdf" && pdftoppm_exists?
8
+ end
9
+
10
+ def pdftoppm_path
11
+ ActiveStorage.paths[:pdftoppm] || "pdftoppm"
12
+ end
13
+
14
+ def pdftoppm_exists?
15
+ return @pdftoppm_exists unless @pdftoppm_exists.nil?
16
+
17
+ @pdftoppm_exists = system(pdftoppm_path, "-v", out: File::NULL, err: File::NULL)
18
+ end
19
+ end
20
+
21
+ def preview
22
+ download_blob_to_tempfile do |input|
23
+ draw_first_page_from input do |output|
24
+ yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+ def draw_first_page_from(file, &block)
31
+ # use 72 dpi to match thumbnail dimesions of the PDF
32
+ draw self.class.pdftoppm_path, "-singlefile", "-r", "72", "-png", file.path, &block
33
+ end
34
+ end
35
+ end
@@ -73,6 +73,11 @@ module ActiveStorage
73
73
  raise NotImplementedError
74
74
  end
75
75
 
76
+ # Return the partial content in the byte +range+ of the file at the +key+.
77
+ def download_chunk(key, range)
78
+ raise NotImplementedError
79
+ end
80
+
76
81
  # Delete the file at the +key+.
77
82
  def delete(key)
78
83
  raise NotImplementedError
@@ -8,14 +8,13 @@ module ActiveStorage
8
8
  # Wraps the Microsoft Azure Storage Blob Service as an Active Storage service.
9
9
  # See ActiveStorage::Service for the generic API documentation that applies to all services.
10
10
  class Service::AzureStorageService < Service
11
- attr_reader :client, :path, :blobs, :container, :signer
11
+ attr_reader :client, :blobs, :container, :signer
12
12
 
13
- def initialize(path:, storage_account_name:, storage_access_key:, container:)
13
+ def initialize(storage_account_name:, storage_access_key:, container:)
14
14
  @client = Azure::Storage::Client.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key)
15
15
  @signer = Azure::Storage::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key)
16
16
  @blobs = client.blob_client
17
17
  @container = container
18
- @path = path
19
18
  end
20
19
 
21
20
  def upload(key, io, checksum: nil)
@@ -41,6 +40,13 @@ module ActiveStorage
41
40
  end
42
41
  end
43
42
 
43
+ def download_chunk(key, range)
44
+ instrument :download_chunk, key: key, range: range do
45
+ _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
46
+ io.force_encoding(Encoding::BINARY)
47
+ end
48
+ end
49
+
44
50
  def delete(key)
45
51
  instrument :delete, key: key do
46
52
  begin
@@ -77,9 +83,9 @@ module ActiveStorage
77
83
 
78
84
  def url(key, expires_in:, filename:, disposition:, content_type:)
79
85
  instrument :url, key: key do |payload|
80
- base_url = url_for(key)
81
86
  generated_url = signer.signed_uri(
82
- URI(base_url), false,
87
+ uri_for(key), false,
88
+ service: "b",
83
89
  permissions: "r",
84
90
  expiry: format_expiry(expires_in),
85
91
  content_disposition: content_disposition_with(type: disposition, filename: filename),
@@ -94,9 +100,12 @@ module ActiveStorage
94
100
 
95
101
  def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
96
102
  instrument :url, key: key do |payload|
97
- base_url = url_for(key)
98
- generated_url = signer.signed_uri(URI(base_url), false, permissions: "rw",
99
- expiry: format_expiry(expires_in)).to_s
103
+ generated_url = signer.signed_uri(
104
+ uri_for(key), false,
105
+ service: "b",
106
+ permissions: "rw",
107
+ expiry: format_expiry(expires_in)
108
+ ).to_s
100
109
 
101
110
  payload[:url] = generated_url
102
111
 
@@ -109,8 +118,8 @@ module ActiveStorage
109
118
  end
110
119
 
111
120
  private
112
- def url_for(key)
113
- "#{path}/#{container}/#{key}"
121
+ def uri_for(key)
122
+ blobs.generate_uri("#{container}/#{key}")
114
123
  end
115
124
 
116
125
  def blob_for(key)
@@ -9,10 +9,10 @@ module ActiveStorage
9
9
  # Wraps a local disk path as an Active Storage service. See ActiveStorage::Service for the generic API
10
10
  # documentation that applies to all services.
11
11
  class Service::DiskService < Service
12
- attr_reader :root, :host
12
+ attr_reader :root
13
13
 
14
- def initialize(root:, host: "http://localhost:3000")
15
- @root, @host = root, host
14
+ def initialize(root:)
15
+ @root = root
16
16
  end
17
17
 
18
18
  def upload(key, io, checksum: nil)
@@ -38,6 +38,15 @@ module ActiveStorage
38
38
  end
39
39
  end
40
40
 
41
+ def download_chunk(key, range)
42
+ instrument :download_chunk, key: key, range: range do
43
+ File.open(path_for(key), "rb") do |file|
44
+ file.seek range.begin
45
+ file.read range.size
46
+ end
47
+ end
48
+ end
49
+
41
50
  def delete(key)
42
51
  instrument :delete, key: key do
43
52
  begin
@@ -69,12 +78,11 @@ module ActiveStorage
69
78
  verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key)
70
79
 
71
80
  generated_url =
72
- Rails.application.routes.url_helpers.rails_disk_service_url(
81
+ url_helpers.rails_disk_service_path(
73
82
  verified_key_with_expiration,
74
83
  filename: filename,
75
84
  disposition: content_disposition_with(type: disposition, filename: filename),
76
- content_type: content_type,
77
- host: host
85
+ content_type: content_type
78
86
  )
79
87
 
80
88
  payload[:url] = generated_url
@@ -96,7 +104,7 @@ module ActiveStorage
96
104
  purpose: :blob_token }
97
105
  )
98
106
 
99
- generated_url = Rails.application.routes.url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: host)
107
+ generated_url = url_helpers.update_rails_disk_service_path(verified_token_with_expiration)
100
108
 
101
109
  payload[:url] = generated_url
102
110
 
@@ -121,11 +129,17 @@ module ActiveStorage
121
129
  path_for(key).tap { |path| FileUtils.mkdir_p File.dirname(path) }
122
130
  end
123
131
 
132
+
124
133
  def ensure_integrity_of(key, checksum)
125
134
  unless Digest::MD5.file(path_for(key)).base64digest == checksum
126
135
  delete key
127
136
  raise ActiveStorage::IntegrityError
128
137
  end
129
138
  end
139
+
140
+
141
+ def url_helpers
142
+ @url_helpers ||= Rails.application.routes.url_helpers
143
+ end
130
144
  end
131
145
  end
@@ -3,7 +3,10 @@
3
3
  gem "google-cloud-storage", "~> 1.8"
4
4
 
5
5
  require "google/cloud/storage"
6
+ require "net/http"
7
+
6
8
  require "active_support/core_ext/object/to_query"
9
+ require "active_storage/filename"
7
10
 
8
11
  module ActiveStorage
9
12
  # Wraps the Google Cloud Storage as an Active Storage service. See ActiveStorage::Service for the generic API
@@ -43,6 +46,16 @@ module ActiveStorage
43
46
  end
44
47
  end
45
48
 
49
+ def download_chunk(key, range)
50
+ instrument :download_chunk, key: key, range: range do
51
+ uri = URI(url(key, expires_in: 30.seconds, filename: ActiveStorage::Filename.new(""), content_type: "application/octet-stream", disposition: "inline"))
52
+
53
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
54
+ client.get(uri, "Range" => "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body
55
+ end
56
+ end
57
+ end
58
+
46
59
  def delete(key)
47
60
  instrument :delete, key: key do
48
61
  begin
@@ -80,10 +93,9 @@ module ActiveStorage
80
93
  end
81
94
  end
82
95
 
83
- def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
96
+ def url_for_direct_upload(key, expires_in:, checksum:, **)
84
97
  instrument :url, key: key do |payload|
85
- generated_url = bucket.signed_url key, method: "PUT", expires: expires_in,
86
- content_type: content_type, content_md5: checksum
98
+ generated_url = bucket.signed_url key, method: "PUT", expires: expires_in, content_md5: checksum
87
99
 
88
100
  payload[:url] = generated_url
89
101
 
@@ -91,8 +103,8 @@ module ActiveStorage
91
103
  end
92
104
  end
93
105
 
94
- def headers_for_direct_upload(key, content_type:, checksum:, **)
95
- { "Content-Type" => content_type, "Content-MD5" => checksum }
106
+ def headers_for_direct_upload(key, checksum:, **)
107
+ { "Content-MD5" => checksum }
96
108
  end
97
109
 
98
110
  private
@@ -9,7 +9,7 @@ module ActiveStorage
9
9
  class Service::MirrorService < Service
10
10
  attr_reader :primary, :mirrors
11
11
 
12
- delegate :download, :exist?, :url, to: :primary
12
+ delegate :download, :download_chunk, :exist?, :url, to: :primary
13
13
 
14
14
  # Stitch together from named services.
15
15
  def self.build(primary:, mirrors:, configurator:, **options) #:nodoc:
@@ -9,8 +9,8 @@ module ActiveStorage
9
9
  class Service::S3Service < Service
10
10
  attr_reader :client, :bucket, :upload_options
11
11
 
12
- def initialize(access_key_id:, secret_access_key:, region:, bucket:, upload: {}, **options)
13
- @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, **options)
12
+ def initialize(bucket:, upload: {}, **options)
13
+ @client = Aws::S3::Resource.new(**options)
14
14
  @bucket = @client.bucket(bucket)
15
15
 
16
16
  @upload_options = upload
@@ -38,6 +38,12 @@ module ActiveStorage
38
38
  end
39
39
  end
40
40
 
41
+ def download_chunk(key, range)
42
+ instrument :download_chunk, key: key, range: range do
43
+ object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY)
44
+ end
45
+ end
46
+
41
47
  def delete(key)
42
48
  instrument :delete, key: key do
43
49
  object_for(key).delete
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0.rc1
4
+ version: 5.2.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-30 00:00:00.000000000 Z
11
+ date: 2018-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 5.2.0.rc1
19
+ version: 5.2.0.rc2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 5.2.0.rc1
26
+ version: 5.2.0.rc2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 5.2.0.rc1
33
+ version: 5.2.0.rc2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 5.2.0.rc1
40
+ version: 5.2.0.rc2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: marcel
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.3.1
55
- - !ruby/object:Gem::Dependency
56
- name: webmock
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 3.2.1
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 3.2.1
69
55
  description: Attach cloud and local files in Rails applications.
70
56
  email: david@loudthinking.com
71
57
  executables: []
@@ -79,8 +65,7 @@ files:
79
65
  - app/controllers/active_storage/blobs_controller.rb
80
66
  - app/controllers/active_storage/direct_uploads_controller.rb
81
67
  - app/controllers/active_storage/disk_controller.rb
82
- - app/controllers/active_storage/previews_controller.rb
83
- - app/controllers/active_storage/variants_controller.rb
68
+ - app/controllers/active_storage/representations_controller.rb
84
69
  - app/controllers/concerns/active_storage/set_blob.rb
85
70
  - app/javascript/activestorage/blob_record.js
86
71
  - app/javascript/activestorage/blob_upload.js
@@ -101,7 +86,6 @@ files:
101
86
  - app/models/active_storage/blob/representable.rb
102
87
  - app/models/active_storage/filename.rb
103
88
  - app/models/active_storage/filename/parameters.rb
104
- - app/models/active_storage/identification.rb
105
89
  - app/models/active_storage/preview.rb
106
90
  - app/models/active_storage/variant.rb
107
91
  - app/models/active_storage/variation.rb
@@ -122,7 +106,8 @@ files:
122
106
  - lib/active_storage/gem_version.rb
123
107
  - lib/active_storage/log_subscriber.rb
124
108
  - lib/active_storage/previewer.rb
125
- - lib/active_storage/previewer/pdf_previewer.rb
109
+ - lib/active_storage/previewer/mupdf_previewer.rb
110
+ - lib/active_storage/previewer/poppler_pdf_previewer.rb
126
111
  - lib/active_storage/previewer/video_previewer.rb
127
112
  - lib/active_storage/service.rb
128
113
  - lib/active_storage/service/azure_storage_service.rb
@@ -137,8 +122,8 @@ homepage: http://rubyonrails.org
137
122
  licenses:
138
123
  - MIT
139
124
  metadata:
140
- source_code_uri: https://github.com/rails/rails/tree/v5.2.0.rc1/activestorage
141
- changelog_uri: https://github.com/rails/rails/blob/v5.2.0.rc1/activestorage/CHANGELOG.md
125
+ source_code_uri: https://github.com/rails/rails/tree/v5.2.0.rc2/activestorage
126
+ changelog_uri: https://github.com/rails/rails/blob/v5.2.0.rc2/activestorage/CHANGELOG.md
142
127
  post_install_message:
143
128
  rdoc_options: []
144
129
  require_paths:
@@ -155,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
140
  version: 1.3.1
156
141
  requirements: []
157
142
  rubyforge_project:
158
- rubygems_version: 2.7.3
143
+ rubygems_version: 2.7.6
159
144
  signing_key:
160
145
  specification_version: 4
161
146
  summary: Local and cloud file storage framework.
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveStorage::PreviewsController < ActionController::Base
4
- include ActiveStorage::SetBlob
5
-
6
- def show
7
- expires_in ActiveStorage::Blob.service.url_expires_in
8
- redirect_to ActiveStorage::Preview.new(@blob, params[:variation_key]).processed.service_url(disposition: params[:disposition])
9
- end
10
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveStorage::Identification
4
- attr_reader :blob
5
-
6
- def initialize(blob)
7
- @blob = blob
8
- end
9
-
10
- def apply
11
- blob.update!(content_type: content_type, identified: true) unless blob.identified?
12
- end
13
-
14
- private
15
- def content_type
16
- Marcel::MimeType.for(identifiable_chunk, name: filename, declared_type: declared_content_type)
17
- end
18
-
19
-
20
- def identifiable_chunk
21
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
22
- client.get(uri, "Range" => "0-4096").body
23
- end
24
- end
25
-
26
- def uri
27
- @uri ||= URI.parse(blob.service_url)
28
- end
29
-
30
-
31
- def filename
32
- blob.filename.to_s
33
- end
34
-
35
- def declared_content_type
36
- blob.content_type
37
- end
38
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveStorage
4
- class Previewer::PDFPreviewer < Previewer
5
- def self.accept?(blob)
6
- blob.content_type == "application/pdf"
7
- end
8
-
9
- def preview
10
- download_blob_to_tempfile do |input|
11
- draw_first_page_from input do |output|
12
- yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
13
- end
14
- end
15
- end
16
-
17
- private
18
- def draw_first_page_from(file, &block)
19
- draw mutool_path, "draw", "-F", "png", "-o", "-", file.path, "1", &block
20
- end
21
-
22
- def mutool_path
23
- ActiveStorage.paths[:mutool] || "mutool"
24
- end
25
- end
26
- end