use super::common::hash_reader;
use super::config::HashAlgorithm;
use super::dolphin::RvzCompressionAlgorithm;
use super::progress::get_bytes_progress_style;
use anyhow::{Context, Result};
use indicatif::ProgressBar;
use nod::common::{Compression, Format};
use nod::read::{DiscOptions, DiscReader, PartitionEncryption};
use nod::write::{DiscWriter, DiscWriterWeight, FormatOptions, ProcessOptions, ScrubLevel};
use std::fs::File;
use std::io::{Seek, Write};
use std::path::Path;
use tokio::task::spawn_blocking;
const PRELOADER_THREADS: usize = 4;
fn open(path: &Path) -> Result<DiscReader> {
DiscReader::new(
path,
&DiscOptions {
partition_encryption: PartitionEncryption::Original,
preloader_threads: PRELOADER_THREADS,
},
)
.with_context(|| format!("Failed to open \"{}\"", path.display()))
}
fn processor_threads(weight: DiscWriterWeight) -> usize {
let available = std::thread::available_parallelism()
.map(|available| available.get())
.unwrap_or(1);
match weight {
DiscWriterWeight::Light => 0,
DiscWriterWeight::Medium => available.min(4),
DiscWriterWeight::Heavy => available,
}
}
fn compression(algorithm: &RvzCompressionAlgorithm, level: usize) -> Compression {
match algorithm {
RvzCompressionAlgorithm::None => Compression::None,
RvzCompressionAlgorithm::Bzip2 => Compression::Bzip2(level.clamp(1, 9) as u8),
RvzCompressionAlgorithm::Lzma => Compression::Lzma(level.clamp(1, 9) as u8),
RvzCompressionAlgorithm::Lzma2 => Compression::Lzma2(level.clamp(1, 9) as u8),
RvzCompressionAlgorithm::Zstd => Compression::Zstandard(level.clamp(1, 22) as i8),
}
}
pub async fn to_iso<P: AsRef<Path>, Q: AsRef<Path>>(
source: P,
destination: Q,
progress_bar: &ProgressBar,
) -> Result<()> {
let source = source.as_ref().to_path_buf();
let destination = destination.as_ref().to_path_buf();
let progress_bar = progress_bar.clone();
spawn_blocking(move || {
let mut disc = open(&source)?;
let mut file = File::create(&destination)
.with_context(|| format!("Failed to create \"{}\"", destination.display()))?;
progress_bar.reset();
progress_bar.set_style(get_bytes_progress_style());
progress_bar.set_length(disc.disc_size());
nod::util::buf_copy(&mut disc, &mut progress_bar.wrap_write(&mut file))
.with_context(|| format!("Failed to write \"{}\"", destination.display()))?;
file.flush()?;
Ok(())
})
.await
.context("Disc reader task failed")?
}
pub async fn hash<P: AsRef<Path>>(
source: P,
progress_bar: &ProgressBar,
hash_algorithm: &HashAlgorithm,
) -> Result<(String, u64)> {
let source = source.as_ref().to_path_buf();
let hash_algorithm = *hash_algorithm;
let progress_bar = progress_bar.clone();
spawn_blocking(move || {
let mut disc = open(&source)?;
progress_bar.set_style(get_bytes_progress_style());
progress_bar.set_length(disc.disc_size());
hash_reader(&mut disc, &progress_bar, &hash_algorithm)
})
.await
.context("Disc reader task failed")?
}
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<()> {
write(
source,
destination,
progress_bar,
FormatOptions {
format: Format::Rvz,
compression: compression(compression_algorithm, compression_level),
block_size: (block_size * 1024) as u32,
},
)
.await
}
pub async fn to_wbfs<P: AsRef<Path>, Q: AsRef<Path>>(
source: P,
destination: Q,
progress_bar: &ProgressBar,
) -> Result<()> {
write(
source,
destination,
progress_bar,
FormatOptions::new(Format::Wbfs),
)
.await
}
async fn write<P: AsRef<Path>, Q: AsRef<Path>>(
source: P,
destination: Q,
progress_bar: &ProgressBar,
options: FormatOptions,
) -> Result<()> {
let source = source.as_ref().to_path_buf();
let destination = destination.as_ref().to_path_buf();
let progress_bar = progress_bar.clone();
spawn_blocking(move || {
let disc = open(&source)?;
let writer = DiscWriter::new(disc, &options)
.with_context(|| format!("Failed to set up the {} writer", options.format))?;
let mut file = File::create(&destination)
.with_context(|| format!("Failed to create \"{}\"", destination.display()))?;
let process_options = ProcessOptions {
processor_threads: processor_threads(writer.weight()),
digest_crc32: false,
digest_md5: false,
digest_sha1: false,
digest_xxh64: false,
scrub: ScrubLevel::None,
};
progress_bar.reset();
progress_bar.set_style(get_bytes_progress_style());
progress_bar.set_length(writer.progress_bound());
let finalization = writer
.process(
|data, progress, _| {
file.write_all(data.as_ref())?;
progress_bar.set_position(progress);
Ok(())
},
&process_options,
)
.with_context(|| format!("Failed to write \"{}\"", destination.display()))?;
if !finalization.header.is_empty() {
file.rewind()?;
file.write_all(finalization.header.as_ref())?;
}
file.flush()?;
Ok(())
})
.await
.context("Disc writer task failed")?
}
pub const BACKEND_NAME: &str = "nod";
pub const SUPPORTS_SCRUB: bool = false;
pub async fn get_version() -> Result<String> {
Ok(String::from("built-in"))
}