1#![allow(dead_code)]
2
3use onestore::{
4 ExGuid, RevisionIndex, Store,
5 document::{Document, Kind},
6};
7
8pub struct View {
9 pub space: ExGuid,
10 pub object: ExGuid,
11 pub revision: ExGuid,
12 pub text: String,
13}
14
15pub fn view(bytes: &[u8]) -> Result<View, Box<dyn std::error::Error>> {
16 let store = Store::parse(bytes)?;
17 if !store.checksum_mismatches.is_empty() {
18 return Err("Transaction checksum damage".into());
19 }
20 let index = RevisionIndex::parse(&store)?;
21 index.validate_current()?;
22 let document = Document::parse(&index)?;
23 let mut found = Vec::new();
24 for (sid, page) in document.pages()? {
25 let space = &document.spaces[&sid];
26 let rid = space.contexts[&ExGuid::default()];
27 let revision = &space.revisions[&rid];
28 let mut pending = vec![page];
29 let mut seen = std::collections::BTreeSet::new();
30 while let Some(oid) = pending.pop() {
31 if !seen.insert(oid) {
32 continue;
33 }
34 let node = &revision.nodes[&oid];
35 pending.extend(
36 node.children
37 .iter()
38 .chain(&node.content)
39 .chain(&node.structure)
40 .copied(),
41 );
42 if let Kind::RichText { text, .. } = &node.kind
43 && text.starts_with("Concurrent edits:")
44 {
45 revision.text_runs(oid)?;
46 found.push(View {
47 space: sid,
48 object: oid,
49 revision: rid,
50 text: text.clone(),
51 });
52 }
53 }
54 }
55 if found.len() != 1 {
56 return Err("Expected one concurrent-edit paragraph".into());
57 }
58 Ok(found.pop().unwrap())
59}
60
61/// The concurrent-edit paragraph of `page`: its text object and text.
62fn in_page(page: &onestore::page::Page) -> Option<(ExGuid, String)> {
63 let mut pending: Vec<&onestore::page::PageParagraph> = page
64 .objects
65 .iter()
66 .flat_map(|object| match object {
67 onestore::page::PageObject::Outline(outline) => outline.paragraphs.iter().collect(),
68 _ => Vec::new(),
69 })
70 .collect();
71 pending.reverse();
72 while let Some(paragraph) = pending.pop() {
73 if let Some(text) = paragraph.text()
74 && text.text.text().starts_with("Concurrent edits:")
75 {
76 return Some((text.id, text.text.text().to_owned()));
77 }
78 if let onestore::page::ParagraphContent::Table(table) = &paragraph.content {
79 pending.extend(
80 table
81 .rows
82 .iter()
83 .flat_map(|row| &row.cells)
84 .flat_map(|cell| &cell.paragraphs)
85 .rev(),
86 );
87 }
88 }
89 None
90}
91
92/// The concurrent-edit paragraph as the queued edits leave it: its page, text object and
93/// text.
94pub fn cached(
95 cache: &notebook::Replica,
96) -> Result<(ExGuid, ExGuid, String), Box<dyn std::error::Error>> {
97 let mut found = Vec::new();
98 for (space, ..) in cache.pages()? {
99 if let Some((object, text)) = in_page(&cache.page(space)?) {
100 found.push((space, object, text));
101 }
102 }
103 match <[_; 1]>::try_from(found) {
104 Ok([found]) => Ok(found),
105 Err(_) => Err("Expected one concurrent-edit paragraph".into()),
106 }
107}