use std::path::{Path, PathBuf};
use std::process::{Output, Stdio};
use bytes::Buf;
use colored::Colorize;
use log::trace;
use tokio::fs::copy;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use crate::built_info::{PKG_NAME, PKG_VERSION};
use crate::dependencies::IMDL;
use crate::errors::{AppError, OutputHandler};
use crate::imdl::TorrentSummary;
use crate::source::SourceIssue;
use crate::source::SourceIssue::Imdl;
pub struct ImdlCommand;
impl ImdlCommand {
#[allow(clippy::uninlined_format_args)]
pub async fn create(
content_dir: &Path,
output_path: &Path,
announce_url: String,
source: String,
) -> Result<Output, AppError> {
let output = Command::new(IMDL)
.arg("torrent")
.arg("create")
.arg(content_dir.to_string_lossy().to_string())
.arg("--private")
.arg("--announce")
.arg(announce_url)
.arg("--comment")
.arg(format!("Created with {} v{}", PKG_NAME, PKG_VERSION))
.arg("--source")
.arg(source.to_uppercase())
.arg("--output")
.arg(output_path.to_string_lossy().to_string())
.arg("--no-created-by")
.arg("--force")
.output()
.await
.or_else(|e| AppError::command(e, "execute create torrent", IMDL))?;
OutputHandler::execute(output, "create torrent", "IMDL")
}
pub async fn show(path: &Path) -> Result<TorrentSummary, AppError> {
let output = Command::new(IMDL)
.arg("torrent")
.arg("show")
.arg("--json")
.arg(path)
.output()
.await
.or_else(|e| AppError::command(e, "execute read torrent", IMDL))?;
let output = OutputHandler::execute(output, "read torrent", "IMDL")?;
let reader = output.stdout.reader();
serde_json::from_reader(reader).or_else(|e| AppError::json(e, "deserialize torrent"))
}
pub async fn verify(
torrent_file: &Path,
directory: &Path,
) -> Result<Option<SourceIssue>, AppError> {
let output = Command::new(IMDL)
.arg("torrent")
.arg("verify")
.arg("--content")
.arg(directory)
.arg(torrent_file)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await
.or_else(|e| AppError::command(e, "execute verify torrent", IMDL))?;
if output.status.success() {
Ok(None)
} else {
let details = String::from_utf8(output.stderr).unwrap_or_default();
Ok(Some(Imdl { details }))
}
}
pub async fn verify_from_buffer(
buffer: &[u8],
directory: &PathBuf,
) -> Result<Vec<SourceIssue>, AppError> {
let mut child = Command::new(IMDL)
.arg("torrent")
.arg("verify")
.arg("--content")
.arg(directory)
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.or_else(|e| AppError::command(e, "execute verify torrent", IMDL))?;
let mut stdin = child.stdin.take().expect("stdin should be available");
stdin
.write_all(buffer)
.await
.or_else(|e| AppError::command(e, "writing buffer to verify torrent", IMDL))?;
drop(stdin);
let output = child
.wait_with_output()
.await
.or_else(|e| AppError::command(e, "get output of verify torrent", IMDL))?;
if output.status.success() {
Ok(Vec::new())
} else {
let details = String::from_utf8(output.stderr).unwrap_or_default();
Ok(vec![Imdl { details }])
}
}
pub async fn duplicate_torrent(
from: &Path,
to: &Path,
content_dir: &Path,
announce_url: String,
source: String,
) -> Result<bool, AppError> {
let torrent = ImdlCommand::show(from).await?;
let torrent_announce = torrent.announce_list.first().and_then(|x| x.first());
if torrent.is_source_equal(&source) && torrent_announce == Some(&announce_url) {
trace!(
"{} {:?} to {:?}",
"Copying".bold(),
from.file_name(),
to.file_name()
);
copy(&from, &to)
.await
.or_else(|e| AppError::io(e, "duplicate torrent"))?;
return Ok(true);
}
if !content_dir.is_dir() {
trace!(
"Torrent content directory does not exist: {}",
content_dir.display()
);
return Ok(false);
}
let verify_issues = ImdlCommand::verify(from, content_dir).await?;
if verify_issues.is_some() {
trace!(
"Torrent content failed verification: {:?}",
from.file_name()
);
return Ok(false);
}
ImdlCommand::create(content_dir, to, announce_url, source).await?;
Ok(true)
}
}