tauri_api/
dir.rs

1use serde::Serialize;
2use std::fs::{self, metadata};
3use std::path::{Path, PathBuf};
4use tempfile::{self, tempdir};
5
6/// The result of the `read_dir` function.
7///
8/// A DiskEntry is either a file or a directory.
9/// The `children` Vec is always `Some` if the entry is a directory.
10#[derive(Debug, Serialize)]
11pub struct DiskEntry {
12  /// The path to this entry.
13  pub path: PathBuf,
14  /// The name of this entry (file name with extension or directory name)
15  pub name: Option<String>,
16  /// The children of this entry if it's a directory.
17  #[serde(skip_serializing_if = "Option::is_none")]
18  pub children: Option<Vec<DiskEntry>>,
19}
20
21/// Checks if the given path is a directory.
22pub fn is_dir<P: AsRef<Path>>(path: P) -> crate::Result<bool> {
23  metadata(path).map(|md| md.is_dir()).map_err(|e| e.into())
24}
25
26/// Reads a directory. Can perform recursive operations.
27pub fn read_dir<P: AsRef<Path>>(path: P, recursive: bool) -> crate::Result<Vec<DiskEntry>> {
28  let mut files_and_dirs: Vec<DiskEntry> = vec![];
29  for entry in fs::read_dir(path)? {
30    let path = entry?.path();
31    let path_as_string = path.display().to_string();
32
33    if let Ok(flag) = is_dir(&path_as_string) {
34      files_and_dirs.push(DiskEntry {
35        path: path.clone(),
36        children: if flag {
37          Some(if recursive {
38            read_dir(&path_as_string, true)?
39          } else {
40            vec![]
41          })
42        } else {
43          None
44        },
45        name: path
46          .file_name()
47          .map(|name| name.to_string_lossy())
48          .map(|name| name.to_string()),
49      });
50    }
51  }
52  Result::Ok(files_and_dirs)
53}
54
55/// Runs a closure with a temp dir argument.
56pub fn with_temp_dir<F: FnOnce(&tempfile::TempDir)>(callback: F) -> crate::Result<()> {
57  let dir = tempdir()?;
58  callback(&dir);
59  dir.close()?;
60  Ok(())
61}
62
63#[cfg(test)]
64mod test {
65  use super::*;
66  use quickcheck_macros::quickcheck;
67  use std::ffi::OsStr;
68  use std::path::PathBuf;
69
70  // check is dir function by passing in arbitrary strings
71  #[quickcheck]
72  fn qc_is_dir(f: String) -> bool {
73    // if the string runs through is_dir and comes out as an OK result then it must be a DIR.
74    if is_dir(f.clone()).is_ok() {
75      PathBuf::from(f).is_dir()
76    } else {
77      true
78    }
79  }
80
81  fn name_from_path(path: PathBuf) -> Option<String> {
82    path
83      .file_name()
84      .map(|name| name.to_string_lossy())
85      .map(|name| name.to_string())
86  }
87
88  #[test]
89  // check the read_dir function with recursive = true
90  fn check_read_dir_recursively() {
91    // define a relative directory string test/
92    let dir = PathBuf::from("test/");
93    // add the files to this directory
94    let mut file_one = dir.clone();
95    file_one.push("test.txt");
96    let mut file_two = dir.clone();
97    file_two.push("test_binary");
98
99    // call walk_dir on the directory
100    let res = read_dir(dir, true);
101
102    // assert that the result is Ok()
103    assert!(res.is_ok());
104
105    // destruct the OK into a vector of DiskEntry Structs
106    if let Ok(vec) = res {
107      // assert that the vector length is only 3
108      assert_eq!(vec.len(), 2);
109
110      // get the first DiskEntry
111      let first = &vec[0];
112      // get the second DiskEntry
113      let second = &vec[1];
114
115      if first.path.extension() == Some(OsStr::new("txt")) {
116        // check the fields for the first DiskEntry
117        assert_eq!(first.path, file_one);
118        assert_eq!(first.children.is_some(), false);
119        assert_eq!(first.name, name_from_path(file_one));
120
121        // check the fields for the third DiskEntry
122        assert_eq!(second.path, file_two);
123        assert_eq!(second.children.is_some(), false);
124        assert_eq!(second.name, name_from_path(file_two));
125      } else {
126        // check the fields for the second DiskEntry
127        assert_eq!(first.path, file_two);
128        assert_eq!(first.children.is_some(), false);
129        assert_eq!(first.name, name_from_path(file_two));
130
131        // check the fields for the third DiskEntry
132        assert_eq!(second.path, file_one);
133        assert_eq!(second.children.is_some(), false);
134        assert_eq!(second.name, name_from_path(file_one));
135      }
136    }
137  }
138
139  #[test]
140  // check the read_dir function with recursive = false
141  fn check_read_dir() {
142    // define a relative directory test/
143    let dir = PathBuf::from("test/");
144
145    // call list_dir_contents on the dir
146    let res = read_dir(dir, false);
147
148    // assert that the result is Ok()
149    assert!(res.is_ok());
150
151    // destruct the vector from the Ok()
152    if let Ok(vec) = res {
153      // assert the length of the vector is 2
154      assert_eq!(vec.len(), 2);
155
156      // get the two DiskEntry structs in this vector
157      let first = &vec[0];
158      let second = &vec[1];
159
160      if first.path.extension() == Some(OsStr::new("txt")) {
161        // check the fields for the first DiskEntry
162        assert_eq!(first.path, PathBuf::from("test/test.txt"));
163        assert_eq!(first.children.is_some(), false);
164        assert_eq!(first.name, Some("test.txt".to_string()));
165
166        // check the fields for the second DiskEntry
167        assert_eq!(second.path, PathBuf::from("test/test_binary"));
168        assert_eq!(second.children.is_some(), false);
169        assert_eq!(second.name, Some("test_binary".to_string()));
170      } else {
171        // check the fields for the first DiskEntry
172        assert_eq!(second.path, PathBuf::from("test/test.txt"));
173        assert_eq!(second.children.is_some(), false);
174        assert_eq!(second.name, Some("test.txt".to_string()));
175
176        // check the fields for the second DiskEntry
177        assert_eq!(first.path, PathBuf::from("test/test_binary"));
178        assert_eq!(first.children.is_some(), false);
179        assert_eq!(first.name, Some("test_binary".to_string()));
180      }
181    }
182  }
183
184  #[test]
185  // test the with_temp_dir function
186  fn check_test_dir() {
187    // create a callback closure that takes in a TempDir type and prints it.
188    let callback = |td: &tempfile::TempDir| {
189      println!("{:?}", td);
190    };
191
192    // execute the with_temp_dir function on the callback
193    let res = with_temp_dir(callback);
194
195    // assert that the result is an OK type.
196    assert!(res.is_ok());
197  }
198}