1use serde::{Deserialize, Serialize};
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct FileInput {
11 pub name: String,
13 pub mime_type: String,
15 pub contents: Vec<u8>,
17 pub path: Option<PathBuf>,
19}
20
21impl FileInput {
22 #[must_use]
24 pub fn new(name: impl Into<String>, mime_type: impl Into<String>, contents: Vec<u8>) -> Self {
25 Self {
26 name: name.into(),
27 mime_type: mime_type.into(),
28 contents,
29 path: None,
30 }
31 }
32
33 #[must_use]
35 pub fn from_path(path: impl AsRef<Path>) -> Self {
36 let path = path.as_ref();
37 let name = path
38 .file_name()
39 .map(|n| n.to_string_lossy().to_string())
40 .unwrap_or_else(|| "unknown".to_string());
41
42 let mime_type = guess_mime_type(&name);
43
44 Self {
45 name,
46 mime_type,
47 contents: Vec::new(), path: Some(path.to_path_buf()),
49 }
50 }
51
52 #[must_use]
54 pub fn from_path_with_contents(path: impl AsRef<Path>, contents: Vec<u8>) -> Self {
55 let path = path.as_ref();
56 let name = path
57 .file_name()
58 .map(|n| n.to_string_lossy().to_string())
59 .unwrap_or_else(|| "unknown".to_string());
60
61 let mime_type = guess_mime_type(&name);
62
63 Self {
64 name,
65 mime_type,
66 contents,
67 path: Some(path.to_path_buf()),
68 }
69 }
70
71 #[must_use]
73 pub fn text(name: impl Into<String>, content: impl Into<String>) -> Self {
74 Self::new(name, "text/plain", content.into().into_bytes())
75 }
76
77 #[must_use]
79 pub fn json(name: impl Into<String>, content: impl Into<String>) -> Self {
80 Self::new(name, "application/json", content.into().into_bytes())
81 }
82
83 #[must_use]
85 pub fn csv(name: impl Into<String>, content: impl Into<String>) -> Self {
86 Self::new(name, "text/csv", content.into().into_bytes())
87 }
88
89 #[must_use]
91 pub fn png(name: impl Into<String>, contents: Vec<u8>) -> Self {
92 Self::new(name, "image/png", contents)
93 }
94
95 #[must_use]
97 pub fn pdf(name: impl Into<String>, contents: Vec<u8>) -> Self {
98 Self::new(name, "application/pdf", contents)
99 }
100
101 #[must_use]
103 pub fn name(&self) -> &str {
104 &self.name
105 }
106
107 #[must_use]
109 pub fn mime_type(&self) -> &str {
110 &self.mime_type
111 }
112
113 #[must_use]
115 pub fn size(&self) -> usize {
116 self.contents.len()
117 }
118
119 #[must_use]
121 pub fn contents(&self) -> &[u8] {
122 &self.contents
123 }
124
125 #[must_use]
127 pub fn contents_string(&self) -> Option<String> {
128 String::from_utf8(self.contents.clone()).ok()
129 }
130
131 #[must_use]
133 pub fn is_empty(&self) -> bool {
134 self.contents.is_empty()
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct Download {
141 pub suggested_filename: String,
143 pub url: String,
145 pub contents: Vec<u8>,
147 pub saved_path: Option<PathBuf>,
149 pub state: DownloadState,
151}
152
153impl Download {
154 #[must_use]
156 pub fn new(url: impl Into<String>, filename: impl Into<String>) -> Self {
157 Self {
158 suggested_filename: filename.into(),
159 url: url.into(),
160 contents: Vec::new(),
161 saved_path: None,
162 state: DownloadState::InProgress,
163 }
164 }
165
166 #[must_use]
168 pub fn completed(
169 url: impl Into<String>,
170 filename: impl Into<String>,
171 contents: Vec<u8>,
172 ) -> Self {
173 Self {
174 suggested_filename: filename.into(),
175 url: url.into(),
176 contents,
177 saved_path: None,
178 state: DownloadState::Completed,
179 }
180 }
181
182 #[must_use]
184 pub fn suggested_filename(&self) -> &str {
185 &self.suggested_filename
186 }
187
188 #[must_use]
190 pub fn url(&self) -> &str {
191 &self.url
192 }
193
194 #[must_use]
196 pub fn size(&self) -> usize {
197 self.contents.len()
198 }
199
200 #[must_use]
202 pub fn is_complete(&self) -> bool {
203 matches!(self.state, DownloadState::Completed)
204 }
205
206 #[must_use]
208 pub fn is_failed(&self) -> bool {
209 matches!(self.state, DownloadState::Failed(_))
210 }
211
212 #[must_use]
214 pub fn path(&self) -> Option<&Path> {
215 self.saved_path.as_deref()
216 }
217
218 pub fn save_as(&mut self, path: impl AsRef<Path>) {
220 self.saved_path = Some(path.as_ref().to_path_buf());
221 self.state = DownloadState::Completed;
222 }
223
224 pub fn cancel(&mut self) {
226 self.state = DownloadState::Cancelled;
227 }
228
229 pub fn fail(&mut self, reason: impl Into<String>) {
231 self.state = DownloadState::Failed(reason.into());
232 }
233
234 #[must_use]
236 pub fn contents(&self) -> &[u8] {
237 &self.contents
238 }
239
240 pub fn delete(&mut self) {
242 self.saved_path = None;
243 self.state = DownloadState::Deleted;
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub enum DownloadState {
250 InProgress,
252 Completed,
254 Cancelled,
256 Failed(String),
258 Deleted,
260}
261
262#[derive(Debug, Clone)]
264pub struct FileChooser {
265 pub multiple: bool,
267 pub accept: Vec<String>,
269 pub files: Vec<FileInput>,
271}
272
273impl FileChooser {
274 #[must_use]
276 pub fn new() -> Self {
277 Self {
278 multiple: false,
279 accept: Vec::new(),
280 files: Vec::new(),
281 }
282 }
283
284 #[must_use]
286 pub fn single() -> Self {
287 Self::new()
288 }
289
290 #[must_use]
292 pub fn multiple() -> Self {
293 Self {
294 multiple: true,
295 ..Self::new()
296 }
297 }
298
299 #[must_use]
301 pub fn accept(mut self, types: impl IntoIterator<Item = impl Into<String>>) -> Self {
302 self.accept = types.into_iter().map(Into::into).collect();
303 self
304 }
305
306 pub fn set_files(&mut self, files: impl IntoIterator<Item = FileInput>) {
308 let files: Vec<FileInput> = files.into_iter().collect();
309 if !self.multiple && files.len() > 1 {
310 if let Some(first) = files.into_iter().next() {
312 self.files = vec![first];
313 }
314 } else {
315 self.files = files;
316 }
317 }
318
319 pub fn set_input_files(&mut self, paths: &[impl AsRef<Path>]) {
321 let files: Vec<FileInput> = paths.iter().map(FileInput::from_path).collect();
322 self.set_files(files);
323 }
324
325 #[must_use]
327 pub fn is_accepted(&self, file: &FileInput) -> bool {
328 if self.accept.is_empty() {
329 return true;
330 }
331
332 let ext = file
333 .name
334 .rsplit('.')
335 .next()
336 .map(|e| format!(".{}", e.to_lowercase()));
337
338 for accept in &self.accept {
339 if accept == &file.mime_type {
340 return true;
341 }
342 if let Some(ref extension) = ext {
343 if accept == extension {
344 return true;
345 }
346 }
347 if accept == "*/*" {
348 return true;
349 }
350 if accept.ends_with("/*") {
352 let prefix = &accept[..accept.len() - 1];
353 if file.mime_type.starts_with(prefix) {
354 return true;
355 }
356 }
357 }
358
359 false
360 }
361
362 #[must_use]
364 pub fn files(&self) -> &[FileInput] {
365 &self.files
366 }
367
368 #[must_use]
370 pub fn file_count(&self) -> usize {
371 self.files.len()
372 }
373
374 #[must_use]
376 pub fn has_files(&self) -> bool {
377 !self.files.is_empty()
378 }
379
380 pub fn clear(&mut self) {
382 self.files.clear();
383 }
384}
385
386impl Default for FileChooser {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392#[derive(Debug, Clone, Default)]
394pub struct DownloadManager {
395 downloads: Vec<Download>,
396}
397
398impl DownloadManager {
399 #[must_use]
401 pub fn new() -> Self {
402 Self::default()
403 }
404
405 pub fn add(&mut self, download: Download) {
407 self.downloads.push(download);
408 }
409
410 #[must_use]
412 pub fn downloads(&self) -> &[Download] {
413 &self.downloads
414 }
415
416 #[must_use]
418 pub fn count(&self) -> usize {
419 self.downloads.len()
420 }
421
422 #[must_use]
424 pub fn last(&self) -> Option<&Download> {
425 self.downloads.last()
426 }
427
428 pub fn last_mut(&mut self) -> Option<&mut Download> {
430 self.downloads.last_mut()
431 }
432
433 #[must_use]
435 pub fn find_by_name(&self, name: &str) -> Option<&Download> {
436 self.downloads.iter().find(|d| d.suggested_filename == name)
437 }
438
439 pub fn clear(&mut self) {
441 self.downloads.clear();
442 }
443
444 #[must_use]
446 pub fn completed(&self) -> Vec<&Download> {
447 self.downloads.iter().filter(|d| d.is_complete()).collect()
448 }
449
450 #[must_use]
452 pub fn wait_for_download(&self) -> Option<&Download> {
453 self.last()
454 }
455}
456
457#[must_use]
459pub fn guess_mime_type(filename: &str) -> String {
460 let ext = filename
461 .rsplit('.')
462 .next()
463 .map(str::to_lowercase)
464 .unwrap_or_default();
465
466 match ext.as_str() {
467 "txt" => "text/plain",
468 "html" | "htm" => "text/html",
469 "css" => "text/css",
470 "js" => "application/javascript",
471 "json" => "application/json",
472 "xml" => "application/xml",
473 "csv" => "text/csv",
474 "pdf" => "application/pdf",
475 "png" => "image/png",
476 "jpg" | "jpeg" => "image/jpeg",
477 "gif" => "image/gif",
478 "svg" => "image/svg+xml",
479 "webp" => "image/webp",
480 "ico" => "image/x-icon",
481 "mp3" => "audio/mpeg",
482 "wav" => "audio/wav",
483 "mp4" => "video/mp4",
484 "webm" => "video/webm",
485 "wasm" => "application/wasm",
486 "zip" => "application/zip",
487 "gz" => "application/gzip",
488 "tar" => "application/x-tar",
489 "doc" => "application/msword",
490 "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
491 "xls" => "application/vnd.ms-excel",
492 "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
493 _ => "application/octet-stream",
494 }
495 .to_string()
496}
497
498#[cfg(test)]
499#[allow(clippy::unwrap_used, clippy::expect_used)]
500mod tests {
501 use super::*;
502
503 #[test]
508 fn h0_file_01_new() {
509 let file = FileInput::new("test.txt", "text/plain", b"hello".to_vec());
510 assert_eq!(file.name(), "test.txt");
511 assert_eq!(file.mime_type(), "text/plain");
512 assert_eq!(file.contents(), b"hello");
513 }
514
515 #[test]
516 fn h0_file_02_from_path() {
517 let file = FileInput::from_path("documents/report.pdf");
518 assert_eq!(file.name(), "report.pdf");
519 assert_eq!(file.mime_type(), "application/pdf");
520 }
521
522 #[test]
523 fn h0_file_03_text() {
524 let file = FileInput::text("notes.txt", "Hello world");
525 assert_eq!(file.mime_type(), "text/plain");
526 assert_eq!(file.contents_string(), Some("Hello world".to_string()));
527 }
528
529 #[test]
530 fn h0_file_04_json() {
531 let file = FileInput::json("data.json", r#"{"key": "value"}"#);
532 assert_eq!(file.mime_type(), "application/json");
533 }
534
535 #[test]
536 fn h0_file_05_csv() {
537 let file = FileInput::csv("data.csv", "a,b,c\n1,2,3");
538 assert_eq!(file.mime_type(), "text/csv");
539 }
540
541 #[test]
542 fn h0_file_06_png() {
543 let file = FileInput::png("image.png", vec![0x89, 0x50, 0x4E, 0x47]);
544 assert_eq!(file.mime_type(), "image/png");
545 }
546
547 #[test]
548 fn h0_file_07_pdf() {
549 let file = FileInput::pdf("doc.pdf", vec![0x25, 0x50, 0x44, 0x46]);
550 assert_eq!(file.mime_type(), "application/pdf");
551 }
552
553 #[test]
558 fn h0_file_08_size() {
559 let file = FileInput::text("test.txt", "12345");
560 assert_eq!(file.size(), 5);
561 }
562
563 #[test]
564 fn h0_file_09_is_empty() {
565 let empty = FileInput::new("empty.txt", "text/plain", vec![]);
566 assert!(empty.is_empty());
567
568 let non_empty = FileInput::text("full.txt", "content");
569 assert!(!non_empty.is_empty());
570 }
571
572 #[test]
573 fn h0_file_10_contents_string_valid() {
574 let file = FileInput::text("test.txt", "Hello");
575 assert_eq!(file.contents_string(), Some("Hello".to_string()));
576 }
577
578 #[test]
579 fn h0_file_11_contents_string_invalid() {
580 let file = FileInput::new("binary.bin", "application/octet-stream", vec![0xFF, 0xFE]);
581 assert!(file.contents_string().is_none());
582 }
583
584 #[test]
589 fn h0_file_12_download_new() {
590 let download = Download::new("http://example.com/file.pdf", "file.pdf");
591 assert_eq!(download.suggested_filename(), "file.pdf");
592 assert_eq!(download.url(), "http://example.com/file.pdf");
593 assert!(!download.is_complete());
594 }
595
596 #[test]
597 fn h0_file_13_download_completed() {
598 let download =
599 Download::completed("http://example.com/data.json", "data.json", b"{}".to_vec());
600 assert!(download.is_complete());
601 assert_eq!(download.size(), 2);
602 }
603
604 #[test]
609 fn h0_file_14_save_as() {
610 let mut download = Download::completed("http://test", "file.txt", b"content".to_vec());
611 download.save_as("/tmp/file.txt");
612
613 assert_eq!(download.path(), Some(Path::new("/tmp/file.txt")));
614 }
615
616 #[test]
617 fn h0_file_15_cancel() {
618 let mut download = Download::new("http://test", "file.txt");
619 download.cancel();
620
621 assert_eq!(download.state, DownloadState::Cancelled);
622 }
623
624 #[test]
625 fn h0_file_16_fail() {
626 let mut download = Download::new("http://test", "file.txt");
627 download.fail("Network error");
628
629 assert!(download.is_failed());
630 assert_eq!(
631 download.state,
632 DownloadState::Failed("Network error".to_string())
633 );
634 }
635
636 #[test]
637 fn h0_file_17_delete() {
638 let mut download = Download::completed("http://test", "file.txt", vec![]);
639 download.save_as("/tmp/file.txt");
640 download.delete();
641
642 assert!(download.path().is_none());
643 assert_eq!(download.state, DownloadState::Deleted);
644 }
645
646 #[test]
651 fn h0_file_18_chooser_new() {
652 let chooser = FileChooser::new();
653 assert!(!chooser.multiple);
654 assert!(!chooser.has_files());
655 }
656
657 #[test]
658 fn h0_file_19_chooser_multiple() {
659 let chooser = FileChooser::multiple();
660 assert!(chooser.multiple);
661 }
662
663 #[test]
664 fn h0_file_20_chooser_accept() {
665 let chooser = FileChooser::new().accept(vec![".pdf", ".doc"]);
666 assert_eq!(chooser.accept.len(), 2);
667 }
668
669 #[test]
670 fn h0_file_21_set_files() {
671 let mut chooser = FileChooser::new();
672 chooser.set_files(vec![FileInput::text("test.txt", "content")]);
673
674 assert_eq!(chooser.file_count(), 1);
675 }
676
677 #[test]
678 fn h0_file_22_set_files_single_mode() {
679 let mut chooser = FileChooser::single();
680 chooser.set_files(vec![
681 FileInput::text("a.txt", "a"),
682 FileInput::text("b.txt", "b"),
683 ]);
684
685 assert_eq!(chooser.file_count(), 1);
687 assert_eq!(chooser.files()[0].name(), "a.txt");
688 }
689
690 #[test]
691 fn h0_file_23_set_files_multiple_mode() {
692 let mut chooser = FileChooser::multiple();
693 chooser.set_files(vec![
694 FileInput::text("a.txt", "a"),
695 FileInput::text("b.txt", "b"),
696 ]);
697
698 assert_eq!(chooser.file_count(), 2);
699 }
700
701 #[test]
706 fn h0_file_24_is_accepted_empty() {
707 let chooser = FileChooser::new();
708 let file = FileInput::text("test.txt", "");
709 assert!(chooser.is_accepted(&file));
710 }
711
712 #[test]
713 fn h0_file_25_is_accepted_mime() {
714 let chooser = FileChooser::new().accept(vec!["text/plain"]);
715 let file = FileInput::text("test.txt", "");
716 assert!(chooser.is_accepted(&file));
717 }
718
719 #[test]
720 fn h0_file_26_is_accepted_extension() {
721 let chooser = FileChooser::new().accept(vec![".pdf"]);
722 let file = FileInput::from_path("doc.pdf");
723 assert!(chooser.is_accepted(&file));
724 }
725
726 #[test]
727 fn h0_file_27_is_accepted_wildcard() {
728 let chooser = FileChooser::new().accept(vec!["image/*"]);
729 let png = FileInput::png("test.png", vec![]);
730 assert!(chooser.is_accepted(&png));
731 }
732
733 #[test]
734 fn h0_file_28_is_not_accepted() {
735 let chooser = FileChooser::new().accept(vec![".pdf"]);
736 let file = FileInput::text("test.txt", "");
737 assert!(!chooser.is_accepted(&file));
738 }
739
740 #[test]
745 fn h0_file_29_manager_new() {
746 let manager = DownloadManager::new();
747 assert_eq!(manager.count(), 0);
748 }
749
750 #[test]
751 fn h0_file_30_manager_add() {
752 let mut manager = DownloadManager::new();
753 manager.add(Download::new("http://test", "file.txt"));
754
755 assert_eq!(manager.count(), 1);
756 }
757
758 #[test]
759 fn h0_file_31_manager_last() {
760 let mut manager = DownloadManager::new();
761 manager.add(Download::new("http://test", "first.txt"));
762 manager.add(Download::new("http://test", "last.txt"));
763
764 let last = manager.last().unwrap();
765 assert_eq!(last.suggested_filename(), "last.txt");
766 }
767
768 #[test]
769 fn h0_file_32_manager_find_by_name() {
770 let mut manager = DownloadManager::new();
771 manager.add(Download::new("http://test/a", "a.txt"));
772 manager.add(Download::new("http://test/b", "b.txt"));
773
774 let found = manager.find_by_name("a.txt").unwrap();
775 assert_eq!(found.url(), "http://test/a");
776 }
777
778 #[test]
779 fn h0_file_33_manager_clear() {
780 let mut manager = DownloadManager::new();
781 manager.add(Download::new("http://test", "file.txt"));
782 manager.clear();
783
784 assert_eq!(manager.count(), 0);
785 }
786
787 #[test]
788 fn h0_file_34_manager_completed() {
789 let mut manager = DownloadManager::new();
790 manager.add(Download::completed("http://test/a", "a.txt", vec![]));
791 manager.add(Download::new("http://test/b", "b.txt"));
792
793 let completed = manager.completed();
794 assert_eq!(completed.len(), 1);
795 }
796
797 #[test]
802 fn h0_file_35_guess_mime_text() {
803 assert_eq!(guess_mime_type("file.txt"), "text/plain");
804 assert_eq!(guess_mime_type("page.html"), "text/html");
805 assert_eq!(guess_mime_type("styles.css"), "text/css");
806 }
807
808 #[test]
809 fn h0_file_36_guess_mime_image() {
810 assert_eq!(guess_mime_type("photo.png"), "image/png");
811 assert_eq!(guess_mime_type("photo.jpg"), "image/jpeg");
812 assert_eq!(guess_mime_type("photo.jpeg"), "image/jpeg");
813 assert_eq!(guess_mime_type("icon.svg"), "image/svg+xml");
814 }
815
816 #[test]
817 fn h0_file_37_guess_mime_app() {
818 assert_eq!(guess_mime_type("data.json"), "application/json");
819 assert_eq!(guess_mime_type("doc.pdf"), "application/pdf");
820 assert_eq!(guess_mime_type("app.wasm"), "application/wasm");
821 }
822
823 #[test]
824 fn h0_file_38_guess_mime_unknown() {
825 assert_eq!(guess_mime_type("file.xyz"), "application/octet-stream");
826 assert_eq!(guess_mime_type("noextension"), "application/octet-stream");
827 }
828
829 #[test]
834 fn h0_file_39_file_input_clone() {
835 let file = FileInput::text("test.txt", "content");
836 let cloned = file;
837 assert_eq!(cloned.name(), "test.txt");
838 }
839
840 #[test]
841 fn h0_file_40_download_clone() {
842 let download = Download::completed("http://test", "file.txt", vec![1, 2, 3]);
843 let cloned = download;
844 assert_eq!(cloned.size(), 3);
845 }
846
847 #[test]
852 fn h0_file_41_from_path_with_contents() {
853 let file = FileInput::from_path_with_contents("docs/report.pdf", b"PDF content".to_vec());
854 assert_eq!(file.name(), "report.pdf");
855 assert_eq!(file.mime_type(), "application/pdf");
856 assert_eq!(file.contents(), b"PDF content");
857 assert!(file.path.is_some());
858 assert_eq!(file.path.unwrap().to_str().unwrap(), "docs/report.pdf");
859 }
860
861 #[test]
862 fn h0_file_42_from_path_no_filename() {
863 let file = FileInput::from_path("/");
865 assert_eq!(file.name(), "unknown");
866 }
867
868 #[test]
869 fn h0_file_43_from_path_with_contents_no_filename() {
870 let file = FileInput::from_path_with_contents("/", vec![1, 2, 3]);
872 assert_eq!(file.name(), "unknown");
873 assert_eq!(file.contents(), &[1, 2, 3]);
874 }
875
876 #[test]
877 fn h0_file_44_download_contents_accessor() {
878 let download = Download::completed("http://test", "file.txt", vec![1, 2, 3, 4, 5]);
879 assert_eq!(download.contents(), &[1, 2, 3, 4, 5]);
880 }
881
882 #[test]
883 fn h0_file_45_file_chooser_set_input_files() {
884 let mut chooser = FileChooser::multiple();
885 chooser.set_input_files(&["file1.txt", "file2.pdf"]);
886 assert_eq!(chooser.file_count(), 2);
887 assert_eq!(chooser.files()[0].name(), "file1.txt");
888 assert_eq!(chooser.files()[1].name(), "file2.pdf");
889 }
890
891 #[test]
892 fn h0_file_46_file_chooser_clear() {
893 let mut chooser = FileChooser::new();
894 chooser.set_files(vec![FileInput::text("test.txt", "content")]);
895 assert!(chooser.has_files());
896 chooser.clear();
897 assert!(!chooser.has_files());
898 assert_eq!(chooser.file_count(), 0);
899 }
900
901 #[test]
902 fn h0_file_47_file_chooser_default() {
903 let chooser = FileChooser::default();
904 assert!(!chooser.multiple);
905 assert!(chooser.accept.is_empty());
906 assert!(chooser.files.is_empty());
907 }
908
909 #[test]
910 fn h0_file_48_download_manager_last_mut() {
911 let mut manager = DownloadManager::new();
912 manager.add(Download::new("http://test", "file.txt"));
913
914 if let Some(download) = manager.last_mut() {
916 download.cancel();
917 }
918
919 assert_eq!(manager.last().unwrap().state, DownloadState::Cancelled);
920 }
921
922 #[test]
923 fn h0_file_49_download_manager_downloads_accessor() {
924 let mut manager = DownloadManager::new();
925 manager.add(Download::new("http://test/a", "a.txt"));
926 manager.add(Download::new("http://test/b", "b.txt"));
927
928 let downloads = manager.downloads();
929 assert_eq!(downloads.len(), 2);
930 assert_eq!(downloads[0].suggested_filename(), "a.txt");
931 assert_eq!(downloads[1].suggested_filename(), "b.txt");
932 }
933
934 #[test]
935 fn h0_file_50_download_manager_wait_for_download() {
936 let mut manager = DownloadManager::new();
937 assert!(manager.wait_for_download().is_none());
938
939 manager.add(Download::new("http://test", "file.txt"));
940 let waited = manager.wait_for_download();
941 assert!(waited.is_some());
942 assert_eq!(waited.unwrap().suggested_filename(), "file.txt");
943 }
944
945 #[test]
946 fn h0_file_51_download_manager_find_by_name_not_found() {
947 let manager = DownloadManager::new();
948 assert!(manager.find_by_name("nonexistent.txt").is_none());
949 }
950
951 #[test]
952 fn h0_file_52_download_manager_last_empty() {
953 let manager = DownloadManager::new();
954 assert!(manager.last().is_none());
955 }
956
957 #[test]
958 fn h0_file_53_download_manager_last_mut_empty() {
959 let mut manager = DownloadManager::new();
960 assert!(manager.last_mut().is_none());
961 }
962
963 #[test]
964 fn h0_file_54_is_accepted_all_wildcard() {
965 let chooser = FileChooser::new().accept(vec!["*/*"]);
966 let file = FileInput::text("test.txt", "");
967 assert!(chooser.is_accepted(&file));
968 }
969
970 #[test]
971 fn h0_file_55_guess_mime_htm() {
972 assert_eq!(guess_mime_type("page.htm"), "text/html");
973 }
974
975 #[test]
976 fn h0_file_56_guess_mime_gif() {
977 assert_eq!(guess_mime_type("animation.gif"), "image/gif");
978 }
979
980 #[test]
981 fn h0_file_57_guess_mime_webp() {
982 assert_eq!(guess_mime_type("image.webp"), "image/webp");
983 }
984
985 #[test]
986 fn h0_file_58_guess_mime_ico() {
987 assert_eq!(guess_mime_type("favicon.ico"), "image/x-icon");
988 }
989
990 #[test]
991 fn h0_file_59_guess_mime_audio() {
992 assert_eq!(guess_mime_type("song.mp3"), "audio/mpeg");
993 assert_eq!(guess_mime_type("sound.wav"), "audio/wav");
994 }
995
996 #[test]
997 fn h0_file_60_guess_mime_video() {
998 assert_eq!(guess_mime_type("movie.mp4"), "video/mp4");
999 assert_eq!(guess_mime_type("clip.webm"), "video/webm");
1000 }
1001
1002 #[test]
1003 fn h0_file_61_guess_mime_archive() {
1004 assert_eq!(guess_mime_type("archive.zip"), "application/zip");
1005 assert_eq!(guess_mime_type("archive.gz"), "application/gzip");
1006 assert_eq!(guess_mime_type("archive.tar"), "application/x-tar");
1007 }
1008
1009 #[test]
1010 fn h0_file_62_guess_mime_office() {
1011 assert_eq!(guess_mime_type("document.doc"), "application/msword");
1012 assert_eq!(
1013 guess_mime_type("document.docx"),
1014 "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
1015 );
1016 assert_eq!(
1017 guess_mime_type("spreadsheet.xls"),
1018 "application/vnd.ms-excel"
1019 );
1020 assert_eq!(
1021 guess_mime_type("spreadsheet.xlsx"),
1022 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1023 );
1024 }
1025
1026 #[test]
1027 fn h0_file_63_guess_mime_xml() {
1028 assert_eq!(guess_mime_type("data.xml"), "application/xml");
1029 }
1030
1031 #[test]
1032 fn h0_file_64_guess_mime_javascript() {
1033 assert_eq!(guess_mime_type("script.js"), "application/javascript");
1034 }
1035
1036 #[test]
1037 fn h0_file_65_file_input_path_accessor() {
1038 let file = FileInput::new("test.txt", "text/plain", vec![]);
1039 assert!(file.path.is_none());
1040
1041 let file_with_path = FileInput::from_path("folder/test.txt");
1042 assert!(file_with_path.path.is_some());
1043 }
1044
1045 #[test]
1046 fn h0_file_66_download_state_debug() {
1047 let states = vec![
1048 DownloadState::InProgress,
1049 DownloadState::Completed,
1050 DownloadState::Cancelled,
1051 DownloadState::Failed("error".to_string()),
1052 DownloadState::Deleted,
1053 ];
1054
1055 for state in &states {
1056 let _ = format!("{:?}", state);
1058 }
1059
1060 assert_eq!(DownloadState::InProgress, DownloadState::InProgress);
1062 assert_ne!(DownloadState::InProgress, DownloadState::Completed);
1063 }
1064
1065 #[test]
1066 fn h0_file_67_file_input_debug() {
1067 let file = FileInput::text("test.txt", "content");
1068 let debug_str = format!("{:?}", file);
1069 assert!(debug_str.contains("test.txt"));
1070 }
1071
1072 #[test]
1073 fn h0_file_68_download_debug() {
1074 let download = Download::new("http://test", "file.txt");
1075 let debug_str = format!("{:?}", download);
1076 assert!(debug_str.contains("file.txt"));
1077 }
1078
1079 #[test]
1080 fn h0_file_69_file_chooser_debug() {
1081 let chooser = FileChooser::new();
1082 let debug_str = format!("{:?}", chooser);
1083 assert!(debug_str.contains("FileChooser"));
1084 }
1085
1086 #[test]
1087 fn h0_file_70_download_manager_debug() {
1088 let manager = DownloadManager::new();
1089 let debug_str = format!("{:?}", manager);
1090 assert!(debug_str.contains("DownloadManager"));
1091 }
1092
1093 #[test]
1094 fn h0_file_71_set_files_single_mode_empty() {
1095 let mut chooser = FileChooser::single();
1096 chooser.set_files(Vec::<FileInput>::new());
1097 assert_eq!(chooser.file_count(), 0);
1098 }
1099
1100 #[test]
1101 fn h0_file_72_set_files_single_mode_exactly_one() {
1102 let mut chooser = FileChooser::single();
1103 chooser.set_files(vec![FileInput::text("single.txt", "content")]);
1104 assert_eq!(chooser.file_count(), 1);
1105 assert_eq!(chooser.files()[0].name(), "single.txt");
1106 }
1107
1108 #[test]
1109 fn h0_file_73_is_accepted_extension_case_insensitive() {
1110 let chooser = FileChooser::new().accept(vec![".pdf"]);
1111 let file = FileInput::from_path("doc.PDF");
1112 assert!(chooser.is_accepted(&file));
1113 }
1114
1115 #[test]
1116 fn h0_file_74_is_accepted_mime_wildcard_image() {
1117 let chooser = FileChooser::new().accept(vec!["image/*"]);
1118
1119 let gif = FileInput::new("test.gif", "image/gif", vec![]);
1120 assert!(chooser.is_accepted(&gif));
1121
1122 let webp = FileInput::new("test.webp", "image/webp", vec![]);
1123 assert!(chooser.is_accepted(&webp));
1124 }
1125
1126 #[test]
1127 fn h0_file_75_is_accepted_mime_wildcard_audio() {
1128 let chooser = FileChooser::new().accept(vec!["audio/*"]);
1129
1130 let mp3 = FileInput::new("test.mp3", "audio/mpeg", vec![]);
1131 assert!(chooser.is_accepted(&mp3));
1132
1133 let txt = FileInput::text("test.txt", "");
1134 assert!(!chooser.is_accepted(&txt));
1135 }
1136
1137 #[test]
1138 fn h0_file_76_download_path_none_when_not_saved() {
1139 let download = Download::new("http://test", "file.txt");
1140 assert!(download.path().is_none());
1141 }
1142
1143 #[test]
1144 fn h0_file_77_set_input_files_single_mode() {
1145 let mut chooser = FileChooser::single();
1146 chooser.set_input_files(&["file1.txt", "file2.txt"]);
1147 assert_eq!(chooser.file_count(), 1);
1149 assert_eq!(chooser.files()[0].name(), "file1.txt");
1150 }
1151
1152 #[test]
1153 fn h0_file_78_file_input_serialize_deserialize() {
1154 let file = FileInput::text("test.txt", "content");
1155 let json = serde_json::to_string(&file).unwrap();
1156 let deserialized: FileInput = serde_json::from_str(&json).unwrap();
1157 assert_eq!(deserialized.name(), "test.txt");
1158 assert_eq!(deserialized.contents_string(), Some("content".to_string()));
1159 }
1160
1161 #[test]
1162 fn h0_file_79_download_serialize_deserialize() {
1163 let download = Download::completed("http://test", "file.txt", b"data".to_vec());
1164 let json = serde_json::to_string(&download).unwrap();
1165 let deserialized: Download = serde_json::from_str(&json).unwrap();
1166 assert_eq!(deserialized.suggested_filename(), "file.txt");
1167 assert!(deserialized.is_complete());
1168 }
1169
1170 #[test]
1171 fn h0_file_80_download_state_serialize_deserialize() {
1172 let states = vec![
1173 DownloadState::InProgress,
1174 DownloadState::Completed,
1175 DownloadState::Cancelled,
1176 DownloadState::Failed("Network error".to_string()),
1177 DownloadState::Deleted,
1178 ];
1179
1180 for state in states {
1181 let json = serde_json::to_string(&state).unwrap();
1182 let deserialized: DownloadState = serde_json::from_str(&json).unwrap();
1183 assert_eq!(state, deserialized);
1184 }
1185 }
1186}