1use std::path::Path;
12
13use crate::app::state::{Entry, EntryKind};
14use crate::preview::graphics::{self, ImageProtocol};
15use crate::preview::provider::{PreviewContent, PreviewCtx, PreviewOutcome, PreviewProvider};
16
17const IMAGE_EXTENSIONS: &[&str] = &[
19 "png", "jpg", "jpeg", "gif", "bmp", "ico", "tiff", "tif", "webp", "avif", "svg",
20];
21
22pub struct ImageProvider;
28
29impl PreviewProvider for ImageProvider {
30 fn can_handle(&self, entry: &Entry) -> bool {
31 if entry.kind != EntryKind::File {
32 return false;
33 }
34 is_image_path(&entry.path)
35 }
36
37 fn preview(&self, entry: &Entry, ctx: &PreviewCtx) -> PreviewOutcome {
38 if graphics::active() == ImageProtocol::None {
39 let content = build_metadata_preview_sync(&entry.path, entry.metadata.as_ref());
42 return PreviewOutcome::Ready(content);
43 }
44
45 crate::workers::image_decode::spawn_image_decode(
46 entry.path.clone(),
47 ctx.generation,
48 ctx.worker_tx.clone(),
49 );
50 PreviewOutcome::Deferred
51 }
52}
53
54pub fn is_image_path(path: &Path) -> bool {
56 path.extension()
57 .and_then(|e| e.to_str())
58 .map(|ext| IMAGE_EXTENSIONS.contains(&ext.to_lowercase().as_str()))
59 .unwrap_or(false)
60}
61
62fn build_metadata_preview_sync(
68 path: &Path,
69 metadata: Option<&std::fs::Metadata>,
70) -> PreviewContent {
71 let ext = path
72 .extension()
73 .and_then(|e| e.to_str())
74 .unwrap_or("unknown")
75 .to_uppercase();
76
77 let size_str = metadata
80 .map(|m| crate::metafmt::size(m.len()))
81 .or_else(|| {
82 std::fs::metadata(path)
83 .map(|m| crate::metafmt::size(m.len()))
84 .ok()
85 })
86 .unwrap_or_else(|| "unknown".to_owned());
87
88 PreviewContent::Binary(vec![
89 format!(" Type : {} image", ext),
90 format!(" Size : {}", size_str),
91 String::new(),
92 " (image previews disabled)".to_owned(),
93 " Set [preview] image_protocol to re-enable them.".to_owned(),
94 ])
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn image_path_recognises_extensions() {
103 for ext in ["png", "jpg", "jpeg", "gif", "bmp", "webp"] {
104 let name = format!("image.{ext}");
105 let path = Path::new(&name);
106 assert!(is_image_path(path), "expected image path for .{ext}");
107 }
108 }
109
110 #[test]
111 fn non_image_extensions_rejected() {
112 for ext in ["rs", "txt", "md", "toml"] {
113 let name = format!("file.{ext}");
114 let path = Path::new(&name);
115 assert!(!is_image_path(path), "should not be image for .{ext}");
116 }
117 }
118
119 #[test]
120 fn metadata_preview_sync_produces_binary_lines() {
121 use tempfile::NamedTempFile;
122 let f = NamedTempFile::with_suffix(".png").unwrap();
123 let meta = std::fs::metadata(f.path()).ok();
124 let content = build_metadata_preview_sync(f.path(), meta.as_ref());
125 assert!(matches!(content, PreviewContent::Binary(_)));
126 }
127}