1use std::path::PathBuf;
10
11use irontide_core::FilePriority;
12use irontide_session::TorrentInfo;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct FlatFileEntry {
21 pub path: PathBuf,
24 pub size: u64,
26 pub progress: u64,
28 pub priority: FilePriority,
30}
31
32#[must_use]
42pub fn build_flat(
43 info: &TorrentInfo,
44 progress: &[u64],
45 priorities: &[FilePriority],
46) -> Vec<FlatFileEntry> {
47 info.files
48 .iter()
49 .enumerate()
50 .map(|(i, entry)| FlatFileEntry {
51 path: entry.path.clone(),
52 size: entry.length,
53 progress: progress.get(i).copied().unwrap_or(0),
54 priority: priorities.get(i).copied().unwrap_or(FilePriority::Normal),
55 })
56 .collect()
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use irontide_core::Id20;
63 use irontide_session::FileInfo;
64
65 fn info(files: Vec<(&str, u64)>) -> TorrentInfo {
66 TorrentInfo {
67 info_hash: Id20([0u8; 20]),
68 name: "test".into(),
69 total_length: files.iter().map(|(_, n)| *n).sum(),
70 piece_length: 16_384,
71 num_pieces: 1,
72 files: files
73 .into_iter()
74 .map(|(p, n)| FileInfo {
75 path: PathBuf::from(p),
76 length: n,
77 })
78 .collect(),
79 private: false,
80 }
81 }
82
83 #[test]
84 fn build_flat_empty_info() {
85 let i = info(vec![]);
86 let out = build_flat(&i, &[], &[]);
87 assert!(out.is_empty());
88 }
89
90 #[test]
91 fn build_flat_single_file() {
92 let i = info(vec![("a.bin", 1000)]);
93 let out = build_flat(&i, &[500], &[FilePriority::High]);
94 assert_eq!(out.len(), 1);
95 assert_eq!(out[0].path, PathBuf::from("a.bin"));
96 assert_eq!(out[0].size, 1000);
97 assert_eq!(out[0].progress, 500);
98 assert_eq!(out[0].priority, FilePriority::High);
99 }
100
101 #[test]
102 fn build_flat_nested_files_preserve_paths() {
103 let i = info(vec![
106 ("readme.txt", 100),
107 ("video/intro.mp4", 50_000),
108 ("video/extras/bts.mkv", 80_000),
109 ]);
110 let out = build_flat(
111 &i,
112 &[100, 25_000, 0],
113 &[FilePriority::Normal, FilePriority::High, FilePriority::Skip],
114 );
115 assert_eq!(out.len(), 3);
116 assert_eq!(out[0].path, PathBuf::from("readme.txt"));
117 assert_eq!(out[1].path, PathBuf::from("video/intro.mp4"));
118 assert_eq!(out[2].path, PathBuf::from("video/extras/bts.mkv"));
119 }
120
121 #[test]
122 fn build_flat_priority_progress_aligned_by_index() {
123 let i = info(vec![("a", 10), ("b", 20), ("c", 30)]);
124 let out = build_flat(
125 &i,
126 &[1, 2, 3],
127 &[FilePriority::Skip, FilePriority::Low, FilePriority::High],
128 );
129 assert_eq!(out[0].progress, 1);
130 assert_eq!(out[0].priority, FilePriority::Skip);
131 assert_eq!(out[1].progress, 2);
132 assert_eq!(out[1].priority, FilePriority::Low);
133 assert_eq!(out[2].progress, 3);
134 assert_eq!(out[2].priority, FilePriority::High);
135 }
136
137 #[test]
138 fn build_flat_length_mismatch_saturates_to_defaults() {
139 let i = info(vec![("a", 10), ("b", 20), ("c", 30)]);
142 let out = build_flat(&i, &[1, 2], &[FilePriority::High]);
143 assert_eq!(out.len(), 3);
144 assert_eq!(out[0].progress, 1);
146 assert_eq!(out[0].priority, FilePriority::High);
147 assert_eq!(out[1].progress, 2);
149 assert_eq!(out[1].priority, FilePriority::Normal);
150 assert_eq!(out[2].progress, 0);
152 assert_eq!(out[2].priority, FilePriority::Normal);
153 }
154}