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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Remove cached wasmCloud files like OCI artifacts or downloaded binaries

use std::{env, fs, io::Result, path::PathBuf};

use crate::config::{downloads_dir, model_cache_dir};

/// A type that allows you to clean up (i.e. drain) a set of caches and folders used by wasmcloud
#[derive(Debug, Clone)]
#[cfg_attr(feature = "cli", derive(clap::Subcommand))]
pub enum Drain {
    /// Remove all cached files created by wasmcloud
    All,
    /// Remove cached files downloaded from OCI registries by wasmCloud
    Oci,
    /// Remove cached binaries extracted from provider archives
    Lib,
    /// Remove cached smithy files downloaded from model urls
    Smithy,
    /// Remove downloaded and generated files from launching wasmCloud hosts
    Downloads,
}

impl IntoIterator for &Drain {
    type Item = PathBuf;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        let paths = match self {
            Drain::All => vec![
                /* Lib    */ env::temp_dir().join("wasmcloudcache"),
                /* Oci    */ env::temp_dir().join("wasmcloud_ocicache"),
                /* Smithy */ model_cache_dir().unwrap_or_default(),
                /* Downloads */ downloads_dir().unwrap_or_default(),
            ],
            Drain::Lib => vec![env::temp_dir().join("wasmcloudcache")],
            Drain::Oci => vec![env::temp_dir().join("wasmcloud_ocicache")],
            Drain::Smithy => vec![model_cache_dir().unwrap_or_default()],
            Drain::Downloads => vec![downloads_dir().unwrap_or_default()],
        };
        paths.into_iter()
    }
}

impl Drain {
    /// Cleans up all data based on the type of Drain requested. Returns a list of paths that were
    /// cleaned
    pub fn drain(self) -> Result<Vec<PathBuf>> {
        self.into_iter()
            .filter(|path| path.exists())
            .map(remove_dir_contents)
            .collect::<Result<Vec<PathBuf>>>()
    }
}

fn remove_dir_contents(path: PathBuf) -> Result<PathBuf> {
    for entry in fs::read_dir(&path)? {
        let path = entry?.path();
        if path.is_dir() {
            fs::remove_dir_all(&path)?;
        } else if path.is_file() {
            fs::remove_file(&path)?;
        }
    }
    Ok(path)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_dir_clean() {
        let tempdir = tempfile::tempdir().expect("Unable to create tempdir");

        let subdir = tempdir.path().join("foobar");
        fs::create_dir(&subdir).unwrap();

        // Create the files and drop the handles
        {
            fs::File::create(subdir.join("baz")).unwrap();
            fs::File::create(tempdir.path().join("baz")).unwrap();
        }

        remove_dir_contents(tempdir.path().to_owned())
            .expect("Shouldn't get an error when cleaning files");
        assert!(
            tempdir
                .path()
                .read_dir()
                .expect("Top level dir should still exist")
                .next()
                .is_none(),
            "Directory should be empty"
        );
    }
}