Skip to main content

devpage_design/wire/
patch.rs

1//! Edits on the wire: [`Edits`] to the `proto::Patch` the watch stream sends
2//! and the store logs, and back — each image and font file in it once.
3
4use super::{decode, encode, encode_own, file, files, font_file, font_files, node_id, unnode_id};
5use crate::proto::{self, op::Op as Kind};
6use crate::scene::{self, Applied, Edit, Edits, Scene, Uses};
7
8/// `edits` as a patch.
9pub fn patch(edits: &Edits) -> proto::Patch {
10    proto::Patch {
11        ops: edits.list.iter().map(op).collect(),
12        images: edits.images.values().map(|f| file(f)).collect(),
13        fonts: edits.fonts.values().map(|f| font_file(f)).collect(),
14        themed: edits.themed,
15    }
16}
17
18fn op(edit: &Edit) -> proto::Op {
19    let op = match edit {
20        Edit::Change(n) => Kind::Change(encode_own(n)),
21        Edit::Insert {
22            parent,
23            after,
24            node,
25        } => Kind::Insert(proto::Insert {
26            parent: parent.as_ref().map(node_id),
27            after: after.as_ref().map(node_id),
28            node: Some(encode(node)),
29        }),
30        Edit::Remove(id) => Kind::Remove(node_id(id)),
31        Edit::Move { id, parent, after } => Kind::Move(proto::Move {
32            id: Some(node_id(id)),
33            parent: Some(node_id(parent)),
34            after: after.as_ref().map(node_id),
35        }),
36    };
37    proto::Op { op: Some(op) }
38}
39
40/// The edits a patch to `scene` carries, or why it doesn't: each image and
41/// font file the scene holds already is taken from it, not decoded or
42/// checked again.
43pub(crate) fn edits(patch: &proto::Patch, scene: &Scene) -> Result<Edits, String> {
44    let list = patch
45        .ops
46        .iter()
47        .map(|o| {
48            Ok(match o.op.as_ref().ok_or("an empty operation")? {
49                Kind::Change(n) => Edit::Change(decode(n)?),
50                Kind::Insert(i) => Edit::Insert {
51                    parent: i.parent.as_ref().map(|p| unnode_id(Some(p))).transpose()?,
52                    after: i.after.as_ref().map(|a| unnode_id(Some(a))).transpose()?,
53                    node: decode(i.node.as_ref().ok_or("an insert with no node")?)?,
54                },
55                Kind::Remove(id) => Edit::Remove(unnode_id(Some(id))?),
56                Kind::Move(m) => Edit::Move {
57                    id: unnode_id(m.id.as_ref())?,
58                    parent: unnode_id(m.parent.as_ref())?,
59                    after: m.after.as_ref().map(|a| unnode_id(Some(a))).transpose()?,
60                },
61            })
62        })
63        .collect::<Result<_, String>>()?;
64    Ok(Edits {
65        list,
66        images: files(&patch.images, &scene.images)?,
67        fonts: font_files(&patch.fonts, &scene.fonts)?,
68        themed: patch.themed,
69    })
70}
71
72/// Applies a patch to `scene`, whose files `uses` counts, as a browser
73/// holding the scene does — or says why it can't, leaving both as they
74/// were.
75pub fn apply(scene: &mut Scene, uses: &mut Uses, patch: &proto::Patch) -> Result<Applied, String> {
76    let edits = edits(patch, scene)?;
77    scene::apply(scene, uses, &edits)
78}
79
80#[cfg(test)]
81mod tests;