123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- //! Utilities for interacting with RAR archives.
- //!
- //! This module relies on the `unrar` utility being present on the system, but
- //! it could also work with the non-free `rar` program. The rar crate isn't
- //! mature enough so we just call out to another program to get the dirty work
- //! done.
-
- #![allow(unused)]
-
- use std::{
- ffi::OsStr,
- fs, io,
- path::{Path, PathBuf},
- process,
- };
-
- #[derive(Debug)]
- pub enum RarError {
- /// If a target file is not found.
- NotFound,
-
- /// An argument (path) was malformed.
- Malformed,
-
- /// If we'd be overwriting something, including if a target dir is a file.
- Overwrite,
-
- /// If there was an error with the `unrar` command.
- Unrar(Option<i32>),
-
- /// If parsing the output from the command failed, this shouldn't happen.
- Parse,
-
- /// Some IO error, could be permissions or that `unrar` isn't installed.
- Io(io::Error),
-
- /// An extraction succeeded but somehow we can't find the file. Maybe an
- /// argument wasn't set properly, but here's where we expected it.
- OutputLost(PathBuf),
- }
-
- /// List files in a RAR archive.
- pub fn list_archive_files(path: &Path) -> Result<Vec<String>, RarError> {
- // Check that the path even exists.
- if !path.exists() {
- return Err(RarError::NotFound);
- }
-
- // Spawn the program and collect the output. If we don't have read perms
- // this is where we'd find out.
- let output = process::Command::new("unrar")
- .arg("lb") // "l"list contents, "b"are
- .arg("-r") // "r"ecurse into subdirectories (maybe not needed?)
- .arg("--") // stop switch scanning
- .arg(path.as_os_str())
- .output()
- .map_err(|e| RarError::Io(e))?;
-
- // If the program failed, just return that.
- if !output.status.success() {
- return Err(RarError::Unrar(output.status.code()));
- }
-
- // Convert the output to a string so that we can use .lines() on it.
- let outstr = String::from_utf8(output.stdout).map_err(|_| RarError::Parse)?;
-
- // Split the output into lines and then just return
- Ok(outstr
- .lines()
- .filter(|s| !s.is_empty())
- .map(String::from)
- .collect())
- }
-
- /// Extracts a file from the specified RAR archive, putting it into the
- /// specified directory and returning the new file's path.
- pub fn extract_archive_file(
- arc: &Path,
- name: &String,
- dest_dir: &Path,
- ) -> Result<PathBuf, RarError> {
- // Check that the archive exists.
- if !arc.exists() {
- return Err(RarError::NotFound);
- }
-
- // Make sure that the destination exists and is a directory.
- if !dest_dir.exists() {
- fs::create_dir_all(dest_dir).map_err(|e| RarError::Io(e))?;
- } else {
- if dest_dir.is_file() {
- return Err(RarError::Overwrite);
- }
- }
-
- // Figure out where the output file should be.
- let dest_file: PathBuf = match PathBuf::from(name).file_name() {
- Some(p) => {
- let mut buf = PathBuf::from(dest_dir);
- buf.push(p);
- buf
- }
- None => return Err(RarError::NotFound),
- };
-
- // Actually extract the file from the archive.
- let mut sub = process::Command::new("unrar")
- .arg("e") // "e"xtract (not using archive paths)
- .arg("-y") // assume "y"es for all prompts
- .arg("-o-") // don't "o"verwrite things
- .arg("--") // stop switch scanning
- .arg(arc.as_os_str())
- .arg(name)
- .arg(dest_dir.as_os_str())
- .spawn()
- .map_err(|e| RarError::Io(e))?;
- let exit = sub.wait().map_err(|e| RarError::Io(e))?;
-
- // If we failed, return that we failed.
- if !exit.success() {
- return Err(RarError::Unrar(exit.code()));
- }
-
- // TODO Make this also report not found if "name" isn't a real file in the
- // archive by looking at the output of the program.
-
- // Now, make sure that the output we expected is where it is, if not then
- // report that it's messed up.
- if dest_file.exists() {
- Ok(dest_file)
- } else {
- // TODO Maybe instead make it find the path from the `unrar` output?
- Err(RarError::OutputLost(dest_file))
- }
- }
|