File size: 3,771 Bytes
d90101d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use anyhow::Result;
use tokio::fs::File;
use tokio::io::AsyncReadExt;

impl crate::ForgeFS {
    /// Checks if a file is binary by examining its content.
    /// This version takes a path and opens the file itself.
    #[cfg(test)]
    async fn is_binary_path<T: AsRef<std::path::Path>>(path: T) -> Result<(bool, String)> {
        use anyhow::Context;

        let path_ref = path.as_ref();
        let mut file = File::open(path_ref)
            .await
            .with_context(|| format!("Failed to open file {}", path_ref.display()))?;

        Self::is_binary(&mut file).await
    }

    /// Checks if a file is binary by examining its content.
    /// This version takes an already opened file handle, allowing for reuse
    /// of the same file handle across multiple operations.
    /// This is a crate-private implementation detail.
    pub(crate) async fn is_binary(file: &mut File) -> Result<(bool, String)> {
        // Read sample data
        let mut sample = vec![0; 8192];
        let bytes_read = file.read(&mut sample).await?;
        sample.truncate(bytes_read);

        // Handle empty files
        if bytes_read == 0 {
            return Ok((true, "Empty file".into()));
        }

        // Get file type info
        let is_text = match infer::get(&sample) {
            Some(info) => matches!(
                info.matcher_type(),
                infer::MatcherType::Text | infer::MatcherType::Doc
            ),
            None => true, // Assume text if type can't be determined
        };

        let description = infer::get(&sample)
            .map(|info| info.mime_type().to_string())
            .unwrap_or_else(|| "Text file (no specific format detected)".into());

        Ok((is_text, description))
    }
}

#[cfg(test)]
mod test {
    use anyhow::Result;
    use tokio::fs;

    async fn create_test_file(content: &[u8]) -> Result<tempfile::NamedTempFile> {
        let file = tempfile::NamedTempFile::new()?;
        fs::write(file.path(), content).await?;
        Ok(file)
    }

    #[tokio::test]
    async fn test_is_binary_file() -> Result<()> {
        // Test text file
        let text_file = create_test_file(b"Hello, world!").await?;
        let (is_text_or_doc, _) = crate::ForgeFS::is_binary_path(text_file.path()).await?;
        assert!(is_text_or_doc, "Text file should be identified as text");

        // Test binary data
        let binary_content = vec![0, 1, 2, 3, 0, 0, 0, 0, 5, 6, 7, 8];
        let binary_file = create_test_file(&binary_content).await?;
        let (is_text_or_doc, file_type) =
            crate::ForgeFS::is_binary_path(binary_file.path()).await?;

        if !is_text_or_doc {
            assert!(
                file_type.contains("binary") || !file_type.contains("text"),
                "Binary file type description should indicate binary"
            );
        }

        // Test PNG file
        let png_header = [
            0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48,
            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
            0x00,
        ];
        let png_file = create_test_file(&png_header).await?;
        let (is_text_or_doc, file_type) = crate::ForgeFS::is_binary_path(png_file.path()).await?;
        assert!(!is_text_or_doc, "PNG file should be identified as binary");
        assert!(
            file_type.contains("image/png"),
            "PNG file type should be correctly identified"
        );

        // Test empty file
        let empty_file = create_test_file(&[]).await?;
        let (is_text_or_doc, _) = crate::ForgeFS::is_binary_path(empty_file.path()).await?;
        assert!(is_text_or_doc, "Empty file should be considered text");

        Ok(())
    }
}