shared_mime/mimedb/
mod.rs

1//! The [MimeDB] type for file type lookup.
2use std::{cmp::Ordering, collections::HashMap};
3
4mod build;
5mod query;
6
7use crate::{
8    fnmatch::FileMatcher,
9    search_queue::SearchQueue,
10    strcache::{CachedString, StringCache},
11};
12
13/// Hold MIME data and facilitate  file type guessing.
14pub struct MimeDB {
15    names: StringCache,
16    type_info: HashMap<CachedString, TypeInfo>,
17    sequence: i32,
18    globs: Vec<GlobRule>,
19}
20
21#[derive(Debug, Clone, Default)]
22struct TypeInfo {
23    description: Option<String>,
24    aliases: Vec<CachedString>,
25    parents: Vec<CachedString>,
26}
27
28#[derive(Debug, Clone)]
29struct GlobRule {
30    matcher: FileMatcher,
31    sequence: i32,
32    weight: i32,
33    mimetype: String,
34}
35
36impl MimeDB {
37    /// construct a new, empty MIME database.
38    pub fn new() -> MimeDB {
39        MimeDB {
40            names: StringCache::new(),
41            type_info: HashMap::new(),
42            sequence: 0,
43            globs: Vec::new(),
44        }
45    }
46
47    /// Get the number of known types.
48    pub fn type_count(&self) -> usize {
49        self.type_info.len()
50    }
51
52    /// Get the number of globs.
53    pub fn glob_count(&self) -> usize {
54        self.globs.len()
55    }
56
57    /// Query whether one type is a subtype of another.
58    pub fn is_subtype(&self, typ: &str, sup: &str) -> bool {
59        // everything is an octet stream
60        if sup == "application/octet-stream" && !typ.starts_with("inode/") {
61            return true;
62        }
63        let mut queue: SearchQueue<CachedString> = SearchQueue::new();
64        queue.maybe_add(self.names.cache(typ));
65        while let Some(q) = queue.get() {
66            if q == sup {
67                return true;
68            } else if sup == "text/plain" && q.starts_with("text/") {
69                return true;
70            }
71            if let Some(info) = self.type_info.get(&q) {
72                for pt in info.parents.iter() {
73                    queue.maybe_add(pt.clone());
74                }
75            }
76        }
77        false
78    }
79
80    /// Get the description of a string.
81    pub fn description(&self, typ: &str) -> Option<&str> {
82        self.type_info
83            .get(typ)
84            .map(|ti| ti.description.as_ref())
85            .flatten()
86            .map(|s| s.as_str())
87    }
88
89    /// Get the aliases of a type.
90    pub fn aliases(&self, typ: &str) -> Vec<&str> {
91        if let Some(ti) = self.type_info.get(typ) {
92            ti.aliases.iter().map(|cs| cs.as_ref()).collect()
93        } else {
94            Vec::new()
95        }
96    }
97
98    /// Get the parents of a type.
99    pub fn parents(&self, typ: &str) -> Vec<&str> {
100        if let Some(ti) = self.type_info.get(typ) {
101            ti.parents.iter().map(|cs| cs.as_ref()).collect()
102        } else {
103            Vec::new()
104        }
105    }
106
107    /// Get all known supertypes of the specified type (including itself).
108    ///
109    /// Types are in discovery order, so closer supertypes are at the beginning of the list.
110    pub fn supertypes(&self, typ: &str) -> Vec<CachedString> {
111        let mut types = Vec::new();
112        let mut queue: SearchQueue<CachedString> = SearchQueue::new();
113        let mut is_text = false;
114
115        // start the queue with the search type
116        queue.maybe_add(self.names.cache(typ));
117
118        // pump until all types are done
119        while let Some(qt) = queue.get() {
120            // this is a supertype
121            types.push(qt.clone());
122
123            // is this a text type?
124            if !is_text && qt.starts_with("text/") {
125                is_text = true;
126            }
127
128            if let Some(info) = self.type_info.get(qt.as_ref()) {
129                for st in info.parents.iter() {
130                    queue.maybe_add(st.clone());
131                }
132            }
133        }
134
135        // add default parent relationships
136        if is_text && !queue.saw("text/plain") {
137            types.push(self.names.cache("text/plain"));
138        }
139        if !typ.starts_with("inode/") && !queue.saw("application/octet-stream") {
140            types.push(self.names.cache("application/octet-stream"));
141        }
142
143        types
144    }
145
146    /// Order two types, where a type is less than its supertypes
147    pub fn compare_types(&self, a: &str, b: &str) -> Ordering {
148        if self.is_subtype(a, b) {
149            Ordering::Less
150        } else if self.is_subtype(b, a) {
151            Ordering::Greater
152        } else {
153            Ordering::Equal
154        }
155    }
156}