fs_encrypt/
files.rs

1use std::{error::Error, fs, path::Path};
2
3#[derive(Debug, PartialEq)]
4pub struct FilePath {
5    pub input_path: String,
6    pub output_path: String,
7}
8
9#[derive(PartialEq)]
10enum PathType {
11    File,
12    Directory,
13}
14
15fn get_pathtype(input_path: &str) -> Result<PathType, Box<dyn Error>> {
16    let input_metadata = fs::metadata(input_path)?;
17
18    if input_metadata.is_file() {
19        return Ok(PathType::File);
20    } else if input_metadata.is_dir() || input_metadata.is_symlink() {
21        return Ok(PathType::Directory);
22    }
23
24    Err("input path is not an existing file or directory".into())
25}
26
27pub fn extend_dir_path<'a>(dir_path: &'a str, file_path: &'a str) -> String {
28    format!("{}/{}", dir_path, file_path)
29}
30
31pub fn get_filepaths(
32    input_path: String,
33    output_path: String,
34) -> Result<Vec<FilePath>, Box<dyn Error>> {
35    let mut filepaths = Vec::<FilePath>::new();
36    let pathtype = get_pathtype(&input_path)?;
37
38    if pathtype == PathType::File {
39        let path = FilePath {
40            input_path,
41            output_path,
42        };
43        filepaths.push(path);
44        return Ok(filepaths);
45    }
46
47    let dirpaths = fs::read_dir(&input_path)?;
48    for entry in dirpaths {
49        let filename = match entry?.file_name().into_string() {
50            Ok(filename) => filename,
51            Err(_) => return Err("could not parse file".into()),
52        };
53
54        let ext_input_path = extend_dir_path(&input_path, &filename);
55        let ext_output_path = extend_dir_path(&output_path, &filename);
56
57        let mut paths = get_filepaths(ext_input_path, ext_output_path)?;
58        filepaths.append(&mut paths);
59    }
60
61    Ok(filepaths)
62}
63
64pub fn read_file(filepath: &str) -> Result<Vec<u8>, Box<dyn Error>> {
65    let contents = fs::read(filepath)?;
66
67    Ok(contents)
68}
69
70pub fn enable_write_to_file(filepath: &str) -> Result<(), Box<dyn Error>> {
71    let mut permissions = fs::metadata(filepath)?.permissions();
72    permissions.set_readonly(false);
73
74    fs::set_permissions(filepath, permissions)?;
75    Ok(())
76}
77
78pub fn write_file(filepath: &str, contents: Vec<u8>) -> Result<(), Box<dyn Error>> {
79    let path = Path::new(filepath);
80
81    if let Some(parent) = path.parent() {
82        fs::create_dir_all(parent)?;
83    }
84
85    let _  = enable_write_to_file(filepath);
86
87    println!("Writing contents to {filepath}");
88    fs::write(path, contents)?;
89
90    Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_read_file_contents() {
99        let contents = read_file("./tests/sample/test_file.txt").unwrap();
100        let exp_contents = b"\
101my text
102second line\n"
103            .to_vec();
104
105        assert_eq!(contents, exp_contents);
106    }
107
108    #[test]
109    fn test_write_file_only() {
110        let contents = b"foo bar baz".to_vec();
111        let path = "./tests/sample/written_file.txt";
112        write_file(path, contents).unwrap();
113
114        fs::remove_file(path).unwrap();
115    }
116
117    #[test]
118    fn test_write_readonly_file() {
119        let original_contents = b"foo bar baz".to_vec();
120        let path = "./tests/sample/written_file.txt";
121        write_file(path, original_contents).unwrap();
122
123        let updated_contents = b"baz bar foo".to_vec();
124        let mut permissions = fs::metadata(path).unwrap().permissions();
125        permissions.set_readonly(true);
126        fs::set_permissions(path, permissions).unwrap();
127        write_file(path, updated_contents).unwrap();
128
129        fs::remove_file(path).unwrap();
130    }
131
132    #[test]
133    fn test_extend_dir_path() {
134        let dir_path = "./tests/directory";
135        let file_path = extend_dir_path(&dir_path, "foo/bar.txt");
136
137        assert_eq!(file_path, "./tests/directory/foo/bar.txt");
138    }
139
140    #[test]
141    fn test_write_file_with_dirs() {
142        let contents = b"file contents".to_vec();
143        let dir_path = "./tests/new_dir";
144        let file_path = extend_dir_path(&dir_path, "new_file.txt");
145
146        write_file(&file_path, contents.clone()).unwrap();
147
148        let read_contents = read_file(&file_path).unwrap();
149        assert_eq!(read_contents, contents);
150
151        fs::remove_dir_all(&dir_path).unwrap();
152    }
153
154    #[test]
155    fn test_get_filepaths() {
156        let contents = b"".to_vec();
157
158        let dir_path = String::from("./tests/filepaths");
159        let input_path = extend_dir_path(&dir_path, "input");
160        let output_path = extend_dir_path(&dir_path, "output");
161
162        let inner_file_path = extend_dir_path(&input_path, "dir1/dir2/inner_file.txt");
163        write_file(&inner_file_path, contents.clone()).unwrap();
164
165        let base_file_path = extend_dir_path(&input_path, "base.txt");
166        write_file(&base_file_path, contents.clone()).unwrap();
167
168        let filepaths = get_filepaths(input_path, output_path).unwrap();
169
170        assert_eq!(
171            filepaths[0],
172            FilePath {
173                input_path: String::from("./tests/filepaths/input/dir1/dir2/inner_file.txt"),
174                output_path: String::from("./tests/filepaths/output/dir1/dir2/inner_file.txt"),
175            }
176        );
177        assert_eq!(
178            filepaths[1],
179            FilePath {
180                input_path: String::from("./tests/filepaths/input/base.txt"),
181                output_path: String::from("./tests/filepaths/output/base.txt"),
182            }
183        );
184
185        fs::remove_dir_all(&dir_path).unwrap();
186    }
187
188    #[test]
189    #[should_panic]
190    fn test_nonexistent_input() {
191        let input_path = String::from("./tests/non/existent/file.txt");
192        let output_path = String::from("./tests");
193
194        get_filepaths(input_path, output_path).unwrap();
195    }
196}