use super::{BTreeSet, Digest, RequestGroup}; /// Applies a `select-file` option value to BitTorrent file selection state. pub(in crate::dispatcher) fn apply_bt_select_file_option( group: &mut RequestGroup, select_file: &str, ) -> Result<(), String> { let Some(mut bt_state) = group.bt().cloned() else { return Ok(()); }; if bt_state.files.is_empty() { return Ok(()); } let selected_indexes = parse_bt_select_file_indexes(select_file, bt_state.files.len())?; for (index, file) in bt_state.files.iter_mut().enumerate() { file.selected = selected_indexes.contains(&(index + 1)); } group.set_bt(bt_state); Ok(()) } /// Parses aria2-style `select-file` syntax into a set of selected file indexes. pub(in crate::dispatcher) fn parse_bt_select_file_indexes( select_file: &str, file_count: usize, ) -> Result, String> { let mut selected = BTreeSet::new(); let trimmed = select_file.trim(); if trimmed.is_empty() { return Err("empty value".to_owned()); } for token in trimmed .split(',') .map(str::trim) .filter(|token| !token.is_empty()) { if let Some((start_raw, end_raw)) = token.split_once('-') { let start = start_raw .trim() .parse::() .map_err(|_| format!("invalid start index `{start_raw}`"))?; let end = end_raw .trim() .parse::() .map_err(|_| format!("invalid end index `{end_raw}`"))?; if start == 0 || end == 0 { return Err("indexes are 1-based".to_owned()); } let (lo, hi) = if start <= end { (start, end) } else { (end, start) }; if hi > file_count { return Err(format!("index {hi} out of range 1..={file_count}")); } for index in lo..=hi { selected.insert(index); } continue; } let index = token .parse::() .map_err(|_| format!("invalid index `{token}`"))?; if index == 0 { return Err("indexes are 1-based".to_owned()); } if index > file_count { return Err(format!("index {index} out of range 1..={file_count}")); } selected.insert(index); } Ok(selected) }