use crate::error::Result;
use crate::session::session_entry::{SessionEntry, decode_hex, escape_uri, unescape_uri};
impl SessionEntry {
pub fn serialize(&self) -> String {
let mut lines = String::new();
let escaped_uris: Vec<String> = self.uris.iter().map(|u| escape_uri(u)).collect();
lines.push_str(&escaped_uris.join("\t"));
lines.push('\n');
lines.push_str(&format!(" GID={:x}\n", self.gid));
if self.paused {
lines.push_str(" PAUSE=true\n");
}
for (key, value) in &self.options {
lines.push_str(&format!(" {}={}\n", key, value));
}
lines.push_str(&format!(" TOTAL_LENGTH={}\n", self.total_length));
lines.push_str(&format!(" COMPLETED_LENGTH={}\n", self.completed_length));
lines.push_str(&format!(" UPLOAD_LENGTH={}\n", self.upload_length));
lines.push_str(&format!(" DOWNLOAD_SPEED={}\n", self.download_speed));
lines.push_str(&format!(" STATUS={}\n", self.status));
match self.error_code {
Some(code) => lines.push_str(&format!(" ERROR_CODE={}\n", code)),
None => lines.push_str(" ERROR_CODE=\n"),
}
match &self.bitfield {
Some(bytes) => {
let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
lines.push_str(&format!(" BITFIELD={}\n", hex));
}
None => lines.push_str(" BITFIELD=\n"),
}
lines.push_str(&format!(" NUM_PIECES={}\n", self.num_pieces.unwrap_or(0)));
lines.push_str(&format!(
" PIECE_LENGTH={}\n",
self.piece_length.unwrap_or(0)
));
match &self.info_hash_hex {
Some(hash) => lines.push_str(&format!(" INFO_HASH={}\n", hash)),
None => lines.push_str(" INFO_HASH=\n"),
}
lines.push_str(&format!(
" RESUME_OFFSET={}\n",
self.resume_offset.unwrap_or(0)
));
lines
}
pub fn deserialize_line(text: &str) -> Result<SessionEntry> {
let mut entry = SessionEntry::new(0, Vec::new());
for raw_line in text.lines() {
let line = raw_line.trim_end();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(rest) = line.strip_prefix(' ') {
let rest_trimmed = rest.trim();
if let Some((key, value)) = rest_trimmed.split_once('=') {
Self::parse_property(key, value, &mut entry);
}
continue;
}
let unescaped = unescape_uri(line.trim());
let uris: Vec<String> = unescaped
.split('\t')
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.collect();
if !uris.is_empty() {
entry.uris = uris;
}
}
Ok(entry)
}
fn parse_property(key: &str, value: &str, entry: &mut SessionEntry) {
match key {
"GID" => {
if let Ok(gid) = u64::from_str_radix(value, 16) {
entry.gid = gid;
}
}
"PAUSE" => {
if value == "true" {
entry.paused = true;
}
}
"TOTAL_LENGTH" => {
if let Ok(v) = value.parse::<u64>() {
entry.total_length = v;
}
}
"COMPLETED_LENGTH" => {
if let Ok(v) = value.parse::<u64>() {
entry.completed_length = v;
}
}
"UPLOAD_LENGTH" => {
if let Ok(v) = value.parse::<u64>() {
entry.upload_length = v;
}
}
"DOWNLOAD_SPEED" => {
if let Ok(v) = value.parse::<u64>() {
entry.download_speed = v;
}
}
"STATUS" => {
if !value.is_empty() {
entry.status = value.to_string();
}
}
"ERROR_CODE" => {
if !value.is_empty()
&& let Ok(code) = value.parse::<i32>()
{
entry.error_code = Some(code);
}
}
"BITFIELD" => {
if !value.is_empty() {
if let Ok(bytes) = decode_hex(value) {
entry.bitfield = Some(bytes);
} else {
tracing::warn!("Invalid BITFIELD hex string, ignoring");
}
}
}
"NUM_PIECES" => {
if let Ok(v) = value.parse::<u32>()
&& v > 0
{
entry.num_pieces = Some(v);
}
}
"PIECE_LENGTH" => {
if let Ok(v) = value.parse::<u32>()
&& v > 0
{
entry.piece_length = Some(v);
}
}
"INFO_HASH" => {
if !value.is_empty() {
entry.info_hash_hex = Some(value.to_string());
}
}
"RESUME_OFFSET" => {
if let Ok(v) = value.parse::<u64>()
&& v > 0
{
entry.resume_offset = Some(v);
}
}
_ => {
entry.options.insert(key.to_string(), value.to_string());
}
}
}
}