| 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 | |
| 6 | use onestore::{CommitError, CommitIo, CommitState, Stamp, Transaction}; |
| 7 | use 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 | |
| 18 | mod sqlite; |
| 19 | pub(crate) use sqlite::install as install_sqlite; |
| 20 | |
| 21 | /// A file's bytes and when they last changed, in milliseconds since 1970. |
| 22 | struct 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. |
| 32 | const RANGES: usize = 64; |
| 33 | |
| 34 | impl 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 | |
| 96 | type Shared = Rc<RefCell<Data>>; |
| 97 | |
| 98 | enum Node { |
| 99 | Directory, |
| 100 | File(Shared), |
| 101 | } |
| 102 | |
| 103 | #[derive(Default)] |
| 104 | struct 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 | |
| 116 | thread_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. |
| 127 | pub 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. |
| 133 | pub 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. |
| 138 | pub 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. |
| 148 | pub 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. |
| 183 | pub 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. |
| 194 | pub 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. |
| 201 | pub 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 | |
| 216 | fn now() -> f64 { |
| 217 | js_sys::Date::now() |
| 218 | } |
| 219 | |
| 220 | /// `path` absolute, without `.` or `..`. |
| 221 | fn 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 | |
| 235 | fn not_found(path: &Path) -> io::Error { |
| 236 | io::Error::new(ErrorKind::NotFound, format!("{} not found", path.display())) |
| 237 | } |
| 238 | |
| 239 | impl 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 | |
| 278 | fn with<T>(act: impl FnOnce(&mut Files) -> T) -> T { |
| 279 | FILES.with_borrow_mut(act) |
| 280 | } |
| 281 | |
| 282 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 283 | pub struct FileType(bool); |
| 284 | |
| 285 | impl 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)] |
| 300 | pub struct Metadata { |
| 301 | directory: bool, |
| 302 | length: u64, |
| 303 | modified: f64, |
| 304 | } |
| 305 | |
| 306 | impl 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 | |
| 329 | fn 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 | |
| 347 | pub 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 | |
| 358 | pub fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<Metadata> { |
| 359 | metadata(path) |
| 360 | } |
| 361 | |
| 362 | pub 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. |
| 368 | pub 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. |
| 379 | pub fn absolute(path: impl AsRef<Path>) -> io::Result<PathBuf> { |
| 380 | Ok(normal(path.as_ref())) |
| 381 | } |
| 382 | |
| 383 | pub 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 | |
| 391 | pub 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 | |
| 397 | pub 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 | |
| 401 | pub 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 | |
| 411 | pub 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. |
| 419 | pub 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 | |
| 427 | pub 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 | |
| 440 | pub 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 | |
| 457 | pub 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 | |
| 472 | pub 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 | |
| 489 | pub 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. |
| 512 | pub 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 | |
| 553 | pub struct DirEntry { |
| 554 | path: PathBuf, |
| 555 | metadata: Metadata, |
| 556 | } |
| 557 | |
| 558 | impl 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 | |
| 576 | pub struct ReadDir(std::vec::IntoIter<DirEntry>); |
| 577 | |
| 578 | impl 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 | |
| 586 | pub 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)] |
| 606 | pub struct OpenOptions { |
| 607 | read: bool, |
| 608 | write: bool, |
| 609 | append: bool, |
| 610 | truncate: bool, |
| 611 | create: bool, |
| 612 | create_new: bool, |
| 613 | } |
| 614 | |
| 615 | impl 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. |
| 675 | pub struct File { |
| 676 | data: Shared, |
| 677 | position: u64, |
| 678 | append: bool, |
| 679 | } |
| 680 | |
| 681 | impl 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 | |
| 742 | impl 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 | |
| 750 | impl 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 | |
| 765 | impl 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 | |
| 778 | impl 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. |
| 794 | pub fn read_file(path: impl AsRef<Path>) -> io::Result<Vec<u8>> { |
| 795 | read(path) |
| 796 | } |
| 797 | |
| 798 | pub 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 | |
| 806 | fn writable(path: impl AsRef<Path>) -> io::Result<File> { |
| 807 | OpenOptions::new().read(true).write(true).open(path) |
| 808 | } |
| 809 | |
| 810 | fn not_committed(error: io::Error) -> CommitError { |
| 811 | CommitError { |
| 812 | state: CommitState::NotCommitted, |
| 813 | error, |
| 814 | } |
| 815 | } |
| 816 | |
| 817 | fn mounted(path: &Path) -> bool { |
| 818 | with(|files| files.mounts.iter().any(|root| path.starts_with(root))) |
| 819 | } |
| 820 | |
| 821 | pub 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. |
| 846 | pub 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 | |
| 854 | pub 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. |
| 860 | pub 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. |
| 871 | pub 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 | } |