Skip to main content

qubit_mime/detector/
file_based_mime_detector.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! File-backed MIME detector helpers.
9
10use std::fmt::Debug;
11use std::io::Write;
12use std::path::Path;
13
14use qubit_local_files::{
15    FileWriteOptions,
16    LocalTempFile,
17};
18
19use crate::{
20    MimeDetectorCore,
21    MimeResult,
22    StreamBasedMimeDetector,
23};
24
25/// Core implementation contract for detectors that only inspect local files.
26pub trait FileBasedMimeDetector: Debug + Send + Sync {
27    /// Gets the shared detector core.
28    ///
29    /// # Returns
30    /// Shared detector configuration and merge/refinement behavior.
31    fn core(&self) -> &MimeDetectorCore;
32
33    /// Gets the maximum number of bytes needed for content inspection.
34    ///
35    /// # Returns
36    /// Content prefix length to stage for byte and reader inputs.
37    fn max_test_bytes(&self) -> usize;
38
39    /// Guesses MIME type names from a filename.
40    ///
41    /// # Parameters
42    /// - `filename`: File path or basename.
43    ///
44    /// # Returns
45    /// Candidate MIME type names ordered by backend confidence.
46    fn guess_from_filename(&self, filename: &str) -> Vec<String>;
47
48    /// Guesses MIME type names from one local file.
49    ///
50    /// # Parameters
51    /// - `file`: Local file path readable by the backend.
52    ///
53    /// # Returns
54    /// Candidate MIME type names ordered by backend confidence.
55    ///
56    /// # Errors
57    /// Returns an error when local-file inspection fails.
58    fn guess_from_local_file(&self, file: &Path) -> MimeResult<Vec<String>>;
59}
60
61impl<T> StreamBasedMimeDetector for T
62where
63    T: FileBasedMimeDetector,
64{
65    /// Gets the shared detector core.
66    fn core(&self) -> &MimeDetectorCore {
67        FileBasedMimeDetector::core(self)
68    }
69
70    /// Gets the maximum content prefix length needed by this detector.
71    fn max_test_bytes(&self) -> usize {
72        FileBasedMimeDetector::max_test_bytes(self)
73    }
74
75    /// Guesses MIME type names from filename rules.
76    fn guess_from_filename(&self, filename: &str) -> Vec<String> {
77        FileBasedMimeDetector::guess_from_filename(self, filename)
78    }
79
80    /// Stages content to a temporary local file before inspection.
81    fn guess_from_content_bytes(
82        &self,
83        content: &[u8],
84    ) -> MimeResult<Vec<String>> {
85        with_temp_file(content, |path| {
86            FileBasedMimeDetector::guess_from_local_file(self, path)
87        })
88    }
89
90    /// Delegates local-file inspection to the file-based hook.
91    fn guess_from_file_stream(
92        &self,
93        file: &Path,
94    ) -> MimeResult<(Vec<String>, Vec<u8>)> {
95        Ok((
96            FileBasedMimeDetector::guess_from_local_file(self, file)?,
97            Vec::new(),
98        ))
99    }
100}
101
102/// Stages content into a temporary file for file-based detectors.
103///
104/// # Parameters
105/// - `content`: Content bytes to stage.
106/// - `detect`: Callback receiving the temporary path.
107///
108/// # Returns
109/// The callback result.
110///
111/// # Errors
112/// Returns [`MimeError::Io`](crate::MimeError::Io) when the temporary file
113/// cannot be written.
114pub(crate) fn with_temp_file<T>(
115    content: &[u8],
116    detect: impl FnOnce(&Path) -> MimeResult<T>,
117) -> MimeResult<T> {
118    let mut file =
119        LocalTempFile::with_name(Some("MimeDetectorTemp-"), Some(".tmp"))?;
120    {
121        let handle = file.writer(FileWriteOptions::default().buffered())?;
122        handle.write_all(content)?;
123    }
124    file.close()?;
125    detect(file.path())
126}