You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rar.rs 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //! Utilities for interacting with RAR archives.
  2. //!
  3. //! This module relies on the `unrar` utility being present on the system, but
  4. //! it could also work with the non-free `rar` program. The rar crate isn't
  5. //! mature enough so we just call out to another program to get the dirty work
  6. //! done.
  7. #![allow(unused)]
  8. use std::{
  9. ffi::OsStr,
  10. fs, io,
  11. path::{Path, PathBuf},
  12. process,
  13. };
  14. #[derive(Debug)]
  15. pub enum RarError {
  16. /// If a target file is not found.
  17. NotFound,
  18. /// An argument (path) was malformed.
  19. Malformed,
  20. /// If we'd be overwriting something, including if a target dir is a file.
  21. Overwrite,
  22. /// If there was an error with the `unrar` command.
  23. Unrar(Option<i32>),
  24. /// If parsing the output from the command failed, this shouldn't happen.
  25. Parse,
  26. /// Some IO error, could be permissions or that `unrar` isn't installed.
  27. Io(io::Error),
  28. /// An extraction succeeded but somehow we can't find the file. Maybe an
  29. /// argument wasn't set properly, but here's where we expected it.
  30. OutputLost(PathBuf),
  31. }
  32. /// List files in a RAR archive.
  33. pub fn list_archive_files(path: &Path) -> Result<Vec<String>, RarError> {
  34. // Check that the path even exists.
  35. if !path.exists() {
  36. return Err(RarError::NotFound);
  37. }
  38. // Spawn the program and collect the output. If we don't have read perms
  39. // this is where we'd find out.
  40. let output = process::Command::new("unrar")
  41. .arg("lb") // "l"list contents, "b"are
  42. .arg("-r") // "r"ecurse into subdirectories (maybe not needed?)
  43. .arg("--") // stop switch scanning
  44. .arg(path.as_os_str())
  45. .output()
  46. .map_err(|e| RarError::Io(e))?;
  47. // If the program failed, just return that.
  48. if !output.status.success() {
  49. return Err(RarError::Unrar(output.status.code()));
  50. }
  51. // Convert the output to a string so that we can use .lines() on it.
  52. let outstr = String::from_utf8(output.stdout).map_err(|_| RarError::Parse)?;
  53. // Split the output into lines and then just return
  54. Ok(outstr
  55. .lines()
  56. .filter(|s| !s.is_empty())
  57. .map(String::from)
  58. .collect())
  59. }
  60. /// Extracts a file from the specified RAR archive, putting it into the
  61. /// specified directory and returning the new file's path.
  62. pub fn extract_archive_file(
  63. arc: &Path,
  64. name: &String,
  65. dest_dir: &Path,
  66. ) -> Result<PathBuf, RarError> {
  67. // Check that the archive exists.
  68. if !arc.exists() {
  69. return Err(RarError::NotFound);
  70. }
  71. // Make sure that the destination exists and is a directory.
  72. if !dest_dir.exists() {
  73. fs::create_dir_all(dest_dir).map_err(|e| RarError::Io(e))?;
  74. } else {
  75. if dest_dir.is_file() {
  76. return Err(RarError::Overwrite);
  77. }
  78. }
  79. // Figure out where the output file should be.
  80. let dest_file: PathBuf = match PathBuf::from(name).file_name() {
  81. Some(p) => {
  82. let mut buf = PathBuf::from(dest_dir);
  83. buf.push(p);
  84. buf
  85. }
  86. None => return Err(RarError::NotFound),
  87. };
  88. // Actually extract the file from the archive.
  89. let mut sub = process::Command::new("unrar")
  90. .arg("e") // "e"xtract (not using archive paths)
  91. .arg("-y") // assume "y"es for all prompts
  92. .arg("-o-") // don't "o"verwrite things
  93. .arg("--") // stop switch scanning
  94. .arg(arc.as_os_str())
  95. .arg(name)
  96. .arg(dest_dir.as_os_str())
  97. .spawn()
  98. .map_err(|e| RarError::Io(e))?;
  99. let exit = sub.wait().map_err(|e| RarError::Io(e))?;
  100. // If we failed, return that we failed.
  101. if !exit.success() {
  102. return Err(RarError::Unrar(exit.code()));
  103. }
  104. // TODO Make this also report not found if "name" isn't a real file in the
  105. // archive by looking at the output of the program.
  106. // Now, make sure that the output we expected is where it is, if not then
  107. // report that it's messed up.
  108. if dest_file.exists() {
  109. Ok(dest_file)
  110. } else {
  111. // TODO Maybe instead make it find the path from the `unrar` output?
  112. Err(RarError::OutputLost(dest_file))
  113. }
  114. }