use crate::testing_prelude::*;
const MOCK_FILE_SIZE: i64 = 1024;
const MOCK_NAME: &str = "album";
pub(crate) struct TorrentBuilder {
entries: Vec<u8>,
}
impl TorrentBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn file<I>(components: I) -> Self
where
I: IntoIterator,
I::Item: Into<RawString>,
{
Self::new()
.with_integer("length", MOCK_FILE_SIZE)
.with_list("path", components)
}
pub fn with_string(mut self, key: &str, value: impl Into<RawString>) -> Self {
self.push_bytes(key.as_bytes());
self.push_bytes(value.into().as_bytes());
self
}
pub fn with_integer(mut self, key: &str, value: i64) -> Self {
self.push_bytes(key.as_bytes());
self.entries
.extend_from_slice(format!("i{value}e").as_bytes());
self
}
pub fn with_list<I>(mut self, key: &str, items: I) -> Self
where
I: IntoIterator,
I::Item: Into<RawString>,
{
self.push_bytes(key.as_bytes());
self.entries.push(b'l');
for item in items {
self.push_bytes(item.into().as_bytes());
}
self.entries.push(b'e');
self
}
pub fn with_dictionary(mut self, key: &str, dictionary: TorrentBuilder) -> Self {
self.push_bytes(key.as_bytes());
self.entries.extend_from_slice(&dictionary.build());
self
}
pub fn with_dictionaries(mut self, key: &str, dictionaries: Vec<TorrentBuilder>) -> Self {
self.push_bytes(key.as_bytes());
self.entries.push(b'l');
for dictionary in dictionaries {
self.entries.extend_from_slice(&dictionary.build());
}
self.entries.push(b'e');
self
}
pub fn with_multi_file<I>(self, components: I) -> Self
where
I: IntoIterator,
I::Item: Into<RawString>,
{
self.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries("files", vec![TorrentBuilder::file(components)])
.with_string("name", MOCK_NAME),
)
}
pub fn build(self) -> Vec<u8> {
let mut bytes = vec![b'd'];
bytes.extend_from_slice(&self.entries);
bytes.push(b'e');
bytes
}
fn push_bytes(&mut self, bytes: &[u8]) {
self.entries
.extend_from_slice(format!("{}:", bytes.len()).as_bytes());
self.entries.extend_from_slice(bytes);
}
}
#[cfg(test)]
mod tests {
use crate::testing_prelude::*;
#[test]
fn torrent_builder_build() {
let builder = TorrentBuilder::new().with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries("files", vec![TorrentBuilder::file(["song.flac"])])
.with_string("name", "album"),
);
let output = builder.build();
assert_eq!(
output,
b"d4:infod5:filesld6:lengthi1024e4:pathl9:song.flaceee4:name5:albumee".to_vec()
);
}
}