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