Adding an Archive Backend
Flux dispatches archive reads through the ArchiveBackend trait
(src/services/archive/backend.rs). Every supported format is one
implementation of that trait plus a small set of registrations scattered
across the archive service. Adding a format means touching these files in
order, missing any one is either a compile error or a format that lists
but never extracts (or vice versa).
Read src/services/archive/backends/zip.rs first if the new format is
seekable and single-file. Read backends/tar.rs first if the new format
is stream-oriented or needs an external decompressor. Read
backends/rar.rs first if the new format has to shell out to a CLI
tool.
Checklist
-
src/services/archive/backends/<fmt>.rs- new file implementingArchiveBackend. The trait requires:list_entries- returns immediate children ofprefix, withname,is_dir,size,mtime,inner_path. Route throughentry::collect_entryso synthesised directory nodes andchild_countare populated correctly.extract_entry_bytes- returns one entry's raw bytes.extract_dir- extracts a subtree to atempfile::TempDirand returns its root.
Override the provided
remove_entry,write_entry,create_directoryonly if the format supports in-place editing. The defaults returnArchiveError::Other(...), which is the correct behaviour for read-only formats. -
src/services/archive/backends/mod.rs- declare the module and re-export the struct:mod <fmt>; pub(in crate::services::archive) use self::<fmt>::<Fmt>Backend;Exports that other modules need (e.g.
extract_dir_<fmt>_atfor the fast path inextract_archive_to_dir) go here too. -
src/services/archive/backend.rs- two edits inget_backend:- Add an
else if name_lc.ends_with(".<ext>") { return Box::new(<Fmt>Backend); }arm. Order matters: the.tar.gzfamily must stay grouped because every one of them begins with.tar. - Add a MIME fallback in the
match mime.as_str() { ... }block for cases where the extension is absent or non-standard (e.g..cbzwhich is a ZIP in disguise, or MIME types that arrive from a network share).
- Add an
-
src/services/archive/format.rs- two functions:is_browsable_archive- add the extension to thematches_extensionlist so the grid treats the file as enterable.is_supported_archive- add the extension to theends_withchain so the CLI--previewandEnterArchiveaccept it.
If only the MIME type is supported (no extension), extend the
matches!onmime.as_str()inis_browsable_archiveinstead. -
src/utils/media/mime.rs- add the MIME type toguess_mime_from_extension's table if the extension is novel enough that GIO's content-type database doesn't already know it. Most common formats are already covered, skip this step for those. -
README.md- add a row to the supported-formats table (or the equivalent block indocs/development.mdif it lives there). -
po/pt_BR.po- any user-facing string the backend emits (ArchiveError::Other(...)messages that reach a toast). Group under# src/services/archive/backends/<fmt>.rs.
Where the reference code lives
backends/zip.rs- the fullest example: streaming read,by_index_rawfor raw copying, password handling, in-archive editing and the.flux-cache/thumbnail writer.backends/tar.rs- stream-oriented, one wrapper per decompressor (flate2,bzip2,xz2,zstd,lz4_flex). Use this when the format has no random access.backends/rar.rs- external CLI wrapper (unarorunrar). Use this when the format has no pure-Rust library.backends/sevenz.rs- pure-Rust random-access archive with AES256.backends/iso.rs- hybrid: native reader for ISO 9660,7zfallback for UDF. Use this if the format has more than one inner filesystem.backends/single.rs- the "one virtual entry" shape (.gz,.bz2,.xz,.zst,.lz4standalone). There is nothing to listlist_entriesreturns a single synthesised entry named after the stripped extension.backends/unsupported.rs- the fallback. Everyget_backendchain ends here, do not delete it.
Behaviour rules
- Never block the main thread. Backends run inside
relm4::spawn_blockingor a Rayon pool, but a backend that itself blocks on a subprocess is fine because it is already off the GTK thread. The rule is about the caller, not the backend. - Never panic on malformed input. Every backend is fuzzed
(
fuzz/fuzz_targets/fuzz_archive_*.rs). ReturnArchiveError::Otherfor truncated headers, corrupt central directories, out-of-range offsets, and any I/O error. - Reject path traversal. A
ZipFileentry named../../etc/passwdmust be rejected inextract_entry_bytes(returnOther("invalid path")) and silently skipped inextract_dir(astar.rsdoes withhas_traversal). The archive is untrusted input. - Return inner paths with forward slashes. Windows-built archives
use
\, normalise with.replace('\\', "/")before routing throughcollect_entry. - Preserve
is_dirfor synthesised nodes. An entry namedfoo/with size 0 is a directory. An entry namedfoo/with size 0 that has children is still a directory. Do not infer from size alone. - Forward the password argument, never cache it. The password lives
on
FluxApp::cached_archive_passwordand is forwarded by the caller. A backend that stores it in a field survives past a session and leaks it into the next archive that happens to use the same struct. - Respect
ArchiveError::PasswordRequiredvsWrongPassword. The UI uses these to decide whether to prompt or to show "incorrect password, try again". A genericOthermessage makes the encrypted archive prompt permanently stuck.
Verifying the result
Once wired up, the new format should:
- appear in the
--helpsupported list (is_supported_archive) - open with
EnterArchiveand--preview - list, navigate into a subfolder, and back out
- extract a single file (
Enteron a file, orSpacefor a preview) - extract the whole archive via the header button
- return
PasswordRequiredon an encrypted archive with no password, andWrongPasswordon a wrong password, if the format supports encryption - pass the corresponding fuzz target (
cargo fuzz run fuzz_archive_<fmt>if you add one, or reusefuzz_archive_treeby feeding it a sample).