Browse Source

Added "rar" module.

experimental/bootyd
treyzania 5 years ago
parent
commit
beddd2eb64
2 changed files with 138 additions and 0 deletions
  1. 3
    0
      core/lib.rs
  2. 135
    0
      core/rar.rs

+ 3
- 0
core/lib.rs View File

@@ -1,3 +1,6 @@
pub mod ident;
pub mod rar;

#[cfg(test)]
mod tests {
#[test]

+ 135
- 0
core/rar.rs View File

@@ -0,0 +1,135 @@
//! 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))
}
}

Loading…
Cancel
Save