1use super::*;
2
3/// A mounted notebook where another writer commits `revision` to the section file just
4/// before Snowbound's rewritten file takes its place.
5struct Racing {
6 inner: Directory,
7 revision: Mutex<Option<(String, Transaction)>>,
8}
9
10impl Racing {
11 fn race(&self, path: &str) {
12 let revision = self.revision.lock().unwrap().take();
13 if let Some((section, transaction)) = revision {
14 assert_eq!(section, path);
15 self.inner.commit(&section, &transaction).unwrap();
16 }
17 }
18}
19
20impl Storage for Racing {
21 fn discover(
22 &self,
23 cache: &mut discover::Cache,
24 limits: discover::Limits,
25 ) -> Result<discover::Folder> {
26 self.inner.discover(cache, limits)
27 }
28 fn location(&self) -> String {
29 self.inner.location()
30 }
31 fn entries(&self, folder: &str) -> io::Result<Vec<discover::Entry>> {
32 self.inner.entries(folder)
33 }
34 fn exists(&self, path: &str) -> bool {
35 self.inner.exists(path)
36 }
37 fn stamp(&self, path: &str) -> io::Result<Stamp> {
38 self.inner.stamp(path)
39 }
40 fn read(&self, path: &str) -> Result<Vec<u8>> {
41 self.inner.read(path)
42 }
43 fn read_file(&self, path: &str, limit: usize) -> Result<Vec<u8>> {
44 self.inner.read_file(path, limit)
45 }
46 fn create(&self, path: &str, bytes: &[u8]) -> Result<()> {
47 self.inner.create(path, bytes)
48 }
49 fn create_directory(&self, path: &str) -> Result<()> {
50 self.inner.create_directory(path)
51 }
52 fn hide(&self, path: &str) -> Result<()> {
53 self.inner.hide(path)
54 }
55 fn rename(&self, from: &str, to: &str) -> Result<()> {
56 self.inner.rename(from, to)
57 }
58 fn rename_root(&self, name: &str, files: &[String]) -> Result<String> {
59 self.inner.rename_root(name, files)
60 }
61 fn replace(&self, from: &str, to: &str) -> Result<()> {
62 self.inner.replace(from, to)
63 }
64 fn delete(&self, path: &str) -> Result<()> {
65 self.inner.delete(path)
66 }
67 fn place(&self, path: &str, ancestor: [u8; 16], name: &str) -> Result<()> {
68 self.inner.place(path, ancestor, name)
69 }
70 fn commit(&self, path: &str, transaction: &Transaction) -> Result<()> {
71 self.inner.commit(path, transaction)
72 }
73 fn confirm(&self, path: &str, base: &Stamp) -> std::result::Result<(), CommitError> {
74 self.inner.confirm(path, base)
75 }
76 fn supersede(&self, path: &str, base: &Stamp, with: &str) -> Result<()> {
77 self.race(path);
78 self.inner.supersede(path, base, with)
79 }
80}
81
82/// A revision another writer commits while a password is being set is never overwritten:
83/// the section keeps it, and the password waits for a later try.
84#[test]
85fn a_password_never_overwrites_a_concurrent_revision() {
86 let temporary = tempfile::tempdir().unwrap();
87 let folder = temporary.path().join("notebook");
88 let cache = temporary.path().join("cache");
89 let creation = PageCreation::new(None, Some("Mine"), "Fixture").unwrap();
90 let path = Notebook::create(&folder, &cache, 0x00f0_a0c0, &creation)
91 .unwrap()
92 .catalog()
93 .sections[0]
94 .path
95 .clone();
96 let file = folder.canonicalize().unwrap().join(&path);
97 let image = std::fs::read(&file).unwrap();
98 let arena = onestore::Arena::default();
99 let mut section = onestore::Section::open(&arena, image).unwrap();
100 let theirs = PageCreation::new(None, Some("Theirs"), "OneNote").unwrap();
101 section
102 .apply(
103 "OneNote",
104 &Edit {
105 at: crate::now(),
106 ops: vec![Op::Section(SectionOp::Create(theirs))],
107 },
108 )
109 .unwrap();
110 let revision = section.seal().unwrap().unwrap();
111
112 let root = folder.canonicalize().unwrap();
113 let storage = Racing {
114 inner: Directory(root.clone()),
115 revision: Mutex::new(Some((path.clone(), revision))),
116 };
117 let mut notebook = Notebook::with(Box::new(storage), Some(root), &cache).unwrap();
118 let set = notebook.set_password(&path, None, Some("secret"));
119 let titles = |image: &[u8]| -> Vec<String> {
120 stored_pages(image)
121 .unwrap()
122 .into_iter()
123 .map(|page| page.page.title)
124 .collect()
125 };
126 let stored = std::fs::read(&file).unwrap();
127 match set {
128 Ok(key) => {
129 let key = key.unwrap();
130 let pages = stored_pages_unlocked(&stored, &key).unwrap();
131 assert!(pages.iter().any(|page| page.page.title == "Theirs"));
132 }
133 Err(_) => assert_eq!(titles(&stored), ["Mine", "Theirs"]),
134 }
135}