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. One thread: no file is ever locked.
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/// A copy, as nothing here writes a file under two names at once: a link is made to put a
418/// finished file in place, then the first name removed.
419pub fn hard_link(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
420 let bytes = read(original)?;
421 let link = normal(link.as_ref());
422 let data = with(|files| files.create(link))?;
423 data.borrow_mut().bytes = bytes;
424 Ok(())
425}
426
427pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
428 let path = normal(path.as_ref());
429 with(|files| {
430 files.parent_exists(&path)?;
431 if files.nodes.contains_key(&path) {
432 return Err(ErrorKind::AlreadyExists.into());
433 }
434 files.nodes.insert(path.clone(), Node::Directory);
435 files.changed.insert(path);
436 Ok(())
437 })
438}
439
440pub fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
441 let path = normal(path.as_ref());
442 for folder in path.ancestors().collect::<Vec<_>>().into_iter().rev() {
443 match with(|files| {
444 files
445 .nodes
446 .get(folder)
447 .map(|node| matches!(node, Node::Directory))
448 }) {
449 Some(true) => {}
450 Some(false) => return Err(ErrorKind::NotADirectory.into()),
451 None => create_dir(folder)?,
452 }
453 }
454 Ok(())
455}
456
457pub fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {
458 let path = normal(path.as_ref());
459 with(|files| {
460 let data = files.file(&path)?;
461 let mut data = data.borrow_mut();
462 if files.changed.remove(&path) {
463 files.durable.push((path.clone(), data.change()));
464 }
465 data.path = None;
466 files.nodes.remove(&path);
467 files.durable.push((path, Change::Removed));
468 Ok(())
469 })
470}
471
472pub fn remove_dir(path: impl AsRef<Path>) -> io::Result<()> {
473 let path = normal(path.as_ref());
474 with(|files| {
475 match files.nodes.get(&path) {
476 Some(Node::Directory) => {}
477 Some(Node::File(_)) => return Err(ErrorKind::NotADirectory.into()),
478 None => return Err(not_found(&path)),
479 }
480 if files.children(&path).next().is_some() {
481 return Err(ErrorKind::DirectoryNotEmpty.into());
482 }
483 files.nodes.remove(&path);
484 files.changed.insert(path);
485 Ok(())
486 })
487}
488
489pub fn remove_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
490 let path = normal(path.as_ref());
491 with(|files| {
492 let gone: Vec<PathBuf> = files
493 .nodes
494 .range(path.clone()..)
495 .take_while(|(each, _)| each.starts_with(&path))
496 .map(|(each, _)| each.clone())
497 .collect();
498 if gone.is_empty() {
499 return Err(not_found(&path));
500 }
501 for each in gone {
502 if let Some(Node::File(data)) = files.nodes.remove(&each) {
503 data.borrow_mut().path = None;
504 }
505 files.changed.insert(each);
506 }
507 Ok(())
508 })
509}
510
511/// Moves a file or folder, replacing a file at `to`, as a POSIX rename does.
512pub fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
513 let (from, to) = (normal(from.as_ref()), normal(to.as_ref()));
514 with(|files| {
515 if !files.nodes.contains_key(&from) {
516 return Err(not_found(&from));
517 }
518 files.parent_exists(&to)?;
519 if from == to {
520 return Ok(());
521 }
522 match files.nodes.get(&to) {
523 Some(Node::Directory) if files.children(&to).next().is_some() => {
524 return Err(ErrorKind::DirectoryNotEmpty.into());
525 }
526 Some(Node::File(data)) => data.borrow_mut().path = None,
527 _ => {}
528 }
529 let moved: Vec<PathBuf> = files
530 .nodes
531 .range(from.clone()..)
532 .take_while(|(each, _)| each.starts_with(&from))
533 .map(|(each, _)| each.clone())
534 .collect();
535 for each in moved {
536 let Some(node) = files.nodes.remove(&each) else {
537 continue;
538 };
539 let target = to.join(each.strip_prefix(&from).unwrap_or(Path::new("")));
540 if let Node::File(data) = &node {
541 let mut data = data.borrow_mut();
542 data.path = Some(target.clone());
543 data.unwritten = None;
544 }
545 files.changed.insert(each);
546 files.changed.insert(target.clone());
547 files.nodes.insert(target, node);
548 }
549 Ok(())
550 })
551}
552
553pub struct DirEntry {
554 path: PathBuf,
555 metadata: Metadata,
556}
557
558impl DirEntry {
559 pub fn path(&self) -> PathBuf {
560 self.path.clone()
561 }
562
563 pub fn file_name(&self) -> OsString {
564 self.path.file_name().unwrap_or_default().to_owned()
565 }
566
567 pub fn file_type(&self) -> io::Result<FileType> {
568 Ok(self.metadata.file_type())
569 }
570
571 pub fn metadata(&self) -> io::Result<Metadata> {
572 Ok(self.metadata.clone())
573 }
574}
575
576pub struct ReadDir(std::vec::IntoIter<DirEntry>);
577
578impl Iterator for ReadDir {
579 type Item = io::Result<DirEntry>;
580
581 fn next(&mut self) -> Option<Self::Item> {
582 self.0.next().map(Ok)
583 }
584}
585
586pub fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
587 let path = normal(path.as_ref());
588 with(|files| {
589 match files.nodes.get(&path) {
590 Some(Node::Directory) => {}
591 Some(Node::File(_)) => return Err(ErrorKind::NotADirectory.into()),
592 None => return Err(not_found(&path)),
593 }
594 let entries: Vec<DirEntry> = files
595 .children(&path)
596 .map(|(child, node)| DirEntry {
597 path: child.clone(),
598 metadata: metadata_of(node),
599 })
600 .collect();
601 Ok(ReadDir(entries.into_iter()))
602 })
603}
604
605#[derive(Clone, Debug, Default)]
606pub struct OpenOptions {
607 read: bool,
608 write: bool,
609 append: bool,
610 truncate: bool,
611 create: bool,
612 create_new: bool,
613}
614
615impl OpenOptions {
616 pub fn new() -> Self {
617 Self::default()
618 }
619
620 pub fn read(&mut self, read: bool) -> &mut Self {
621 self.read = read;
622 self
623 }
624
625 pub fn write(&mut self, write: bool) -> &mut Self {
626 self.write = write;
627 self
628 }
629
630 pub fn append(&mut self, append: bool) -> &mut Self {
631 self.append = append;
632 self
633 }
634
635 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
636 self.truncate = truncate;
637 self
638 }
639
640 pub fn create(&mut self, create: bool) -> &mut Self {
641 self.create = create;
642 self
643 }
644
645 pub fn create_new(&mut self, create_new: bool) -> &mut Self {
646 self.create_new = create_new;
647 self
648 }
649
650 pub fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
651 let path = normal(path.as_ref());
652 let data = with(|files| match files.file(&path) {
653 Ok(_) if self.create_new => Err(ErrorKind::AlreadyExists.into()),
654 Err(error)
655 if error.kind() == ErrorKind::NotFound && (self.create || self.create_new) =>
656 {
657 files.create(path.clone())
658 }
659 found => found,
660 })?;
661 if self.truncate && self.write {
662 let mut data = data.borrow_mut();
663 data.bytes.clear();
664 data.wrote(0..0);
665 }
666 Ok(File {
667 data,
668 position: 0,
669 append: self.append,
670 })
671 }
672}
673
674/// An open file, which reads and writes the file under whatever name it has since.
675pub struct File {
676 data: Shared,
677 position: u64,
678 append: bool,
679}
680
681impl File {
682 pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
683 OpenOptions::new().read(true).open(path)
684 }
685
686 pub fn create(path: impl AsRef<Path>) -> io::Result<Self> {
687 OpenOptions::new()
688 .write(true)
689 .create(true)
690 .truncate(true)
691 .open(path)
692 }
693
694 pub fn create_new(path: impl AsRef<Path>) -> io::Result<Self> {
695 OpenOptions::new()
696 .read(true)
697 .write(true)
698 .create_new(true)
699 .open(path)
700 }
701
702 pub fn metadata(&self) -> io::Result<Metadata> {
703 Ok(metadata_of(&Node::File(Rc::clone(&self.data))))
704 }
705
706 pub fn sync_all(&self) -> io::Result<()> {
707 self.data.borrow_mut().flush();
708 Ok(())
709 }
710
711 pub fn sync_data(&self) -> io::Result<()> {
712 self.sync_all()
713 }
714
715 pub fn set_len(&self, size: u64) -> io::Result<()> {
716 let mut data = self.data.borrow_mut();
717 let old = data.bytes.len();
718 data.bytes.resize(size as usize, 0);
719 data.wrote(old.min(size as usize)..size as usize);
720 Ok(())
721 }
722
723 fn read_from(&self, offset: u64, output: &mut [u8]) -> usize {
724 let data = self.data.borrow();
725 let rest = data.bytes.get(offset as usize..).unwrap_or_default();
726 let count = rest.len().min(output.len());
727 output[..count].copy_from_slice(&rest[..count]);
728 count
729 }
730
731 fn write_to(&self, offset: u64, input: &[u8]) {
732 let mut data = self.data.borrow_mut();
733 let end = offset as usize + input.len();
734 if data.bytes.len() < end {
735 data.bytes.resize(end, 0);
736 }
737 data.bytes[offset as usize..end].copy_from_slice(input);
738 data.wrote(offset as usize..end);
739 }
740}
741
742impl Read for File {
743 fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
744 let count = self.read_from(self.position, output);
745 self.position += count as u64;
746 Ok(count)
747 }
748}
749
750impl Write for File {
751 fn write(&mut self, input: &[u8]) -> io::Result<usize> {
752 if self.append {
753 self.position = self.data.borrow().bytes.len() as u64;
754 }
755 self.write_to(self.position, input);
756 self.position += input.len() as u64;
757 Ok(input.len())
758 }
759
760 fn flush(&mut self) -> io::Result<()> {
761 Ok(())
762 }
763}
764
765impl Seek for File {
766 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
767 let length = self.data.borrow().bytes.len() as i128;
768 let target = match position {
769 SeekFrom::Start(offset) => i128::from(offset),
770 SeekFrom::End(offset) => length + i128::from(offset),
771 SeekFrom::Current(offset) => i128::from(self.position) + i128::from(offset),
772 };
773 self.position = u64::try_from(target).map_err(|_| ErrorKind::InvalidInput)?;
774 Ok(self.position)
775 }
776}
777
778impl CommitIo for File {
779 fn read_at(&mut self, offset: u64, bytes: &mut [u8]) -> io::Result<usize> {
780 Ok(self.read_from(offset, bytes))
781 }
782
783 fn write_at(&mut self, offset: u64, bytes: &[u8]) -> io::Result<usize> {
784 self.write_to(offset, bytes);
785 Ok(bytes.len())
786 }
787
788 fn flush(&mut self) -> io::Result<()> {
789 Ok(())
790 }
791}
792
793/// `onestore::read_file`: a read here is whole, as nothing commits while it runs.
794pub fn read_file(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
795 read(path)
796}
797
798pub fn read_file_limited(path: impl AsRef<Path>, limit: usize) -> io::Result<Vec<u8>> {
799 let bytes = read(path)?;
800 if bytes.len() > limit {
801 return Err(ErrorKind::FileTooLarge.into());
802 }
803 Ok(bytes)
804}
805
806fn writable(path: impl AsRef<Path>) -> io::Result<File> {
807 OpenOptions::new().read(true).write(true).open(path)
808}
809
810fn not_committed(error: io::Error) -> CommitError {
811 CommitError {
812 state: CommitState::NotCommitted,
813 error,
814 }
815}
816
817fn mounted(path: &Path) -> bool {
818 with(|files| files.mounts.iter().any(|root| path.starts_with(root)))
819}
820
821pub fn commit_file(transaction: &Transaction, path: impl AsRef<Path>) -> Result<(), CommitError> {
822 let path = normal(path.as_ref());
823 if !mounted(&path) {
824 return transaction.commit(&mut writable(&path).map_err(not_committed)?);
825 }
826 let mut image = File {
827 data: Rc::new(RefCell::new(Data {
828 bytes: read(&path).map_err(not_committed)?,
829 modified: now(),
830 path: None,
831 unwritten: None,
832 })),
833 position: 0,
834 append: false,
835 };
836 transaction.commit(&mut image)?;
837 let image = std::mem::take(&mut image.data.borrow_mut().bytes);
838 with(|files| files.committed.insert(path, image));
839 Err(CommitError {
840 state: CommitState::Unknown,
841 error: io::Error::new(ErrorKind::WouldBlock, "Writing the section to its folder"),
842 })
843}
844
845/// A mounted file is durable as the host puts it back, and keeps the version its commit wrote.
846pub fn confirm_file(path: impl AsRef<Path>, base: &Stamp) -> Result<(), CommitError> {
847 let mut file = writable(&path).map_err(not_committed)?;
848 if mounted(&normal(path.as_ref())) {
849 return base.check(&mut file).map_err(not_committed);
850 }
851 onestore::confirm(&mut file, base)
852}
853
854pub fn place_file(path: impl AsRef<Path>, ancestor: [u8; 16], name: &str) -> io::Result<()> {
855 onestore::place(&mut writable(path)?, ancestor, name)
856}
857
858/// Puts the file at `with` in the place of the file at `path`, provided `path` still has
859/// `base`'s stamp.
860pub fn supersede_file(
861 path: impl AsRef<Path>,
862 base: &Stamp,
863 with: impl AsRef<Path>,
864) -> Result<(), CommitError> {
865 base.check(&mut writable(&path).map_err(not_committed)?)
866 .map_err(not_committed)?;
867 rename(with, path).map_err(not_committed)
868}
869
870/// Waits until the browser host has durably written every ordered flush handed to it.
871pub async fn durable() -> io::Result<()> {
872 use wasm_bindgen::prelude::*;
873 #[wasm_bindgen]
874 extern "C" {
875 #[wasm_bindgen(catch, js_namespace = globalThis, js_name = snowboundFlushStorage)]
876 fn flush_storage() -> Result<js_sys::Promise, JsValue>;
877 }
878 let failure = |error: JsValue| {
879 io::Error::new(
880 ErrorKind::WouldBlock,
881 error
882 .as_string()
883 .unwrap_or_else(|| "Browser storage failed".into()),
884 )
885 };
886 wasm_bindgen_futures::JsFuture::from(flush_storage().map_err(failure)?)
887 .await
888 .map_err(failure)?;
889 Ok(())
890}