path_planning/
run.rs

1/* Copyright (C) 2020 Dylan Staatz - All Rights Reserved. */
2
3use std::fs::read_dir;
4use std::fs::{create_dir, remove_dir_all, File};
5use std::io::{BufReader, BufWriter, Write};
6use std::path::{Path, PathBuf};
7use std::result::Result as StdResult;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::Arc;
10
11use bincode;
12use ctrlc::set_handler;
13use log::LevelFilter;
14use ron::{self, ser::PrettyConfig};
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16use simple_logging::log_to_file;
17use tar::{Archive as TarArchive, Builder as TarBuilder};
18use time::{Duration, Error as TimeError, OffsetDateTime};
19
20use crate::error::{IndexOutOfBounds, Result, StatesNotRecorded};
21use crate::params::FromParams;
22
23/// A process to be ran that produces state information after each step
24pub trait Runnable: FromParams + Sized {
25  /// Serializable state information
26  type State: Clone;
27
28  /// Make progress on the algorithm and return state information.
29  ///
30  /// Return None once the algorithm is complete.
31  fn step(&mut self) -> Option<&Self::State>;
32}
33
34pub const DATA_DIR_NAME: &'static str = "data";
35pub const SETTINGS_FILE_NAME: &'static str = "settings.ron";
36pub const STATS_FILE_NAME: &'static str = "stats.ron";
37pub const PARAMS_FILE_NAME: &'static str = "params.ron";
38pub const LOG_FILE_NAME: &'static str = "run.log";
39
40/// Runs the Runnable with the given settings
41pub fn run_and_save<R: Runnable>(mut settings: Settings) -> Result<Run<R>>
42where
43  R::Params: Serialize + DeserializeOwned,
44  R::State: Serialize,
45{
46  let mut stats = Stats::new_paused()?; // PAUSE-TIMER, initialization
47
48  // Setup control+c handling
49  let is_running = Arc::new(AtomicBool::new(true));
50  let r = is_running.clone();
51
52  set_handler(move || {
53    r.store(false, Ordering::SeqCst);
54  })?;
55
56  if settings.verbose {
57    match settings.name {
58      Some(ref name) => println!("Starting run for {}", name),
59      None => println!("Starting run"),
60    }
61    println!("\tmax_iterations: {:?}", settings.max_iterations);
62    println!("\tkeep_in_memory: {}", settings.keep_in_memory);
63    println!("\toutput_dir: {:?}", settings.output_dir);
64    println!("\tfilename_prefix: {:?}", settings.filename_prefix);
65    println!("\tfilename_suffix: {:?}", settings.filename_suffix);
66    println!();
67  }
68
69  // Load parameters file
70  let params: R::Params = load_ron(&settings.parameter_file)?;
71  if settings.verbose {
72    println!("Parameters loaded");
73  }
74
75  // Setup directory structure
76  let (run_dir, data_dir) = if let Some(output_dir) = settings.output_dir {
77    // Directory where setttings, stats, logs, and params are saved to
78    let run_dir = output_dir;
79    create_dir(&run_dir)?;
80
81    // Setup log file
82    log_to_file(run_dir.join(LOG_FILE_NAME), LevelFilter::Info)?;
83
84    // Directory where all the state information is saved to
85    let data_dir = run_dir.join(DATA_DIR_NAME);
86    create_dir(&data_dir)?;
87
88    (Some(run_dir), Some(data_dir))
89  } else {
90    (None, None)
91  };
92
93  // Setup algorithm with params
94  let mut runnable = R::from_params(params.clone())?;
95
96  settings.output_dir = run_dir;
97
98  let mut states = if settings.keep_in_memory {
99    Some(StateStorage::new_memory())
100  } else {
101    match data_dir {
102      Some(dir) => Some(StateStorage::new_disk(
103        dir,
104        settings.filename_prefix.clone(),
105        settings.filename_suffix.clone(),
106      )),
107      None => None,
108    }
109  };
110
111  // Main loop
112  while is_running.load(Ordering::SeqCst) {
113    if let Some(max_iterations) = settings.max_iterations {
114      if settings.verbose {
115        print!(
116          "\rRunning iteration: ({}/{})",
117          stats.iterations + 1,
118          max_iterations
119        );
120        std::io::stdout().flush()?;
121      }
122      log::info!(
123        "Running iteration: ({}/{})",
124        stats.iterations + 1,
125        max_iterations
126      );
127    } else {
128      if settings.verbose {
129        print!("\rRunning iteration {}", stats.iterations + 1);
130        std::io::stdout().flush()?;
131      }
132      log::info!("Running iteration {}", stats.iterations + 1);
133    }
134
135    stats.start()?; // START-TIMER: Only record time as running when algorithm is running
136    let state_opt = runnable.step();
137    stats.pause()?; // PAUSE-TIMER
138
139    match state_opt {
140      Some(state) => {
141        // Save this state to storage if exists
142        match states.as_mut() {
143          Some(StateStorage::InMemory(states)) => {
144            states.push(stats.timestamp(state.clone())?);
145
146            if settings.verbose {
147              print!(", Saved to memory");
148            }
149          }
150          Some(StateStorage::OnDisk {
151            dir,
152            prefix,
153            suffix,
154            count,
155            ..
156          }) => {
157            // Save to file
158            let path =
159              get_indexed_path(dir, prefix.as_ref(), suffix.as_ref(), *count);
160            save_bincode(&path, &stats.timestamp(state)?)?;
161            *count += 1;
162
163            if settings.verbose {
164              print!(", Saved to disk");
165            }
166          }
167          None => {
168            if settings.verbose {
169              print!(", State information not saved");
170            }
171          }
172        }
173
174        stats.next_iteration();
175        // Break if max iterations is set and reached
176        if let Some(max_iterations) = settings.max_iterations {
177          if stats.iterations >= max_iterations {
178            break;
179          }
180        }
181      }
182      None => break,
183    }
184  }
185  println!();
186
187  // We are done tracking the "Running" time of our algorithm
188  stats.pause()?;
189
190  let mut run = Run {
191    settings,
192    stats,
193    params,
194    states,
195  };
196
197  // Save to files if not done already
198  run.save()?;
199
200  if run.settings.verbose {
201    println!(
202      "Algorithm running time: {:.4} seconds",
203      run.stats.get_running_time().as_seconds_f64()
204    );
205
206    if let Some(total_time) = run.stats.get_total_time() {
207      println!("Total time: {:.4} seconds", total_time.as_seconds_f64());
208    }
209  }
210
211  return Ok(run);
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
215pub struct Settings {
216  pub verbose: bool,
217  pub algorithm: String,
218  pub name: Option<String>,
219  pub parameter_file: PathBuf,
220  pub max_iterations: Option<usize>,
221  pub keep_in_memory: bool,
222  pub double: bool,
223  pub output_dir: Option<PathBuf>,
224  pub filename_prefix: Option<String>,
225  pub filename_suffix: Option<String>,
226}
227
228impl Settings {
229  pub fn load<P: AsRef<Path>>(dir: P) -> Result<Self> {
230    // Load settings from file
231    let path = dir.as_ref().join(SETTINGS_FILE_NAME);
232    let mut settings: Self = load_ron(&path)?;
233    if settings.verbose {
234      println!("Settings loaded from \"{}\"", path.display());
235    }
236
237    settings.output_dir = Some(dir.as_ref().to_path_buf());
238
239    Ok(settings)
240  }
241}
242
243#[derive(Debug, Clone, Copy)]
244enum TimerState {
245  Running { last_start_time: OffsetDateTime },
246  Paused,
247  Finished,
248}
249
250impl Default for TimerState {
251  fn default() -> Self {
252    TimerState::Finished
253  }
254}
255
256#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
257pub struct Stats {
258  start_time: OffsetDateTime,
259  #[serde(skip)]
260  state: TimerState,
261  total_time: Option<Duration>,
262  running_time: Duration,
263  iterations: usize,
264}
265
266impl Stats {
267  /// Creates new with running timer paused but total timer is started
268  fn new_paused() -> Result<Self> {
269    Ok(Self {
270      start_time: Self::time_now()?,
271      state: TimerState::Paused,
272      total_time: None,
273      running_time: Duration::ZERO,
274      iterations: 0,
275    })
276  }
277
278  /// Converts T to a TimeStamped<T> using the total and running timers
279  fn timestamp<T>(&self, obj: T) -> Result<TimeStamped<T>> {
280    Ok(TimeStamped {
281      time_since_start: self.total_time_since_start()?,
282      time_running_since_start: self.running_time_since_start()?,
283      obj,
284    })
285  }
286
287  fn time_now() -> StdResult<OffsetDateTime, TimeError> {
288    Ok(OffsetDateTime::now_utc())
289  }
290
291  fn running_time_since_start(&self) -> Result<Duration> {
292    match self.state {
293      TimerState::Running { last_start_time } => {
294        let since_last_start = Self::time_now()? - last_start_time;
295        Ok(since_last_start + self.get_running_time())
296      }
297      TimerState::Paused | TimerState::Finished => Ok(self.get_running_time()),
298    }
299  }
300
301  fn total_time_since_start(&self) -> Result<Duration> {
302    Ok(Self::time_now()? - self.start_time)
303  }
304
305  /// Start the timer that tracks "running" time
306  fn start(&mut self) -> Result<()> {
307    match self.state {
308      TimerState::Running { .. } => {
309        // TODO: Add warning log that we are trying to start a running timer
310        Ok(())
311      }
312      TimerState::Paused => {
313        self.state = TimerState::Running {
314          last_start_time: Self::time_now()?,
315        };
316        Ok(())
317      }
318      TimerState::Finished => {
319        // TODO: Add warning log that we are trying to start a finished timer
320        Ok(())
321      }
322    }
323  }
324
325  /// Stop the timer that tracks "running" time
326  fn pause(&mut self) -> Result<()> {
327    match self.state {
328      TimerState::Running { .. } => {
329        self.running_time = self.running_time_since_start()?;
330        self.state = TimerState::Paused;
331        Ok(())
332      }
333      TimerState::Paused => {
334        // TODO: Add warning log that we are trying to pause a paused timer
335        Ok(())
336      }
337      TimerState::Finished => {
338        // TODO: Add warning log that we are trying to pause a finished timer
339        Ok(())
340      }
341    }
342  }
343
344  fn finish(&mut self) -> Result<()> {
345    self.pause()?;
346    self.state = TimerState::Finished;
347    self.total_time = Some(self.total_time_since_start()?);
348    Ok(())
349  }
350
351  /// Get the total amount of time without pauses until the timer finished
352  ///
353  /// Returns None if timer hasn't finished
354  pub fn get_total_time(&self) -> Option<&Duration> {
355    self.total_time.as_ref()
356  }
357
358  /// Gets the amount of time spent "running"
359  pub fn get_running_time(&self) -> Duration {
360    self.running_time
361  }
362
363  fn next_iteration(&mut self) {
364    self.iterations += 1;
365  }
366
367  pub fn get_iterations(&self) -> usize {
368    self.iterations
369  }
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(bound(
374  serialize = "T: Serialize",
375  deserialize = "T: DeserializeOwned"
376))]
377enum StateStorage<T> {
378  /// All the states are stored in memory
379  InMemory(Vec<T>),
380  /// All the states are stored on disk
381  OnDisk {
382    /// The directory where the states are being stored
383    dir: PathBuf,
384    /// The optional prefix to add before the iteration number in the filename
385    prefix: Option<String>,
386    /// The optional suffix to add after the iteration number in the filename
387    suffix: Option<String>,
388    /// The number of states serialized
389    count: usize,
390    /// The last state deserialized so we can return reference to it
391    #[serde(skip)]
392    last: Option<T>,
393  },
394}
395
396impl<T> StateStorage<T> {
397  fn new_memory() -> Self {
398    Self::InMemory(Vec::new())
399  }
400
401  fn load_memory(
402    dir: &PathBuf,
403    prefix: Option<String>,
404    suffix: Option<String>,
405    count: usize,
406    verbose: bool,
407  ) -> Result<Self>
408  where
409    T: DeserializeOwned,
410  {
411    let mut vec = Vec::new();
412    for i in 0..count {
413      let path = get_indexed_path(dir, prefix.as_ref(), suffix.as_ref(), i);
414      let new = load_bincode(path)?;
415      vec.push(new);
416
417      if verbose {
418        print!("\r> Loading states from file: ({}/{})", i + 1, count);
419      }
420    }
421    if verbose {
422      println!(" Done");
423    }
424
425    Ok(Self::InMemory(vec))
426  }
427
428  fn new_disk(
429    dir: PathBuf,
430    prefix: Option<String>,
431    suffix: Option<String>,
432  ) -> Self {
433    Self::OnDisk {
434      dir,
435      prefix,
436      suffix,
437      count: 0,
438      last: None,
439    }
440  }
441
442  fn load_disk(
443    dir: &PathBuf,
444    prefix: Option<String>,
445    suffix: Option<String>,
446    count: usize,
447  ) -> Result<Self> {
448    // TODO: ensure that all iteration files exist in in this directory
449    Ok(Self::OnDisk {
450      dir: dir.clone(),
451      prefix,
452      suffix,
453      count,
454      last: None,
455    })
456  }
457
458  fn get(&mut self, index: usize) -> Result<&T>
459  where
460    T: DeserializeOwned,
461  {
462    match self {
463      StateStorage::InMemory(states) => match states.get(index) {
464        Some(obj) => Ok(obj),
465        None => Err(IndexOutOfBounds)?,
466      },
467      StateStorage::OnDisk {
468        dir,
469        prefix,
470        suffix,
471        count,
472        last,
473      } => {
474        if index >= *count {
475          Err(IndexOutOfBounds)?;
476        }
477
478        // Load from file
479        let path =
480          get_indexed_path(dir, prefix.as_ref(), suffix.as_ref(), index);
481        let ref_ = last.insert(load_bincode(&path)?);
482        Ok(ref_)
483      }
484    }
485  }
486
487  fn try_get_all(&self) -> Option<&Vec<T>> {
488    match self {
489      StateStorage::InMemory(states) => Some(states),
490      StateStorage::OnDisk {
491        dir: _,
492        prefix: _,
493        suffix: _,
494        count: _,
495        last: _,
496      } => None,
497    }
498  }
499
500  fn count(&self) -> usize {
501    match self {
502      StateStorage::InMemory(states) => states.len(),
503      StateStorage::OnDisk {
504        dir: _,
505        prefix: _,
506        suffix: _,
507        count,
508        last: _,
509      } => *count,
510    }
511  }
512}
513
514impl<T> Drop for StateStorage<T> {
515  fn drop(&mut self) {
516    if let StateStorage::OnDisk {
517      dir,
518      prefix: _,
519      suffix: _,
520      count: _,
521      last: _,
522    } = self
523    {
524      // Remove data directory
525      remove_dir_all(&dir).unwrap();
526      println!("> Removed \"{}\"", dir.display());
527    }
528  }
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize)]
532#[serde(bound(
533  serialize = "T: Serialize",
534  deserialize = "T: DeserializeOwned"
535))]
536pub struct TimeStamped<T> {
537  time_since_start: Duration,
538  time_running_since_start: Duration,
539  obj: T,
540}
541
542impl<T> TimeStamped<T> {
543  pub fn get_timestamp(&self) -> Duration {
544    self.time_since_start
545  }
546
547  pub fn get_running_timestamp(&self) -> Duration {
548    self.time_running_since_start
549  }
550
551  pub fn get_value(&self) -> &T {
552    &self.obj
553  }
554}
555
556impl<T> From<TimeStamped<&T>> for TimeStamped<T>
557where
558  T: Clone,
559{
560  fn from(timestamped: TimeStamped<&T>) -> Self {
561    Self {
562      time_since_start: timestamped.time_since_start,
563      time_running_since_start: timestamped.time_running_since_start,
564      obj: timestamped.obj.clone(),
565    }
566  }
567}
568
569#[derive(Debug)]
570pub struct Run<R: Runnable> {
571  settings: Settings,
572  stats: Stats,
573  params: R::Params,
574  states: Option<StateStorage<TimeStamped<R::State>>>,
575}
576
577impl<R: Runnable> Clone for Run<R> {
578  fn clone(&self) -> Self {
579    Self {
580      settings: self.settings.clone(),
581      stats: self.stats.clone(),
582      params: self.params.clone(),
583      states: self.states.clone(),
584    }
585  }
586}
587
588impl<R: Runnable> Run<R> {
589  pub fn name(&self) -> Option<&String> {
590    self.settings.name.as_ref()
591  }
592
593  pub fn settings(&self) -> &Settings {
594    &self.settings
595  }
596
597  pub fn params(&self) -> &R::Params {
598    &self.params
599  }
600
601  pub fn max_iterations(&self) -> Option<usize> {
602    self.settings.max_iterations
603  }
604
605  pub fn count(&self) -> usize {
606    match self.states {
607      Some(ref states) => states.count(),
608      None => 0,
609    }
610  }
611
612  pub fn get(&mut self, index: usize) -> Result<&TimeStamped<R::State>>
613  where
614    R::State: DeserializeOwned,
615  {
616    match self.states {
617      Some(ref mut states) => states.get(index),
618      None => Err(StatesNotRecorded)?,
619    }
620  }
621
622  pub fn try_get_all(&self) -> Option<&Vec<TimeStamped<R::State>>> {
623    match self.states {
624      Some(ref states) => states.try_get_all(),
625      None => None,
626    }
627  }
628
629  pub fn save(&mut self) -> Result<()>
630  where
631    R::Params: Serialize,
632    R::State: Serialize,
633  {
634    // Save all states if they are in memory and output directory is defined
635    match &self.states {
636      Some(StateStorage::InMemory(states)) => {
637        if let Some(ref output_dir) = self.settings.output_dir {
638          // Save to files
639          let dir = output_dir.join(DATA_DIR_NAME);
640          for (i, state) in states.iter().enumerate() {
641            let path = get_indexed_path(
642              &dir,
643              self.settings.filename_prefix.clone(),
644              self.settings.filename_suffix.clone(),
645              i,
646            );
647            save_bincode(&path, state)?;
648
649            if self.settings.verbose {
650              print!("\r> Saving states to file: ({}/{})", i + 1, states.len());
651            }
652          }
653          if self.settings.verbose {
654            println!(" Done");
655          }
656        }
657      }
658      Some(StateStorage::OnDisk {
659        dir: _,
660        prefix: _,
661        suffix: _,
662        count: _,
663        last: _,
664      }) => {
665        // In this mode, states have already been saved to disk as the algorithm
666        // produced them
667      }
668      None => (), // No data to save
669    }
670
671    if let Some(ref output_dir) = self.settings.output_dir {
672      // Compressing data directory to tarbal
673      let data_dir = output_dir.join(DATA_DIR_NAME);
674      let data_file = output_dir.join(format!("{}.tar", DATA_DIR_NAME));
675      let file = File::create(&data_file)?;
676      // let enc = GzEncoder::new(file, Compression::default());
677      let mut tar = TarBuilder::new(file);
678      for (i, entry) in read_dir(&data_dir)?.enumerate() {
679        let entry = entry?;
680        if entry.file_type()?.is_file() {
681          let file_path = entry.path();
682          let mut file = File::open(&file_path)?;
683          let archive_path = file_path.strip_prefix(&data_dir)?;
684          if self.settings.verbose {
685            print!(
686              "\r> Compressing states: ({}/{})",
687              i + 1,
688              self.stats.iterations
689            );
690          }
691          tar.append_file(archive_path, &mut file)?;
692        }
693      }
694      let file = tar.into_inner()?;
695      // let file = enc.finish()?;
696      drop(file);
697      if self.settings.verbose {
698        println!(" Done\n> Data archived to \"{}\"", data_file.display());
699      }
700
701      // // Remove data directory
702      // remove_dir_all(&data_dir)?;
703      // if self.settings.verbose {
704      //   println!("> Removed \"{}\"", data_dir.to_str().unwrap());
705      // }
706
707      // Save settings to file
708      let path = output_dir.join(SETTINGS_FILE_NAME);
709      save_ron(&path, &self.settings)?;
710      if self.settings.verbose {
711        println!("> Settings saved to \"{}\"", path.display());
712      }
713
714      // Save params to file
715      let path = output_dir.join(PARAMS_FILE_NAME);
716      save_ron(&path, &self.params)?;
717      if self.settings.verbose {
718        println!("> Parameters saved to \"{}\"", path.display());
719      }
720
721      // Finish timer and save stats to file
722      self.stats.finish()?;
723      let path = output_dir.join(STATS_FILE_NAME);
724      save_ron(&path, &self.stats)?;
725      if self.settings.verbose {
726        println!("> Statistics saved to \"{}\"", path.display());
727      }
728    }
729
730    Ok(())
731  }
732
733  /// Requires that settings are loaded separately and the correct [`Runnable`]
734  /// is determined from 'algorithm` field in [`Settings`].
735  pub fn load(settings: Settings) -> Result<Self>
736  where
737    R::Params: DeserializeOwned,
738    R::State: Serialize + DeserializeOwned,
739  {
740    let dir = match settings.output_dir {
741      Some(ref output_dir) => output_dir,
742      None => Err(StatesNotRecorded)?,
743    };
744
745    if settings.verbose {
746      match settings.name {
747        Some(ref name) => println!("Visualizing {}", name),
748        None => println!("Visualizing"),
749      }
750      println!("\tmax_iterations: {:?}", settings.max_iterations);
751      println!("\tkeep_in_memory: {}", settings.keep_in_memory);
752      println!("\toutput_dir: {:?}", settings.output_dir);
753      println!("\tfilename_prefix: {:?}", settings.filename_prefix);
754      println!("\tfilename_suffix: {:?}", settings.filename_suffix);
755      println!();
756    }
757
758    // Load stats from file
759    let path = dir.join(STATS_FILE_NAME);
760    let stats: Stats = load_ron(&path)?;
761    if settings.verbose {
762      println!("> Statistics loaded from \"{}\"", path.display());
763    }
764
765    // Load params from file
766    let path = dir.join(PARAMS_FILE_NAME);
767    let params: R::Params = load_ron(&path)?;
768    if settings.verbose {
769      println!("> Parameters loaded from \"{}\"", path.display());
770    }
771
772    // Uncompress data into directory
773    let data_dir = dir.join(DATA_DIR_NAME);
774    create_dir(&data_dir)?;
775    let data_file = dir.join(format!("{}.tar", DATA_DIR_NAME));
776    let file = File::open(data_file)?;
777    // let tar = GzDecoder::new(file);
778    let mut archive = TarArchive::new(file);
779    for (i, entry) in archive.entries()?.enumerate() {
780      let mut file = entry?;
781      print!("\r> Uncompressing states: ({}/{})", i + 1, stats.iterations);
782      file.unpack_in(&data_dir)?;
783    }
784    println!(" Done");
785
786    // Load states from data directory
787    let states = if settings.keep_in_memory {
788      Some(StateStorage::load_memory(
789        &data_dir,
790        settings.filename_prefix.clone(),
791        settings.filename_suffix.clone(),
792        stats.get_iterations(),
793        settings.verbose,
794      )?)
795    } else {
796      Some(StateStorage::load_disk(
797        &data_dir,
798        settings.filename_prefix.clone(),
799        settings.filename_suffix.clone(),
800        stats.get_iterations(),
801      )?)
802    };
803
804    Ok(Run {
805      settings,
806      stats,
807      params,
808      states,
809    })
810  }
811}
812
813pub fn save_ron<T, P>(path: P, obj: &T) -> Result<()>
814where
815  T: Serialize,
816  P: AsRef<Path>,
817{
818  let file = BufWriter::new(File::create(path)?);
819  Ok(ron::ser::to_writer_pretty(
820    file,
821    obj,
822    PrettyConfig::default(),
823  )?)
824}
825
826pub fn load_ron<T, P>(path: P) -> Result<T>
827where
828  T: DeserializeOwned,
829  P: AsRef<Path>,
830{
831  let file = BufReader::new(File::open(path)?);
832  Ok(ron::de::from_reader(file)?)
833}
834
835pub fn save_bincode<T, P>(path: P, obj: &T) -> Result<()>
836where
837  T: Serialize,
838  P: AsRef<Path>,
839{
840  let file = BufWriter::new(File::create(path)?);
841  Ok(bincode::serialize_into(file, obj)?)
842}
843
844pub fn load_bincode<T, P>(path: P) -> Result<T>
845where
846  T: DeserializeOwned,
847  P: AsRef<Path>,
848{
849  let file = BufReader::new(File::open(path)?);
850  Ok(bincode::deserialize_from(file)?)
851}
852
853fn get_indexed_path<P, S1, S2>(
854  directory: P,
855  prefix: Option<S1>,
856  suffix: Option<S2>,
857  index: usize,
858) -> PathBuf
859where
860  P: AsRef<Path>,
861  S1: AsRef<str>,
862  S2: AsRef<str>,
863{
864  let filename = match (prefix, suffix) {
865    (Some(prefix), Some(suffix)) => {
866      prefix.as_ref().to_owned() + &index.to_string() + suffix.as_ref()
867    }
868    (Some(prefix), None) => prefix.as_ref().to_owned() + &index.to_string(),
869    (None, Some(suffix)) => index.to_string() + suffix.as_ref(),
870    (None, None) => index.to_string(),
871  };
872  directory.as_ref().join(filename)
873}