forked from lichess-org/lila
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBSONHandlers.scala
343 lines (314 loc) · 13.7 KB
/
BSONHandlers.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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
package lila.study
import chess.format.pgn.{ Glyph, Glyphs, Tag, Tags }
import chess.format.{ Uci, UciCharPair, FEN }
import chess.variant.{ Variant, Crazyhouse }
import chess.{ Centis, Pos, Role, PromotableRole }
import org.joda.time.DateTime
import reactivemongo.bson._
import lila.db.BSON
import lila.db.BSON.{ Reader, Writer }
import lila.db.dsl._
import lila.game.BSONHandlers.FENBSONHandler
import lila.tree.Eval
import lila.tree.Eval.Score
import lila.tree.Node.{ Shape, Shapes, Comment, Comments, Gamebook }
import lila.common.Iso
import lila.common.Iso._
object BSONHandlers {
import Chapter._
implicit val StudyIdBSONHandler = stringIsoHandler(Study.idIso)
implicit val StudyNameBSONHandler = stringIsoHandler(Study.nameIso)
implicit val ChapterIdBSONHandler = stringIsoHandler(Chapter.idIso)
implicit val ChapterNameBSONHandler = stringIsoHandler(Chapter.nameIso)
implicit val CentisBSONHandler = intIsoHandler(Iso.centisIso)
private implicit val PosBSONHandler = new BSONHandler[BSONString, Pos] {
def read(bsonStr: BSONString): Pos = Pos.posAt(bsonStr.value) err s"No such pos: ${bsonStr.value}"
def write(x: Pos) = BSONString(x.key)
}
implicit val ColorBSONHandler = new BSONHandler[BSONBoolean, chess.Color] {
def read(b: BSONBoolean) = chess.Color(b.value)
def write(c: chess.Color) = BSONBoolean(c.white)
}
implicit val ShapeBSONHandler = new BSON[Shape] {
def reads(r: Reader) = {
val brush = r str "b"
r.getO[Pos]("p") map { pos =>
Shape.Circle(brush, pos)
} getOrElse Shape.Arrow(brush, r.get[Pos]("o"), r.get[Pos]("d"))
}
def writes(w: Writer, t: Shape) = t match {
case Shape.Circle(brush, pos) => $doc("b" -> brush, "p" -> pos.key)
case Shape.Arrow(brush, orig, dest) => $doc("b" -> brush, "o" -> orig.key, "d" -> dest.key)
}
}
implicit val PromotableRoleHandler = new BSONHandler[BSONString, PromotableRole] {
def read(bsonStr: BSONString): PromotableRole = bsonStr.value.headOption flatMap Role.allPromotableByForsyth.get err s"No such role: ${bsonStr.value}"
def write(x: PromotableRole) = BSONString(x.forsyth.toString)
}
implicit val RoleHandler = new BSONHandler[BSONString, Role] {
def read(bsonStr: BSONString): Role = bsonStr.value.headOption flatMap Role.allByForsyth.get err s"No such role: ${bsonStr.value}"
def write(x: Role) = BSONString(x.forsyth.toString)
}
implicit val UciHandler = new BSONHandler[BSONString, Uci] {
def read(bs: BSONString): Uci = Uci(bs.value) err s"Bad UCI: ${bs.value}"
def write(x: Uci) = BSONString(x.uci)
}
implicit val UciCharPairHandler = new BSONHandler[BSONString, UciCharPair] {
def read(bsonStr: BSONString): UciCharPair = bsonStr.value.toArray match {
case Array(a, b) => UciCharPair(a, b)
case _ => sys error s"Invalid UciCharPair ${bsonStr.value}"
}
def write(x: UciCharPair) = BSONString(x.toString)
}
import Study.IdName
implicit val StudyIdNameBSONHandler = Macros.handler[IdName]
import Uci.WithSan
private implicit val UciWithSanBSONHandler = Macros.handler[WithSan]
implicit val ShapesBSONHandler: BSONHandler[BSONArray, Shapes] =
isoHandler[Shapes, List[Shape], BSONArray](
(s: Shapes) => s.value,
Shapes(_)
)
private implicit val CommentIdBSONHandler = stringAnyValHandler[Comment.Id](_.value, Comment.Id.apply)
private implicit val CommentTextBSONHandler = stringAnyValHandler[Comment.Text](_.value, Comment.Text.apply)
implicit val CommentAuthorBSONHandler = new BSONHandler[BSONValue, Comment.Author] {
def read(bsonValue: BSONValue): Comment.Author = bsonValue match {
case BSONString(lila.user.User.lichessId) => Comment.Author.Lichess
case BSONString(name) => Comment.Author.External(name)
case doc: Bdoc => {
for {
id <- doc.getAs[String]("id")
name <- doc.getAs[String]("name")
} yield Comment.Author.User(id, name)
} err s"Invalid comment author $doc"
case _ => Comment.Author.Unknown
}
def write(x: Comment.Author): BSONValue = x match {
case Comment.Author.User(id, name) => $doc("id" -> id, "name" -> name)
case Comment.Author.External(name) => BSONString(s"${name.trim}")
case Comment.Author.Lichess => BSONString("l")
case Comment.Author.Unknown => BSONString("")
}
}
private implicit val CommentBSONHandler = Macros.handler[Comment]
implicit val CommentsBSONHandler: BSONHandler[BSONArray, Comments] =
isoHandler[Comments, List[Comment], BSONArray](
(s: Comments) => s.value,
Comments(_)
)
implicit val GamebookBSONHandler = Macros.handler[Gamebook]
private implicit def CrazyDataBSONHandler: BSON[Crazyhouse.Data] = new BSON[Crazyhouse.Data] {
private def writePocket(p: Crazyhouse.Pocket) = p.roles.map(_.forsyth).mkString
private def readPocket(p: String) = Crazyhouse.Pocket(p.flatMap(chess.Role.forsyth)(scala.collection.breakOut))
def reads(r: Reader) = Crazyhouse.Data(
promoted = r.getsD[Pos]("o").toSet,
pockets = Crazyhouse.Pockets(
white = readPocket(r.strD("w")),
black = readPocket(r.strD("b"))
)
)
def writes(w: Writer, s: Crazyhouse.Data) = $doc(
"o" -> w.listO(s.promoted.toList),
"w" -> w.strO(writePocket(s.pockets.white)),
"b" -> w.strO(writePocket(s.pockets.black))
)
}
implicit val GlyphsBSONHandler = new BSONHandler[Barr, Glyphs] {
private val idsHandler = bsonArrayToListHandler[Int]
def read(b: Barr) = Glyphs.fromList(idsHandler read b flatMap Glyph.find)
// must be BSONArray and not $arr!
def write(x: Glyphs) = BSONArray(x.toList.map(_.id).map(BSONInteger.apply))
}
implicit val EvalScoreBSONHandler = new BSONHandler[BSONInteger, Score] {
private val mateFactor = 1000000
def read(i: BSONInteger) = Score {
val v = i.value
if (v >= mateFactor || v <= -mateFactor) Right(Eval.Mate(v / mateFactor))
else Left(Eval.Cp(v))
}
def write(e: Score) = BSONInteger {
e.value.fold(
cp => cp.value atLeast (-mateFactor + 1) atMost (mateFactor - 1),
mate => mate.value * mateFactor
)
}
}
implicit def NodeBSONHandler: BSON[Node] = new BSON[Node] {
def reads(r: Reader) = Node(
id = r.get[UciCharPair]("i"),
ply = r int "p",
move = WithSan(r.get[Uci]("u"), r.str("s")),
fen = r.get[FEN]("f"),
check = r boolD "c",
shapes = r.getO[Shapes]("h") | Shapes.empty,
comments = r.getO[Comments]("co") | Comments.empty,
gamebook = r.getO[Gamebook]("ga"),
glyphs = r.getO[Glyphs]("g") | Glyphs.empty,
score = r.getO[Score]("e"),
crazyData = r.getO[Crazyhouse.Data]("z"),
clock = r.getO[Centis]("l"),
children = r.get[Node.Children]("n"),
forceVariation = r boolD "fv"
)
def writes(w: Writer, s: Node) = $doc(
"i" -> s.id,
"p" -> s.ply,
"u" -> s.move.uci,
"s" -> s.move.san,
"f" -> s.fen,
"c" -> w.boolO(s.check),
"h" -> s.shapes.value.nonEmpty.option(s.shapes),
"co" -> s.comments.value.nonEmpty.option(s.comments),
"ga" -> s.gamebook,
"g" -> s.glyphs.nonEmpty,
"e" -> s.score,
"l" -> s.clock,
"z" -> s.crazyData,
"n" -> (if (s.ply < Node.MAX_PLIES) s.children else Node.emptyChildren),
"fv" -> w.boolO(s.forceVariation)
)
}
import Node.Root
private[study] implicit def NodeRootBSONHandler: BSON[Root] = new BSON[Root] {
def reads(r: Reader) = Root(
ply = r int "p",
fen = r.get[FEN]("f"),
check = r boolD "c",
shapes = r.getO[Shapes]("h") | Shapes.empty,
comments = r.getO[Comments]("co") | Comments.empty,
gamebook = r.getO[Gamebook]("ga"),
glyphs = r.getO[Glyphs]("g") | Glyphs.empty,
score = r.getO[Score]("e"),
clock = r.getO[Centis]("l"),
crazyData = r.getO[Crazyhouse.Data]("z"),
children = r.get[Node.Children]("n")
)
def writes(w: Writer, s: Root) = $doc(
"p" -> s.ply,
"f" -> s.fen,
"c" -> w.boolO(s.check),
"h" -> s.shapes.value.nonEmpty.option(s.shapes),
"co" -> s.comments.value.nonEmpty.option(s.comments),
"ga" -> s.gamebook,
"g" -> s.glyphs.nonEmpty,
"e" -> s.score,
"l" -> s.clock,
"z" -> s.crazyData,
"n" -> s.children
)
}
implicit val ChildrenBSONHandler = new BSONHandler[Barr, Node.Children] {
private val nodesHandler = bsonArrayToVectorHandler[Node]
def read(b: Barr) = try {
Node.Children(nodesHandler read b)
} catch {
case e: StackOverflowError =>
println(s"study handler ${e.toString}")
Node.emptyChildren
}
def write(x: Node.Children) = try {
nodesHandler write x.nodes
} catch {
case e: StackOverflowError =>
println(s"study handler ${e.toString}")
$arr()
}
}
implicit val PathBSONHandler = new BSONHandler[BSONString, Path] {
def read(b: BSONString): Path = Path(b.value)
def write(x: Path) = BSONString(x.toString)
}
implicit val VariantBSONHandler = new BSONHandler[BSONInteger, Variant] {
def read(b: BSONInteger): Variant = Variant(b.value) err s"No such variant: ${b.value}"
def write(x: Variant) = BSONInteger(x.id)
}
implicit val PgnTagBSONHandler = new BSONHandler[BSONString, Tag] {
def read(b: BSONString): Tag = b.value.split(":", 2) match {
case Array(name, value) => Tag(name, value)
case _ => sys error s"Invalid pgn tag ${b.value}"
}
def write(t: Tag) = BSONString(s"${t.name}:${t.value}")
}
implicit val PgnTagsBSONHandler: BSONHandler[BSONArray, Tags] =
isoHandler[Tags, List[Tag], BSONArray](
(s: Tags) => s.value,
Tags(_)
)
private implicit val ChapterSetupBSONHandler = Macros.handler[Chapter.Setup]
implicit val ChapterRelayBSONHandler = Macros.handler[Chapter.Relay]
implicit val ChapterServerEvalBSONHandler = Macros.handler[Chapter.ServerEval]
import Chapter.Ply
implicit val PlyBSONHandler = intAnyValHandler[Ply](_.value, Ply.apply)
implicit val ChapterBSONHandler = Macros.handler[Chapter]
implicit val ChapterMetadataBSONHandler = Macros.handler[Chapter.Metadata]
private implicit val ChaptersMap = BSON.MapDocument.MapHandler[Chapter.Id, Chapter]
implicit val PositionRefBSONHandler = new BSONHandler[BSONString, Position.Ref] {
def read(b: BSONString) = Position.Ref.decode(b.value) err s"Invalid position ${b.value}"
def write(x: Position.Ref) = BSONString(x.encode)
}
implicit val StudyMemberRoleBSONHandler = new BSONHandler[BSONString, StudyMember.Role] {
def read(b: BSONString) = StudyMember.Role.byId get b.value err s"Invalid role ${b.value}"
def write(x: StudyMember.Role) = BSONString(x.id)
}
private case class DbMember(role: StudyMember.Role, addedAt: DateTime)
private implicit val DbMemberBSONHandler = Macros.handler[DbMember]
private[study] implicit val StudyMemberBSONWriter = new BSONWriter[StudyMember, Bdoc] {
def write(x: StudyMember) = DbMemberBSONHandler write DbMember(x.role, x.addedAt)
}
private[study] implicit val MembersBSONHandler = new BSONHandler[Bdoc, StudyMembers] {
private val mapHandler = BSON.MapDocument.MapHandler[String, DbMember]
def read(b: Bdoc) = StudyMembers(mapHandler read b map {
case (id, dbMember) => id -> StudyMember(id, dbMember.role, dbMember.addedAt)
})
def write(x: StudyMembers) = BSONDocument(x.members.mapValues(StudyMemberBSONWriter.write))
}
import Study.Visibility
private[study] implicit val VisibilityHandler: BSONHandler[BSONString, Visibility] = new BSONHandler[BSONString, Visibility] {
def read(bs: BSONString) = Visibility.byKey get bs.value err s"Invalid visibility ${bs.value}"
def write(x: Visibility) = BSONString(x.key)
}
import Study.From
private[study] implicit val FromHandler: BSONHandler[BSONString, From] = new BSONHandler[BSONString, From] {
def read(bs: BSONString) = bs.value.split(' ') match {
case Array("scratch") => From.Scratch
case Array("game", id) => From.Game(id)
case Array("study", id) => From.Study(Study.Id(id))
case Array("relay") => From.Relay(none)
case Array("relay", id) => From.Relay(Study.Id(id).some)
case _ => sys error s"Invalid from ${bs.value}"
}
def write(x: From) = BSONString(x match {
case From.Scratch => "scratch"
case From.Game(id) => s"game $id"
case From.Study(id) => s"study $id"
case From.Relay(id) => s"relay${id.fold("")(" " + _)}"
})
}
import Settings.UserSelection
private[study] implicit val UserSelectionHandler: BSONHandler[BSONString, UserSelection] = new BSONHandler[BSONString, UserSelection] {
def read(bs: BSONString) = UserSelection.byKey get bs.value err s"Invalid user selection ${bs.value}"
def write(x: UserSelection) = BSONString(x.key)
}
implicit val SettingsBSONHandler = new BSON[Settings] {
def reads(r: Reader) = Settings(
computer = r.get[UserSelection]("computer"),
explorer = r.get[UserSelection]("explorer"),
cloneable = r.getO[UserSelection]("cloneable") | Settings.init.cloneable,
chat = r.getO[UserSelection]("chat") | Settings.init.chat,
sticky = r.getO[Boolean]("sticky") | Settings.init.sticky
)
private val writer = Macros.writer[Settings]
def writes(w: Writer, s: Settings) = writer write s
}
import Study.Likes
implicit val LikesBSONHandler = intAnyValHandler[Likes](_.value, Likes.apply)
import Study.Rank
private[study] implicit val RankBSONHandler = dateIsoHandler[Rank](Iso[DateTime, Rank](Rank.apply, _.value))
implicit val StudyBSONHandler = Macros.handler[Study]
implicit val lightStudyBSONReader = new BSONDocumentReader[Study.LightStudy] {
def read(doc: BSONDocument) = Study.LightStudy(
isPublic = doc.getAs[String]("visibility") has "public",
contributors = doc.getAs[StudyMembers]("members").??(_.contributorIds)
)
}
}