-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
BinaryFormatExt.scala
84 lines (63 loc) · 3.12 KB
/
BinaryFormatExt.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package japgolly.webapputil.boopickle
import boopickle.{PickleImpl, Pickler, UnpickleImpl}
import japgolly.scalajs.react.callback.AsyncCallback
import japgolly.webapputil.binary._
import java.nio.ByteBuffer
import scala.scalajs.js
object BinaryFormatExt {
trait Implicits {
@inline final implicit def BinaryFormatBoopickleExt[A](self: BinaryFormat[A]): Implicits.BinaryFormatBoopickleExt[A] =
new Implicits.BinaryFormatBoopickleExt[A](self)
@inline final implicit def BinaryFormatBoopickleStaticExt(self: BinaryFormat.type): Implicits.BinaryFormatBoopickleStaticExt =
new Implicits.BinaryFormatBoopickleStaticExt(self)
}
object Implicits extends Implicits {
final class BinaryFormatBoopickleExt[A](private val self: BinaryFormat[A]) extends AnyVal {
type ThisIsBinary = BinaryFormat[A] =:= BinaryFormat[BinaryData]
def pickle[B](implicit pickler: SafePickler[B], ev: ThisIsBinary): BinaryFormat[B] =
ev(self).xmap(pickler.decodeOrThrow)(pickler.encode)
def pickleBasic[B](implicit pickler: Pickler[B], ev: ThisIsBinary): BinaryFormat[B] = {
val unpickle = UnpickleImpl[B]
ev(self)
.xmap[ByteBuffer](_.unsafeByteBuffer)(BinaryData.unsafeFromByteBuffer)
.xmap(unpickle.fromBytes(_))(PickleImpl.intoBytes(_))
}
}
final class BinaryFormatBoopickleStaticExt(private val self: BinaryFormat.type) extends AnyVal {
@inline def pickleCompressEncrypt[A](c: Compression, e: Encryption)(implicit pickler: SafePickler[A]): BinaryFormat[A] =
BinaryFormatExt.pickleCompressEncrypt(c, e)
@inline def versioned[A](oldest: BinaryFormat[A], toLatest: BinaryFormat[A]*): BinaryFormat[A] =
BinaryFormatExt.versioned(oldest, toLatest: _*)
}
}
// ===================================================================================================================
def versioned[A](oldest: BinaryFormat[A], toLatest: BinaryFormat[A]*): BinaryFormat[A] = {
val layers = oldest +: toLatest.toArray
val decoders = layers
val decoderIndices = decoders.indices
val latestVer = decoders.length - 1
val latestVerHeader = BinaryData.byte(latestVer.toByte)
val encoder = layers.last
def encode(a: A): AsyncCallback[BinaryData] =
encoder.encode(a).map(latestVerHeader ++ _)
def decode(bin: BinaryData): AsyncCallback[A] =
AsyncCallback.suspend {
if (bin.isEmpty)
throw js.JavaScriptException("No data")
val ver = bin.unsafeArray(0).toInt
if (decoderIndices.contains(ver)) {
val binBody = bin.drop(1)
decoders(ver).decode(binBody)
} else if (ver < 0)
throw js.JavaScriptException("Bad data")
else
SafePicklerUtil.unsupportedVer(ver, latestVer)
}
BinaryFormat.async(decode)(encode)
}
def pickleCompressEncrypt[A](c: Compression, e: Encryption)(implicit pickler: SafePickler[A]): BinaryFormat[A] =
BinaryFormat.id
.encrypt(e) // 3. Encryption is the very last step
.compress(c) // 2. Compress the binary *before* encrypting
.pickle[A] // 1. Generate binary first
}