mimedb
mimedb reads the freedesktop shared-mime-info database from the same
files the desktop reads, and decides a file's MIME type by GLib's rules. The
answer agrees with gio info and the file manager. It is a plain Rust
library with no Bus verbs, and its only dependency is libc.
Two components use it:
- Mix, through the
mime_type()builtin (behind themixcrate'smimefeature; see Mix I/O). - thumbs, which types a file before choosing a thumbnailer for it.
The source is
libs/mimedb.
The database
Each mime directory under $XDG_DATA_HOME (default ~/.local/share) and
$XDG_DATA_DIRS (default /usr/local/share:/usr/share) can hold globs2
(or the older globs), magic, aliases and subclasses. Directories are
read highest precedence first, and relative entries are ignored, as the XDG
spec requires. A higher directory's __NOGLOBS__ and __NOMAGIC__ entries
clear a lower one's rules for that type. Missing directories and files are
skipped. A file over 16 MiB (MAX_FILE) is skipped, and so is anything that
is not a regular file once opened: a FIFO in a database directory is never
waited on.
How a type is decided
- A non-regular file is its
inode/*type (inode/directory,inode/fifoand so on), and an empty file isapplication/x-zerosize. - If exactly one glob matches the name, that is the answer.
- Otherwise the leading bytes are sniffed: the highest-priority magic
match, or
text/plainwhen the content looks like text. - With no glob match, the sniffed type is the answer, or
application/octet-stream(uncertain) if nothing was sniffed. With several glob matches, a sniffed type of priority 80 or more wins; failing that, the first glob type that is a subclass of the sniffed type wins; failing that, the first glob type wins, marked uncertain.
Globs are matched in GLib's order: a literal name, then the longest
*.suffix, then full patterns. At most SNIFF_MAX (16384) bytes are read,
GLib's limit, and fewer when the magic rules reach less far.
One known limit: full patterns use the crate's own fnmatch, not glibc's.
POSIX named classes use Unicode's predicates instead of a locale's tables,
and collating symbols cannot end a range. The stock database uses neither.
API
Database::load()loads the desktop's database (xdg_mime_dirs());Database::load_from(&dirs)loads givenmimedirectories, highest precedence first.for_path(path, content) -> PathGuesstypes a file, following symlinks. Withcontentfalse the file is never opened. A missing path is typed by its name (exists: false); an unreadable one by its name, uncertain.guess(name, data) -> Guessdecides from an optional name and optional leading bytes, for a caller that already has the file open.Guess { mime, by, uncertain }, wherebyis aBy:Stat,Glob,Magic,Content(an empty file or the text check) orFallback.By::as_str()gives the lower-case name.globs(name),sniff(data),unalias(mime),is_subclass(mime, base)andparents(mime). Parents come nearest first and include the spec's implicit ones:text/plainfortext/*types andapplication/octet-streamfor everything butinode/*. Cycles insubclassesare visited once.is_stale()reports whether any directory or database file has changed (timestamp or size) since loading, so a long-lived caller can reload.sniff_len()is how many bytes to read before callingguess.text_fallback(data)andlooks_like_text(data)are the two text checks.
Example
thumbs reads the start of a file it already has open and asks for its type:
let db = mimedb::Database::load();
let mut data = Vec::new();
source.take(db.sniff_len() as u64).read_to_end(&mut data)?;
let mime = db.guess(Some("photo.jpg"), Some(&data)).mime;
From Mix, the same answer comes from mime_type(), which keeps one
database per thread and reloads it when is_stale() says so or the XDG
directories change:
$r = mime_type("/etc/hosts")
print($r.mime)
Source and tests
src/lib.rs: the database, loading and layering, the decision rules, and the subclass graph.src/glob.rs: parsingglobs2/globsand the desktop's match order, includingfnmatch.src/magic.rs: parsing and matching the binarymagicfile.
Unit tests in each module build private mime directories in a temporary
directory, so they never depend on the host's /usr/share/mime. They cover
layer clearing, staleness after a same-size rewrite, oversized files,
priority-zero magic, cycles and very wide parent graphs. The mix crate's
tests/mime_type.rs
tests the builtin against a private database the same way.