1use std::cmp::max;
12use std::path::{Path, PathBuf};
13use std::sync::mpsc::Sender;
14use std::thread::available_parallelism;
15use std::{fs, io};
16
17use threadpool::ThreadPool;
18
19pub(crate) trait PathError {
20 fn new<S, P: AsRef<Path>>(e: S, path: P) -> Self
21 where
22 S: ToString + Sized;
23}
24
25pub(crate) struct DocFS {
26 sync_only: bool,
27 errors: Option<Sender<String>>,
28 pool: ThreadPool,
29}
30
31impl DocFS {
32 pub(crate) fn new(errors: Sender<String>) -> DocFS {
33 const MINIMUM_NB_THREADS: usize = 2;
34 DocFS {
35 sync_only: false,
36 errors: Some(errors),
37 pool: ThreadPool::new(
38 available_parallelism()
39 .map(|nb| max(nb.get(), MINIMUM_NB_THREADS))
40 .unwrap_or(MINIMUM_NB_THREADS),
41 ),
42 }
43 }
44
45 pub(crate) fn set_sync_only(&mut self, sync_only: bool) {
46 self.sync_only = sync_only;
47 }
48
49 pub(crate) fn close(&mut self) {
50 self.errors = None;
51 }
52
53 pub(crate) fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
54 fs::create_dir_all(path)
58 }
59
60 pub(crate) fn write<E>(
61 &self,
62 path: PathBuf,
63 contents: impl 'static + Send + AsRef<[u8]>,
64 ) -> Result<(), E>
65 where
66 E: PathError,
67 {
68 if !self.sync_only {
69 let sender = self.errors.clone().expect("can't write after closing");
72 self.pool.execute(move || {
73 fs::write(&path, contents).unwrap_or_else(|e| {
74 sender.send(format!("\"{path}\": {e}", path = path.display())).unwrap_or_else(
75 |_| panic!("failed to send error on \"{}\"", path.display()),
76 )
77 });
78 });
79 } else {
80 fs::write(&path, contents).map_err(|e| E::new(e, path))?;
81 }
82
83 Ok(())
84 }
85}
86
87impl Drop for DocFS {
88 fn drop(&mut self) {
89 self.pool.join();
90 }
91}