@@ -2,7 +2,11 @@ use std::fs::File;
22use std:: io:: Write ;
33use std:: path:: { Path , PathBuf } ;
44
5+ use serde:: { Deserialize , Serialize } ;
6+ use serde_bytes:: ByteBuf ;
57use tempfile:: { tempdir, TempDir } ;
8+ use torrust_index_backend:: services:: hasher:: sha1;
9+ use torrust_index_backend:: utils:: hex:: into_bytes;
610use uuid:: Uuid ;
711
812use super :: file:: { create_torrent, parse_torrent, TorrentFileInfo } ;
@@ -94,6 +98,45 @@ impl TestTorrent {
9498 }
9599 }
96100
101+ pub fn with_custom_info_dict_field ( id : Uuid , file_contents : & str , custom : & str ) -> Self {
102+ let temp_dir = temp_dir ( ) ;
103+
104+ let torrents_dir_path = temp_dir. path ( ) . to_owned ( ) ;
105+
106+ // Create the torrent in memory
107+ let torrent = TestTorrentWithCustomInfoField :: with_contents ( id, file_contents, custom) ;
108+
109+ // Bencode the torrent
110+ let torrent_data = TestTorrentWithCustomInfoField :: encode ( & torrent) . unwrap ( ) ;
111+
112+ // Torrent temporary file path
113+ let filename = format ! ( "file-{id}.txt.torrent" ) ;
114+ let torrent_path = torrents_dir_path. join ( filename. clone ( ) ) ;
115+
116+ // Write the torrent file to the temporary file
117+ let mut file = File :: create ( torrent_path. clone ( ) ) . unwrap ( ) ;
118+ file. write_all ( & torrent_data) . unwrap ( ) ;
119+
120+ // Load torrent binary file
121+ let torrent_file = BinaryFile :: from_file_at_path ( & torrent_path) ;
122+
123+ // Load torrent file metadata
124+ let torrent_info = parse_torrent ( & torrent_path) ;
125+
126+ let torrent_to_index = TorrentIndexInfo {
127+ title : format ! ( "title-{id}" ) ,
128+ description : format ! ( "description-{id}" ) ,
129+ category : software_predefined_category_name ( ) ,
130+ torrent_file,
131+ name : filename,
132+ } ;
133+
134+ TestTorrent {
135+ file_info : torrent_info,
136+ index_info : torrent_to_index,
137+ }
138+ }
139+
97140 pub fn info_hash ( & self ) -> InfoHash {
98141 self . file_info . info_hash . clone ( )
99142 }
@@ -128,3 +171,65 @@ pub fn random_txt_file(dir: &Path, id: &Uuid) -> String {
128171pub fn temp_dir ( ) -> TempDir {
129172 tempdir ( ) . unwrap ( )
130173}
174+
175+ /// A minimal torrent file with a custom field in the info dict.
176+ ///
177+ /// ```json
178+ /// {
179+ /// "info": {
180+ /// "length": 602515,
181+ /// "name": "mandelbrot_set_01",
182+ /// "piece length": 32768,
183+ /// "pieces": "<hex>8A 88 32 BE ED 05 5F AA C4 AF 4A 90 4B 9A BF 0D EC 83 42 1C 73 39 05 B8 D6 20 2C 1B D1 8A 53 28 1F B5 D4 23 0A 23 C8 DB AC C4 E6 6B 16 12 08 C7 A4 AD 64 45 70 ED 91 0D F1 38 E7 DF 0C 1A D0 C9 23 27 7C D1 F9 D4 E5 A1 5F F5 E5 A0 E4 9E FB B1 43 F5 4B AD 0E D4 9D CB 49 F7 E6 7B BA 30 5F AF F9 88 56 FB 45 9A B4 95 92 3E 2C 7F DA A6 D3 82 E7 63 A3 BB 4B 28 F3 57 C7 CB 7D 8C 06 E3 46 AB D7 E8 8E 8A 8C 9F C7 E6 C5 C5 64 82 ED 47 BB 2A F1 B7 3F A5 3C 5B 9C AF 43 EC 2A E1 08 68 9A 49 C8 BF 1B 07 AD BE E9 2D 7E BE 9C 18 7F 4C A1 97 0E 54 3A 18 94 0E 60 8D 5C 69 0E 41 46 0D 3C 9A 37 F6 81 62 4F 95 C0 73 92 CA 9A D5 A9 89 AC 8B 85 12 53 0B FB E2 96 26 3E 26 A6 5B 70 53 48 65 F3 6C 27 0F 6B BD 1C EE EB 1A 9D 5F 77 A8 D8 AF D8 14 82 4A E0 B4 62 BC F1 A5 F5 F2 C7 60 F8 38 C8 5B 0B A9 07 DD 86 FA C0 7B F0 26 D7 D1 9A 42 C3 1F 9F B9 59 83 10 62 41 E9 06 3C 6D A1 19 75 01 57 25 9E B7 FE DF 91 04 D4 51 4B 6D 44 02 8D 31 8E 84 26 95 0F 30 31 F0 2C 16 39 BD 53 1D CF D3 5E 3E 41 A9 1E 14 3F 73 24 AC 5E 9E FC 4D C5 70 45 0F 45 8B 9B 52 E6 D0 26 47 8F 43 08 9E 2A 7C C5 92 D5 86 36 FE 48 E9 B8 86 84 92 23 49 5B EE C4 31 B2 1D 10 75 8E 4C 07 84 8F</hex>",
184+ /// "custom": "custom03"
185+ /// }
186+ /// }
187+ /// ```
188+ ///
189+ /// Changing the value of the `custom` field will change the info-hash of the torrent.
190+ #[ derive( PartialEq , Debug , Clone , Serialize , Deserialize ) ]
191+ pub struct TestTorrentWithCustomInfoField {
192+ pub info : InfoDictWithCustomField ,
193+ }
194+
195+ /// A minimal torrent info dict with a custom field.
196+ #[ derive( PartialEq , Eq , Debug , Clone , Serialize , Deserialize ) ]
197+ pub struct InfoDictWithCustomField {
198+ #[ serde( default ) ]
199+ pub length : i64 ,
200+ #[ serde( default ) ]
201+ pub name : String ,
202+ #[ serde( rename = "piece length" ) ]
203+ pub piece_length : i64 ,
204+ #[ serde( default ) ]
205+ pub pieces : ByteBuf ,
206+ #[ serde( default ) ]
207+ pub custom : String ,
208+ }
209+
210+ impl TestTorrentWithCustomInfoField {
211+ pub fn with_contents ( id : Uuid , file_contents : & str , custom : & str ) -> Self {
212+ let sha1_of_file_contents = sha1 ( file_contents) ;
213+ let pieces = into_bytes ( & sha1_of_file_contents) . expect ( "sha1 of test torrent contents cannot be converted to bytes" ) ;
214+
215+ Self {
216+ info : InfoDictWithCustomField {
217+ length : i64:: try_from ( file_contents. len ( ) ) . expect ( "file contents size in bytes cannot exceed i64::MAX" ) ,
218+ name : format ! ( "file-{id}.txt" ) ,
219+ piece_length : 16384 ,
220+ pieces : ByteBuf :: from ( pieces) ,
221+ custom : custom. to_owned ( ) ,
222+ } ,
223+ }
224+ }
225+
226+ pub fn encode ( torrent : & Self ) -> Result < Vec < u8 > , serde_bencode:: Error > {
227+ match serde_bencode:: to_bytes ( torrent) {
228+ Ok ( bencode_bytes) => Ok ( bencode_bytes) ,
229+ Err ( e) => {
230+ eprintln ! ( "{e:?}" ) ;
231+ Err ( e)
232+ }
233+ }
234+ }
235+ }
0 commit comments