1//! Edits as ops through a `Section`, sealed: the image an edit leaves, for tests that read
2//! it back.
3#![allow(dead_code)]
4
5use onestore::{
6 Arena, ExGuid, Section, Transaction,
7 op::{Edit, Op, OpError, PageOp},
8 page::Page,
9};
10
11/// FILETIME now, as an edit's `at`.
12pub fn now() -> u64 {
13 let unix = std::time::SystemTime::now()
14 .duration_since(std::time::UNIX_EPOCH)
15 .unwrap();
16 (unix.as_secs() + 11_644_473_600) * 10_000_000 + u64::from(unix.subsec_nanos() / 100)
17}
18
19/// The transaction `ops` seal into on `source`, as one edit by `author`; none when they
20/// store nothing.
21pub fn transaction(
22 source: &[u8],
23 author: &str,
24 ops: Vec<Op>,
25) -> Result<Option<Transaction>, OpError> {
26 let arena = Arena::default();
27 let mut section = Section::open(&arena, source.to_vec()).map_err(OpError::Failed)?;
28 section.apply(author, &Edit { at: now(), ops })?;
29 section.seal().map_err(OpError::Failed)
30}
31
32/// `source` after `ops`, applied as one edit and sealed.
33pub fn edited(source: &[u8], ops: Vec<Op>) -> Result<Vec<u8>, OpError> {
34 let mut image = source.to_vec();
35 if let Some(transaction) = transaction(source, "Author", ops)? {
36 transaction.apply(&mut image).map_err(OpError::Failed)?;
37 }
38 Ok(image)
39}
40
41/// `source` after `ops` on the page in `space`.
42pub fn page_edited(source: &[u8], space: ExGuid, ops: Vec<PageOp>) -> Result<Vec<u8>, OpError> {
43 edited(
44 source,
45 ops.into_iter().map(|op| Op::Page { space, op }).collect(),
46 )
47}
48
49/// `source` with the page in `space` taken to `after` by the ops `op::lower_page` finds,
50/// as a test states an edit by the page it leaves.
51pub fn saved(source: &[u8], space: ExGuid, after: &Page) -> Result<Vec<u8>, String> {
52 let arena = Arena::default();
53 let section = Section::open(&arena, source.to_vec()).map_err(|error| error.to_string())?;
54 let before = section.page(space).map_err(|error| error.to_string())?;
55 let ops = onestore::op::lower_page(&before, after).map_err(|error| error.to_string())?;
56 page_edited(source, space, ops).map_err(|error| error.to_string())
57}
58
59/// A plain paragraph holding `text` in OneNote's default text style, with fresh identities.
60pub fn paragraph(text: &str) -> onestore::page::PageParagraph {
61 use onestore::page::{PageParagraph, Paragraph, ParagraphContent, TextObject, text::new_id};
62 let format = onestore::document::Format {
63 font: Some("Calibri".into()),
64 font_size: Some(11.0),
65 language: Some(0x409),
66 ..Default::default()
67 };
68 PageParagraph {
69 id: new_id().unwrap(),
70 parent: None,
71 level: 1,
72 style: None,
73 format: Default::default(),
74 content: ParagraphContent::Text(TextObject {
75 id: new_id().unwrap(),
76 date_field: None,
77 text: Paragraph::new(text.into(), format),
78 tags: Vec::new(),
79 }),
80 lists: Vec::new(),
81 tags: Vec::new(),
82 media: Default::default(),
83 collapsed: false,
84 }
85}
86
87/// A new outline at `(x, y)` in points holding `paragraphs`.
88pub fn outline(
89 x: f32,
90 y: f32,
91 paragraphs: Vec<onestore::page::PageParagraph>,
92) -> onestore::page::PageObject {
93 use onestore::page::{Outline, PageObject, text::new_id};
94 PageObject::Outline(Outline {
95 id: new_id().unwrap(),
96 title: false,
97 min_width: None,
98 layout: onestore::document::Layout {
99 x: Some(x),
100 y: Some(y),
101 ..Default::default()
102 },
103 indents: Vec::new(),
104 paragraphs,
105 unsupported: Vec::new(),
106 })
107}
108
109/// An edit applied through a `Section`: the image it leaves and the transaction publishing
110/// it, none when it stores nothing.
111pub struct Applied {
112 pub image: Vec<u8>,
113 pub transaction: Option<Transaction>,
114}
115
116impl Applied {
117 pub fn as_bytes(&self) -> &[u8] {
118 &self.image
119 }
120
121 /// Publishes the transaction under the filesystem adapter's exclusion.
122 #[cfg(any(unix, windows))]
123 pub fn commit_file(
124 &self,
125 path: impl AsRef<std::path::Path>,
126 ) -> Result<(), onestore::CommitError> {
127 self.transaction
128 .as_ref()
129 .expect("the edit stores a revision")
130 .commit_file(path)
131 }
132
133 /// Publishes the transaction under caller-held exclusion.
134 pub fn commit(&self, io: &mut impl onestore::CommitIo) -> Result<(), onestore::CommitError> {
135 self.transaction
136 .as_ref()
137 .expect("the edit stores a revision")
138 .commit(io)
139 }
140}
141
142/// `ops` applied to `source` as one edit by `author`.
143pub fn apply(source: &[u8], author: &str, ops: Vec<Op>) -> Result<Applied, OpError> {
144 let transaction = transaction(source, author, ops)?;
145 let mut image = source.to_vec();
146 if let Some(transaction) = &transaction {
147 transaction.apply(&mut image).map_err(OpError::Failed)?;
148 }
149 Ok(Applied { image, transaction })
150}
151
152/// `saved` as an `Applied`, to publish it.
153pub fn save(source: &[u8], space: ExGuid, after: &Page) -> Result<Applied, String> {
154 let arena = Arena::default();
155 let section = Section::open(&arena, source.to_vec()).map_err(|error| error.to_string())?;
156 let before = section.page(space).map_err(|error| error.to_string())?;
157 let ops = onestore::op::lower_page(&before, after).map_err(|error| error.to_string())?;
158 let ops = ops.into_iter().map(|op| Op::Page { space, op }).collect();
159 apply(source, "Author", ops).map_err(|error| error.to_string())
160}
161
162/// `op` on the page in `space`, applied to `source`.
163pub fn page_op(source: &[u8], space: ExGuid, op: PageOp) -> Result<Applied, OpError> {
164 apply(source, "Author", vec![Op::Page { space, op }])
165}
166
167/// `op` on the section, applied to `source`.
168pub fn section_op(source: &[u8], op: onestore::op::SectionOp) -> Result<Applied, OpError> {
169 apply(source, "Author", vec![Op::Section(op)])
170}
171
172/// Adds an outline at `(x, y)` holding one paragraph of `text`: the op and the identities
173/// of the outline and its text.
174pub fn new_outline(x: f32, y: f32, text: &str) -> (PageOp, ExGuid, ExGuid) {
175 let paragraph = paragraph(text);
176 let text = paragraph.text().unwrap().id;
177 let object = outline(x, y, vec![paragraph]);
178 let id = object.id();
179 (
180 PageOp::Add {
181 object,
182 before: None,
183 },
184 id,
185 text,
186 )
187}