use super::common::*;
use super::config::*;
use super::mimetype::*;
use super::model::*;
use super::progress::*;
use super::util::*;
use anyhow::{Result, bail};
use indicatif::ProgressBar;
use sqlx::SqliteConnection;
use std::path::Path;
use strum::{Display, EnumString, VariantNames};
#[cfg(not(feature = "nod"))]
use self::tool as backend;
#[cfg(feature = "nod")]
use super::nod as backend;
pub use backend::BACKEND_NAME;
pub const RVZ_BLOCK_SIZE_RANGE: [usize; 2] = [32, 2048];
pub const RVZ_COMPRESSION_LEVEL_RANGE: [usize; 2] = [1, 22];
#[derive(Display, PartialEq, EnumString, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum RvzCompressionAlgorithm {
None,
Zstd,
Bzip2,
Lzma,
Lzma2,
}
pub struct RvzRomfile {
pub romfile: CommonRomfile,
}
impl GetRomfile for RvzRomfile {
fn romfile(&self) -> &CommonRomfile {
&self.romfile
}
}
impl HashAndSize for RvzRomfile {
async fn get_hash_and_size(
&self,
connection: &mut SqliteConnection,
progress_bar: &ProgressBar,
position: usize,
total: usize,
hash_algorithm: &HashAlgorithm,
) -> Result<(String, u64)> {
progress_bar.reset();
progress_bar.set_message(format!(
"Computing {} ({}/{})",
hash_algorithm, position, total
));
#[cfg(feature = "nod")]
let (hash, size) = {
let _ = connection;
backend::hash(&self.romfile.path, progress_bar, hash_algorithm).await?
};
#[cfg(not(feature = "nod"))]
let (hash, size) = {
let tmp_directory = create_tmp_directory(connection).await?;
let iso_romfile = self.to_iso(progress_bar, &tmp_directory).await?;
let hash_and_size = iso_romfile
.romfile
.get_hash_and_size(connection, progress_bar, position, total, hash_algorithm)
.await?;
iso_romfile.romfile.delete(progress_bar, true).await?;
hash_and_size
};
progress_bar.set_message("");
Ok((hash, size))
}
}
impl Check for RvzRomfile {
async fn check(
&self,
connection: &mut SqliteConnection,
progress_bar: &ProgressBar,
header: &Option<Header>,
roms: &[&Rom],
) -> Result<()> {
print_action(progress_bar, &format!("Checking \"{}\"", self.romfile));
if header.is_none() {
let rom = roms[0];
let hash_algorithm = get_hash_algorithm(rom)?;
let (hash, size) = self
.get_hash_and_size(connection, progress_bar, 1, 1, &hash_algorithm)
.await?;
return compare_hash_and_size(rom, &hash, size, &hash_algorithm);
}
let tmp_directory = create_tmp_directory(connection).await?;
let iso_romfile = self.to_iso(progress_bar, &tmp_directory.path()).await?;
iso_romfile
.romfile
.check(connection, progress_bar, header, roms)
.await?;
Ok(())
}
}
impl ToIso for RvzRomfile {
async fn to_iso<P: AsRef<Path>>(
&self,
progress_bar: &ProgressBar,
destination_directory: &P,
) -> Result<IsoRomfile> {
progress_bar.set_message("Extracting rvz");
print_action(
progress_bar,
&format!(
"Extracting \"{}\"",
self.romfile.path.file_name().unwrap().to_str().unwrap()
),
);
let path = destination_directory
.as_ref()
.join(self.romfile.path.file_name().unwrap())
.with_extension(ISO_EXTENSION);
backend::to_iso(&self.romfile.path, &path, progress_bar).await?;
stop_action(progress_bar);
CommonRomfile::from_path(&path)?.as_iso()
}
}
pub trait ToRvz {
async fn to_rvz<P: AsRef<Path>>(
&self,
progress_bar: &ProgressBar,
destination_directory: &P,
compression_algorithm: &RvzCompressionAlgorithm,
compression_level: usize,
block_size: usize,
scrub: bool,
) -> Result<RvzRomfile>;
}
impl ToRvz for IsoRomfile {
async fn to_rvz<P: AsRef<Path>>(
&self,
progress_bar: &ProgressBar,
destination_directory: &P,
compression_algorithm: &RvzCompressionAlgorithm,
compression_level: usize,
block_size: usize,
scrub: bool,
) -> Result<RvzRomfile> {
progress_bar.set_message("Creating rvz");
let path = destination_directory
.as_ref()
.join(self.romfile.path.file_name().unwrap())
.with_extension(RVZ_EXTENSION);
print_action(
progress_bar,
&format!(
"Creating \"{}\"",
path.file_name().unwrap().to_str().unwrap()
),
);
if scrub && !backend::SUPPORTS_SCRUB {
print_warning(
progress_bar,
"RVZ_SCRUB is unsupported by this build's backend, writing unscrubbed",
);
}
backend::to_rvz(
&self.romfile.path,
&path,
progress_bar,
compression_algorithm,
compression_level,
block_size,
scrub,
)
.await?;
stop_action(progress_bar);
CommonRomfile::from_path(&path)?.as_rvz()
}
}
pub trait AsRvz {
fn as_rvz(self) -> Result<RvzRomfile>;
}
impl AsRvz for CommonRomfile {
fn as_rvz(self) -> Result<RvzRomfile> {
if self
.path
.extension()
.unwrap()
.to_str()
.unwrap()
.to_lowercase()
!= RVZ_EXTENSION
{
bail!("Not a valid rvz");
}
Ok(RvzRomfile { romfile: self })
}
}
pub async fn get_version() -> Result<String> {
backend::get_version().await
}
#[cfg(not(feature = "nod"))]
mod tool {
use super::RvzCompressionAlgorithm;
use crate::progress::start_action;
use crate::util::{get_executable_path, run_tool, tool_version};
use anyhow::Result;
use indicatif::ProgressBar;
use std::path::Path;
use tokio::process::Command;
pub const DOLPHIN_TOOL_EXECUTABLES: &[&str] = &["dolphin-tool", "DolphinTool"];
pub const BACKEND_NAME: &str = "dolphin-tool";
pub const SUPPORTS_SCRUB: bool = true;
pub async fn to_iso<P: AsRef<Path>, Q: AsRef<Path>>(
source: P,
destination: Q,
progress_bar: &ProgressBar,
) -> Result<()> {
start_action(progress_bar, None);
run_tool(
Command::new(get_executable_path(DOLPHIN_TOOL_EXECUTABLES)?)
.arg("convert")
.arg("-f")
.arg("iso")
.arg("-i")
.arg(source.as_ref())
.arg("-o")
.arg(destination.as_ref()),
)
.await?;
Ok(())
}
pub async fn to_rvz<P: AsRef<Path>, Q: AsRef<Path>>(
source: P,
destination: Q,
progress_bar: &ProgressBar,
compression_algorithm: &RvzCompressionAlgorithm,
compression_level: usize,
block_size: usize,
scrub: bool,
) -> Result<()> {
start_action(progress_bar, None);
let mut command = Command::new(get_executable_path(DOLPHIN_TOOL_EXECUTABLES)?);
command
.arg("convert")
.arg("-f")
.arg("rvz")
.arg("-c")
.arg(compression_algorithm.to_string())
.arg("-l")
.arg(compression_level.to_string())
.arg("-b")
.arg((block_size * 1024).to_string())
.arg("-i")
.arg(source.as_ref())
.arg("-o")
.arg(destination.as_ref());
if scrub {
command.arg("-s");
}
run_tool(&mut command).await?;
Ok(())
}
pub async fn get_version() -> Result<String> {
tool_version(
get_executable_path(DOLPHIN_TOOL_EXECUTABLES)?,
"dolphin-tool",
&[],
false,
0,
None,
)
.await
}
}