1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
use crate::{
    command::CommandExecutor,
    filesystem::FileSystemClean,
    models::{RemovalAction, RemovalCandidate},
};
use eyre::Report;
use eyre::Result;

pub struct Cleaner<CS, CE, FS, N>
where
    CS: IntoIterator<Item = RemovalCandidate>,
    FS: FileSystemClean,
    CE: CommandExecutor,
    N: CleanerNotifier,
{
    candidates: CS,
    fs: FS,
    command_executor: CE,
    notifier: N,
}

pub trait CleanerNotifier {
    fn notify_removal_started(&self, candidate: &RemovalCandidate);
    fn notify_removal_success(&self, candidate: RemovalCandidate);
    fn notify_removal_failed(&self, candidate: RemovalCandidate, report: Report);
    fn notify_removal_finish(&self);
}

impl<CS, CE, FS, N> Cleaner<CS, CE, FS, N>
where
    CS: IntoIterator<Item = RemovalCandidate>,
    FS: FileSystemClean,
    CE: CommandExecutor,
    N: CleanerNotifier,
{
    pub fn new(candidates: CS, fs: FS, command_executor: CE, notifier: N) -> Self {
        Self {
            candidates,
            fs,
            command_executor,
            notifier,
        }
    }

    pub fn clean(self) {
        for candidate in self.candidates {
            self.notifier.notify_removal_started(&candidate);
            match clean_candidate(&self.fs, &self.command_executor, &candidate) {
                Ok(_) => {
                    self.notifier.notify_removal_success(candidate);
                }
                Err(report) => {
                    self.notifier.notify_removal_failed(candidate, report);
                }
            }
        }
        self.notifier.notify_removal_finish();
    }
}

fn clean_candidate(
    fs: &impl FileSystemClean,
    command_executor: &impl CommandExecutor,
    candidate: &RemovalCandidate,
) -> Result<()> {
    match &candidate.action {
        RemovalAction::Delete { file_info, .. } => fs.remove_file(file_info),
        RemovalAction::RunCommand { work_dir, command } => {
            command_executor.execute_command(work_dir, command)
        }
    }
}