qbt-clean 0.155.0

Automated rules-based cleaning of qBittorrent torrents.
#[serde_with::serde_as]
#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,serde::Deserialize,serde::Serialize)]
#[serde(transparent)]
pub struct InfoHash(
	#[serde_as(as="serde_with::hex::Hex")] [u8; 20],
);

impl InfoHash {
	#[cfg(test)]
	pub fn example() -> Self {
		Self([0u8; 20])
	}
}

impl std::str::FromStr for InfoHash {
	type Err = anyhow::Error;

	fn from_str(s: &str) -> anyhow::Result<Self> {
		anyhow::ensure!(
			s.len() == 40 && s.is_ascii(),
			"info hash must be 40 ASCII hex chars, got {:?}", s);
		let mut bytes = [0u8; 20];
		for (i, byte) in bytes.iter_mut().enumerate() {
			*byte = u8::from_str_radix(&s[i*2..i*2+2], 16)?;
		}
		Ok(InfoHash(bytes))
	}
}

impl std::fmt::Display for InfoHash {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for b in self.0 {
			write!(f, "{:02x}", b)?;
		}
		Ok(())
	}
}

impl std::fmt::Debug for InfoHash {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "InfoHash({})", self)
	}
}