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}