dir.rs 7.1 KB

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