1use std::path::{Path, PathBuf};
2
3use tracing::*;
4
5use crate::Strategy;
6
7pub struct FileExists;
8
9impl<O> Strategy<O> for FileExists {
10 type E = ();
11 fn skip(&self, output: &Path, _args_hash: u64, _code_hash: u64) -> bool {
12 let exists = output.exists();
13 if exists {
14 warn!(?output, "Skipping as output exists");
15 }
16 exists
17 }
18}
19
20pub fn append_ext(ext: impl AsRef<std::ffi::OsStr>, path: &Path) -> PathBuf {
22 let mut os_string: std::ffi::OsString = path.into();
23 os_string.push(".");
24 os_string.push(ext.as_ref());
25 os_string.into()
26}
27pub struct Markers<E> {
28 pub failure_marker: bool,
30 pub retriable: Box<dyn Fn(&E) -> bool + Sync + Send>,
31 pub success_marker: bool,
34 pub hashes: bool,
36 pub folder: bool,
39}
40impl<E> Default for Markers<E> {
41 fn default() -> Self {
42 Self {
43 failure_marker: true,
44 retriable: Box::new(|_| true),
45 success_marker: true,
46 hashes: true,
47 folder: false,
48 }
49 }
50}
51
52impl<E> Markers<E> {
53 fn marker_path(&self, success: bool, output: &Path) -> PathBuf {
54 let name = if success { "success" } else { "failure" };
55 if self.folder {
56 output.join(name)
57 } else {
58 append_ext(name, output)
59 }
60 }
61 fn hashes_str(&self, args_hash: u64, code_hash: u64) -> String {
62 if self.hashes {
63 format!("{}\n{}", args_hash, code_hash)
64 } else {
65 Default::default()
66 }
67 }
68 pub fn folder(mut self) -> Self {
69 self.folder = true;
70 self
71 }
72 pub fn retriable(mut self, retriable: impl Fn(&E) -> bool + Sync + Send + 'static) -> Self {
73 self.retriable = Box::new(retriable);
74 self
75 }
76}
77
78impl<T, E> Strategy<Result<T, E>> for Markers<E> {
79 type E = std::io::Error;
80 fn skip(&self, output: &Path, args_hash: u64, code_hash: u64) -> bool {
81 let check_marker = |path: &Path| {
82 if let Ok(s) = std::fs::read_to_string(path) {
83 if s == self.hashes_str(args_hash, code_hash) {
84 return true;
85 }
86 }
87 false
88 };
89 if self.failure_marker {
90 let marker = self.marker_path(false, output);
91 if check_marker(&marker) {
93 warn!(?marker, "Skipping due to failure marker");
94 return true;
95 }
96 }
97 if self.success_marker {
98 let marker = self.marker_path(true, output);
99 match (check_marker(&marker), output.exists()) {
100 (true, false) => {
101 warn!(?marker, "Success marker exists, but not the output file");
102 false
103 }
104 (true, true) => {
105 warn!(?marker, "Skipping due to success marker");
106 true
107 }
108 (false, _) => false,
109 }
110 } else {
111 output.exists()
113 }
114 }
115 fn callback(
116 &self,
117 result: &Result<T, E>,
118 output: &Path,
119 args_hash: u64,
120 code_hash: u64,
121 ) -> Result<(), std::io::Error> {
122 let write_markers = |success: bool| {
123 if self.folder {
125 std::fs::create_dir_all(output)?;
126 }
127 let path = self.marker_path(success, output);
128 debug!(
129 ?path,
130 "Writing {} marker",
131 if success { "success" } else { "failure" }
132 );
133 std::fs::write(path, self.hashes_str(args_hash, code_hash))?;
134 let _ = std::fs::remove_file(self.marker_path(!success, output));
136 std::io::Result::Ok(())
137 };
138 match result {
139 Ok(_) if self.success_marker => {
140 write_markers(true)?;
141 }
142 Err(e) if self.failure_marker => {
143 if (self.retriable)(e) {
144 debug!("Not writing a failure marker as the error is retriable");
145 } else {
146 write_markers(false)?;
147 }
148 }
149 _ => {}
150 }
151 Ok(())
152 }
153}