1//! The browser's file system: std's calls over files held in memory, which the host loads
2//! before anything opens (`restore`) and writes out as they change, by the byte ranges that
3//! changed (`changes`). SQLite reaches the same files through `sqlite`, its default VFS here,
4//! so a replica is a file like any other.
5
6use onestore::{CommitError, CommitIo, CommitState, Stamp, Transaction};
7use std::{
8 cell::RefCell,
9 collections::{BTreeMap, BTreeSet},
10 ffi::OsString,
11 io::{self, ErrorKind, Read, Seek, SeekFrom, Write},
12 ops::Range,
13 path::{Component, Path, PathBuf},
14 rc::Rc,
15 time::{Duration, SystemTime, UNIX_EPOCH},
16};
17
18mod sqlite;
19pub(crate) use sqlite::install as install_sqlite;
20
21/// A file's bytes and when they last changed, in milliseconds since 1970.
22struct Data {
23 bytes: Vec<u8>,
24 modified: f64,
25 /// Where the file is, which a rename moves; none once it is removed.
26 path: Option<PathBuf>,
27 /// The ranges written since the host last wrote the file out, or none for all of it.
28 unwritten: Option<Vec<Range<usize>>>,
29}
30
31/// More ranges than this write the whole file out instead.
32const RANGES: usize = 64;
33
34impl Data {
35 fn flush(&mut self) {
36 if let Some(path) = self.path.clone() {
37 with(|files| {
38 if files.changed.remove(&path) {
39 files.durable.push((path, self.change()));
40 }
41 });
42 }
43 }
44
45 fn change(&mut self) -> Change {
46 let length = self.bytes.len();
47 let ranges = self
48 .unwritten
49 .replace(Vec::new())
50 .unwrap_or_else(|| std::iter::once(0..length).collect());
51 Change::File {
52 length: length as u64,
53 ranges: ranges
54 .into_iter()
55 .map(|range| range.start.min(length)..range.end.min(length))
56 .filter(|range| !range.is_empty())
57 .map(|range| (range.start as u64, self.bytes[range].to_vec()))
58 .collect(),
59 }
60 }
61 fn new(
62 bytes: Vec<u8>,
63 modified: f64,
64 path: PathBuf,
65 unwritten: Option<Vec<Range<usize>>>,
66 ) -> Shared {
67 Rc::new(RefCell::new(Self {
68 bytes,
69 modified,
70 path: Some(path),
71 unwritten,
72 }))
73 }
74
75 /// Notes `range` written now, merged into a written range it meets.
76 fn wrote(&mut self, range: Range<usize>) {
77 self.modified = now();
78 if let Some(ranges) = &mut self.unwritten {
79 match ranges
80 .iter_mut()
81 .find(|known| known.start <= range.end && range.start <= known.end)
82 {
83 Some(known) => *known = known.start.min(range.start)..known.end.max(range.end),
84 None => ranges.push(range),
85 }
86 if ranges.len() > RANGES {
87 self.unwritten = None;
88 }
89 }
90 if let Some(path) = self.path.clone() {
91 with(|files| files.changed.insert(path));
92 }
93 }
94}
95
96type Shared = Rc<RefCell<Data>>;
97
98enum Node {
99 Directory,
100 File(Shared),
101}
102
103#[derive(Default)]
104struct Files {
105 nodes: BTreeMap<PathBuf, Node>,
106 /// Paths written, made or removed since the host last asked.
107 changed: BTreeSet<PathBuf>,
108 /// Flushes and removals in order, including SQLite's WAL before its database checkpoint.
109 durable: Vec<(PathBuf, Change)>,
110 /// Folders the host mirrors from elsewhere (`mount`).
111 mounts: BTreeSet<PathBuf>,
112 /// Images committed under a mount, waiting for the host to write them (`committed`).
113 committed: BTreeMap<PathBuf, Vec<u8>>,
114}
115
116thread_local! {
117 static FILES: RefCell<Files> = RefCell::new(Files {
118 nodes: BTreeMap::from([(PathBuf::from("/"), Node::Directory)]),
119 ..Files::default()
120 });
121}
122
123/// Mirrors the folder at `root` from somewhere only the host reaches, as a folder on the
124/// user's disk. A commit to a section there goes to the host (`committed`) and stands only
125/// once the host has written it and put the file back (`restore`): until then it is uncertain,
126/// as a commit whose answer was lost, so an edit counts as published only once it is on disk.
127pub fn mount(root: impl AsRef<Path>) {
128 let root = normal(root.as_ref());
129 with(|files| files.mounts.insert(root));
130}
131
132/// The images committed under mounts since the last call, latest per file.
133pub fn committed() -> Vec<(PathBuf, Vec<u8>)> {
134 with(|files| std::mem::take(&mut files.committed).into_iter().collect())
135}
136
137/// What a path holds, as the host keeps it.
138pub enum Saved {
139 Directory,
140 /// The bytes, and when they last changed in milliseconds since 1970.
141 File(Vec<u8>, f64),
142 /// Nothing: what was there went, with what it held.
143 Gone,
144}
145
146/// Puts what the host kept at `path` back, as it was before the page last closed or as it
147/// now stands where it is kept.
148pub fn restore(path: impl AsRef<Path>, saved: Saved) {
149 let path = normal(path.as_ref());
150 FILES.with_borrow_mut(|files| {
151 let node = match (saved, files.nodes.get(&path)) {
152 (Saved::Gone, _) => {
153 let gone: Vec<PathBuf> = files
154 .nodes
155 .range(path.clone()..)
156 .take_while(|(each, _)| each.starts_with(&path))
157 .map(|(each, _)| each.clone())
158 .collect();
159 for each in gone {
160 if let Some(Node::File(data)) = files.nodes.remove(&each) {
161 data.borrow_mut().path = None;
162 }
163 }
164 return;
165 }
166 (Saved::Directory, _) => Node::Directory,
167 (Saved::File(bytes, modified), Some(Node::File(data))) => {
168 let mut held = data.borrow_mut();
169 held.bytes = bytes;
170 held.modified = modified;
171 held.unwritten = Some(Vec::new());
172 return;
173 }
174 (Saved::File(bytes, modified), _) => {
175 Node::File(Data::new(bytes, modified, path.clone(), Some(Vec::new())))
176 }
177 };
178 files.nodes.insert(path, node);
179 });
180}
181
182/// How a path changed, as the host writes it out.
183pub enum Change {
184 Removed,
185 Directory,
186 /// The file's length, and the ranges that changed with their bytes.
187 File {
188 length: u64,
189 ranges: Vec<(u64, Vec<u8>)>,
190 },
191}
192
193/// Whether any path changed since `changes` was last called.
194pub fn changed() -> bool {
195 FILES.with_borrow(|files| {
196 !files.durable.is_empty() || !files.changed.is_empty() || !files.committed.is_empty()
197 })
198}
199
200/// Ordered file flushes, followed by changes not yet flushed.
201pub fn changes() -> Vec<(PathBuf, Change)> {
202 FILES.with_borrow_mut(|files| {
203 let mut changes = std::mem::take(&mut files.durable);
204 changes.extend(std::mem::take(&mut files.changed).into_iter().map(|path| {
205 let change = match files.nodes.get(&path) {
206 None => Change::Removed,
207 Some(Node::Directory) => Change::Directory,
208 Some(Node::File(data)) => data.borrow_mut().change(),
209 };
210 (path, change)
211 }));
212 changes
213 })
214}
215
216fn now() -> f64 {
217 js_sys::Date::now()
218}
219
220/// `path` absolute, without `.` or `..`.
221fn normal(path: &Path) -> PathBuf {
222 let mut normal = PathBuf::from("/");
223 for component in path.components() {
224 match component {
225 Component::Normal(name) => normal.push(name),
226 Component::ParentDir => {
227 normal.pop();
228 }
229 Component::RootDir | Component::CurDir | Component::Prefix(_) => {}
230 }
231 }
232 normal
233}
234
235fn not_found(path: &Path) -> io::Error {
236 io::Error::new(ErrorKind::NotFound, format!("{} not found", path.display()))
237}
238
239impl Files {
240 fn file(&self, path: &Path) -> io::Result<Shared> {
241 match self.nodes.get(path) {
242 Some(Node::File(data)) => Ok(Rc::clone(data)),
243 Some(Node::Directory) => Err(ErrorKind::IsADirectory.into()),
244 None => Err(not_found(path)),
245 }
246 }
247
248 fn parent_exists(&self, path: &Path) -> io::Result<()> {
249 match path.parent().map(|parent| self.nodes.get(parent)) {
250 None | Some(Some(Node::Directory)) => Ok(()),
251 Some(Some(Node::File(_))) => Err(ErrorKind::NotADirectory.into()),
252 Some(None) => Err(not_found(path.parent().unwrap_or(path))),
253 }
254 }
255
256 /// A new empty file at `path`, whose folder exists.
257 fn create(&mut self, path: PathBuf) -> io::Result<Shared> {
258 self.parent_exists(&path)?;
259 if self.nodes.contains_key(&path) {
260 return Err(ErrorKind::AlreadyExists.into());
261 }
262 let data = Data::new(Vec::new(), now(), path.clone(), None);
263 self.nodes
264 .insert(path.clone(), Node::File(Rc::clone(&data)));
265 self.changed.insert(path);
266 Ok(data)
267 }
268
269 fn children(&self, folder: &Path) -> impl Iterator<Item = (&PathBuf, &Node)> {
270 self.nodes
271 .range(folder.to_path_buf()..)
272 .skip(1)
273 .take_while(move |(path, _)| path.starts_with(folder))
274 .filter(move |(path, _)| path.parent() == Some(folder))
275 }
276}
277
278fn with<T>(act: impl FnOnce(&mut Files) -> T) -> T {
279 FILES.with_borrow_mut(act)
280}
281
282#[derive(Clone, Copy, Debug, PartialEq, Eq)]
283pub struct FileType(bool);
284
285impl FileType {
286 pub fn is_dir(&self) -> bool {
287 self.0
288 }
289
290 pub fn is_file(&self) -> bool {
291 !self.0
292 }
293
294 pub fn is_symlink(&self) -> bool {
295 false
296 }
297}
298
299#[derive(Clone, Debug)]
300pub struct Metadata {
301 directory: bool,
302 length: u64,
303 modified: f64,
304}
305
306impl Metadata {
307 pub fn is_dir(&self) -> bool {
308 self.directory
309 }
310
311 pub fn is_file(&self) -> bool {
312 !self.directory
313 }
314
315 pub fn file_type(&self) -> FileType {
316 FileType(self.directory)
317 }
318
319 #[allow(clippy::len_without_is_empty, reason = "std's Metadata has none")]
320 pub fn len(&self) -> u64 {
321 self.length
322 }
323
324 pub fn modified(&self) -> io::Result<SystemTime> {
325 Ok(UNIX_EPOCH + Duration::from_secs_f64(self.modified.max(0.0) / 1e3))
326 }
327}
328
329fn metadata_of(node: &Node) -> Metadata {
330 match node {
331 Node::Directory => Metadata {
332 directory: true,
333 length: 0,
334 modified: 0.0,
335 },
336 Node::File(data) => {
337 let data = data.borrow();
338 Metadata {
339 directory: false,
340 length: data.bytes.len() as u64,
341 modified: data.modified,
342 }
343 }
344 }
345}
346
347pub fn metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
348 let path = normal(path.as_ref());
349 with(|files| {
350 files
351 .nodes
352 .get(&path)
353 .map(metadata_of)
354 .ok_or_else(|| not_found(&path))
355 })
356}
357
358pub fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
359 metadata(path)
360}
361
362pub fn exists(path: impl AsRef<Path>) -> io::Result<bool> {
363 let path = normal(path.as_ref());
364 Ok(with(|files| files.nodes.contains_key(&path)))
365}
366
367/// This page's number among the processes that may share its files, as other tabs do.
368pub fn process_id() -> u32 {
369 use std::sync::OnceLock;
370 static ID: OnceLock<u32> = OnceLock::new();
371 *ID.get_or_init(|| {
372 let mut bytes = [0; 4];
373 let _ = getrandom::fill(&mut bytes);
374 u32::from_ne_bytes(bytes)
375 })
376}
377
378/// `path` from the root, as every path here is.
379pub fn absolute(path: impl AsRef<Path>) -> io::Result<PathBuf> {
380 Ok(normal(path.as_ref()))
381}
382
383pub fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
384 let path = normal(path.as_ref());
385 match with(|files| files.nodes.contains_key(&path)) {
386 true => Ok(path),
387 false => Err(not_found(&path)),
388 }
389}
390
391pub fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
392 let path = normal(path.as_ref());
393 let data = with(|files| files.file(&path))?;
394 Ok(data.borrow().bytes.clone())
395}
396
397pub fn read_to_string(path: impl AsRef<Path>) -> io::Result<String> {
398 String::from_utf8(read(path)?).map_err(|error| io::Error::new(ErrorKind::InvalidData, error))
399}
400
401pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {
402 let path = normal(path.as_ref());
403 let data = with(|files| files.file(&path).or_else(|_| files.create(path.clone())))?;
404 let mut data = data.borrow_mut();
405 data.bytes = contents.as_ref().to_vec();
406 let length = data.bytes.len();
407 data.wrote(0..length);
408 Ok(())
409}
410
411pub fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<u64> {
412 let bytes = read(from)?;
413 write(to, &bytes)?;
414 Ok(bytes.len() as u64)
415}
416
417/// Moves the file `from` to `to`, refusing to replace a file there.
418pub fn rename_new(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
419 let bytes = read(&from)?;
420 let to = normal(to.as_ref());
421 let data = with(|files| files.create(to))?;
422 data.borrow_mut().bytes = bytes;
423 remove_file(from)
424}
425
426pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
427 let path = normal(path.as_ref());
428 with(|files| {
429 files.parent_exists(&path)?;
430 if files.nodes.contains_key(&path) {
431 return Err(ErrorKind::AlreadyExists.into());
432 }
433 files.nodes.insert(path.clone(), Node::Directory);
434 files.changed.insert(path);
435 Ok(())
436 })
437}
438
439pub fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
440 let path = normal(path.as_ref());
441 for folder in path.ancestors().collect::<Vec<_>>().into_iter().rev() {
442 match with(|files| {
443 files
444 .nodes
445 .get(folder)
446 .map(|node| matches!(node, Node::Directory))
447 }) {
448 Some(true) => {}
449 Some(false) => return Err(ErrorKind::NotADirectory.into()),
450 None => create_dir(folder)?,
451 }
452 }
453 Ok(())
454}
455
456pub fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {
457 let path = normal(path.as_ref());
458 with(|files| {
459 let data = files.file(&path)?;
460 let mut data = data.borrow_mut();
461 if files.changed.remove(&path) {
462 files.durable.push((path.clone(), data.change()));
463 }
464 data.path = None;
465 files.nodes.remove(&path);
466 files.durable.push((path, Change::Removed));
467 Ok(())
468 })
469}
470
471pub fn remove_dir(path: impl AsRef<Path>) -> io::Result<()> {
472 let path = normal(path.as_ref());
473 with(|files| {
474 match files.nodes.get(&path) {
475 Some(Node::Directory) => {}
476 Some(Node::File(_)) => return Err(ErrorKind::NotADirectory.into()),
477 None => return Err(not_found(&path)),
478 }
479 if files.children(&path).next().is_some() {
480 return Err(ErrorKind::DirectoryNotEmpty.into());
481 }
482 files.nodes.remove(&path);
483 files.changed.insert(path);
484 Ok(())
485 })
486}
487
488pub fn remove_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
489 let path = normal(path.as_ref());
490 with(|files| {
491 let gone: Vec<PathBuf> = files
492 .nodes
493 .range(path.clone()..)
494 .take_while(|(each, _)| each.starts_with(&path))
495 .map(|(each, _)| each.clone())
496 .collect();
497 if gone.is_empty() {
498 return Err(not_found(&path));
499 }
500 for each in gone {
501 if let Some(Node::File(data)) = files.nodes.remove(&each) {
502 data.borrow_mut().path = None;
503 }
504 files.changed.insert(each);
505 }
506 Ok(())
507 })
508}
509
510/// Moves a file or folder, replacing a file at `to`, as a POSIX rename does.
511pub fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
512 let (from, to) = (normal(from.as_ref()), normal(to.as_ref()));
513 with(|files| {
514 if !files.nodes.contains_key(&from) {
515 return Err(not_found(&from));
516 }
517 files.parent_exists(&to)?;
518 if from == to {
519 return Ok(());
520 }
521 match files.nodes.get(&to) {
522 Some(Node::Directory) if files.children(&to).next().is_some() => {
523 return Err(ErrorKind::DirectoryNotEmpty.into());
524 }
525 Some(Node::File(data)) => data.borrow_mut().path = None,
526 _ => {}
527 }
528 let moved: Vec<PathBuf> = files
529 .nodes
530 .range(from.clone()..)
531 .take_while(|(each, _)| each.starts_with(&from))
532 .map(|(each, _)| each.clone())
533 .collect();
534 for each in moved {
535 let Some(node) = files.nodes.remove(&each) else {
536 continue;
537 };
538 let target = to.join(each.strip_prefix(&from).unwrap_or(Path::new("")));
539 if let Node::File(data) = &node {
540 let mut data = data.borrow_mut();
541 data.path = Some(target.clone());
542 data.unwritten = None;
543 }
544 files.changed.insert(each);
545 files.changed.insert(target.clone());
546 files.nodes.insert(target, node);
547 }
548 Ok(())
549 })
550}
551
552pub struct DirEntry {
553 path: PathBuf,
554 metadata: Metadata,
555}
556
557impl DirEntry {
558 pub fn path(&self) -> PathBuf {
559 self.path.clone()
560 }
561
562 pub fn file_name(&self) -> OsString {
563 self.path.file_name().unwrap_or_default().to_owned()
564 }
565
566 pub fn file_type(&self) -> io::Result<FileType> {
567 Ok(self.metadata.file_type())
568 }
569
570 pub fn metadata(&self) -> io::Result<Metadata> {
571 Ok(self.metadata.clone())
572 }
573}
574
575pub struct ReadDir(std::vec::IntoIter<DirEntry>);
576
577impl Iterator for ReadDir {
578 type Item = io::Result<DirEntry>;
579
580 fn next(&mut self) -> Option<Self::Item> {
581 self.0.next().map(Ok)
582 }
583}
584
585pub fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
586 let path = normal(path.as_ref());
587 with(|files| {
588 match files.nodes.get(&path) {
589 Some(Node::Directory) => {}
590 Some(Node::File(_)) => return Err(ErrorKind::NotADirectory.into()),
591 None => return Err(not_found(&path)),
592 }
593 let entries: Vec<DirEntry> = files
594 .children(&path)
595 .map(|(child, node)| DirEntry {
596 path: child.clone(),
597 metadata: metadata_of(node),
598 })
599 .collect();
600 Ok(ReadDir(entries.into_iter()))
601 })
602}
603
604#[derive(Clone, Debug, Default)]
605pub struct OpenOptions {
606 read: bool,
607 write: bool,
608 append: bool,
609 truncate: bool,
610 create: bool,
611 create_new: bool,
612}
613
614impl OpenOptions {
615 pub fn new() -> Self {
616 Self::default()
617 }
618
619 pub fn read(&mut self, read: bool) -> &mut Self {
620 self.read = read;
621 self
622 }
623
624 pub fn write(&mut self, write: bool) -> &mut Self {
625 self.write = write;
626 self
627 }
628
629 pub fn append(&mut self, append: bool) -> &mut Self {
630 self.append = append;
631 self
632 }
633
634 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
635 self.truncate = truncate;
636 self
637 }
638
639 pub fn create(&mut self, create: bool) -> &mut Self {
640 self.create = create;
641 self
642 }
643
644 pub fn create_new(&mut self, create_new: bool) -> &mut Self {
645 self.create_new = create_new;
646 self
647 }
648
649 pub fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
650 let path = normal(path.as_ref());
651 let data = with(|files| match files.file(&path) {
652 Ok(_) if self.create_new => Err(ErrorKind::AlreadyExists.into()),
653 Err(error)
654 if error.kind() == ErrorKind::NotFound && (self.create || self.create_new) =>
655 {
656 files.create(path.clone())
657 }
658 found => found,
659 })?;
660 if self.truncate && self.write {
661 let mut data = data.borrow_mut();
662 data.bytes.clear();
663 data.wrote(0..0);
664 }
665 Ok(File {
666 data,
667 position: 0,
668 append: self.append,
669 })
670 }
671}
672
673/// An open file, which reads and writes the file under whatever name it has since.
674pub struct File {
675 data: Shared,
676 position: u64,
677 append: bool,
678}
679
680impl File {
681 pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
682 OpenOptions::new().read(true).open(path)
683 }
684
685 pub fn create(path: impl AsRef<Path>) -> io::Result<Self> {
686 OpenOptions::new()
687 .write(true)
688 .create(true)
689 .truncate(true)
690 .open(path)
691 }
692
693 pub fn create_new(path: impl AsRef<Path>) -> io::Result<Self> {
694 OpenOptions::new()
695 .read(true)
696 .write(true)
697 .create_new(true)
698 .open(path)
699 }
700
701 pub fn metadata(&self) -> io::Result<Metadata> {
702 Ok(metadata_of(&Node::File(Rc::clone(&self.data))))
703 }
704
705 pub fn sync_all(&self) -> io::Result<()> {
706 self.data.borrow_mut().flush();
707 Ok(())
708 }
709
710 pub fn sync_data(&self) -> io::Result<()> {
711 self.sync_all()
712 }
713
714 pub fn set_len(&self, size: u64) -> io::Result<()> {
715 let mut data = self.data.borrow_mut();
716 let old = data.bytes.len();
717 data.bytes.resize(size as usize, 0);
718 data.wrote(old.min(size as usize)..size as usize);
719 Ok(())
720 }
721
722 fn read_from(&self, offset: u64, output: &mut [u8]) -> usize {
723 let data = self.data.borrow();
724 let rest = data.bytes.get(offset as usize..).unwrap_or_default();
725 let count = rest.len().min(output.len());
726 output[..count].copy_from_slice(&rest[..count]);
727 count
728 }
729
730 fn write_to(&self, offset: u64, input: &[u8]) {
731 let mut data = self.data.borrow_mut();
732 let end = offset as usize + input.len();
733 if data.bytes.len() < end {
734 data.bytes.resize(end, 0);
735 }
736 data.bytes[offset as usize..end].copy_from_slice(input);
737 data.wrote(offset as usize..end);
738 }
739}
740
741impl Read for File {
742 fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
743 let count = self.read_from(self.position, output);
744 self.position += count as u64;
745 Ok(count)
746 }
747}
748
749impl Write for File {
750 fn write(&mut self, input: &[u8]) -> io::Result<usize> {
751 if self.append {
752 self.position = self.data.borrow().bytes.len() as u64;
753 }
754 self.write_to(self.position, input);
755 self.position += input.len() as u64;
756 Ok(input.len())
757 }
758
759 fn flush(&mut self) -> io::Result<()> {
760 Ok(())
761 }
762}
763
764impl Seek for File {
765 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
766 let length = self.data.borrow().bytes.len() as i128;
767 let target = match position {
768 SeekFrom::Start(offset) => i128::from(offset),
769 SeekFrom::End(offset) => length + i128::from(offset),
770 SeekFrom::Current(offset) => i128::from(self.position) + i128::from(offset),
771 };
772 self.position = u64::try_from(target).map_err(|_| ErrorKind::InvalidInput)?;
773 Ok(self.position)
774 }
775}
776
777impl CommitIo for File {
778 fn read_at(&mut self, offset: u64, bytes: &mut [u8]) -> io::Result<usize> {
779 Ok(self.read_from(offset, bytes))
780 }
781
782 fn write_at(&mut self, offset: u64, bytes: &[u8]) -> io::Result<usize> {
783 self.write_to(offset, bytes);
784 Ok(bytes.len())
785 }
786
787 fn flush(&mut self) -> io::Result<()> {
788 Ok(())
789 }
790}
791
792/// `onestore::read_file`: a read here is whole, as nothing commits while it runs.
793pub fn read_file(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
794 read(path)
795}
796
797pub fn read_file_limited(path: impl AsRef<Path>, limit: usize) -> io::Result<Vec<u8>> {
798 let bytes = read(path)?;
799 if bytes.len() > limit {
800 return Err(ErrorKind::FileTooLarge.into());
801 }
802 Ok(bytes)
803}
804
805fn writable(path: impl AsRef<Path>) -> io::Result<File> {
806 OpenOptions::new().read(true).write(true).open(path)
807}
808
809fn not_committed(error: io::Error) -> CommitError {
810 CommitError {
811 state: CommitState::NotCommitted,
812 error,
813 }
814}
815
816fn mounted(path: &Path) -> bool {
817 with(|files| files.mounts.iter().any(|root| path.starts_with(root)))
818}
819
820pub fn commit_file(transaction: &Transaction, path: impl AsRef<Path>) -> Result<(), CommitError> {
821 let path = normal(path.as_ref());
822 if !mounted(&path) {
823 return transaction.commit(&mut writable(&path).map_err(not_committed)?);
824 }
825 let mut image = File {
826 data: Rc::new(RefCell::new(Data {
827 bytes: read(&path).map_err(not_committed)?,
828 modified: now(),
829 path: None,
830 unwritten: None,
831 })),
832 position: 0,
833 append: false,
834 };
835 transaction.commit(&mut image)?;
836 let image = std::mem::take(&mut image.data.borrow_mut().bytes);
837 with(|files| files.committed.insert(path, image));
838 Err(CommitError {
839 state: CommitState::Unknown,
840 error: io::Error::new(ErrorKind::WouldBlock, "Writing the section to its folder"),
841 })
842}
843
844/// A mounted file is durable as the host puts it back, and keeps the version its commit wrote.
845pub fn confirm_file(path: impl AsRef<Path>, base: &Stamp) -> Result<(), CommitError> {
846 let mut file = writable(&path).map_err(not_committed)?;
847 if mounted(&normal(path.as_ref())) {
848 return base.check(&mut file).map_err(not_committed);
849 }
850 onestore::confirm(&mut file, base)
851}
852
853pub fn place_file(path: impl AsRef<Path>, ancestor: [u8; 16], name: &str) -> io::Result<()> {
854 onestore::place(&mut writable(path)?, ancestor, name)
855}
856
857/// Puts the file at `with` in the place of the file at `path`, provided `path` still has
858/// `base`'s stamp.
859pub fn supersede_file(
860 path: impl AsRef<Path>,
861 base: &Stamp,
862 with: impl AsRef<Path>,
863) -> Result<(), CommitError> {
864 base.check(&mut writable(&path).map_err(not_committed)?)
865 .map_err(not_committed)?;
866 rename(with, path).map_err(not_committed)
867}
868
869/// Waits until the browser host has durably written every ordered flush handed to it.
870pub async fn durable() -> io::Result<()> {
871 use wasm_bindgen::prelude::*;
872 #[wasm_bindgen]
873 extern "C" {
874 #[wasm_bindgen(catch, js_namespace = globalThis, js_name = snowboundFlushStorage)]
875 fn flush_storage() -> Result<js_sys::Promise, JsValue>;
876 }
877 let failure = |error: JsValue| {
878 io::Error::new(
879 ErrorKind::WouldBlock,
880 error
881 .as_string()
882 .unwrap_or_else(|| "Browser storage failed".into()),
883 )
884 };
885 wasm_bindgen_futures::JsFuture::from(flush_storage().map_err(failure)?)
886 .await
887 .map_err(failure)?;
888 Ok(())
889}