shared_mime/mimedb/
query.rs1use std::ffi::OsStr;
3use std::fs::Metadata;
4#[cfg(unix)]
5use std::os::unix::fs::FileTypeExt;
6use std::os::unix::fs::MetadataExt;
7
8use log::*;
9
10use crate::{query::FileQuery, Answer, QueryError};
11
12use super::MimeDB;
13
14enum MetaAnswer {
15 Inode(&'static str),
16 File(u64),
17}
18
19fn type_for_meta(meta: &Metadata) -> MetaAnswer {
20 let ft = meta.file_type();
21 if ft.is_dir() {
22 return MetaAnswer::Inode("inode/directory");
23 } else if ft.is_symlink() {
24 return MetaAnswer::Inode("inode/symlink");
25 }
26
27 #[cfg(unix)]
28 if ft.is_block_device() {
29 return MetaAnswer::Inode("inode/blockdevice");
30 } else if ft.is_char_device() {
31 return MetaAnswer::Inode("inode/chardevice");
32 } else if ft.is_fifo() {
33 return MetaAnswer::Inode("inode/fifo");
34 } else if ft.is_socket() {
35 return MetaAnswer::Inode("inode/socket");
36 }
37
38 MetaAnswer::File(meta.size())
39}
40
41impl MimeDB {
42 pub fn query(&self, query: &FileQuery<'_>) -> Result<Answer<'_>, QueryError> {
44 let dbg_name = if let Some(name) = query.filename {
45 name.to_string_lossy()
46 } else {
47 "⟨unnamed⟩".into()
48 };
49 let size = if let Some(meta) = &query.metadata {
51 debug!("{}: looking up with metadata", dbg_name);
52 match type_for_meta(meta) {
53 MetaAnswer::Inode(tstr) => return Ok(Answer::definite(tstr)),
55 MetaAnswer::File(size) => Some(size),
56 }
57 } else {
58 None
59 };
60
61 let mut ans = Answer::unknown();
63 if let Some(name) = query.filename {
64 debug!("{}: looking up with file name", dbg_name);
65 ans = self.query_filename(name);
66 }
67
68 if ans.is_unknown() && size == Some(0) {
69 ans = Answer::definite("application/x-zerosize")
70 }
71
72 if ans.is_unknown() {
74 ans = Answer::definite("application/octet-stream")
75 }
76
77 Ok(ans)
78 }
79
80 pub fn query_meta(&self, meta: &Metadata) -> Answer<'_> {
84 match type_for_meta(meta) {
85 MetaAnswer::Inode(mt) => Answer::definite(mt),
86 MetaAnswer::File(_) => Answer::definite("application/octet-stream"),
87 }
88 }
89
90 pub fn query_filename<S: AsRef<OsStr>>(&self, name: S) -> Answer<'_> {
92 let name = name.as_ref();
93 let display = name.to_string_lossy();
94 debug!("looking up filename {}", display);
95 let mut sw = None;
96 let mut matches = Vec::new();
97 let pbs = name.as_encoded_bytes();
98 for glob in self.globs.iter() {
99 if let Some((s, w)) = sw {
100 if s > glob.sequence || w > glob.weight {
101 break;
103 }
104 }
105 if glob.matcher.matches(pbs) {
106 sw = Some((glob.sequence, glob.weight));
107 matches.push(glob.mimetype.as_str());
108 }
109 }
110 let ambiguous = self.coalesce_fn_matches(&display, &mut matches);
111 Answer::new(matches, ambiguous)
112 }
113
114 fn coalesce_fn_matches(&self, name: &str, matches: &mut Vec<&str>) -> bool {
115 let mut ambiguous = matches.len() > 1;
116 if ambiguous {
119 debug!("{}: {} matches, sorting", name, matches.len());
122 matches.sort_by(|a, b| self.compare_types(a, b).reverse());
124 let root = matches[0];
125 ambiguous = !matches[1..].iter().all(|t| self.is_subtype(t, root));
126 if ambiguous {
127 debug!("{}: ambiguous match", name)
128 } else {
129 debug!("{}: best match {}", name, root)
130 }
131 }
132 ambiguous
133 }
134}