|  | 
|  | 1 | +package io.udash.utils | 
|  | 2 | + | 
|  | 3 | +import java.io.{IOException, InputStream} | 
|  | 4 | + | 
|  | 5 | +import org.scalajs.dom._ | 
|  | 6 | +import org.scalajs.dom.html.Anchor | 
|  | 7 | +import org.scalajs.dom.raw.Blob | 
|  | 8 | +import scalatags.JsDom | 
|  | 9 | + | 
|  | 10 | +import scala.scalajs.js | 
|  | 11 | +import scala.concurrent.{Future, Promise} | 
|  | 12 | +import scala.scalajs.js.annotation.JSGlobal | 
|  | 13 | +import scala.scalajs.js.typedarray.ArrayBuffer | 
|  | 14 | +import scala.util.Try | 
|  | 15 | + | 
|  | 16 | +@js.native | 
|  | 17 | +@JSGlobal | 
|  | 18 | +sealed class FileReaderSync() extends js.Object { | 
|  | 19 | + def readAsArrayBuffer(blob: Blob): ArrayBuffer = js.native | 
|  | 20 | +} | 
|  | 21 | + | 
|  | 22 | +sealed class FileBufferedInputStream(val file: File) extends InputStream { | 
|  | 23 | + val fileReaderSync = new FileReaderSync() | 
|  | 24 | + | 
|  | 25 | + var filePos: Int = 0 | 
|  | 26 | + var pos: Int = 0 | 
|  | 27 | + | 
|  | 28 | + var buffer: Array[Byte] = Array.empty | 
|  | 29 | + | 
|  | 30 | + override def read(): Int = { | 
|  | 31 | + if (pos >= buffer.length) { | 
|  | 32 | + import js.typedarray._ | 
|  | 33 | + | 
|  | 34 | + if (filePos >= file.size) { | 
|  | 35 | + return -1 | 
|  | 36 | + } | 
|  | 37 | + | 
|  | 38 | + val len = math.min(filePos + 1024, file.size.toInt) | 
|  | 39 | + val slice = file.slice(filePos, len) | 
|  | 40 | + buffer = new Int8Array(fileReaderSync.readAsArrayBuffer(slice)).toArray | 
|  | 41 | + filePos += buffer.length | 
|  | 42 | + pos = 0 | 
|  | 43 | + } | 
|  | 44 | + val r = buffer(pos).toInt & 0xff | 
|  | 45 | + pos += 1 | 
|  | 46 | + r | 
|  | 47 | + } | 
|  | 48 | +} | 
|  | 49 | + | 
|  | 50 | +object FileService { | 
|  | 51 | + | 
|  | 52 | + final val OctetStreamType = "application/octet-stream" | 
|  | 53 | + | 
|  | 54 | + /** | 
|  | 55 | + * Converts specified bytes array to string that contains URL | 
|  | 56 | + * that representing the array given in the parameter with optionally specified mime-type. | 
|  | 57 | + * | 
|  | 58 | + * Keep in mind that returned URL should be revoked via `org.scalajs.dom.revokeObjectURL(url)`. | 
|  | 59 | + */ | 
|  | 60 | + def asURL(bytes: Array[Byte], mimeType: String = OctetStreamType): String = { | 
|  | 61 | + import js.typedarray._ | 
|  | 62 | + | 
|  | 63 | + val jsBytes = js.Array[js.Any](bytes.toTypedArray) | 
|  | 64 | + val blob = new Blob(jsBytes, BlobPropertyBag(mimeType)) | 
|  | 65 | + URL.createObjectURL(blob) | 
|  | 66 | + } | 
|  | 67 | + | 
|  | 68 | + /** | 
|  | 69 | + * Create an anchor element that on click downloads byte array as a file with specified name. | 
|  | 70 | + * | 
|  | 71 | + * Keep in mind that anchor's href URL should be revoked via `org.scalajs.dom.revokeObjectURL(url)`. | 
|  | 72 | + */ | 
|  | 73 | + def asAnchor(filename: String, bytes: Array[Byte], mimeType: String = OctetStreamType): JsDom.TypedTag[Anchor] = { | 
|  | 74 | + import JsDom.all._ | 
|  | 75 | + | 
|  | 76 | + val download = attr("download") | 
|  | 77 | + a(href := asURL(bytes, mimeType), download := filename) | 
|  | 78 | + } | 
|  | 79 | + | 
|  | 80 | + /** | 
|  | 81 | + * Asynchronously convert specified file to bytes array. | 
|  | 82 | + */ | 
|  | 83 | + def asBytesArray(file: File): Future[Array[Byte]] = { | 
|  | 84 | + import js.typedarray._ | 
|  | 85 | + | 
|  | 86 | + val fileReader = new FileReader() | 
|  | 87 | + val promise = Promise[Array[Byte]]() | 
|  | 88 | + | 
|  | 89 | + fileReader.onerror = (e: Event) => | 
|  | 90 | + promise.failure(new IOException(e.toString)) | 
|  | 91 | + | 
|  | 92 | + fileReader.onabort = (e: Event) => | 
|  | 93 | + promise.failure(new IOException(e.toString)) | 
|  | 94 | + | 
|  | 95 | + fileReader.onload = (_: UIEvent) => | 
|  | 96 | + promise.complete(Try( | 
|  | 97 | + new Int8Array(fileReader.result.asInstanceOf[ArrayBuffer]).toArray | 
|  | 98 | + )) | 
|  | 99 | + | 
|  | 100 | + fileReader.readAsArrayBuffer(file) | 
|  | 101 | + | 
|  | 102 | + promise.future | 
|  | 103 | + } | 
|  | 104 | + | 
|  | 105 | + /** | 
|  | 106 | + * Convert specified file to InputStream with blocking I/O | 
|  | 107 | + * | 
|  | 108 | + * Because it is using synchronous I/O that could potentially this API can be used only inside worker. | 
|  | 109 | + * | 
|  | 110 | + * This method is using FileReaderSync that is part of Working Draft File API. | 
|  | 111 | + * Anyway it is supported for majority of modern browsers | 
|  | 112 | + */ | 
|  | 113 | + def asInputStream(file: File): InputStream = | 
|  | 114 | + new FileBufferedInputStream(file) | 
|  | 115 | +} | 
0 commit comments