dir.rs 6.1 KB

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