calternal-plugin-files
Files API. All user paths are resolved below the authenticated Home before
they reach calternal-fs; its handle-relative operations enforce confinement.
A share or public link records a file or folder item ID, not authority over a pathname. The Index stores that ID with a device, inode, size, and modification token. Grant checks compare the current Home entry; a changed directory token is accepted only when its inode and saved parent token match. Files routes carry IDs through rename, move, Trash, and restore with durable intents. A new item at an old pathname gets a new ID, so an old grant cannot authorize it. A replacement upload preserves the old ID only after it checks the captured target ID and content hash.
A directory keeps its ID after a child write only when its own device and
inode match and its saved parent fingerprint is unchanged. The parent token
distinguishes that write from replacement evidence; FileFingerprint has
no birth time (#627).
The data directory is authoritative for bytes. On startup, journal replay runs before Files reconciles pending intents and scans each Home. The scan refreshes content hashes and fingerprints before requests run. Voice Memos repair is queued as bounded background work (#618, #621; DESIGN §40). Each repair attempt completes landed Files intents before scanning or removing a merged source, including after a same-process Note write failure. Trash and restore adopt all Note/Task projections under the existing Notes writer lock before their response or Files SSE hint (#623; DESIGN §40). Request timing exposes selected operations without User data (#549). Range fields are singular and textual so agent continuations cannot silently fall back to an unbounded full response (#760). Transfer and conditional request headers are declared at each OpenAPI route boundary (#815). The app-wide journal reuses committed Files feed rows (#668, DESIGN §59). Its adapter returns current indexed headers after the existing live Share check; legacy sync cursors and event payloads keep their original contract.
Source: crates/plugins/files/src/lib.rs
Modules
Section titled “Modules”| Module | Summary |
|---|---|
user_bytes |
Response headers for user bytes: every route that streams a file a user wrote (downloads, versions, thumbnails, public links, video) builds its headers here, so one rule decides what a browser may do with them. |
canvas_assets |
Files adapter for Canvas preparation (#989, DESIGN §60). |
Re-exports
Section titled “Re-exports”pub use media::MediaFailurepub use media::MediaHealthpub use media::media_healthpub use calternal_notes_core::attachment_folderpub use calternal_notes_core::web_clip_attachment_pathpub use media::media_sandbox_commandpub use media::run_media_outputpub use media::run_media_processpub use media::run_media_streampub use user_bytes::strip_unsafe_name_chars
Structs
Section titled “Structs”AgentFileUndoConflict
Section titled “AgentFileUndoConflict”pub struct AgentFileUndoConflictA file conflict found before any part of a turn is reversed.
Fields
pub path: String: Home-relative path involved in the conflict.pub reason: String: Short reason that can be shown in an undo report.
Implements: Clone, Debug, Eq, PartialEq
Source: crates/plugins/files/src/agent_undo.rs:18
CalendarShareRoot
Section titled “CalendarShareRoot”pub struct CalendarShareRootA currently valid incoming Share root for a Calendar projection.
Fields
pub owner_id: String: Home that owns the shared path.pub path: String: Home-relative path granted to the recipient.
Implements: Clone, Debug, Eq, PartialEq
Source: crates/plugins/files/src/lib.rs:208
DavFilesProvider
Section titled “DavFilesProvider”pub struct DavFilesProviderThe production DAV provider delegates all storage and metadata changes to the Files service. The HTTP layer supplies an identity only after auth checks the WebDAV protocol and its Home prefix.
Implements: Clone, FileDavProvider
DavFilesProvider::new
Section titled “DavFilesProvider::new”pub fn new(state: Arc<FilesState>) -> SelfNo doc comment.
Source: crates/plugins/files/src/dav.rs:67
FilesState
Section titled “FilesState”pub struct FilesStateNo doc comment.
Fields
pub root: Rootpub db: Db
Implements: Clone
FilesState::new
Section titled “FilesState::new”pub fn new(root: Root, db: Db) -> SelfNo doc comment.
FilesState::subscribe_change_feed_wakeups
Section titled “FilesState::subscribe_change_feed_wakeups”pub fn subscribe_change_feed_wakeups(&self) -> tokio::sync::broadcast::Receiver<()>Subscribe to the existing Files feed wake signal. The signal carries no data; consumers read the durable, User-scoped change feed after wakeup.
FilesState::set_share_change_hook
Section titled “FilesState::set_share_change_hook”pub fn set_share_change_hook(&self, hook: Arc<dyn Fn(String) + Send + Sync>)Tell derived projections that a recipient’s active Share roots changed. The Files Index remains the authority; the hook only requests a rebuild.
FilesState::notify_share_change
Section titled “FilesState::notify_share_change”pub fn notify_share_change(&self, recipient: String)Wake derived projections after committed grant or Group membership changes (#1028). This is a refresh signal; callers must still use the live grant resolver.
FilesState::known_item_ids_for_paths
Section titled “FilesState::known_item_ids_for_paths”pub async fn known_item_ids_for_paths( &self, paths: &[String], ) -> Result<HashMap<String, String>, String>Return known stable IDs for Home paths without creating Index records. Scrub reports use this lookup so links can follow renames while a read never turns a stale report path into a new identity.
FilesState::with_clock
Section titled “FilesState::with_clock”pub fn with_clock(mut self, clock: Arc<dyn calternal_db::Clock>) -> SelfReplace the time source for upload leases (tests use a fake clock).
FilesState::with_trusted_proxies
Section titled “FilesState::with_trusted_proxies”pub fn with_trusted_proxies(mut self, trusted_proxies: Vec<ipnet::IpNet>) -> SelfApply the same trusted proxy boundary used by authentication.
FilesState::publish_home_change
Section titled “FilesState::publish_home_change”pub async fn publish_home_change(&self, user: &str, path: &str) -> Result<(), String>Publish a validated Home-relative change so every Files subscriber can refresh its view.
FilesState::cleanup_expired_uploads
Section titled “FilesState::cleanup_expired_uploads”pub async fn cleanup_expired_uploads(&self) -> Result<(), String>Remove expired tus state and its reserved chunks after a scheduled job.
FilesState::restore_upload_reservations
Section titled “FilesState::restore_upload_reservations”pub async fn restore_upload_reservations(&self) -> Result<(), String>Restore durable upload reservations before the server accepts writes.
FilesState::read_indexed_item_for_user
Section titled “FilesState::read_indexed_item_for_user”pub async fn read_indexed_item_for_user( &self, principal: &PluginRequestContext, item_id: &str, ) -> Result<Option<ReadableFile>, String>Resolve and open a file by stable identity for its owner or an active incoming Share. The Index path is accepted only after the current inode and fingerprint match the stored item ID; a path reused by a new file therefore cannot inherit old access.
FilesState::reconcile_all
Section titled “FilesState::reconcile_all”pub async fn reconcile_all(&self) -> Result<(), String>Reconcile identity before serving, then queue Voice Memos repair. Recording moves must never delay startup (#618; DESIGN §40).
FilesState::adopt_change
Section titled “FilesState::adopt_change”pub async fn adopt_change( &self, path: &RelPath, before: Option<calternal_fs::FileFingerprint>, ) -> Result<(), String>Adopt a watcher-observed Home change after the writer has completed. The mutation lock serializes this with Files intents. Without a prior fingerprint, keep an existing path’s identity; a missing path is removed. Root events also refresh ancestor tokens from held write proofs; watcher hints never race that authority with a path-only parent scan (§54, #1034). A changed fingerprint always needs a fresh digest, even when its item identity is preserved, so DAV ETags describe the adopted bytes (#966).
FilesState::adopt_root_change
Section titled “FilesState::adopt_root_change”pub async fn adopt_root_change(&self, change: &calternal_fs::FsChange) -> Result<(), String>Consume a durable Root event with held-directory continuity evidence. Watcher events keep the conservative replacement rule (§54, #1034).
FilesState::adopt_if_missing
Section titled “FilesState::adopt_if_missing”pub async fn adopt_if_missing(&self, path: &RelPath) -> Result<(), String>A data-directory watcher adopts newly appeared items. Existing rows are left for the Root change bus or a full reconcile, which can distinguish an atomic server write from an unrelated replacement.
FilesState::generate_thumbnail
Section titled “FilesState::generate_thumbnail”pub async fn generate_thumbnail(&self, payload: serde_json::Value) -> Result<(), String>No doc comment.
FilesState::queue_thumbnail
Section titled “FilesState::queue_thumbnail”pub async fn queue_thumbnail(&self, owner: &str, path: &str, hash: &str) -> Result<(), String>Queue one thumbnail through the Files worker so plugins do not create a second image-processing pipeline. The request’s DB phase includes queue insertion and its bounded SQLite retries, not worker execution (#549).
Source: crates/plugins/files/src/lib.rs:611
ImageDimensions
Section titled “ImageDimensions”pub struct ImageDimensionsDimensions reported by a header-only metadata probe.
Fields
pub width: u32pub height: u32
Implements: Clone, Copy, Debug, Eq, PartialEq
Source: crates/plugins/files/src/media.rs:94
MediaSlotPermit
Section titled “MediaSlotPermit”pub struct MediaSlotPermitHold one process-wide media slot until its snapshot and decoder work end. The job queues have separate worker limits; this permit bounds media RAM and subprocess work across thumbnails and video transcodes (#501 / §39).
Source: crates/plugins/files/src/lib.rs:504
ReadableFile
Section titled “ReadableFile”pub struct ReadableFileA file opened after its current immutable identity and reader access pass the Files Index and Share checks.
Fields
pub owner_id: String: Home that owns the file.pub item_id: String: Stable Files identity. It survives a rename or move.pub hash: String: Current BLAKE3 content hash from the Files Index.pub mime_type: Option<String>: MIME type inferred when the current Index row was written.pub size: u64: Size read from the opened file handle.pub file: File: Confined, regular file handle.
Source: crates/plugins/files/src/lib.rs:226
FilesIndexReadError
Section titled “FilesIndexReadError”pub enum FilesIndexReadErrorWhy Calendar could not read the active Share roots from the Files Index.
Variants
Busy: SQLite reported transient writer or table lock contention.Failed: The Index query or a stored row failed for another reason.
Implements: Clone, Copy, Debug, PartialEq, Eq
Source: crates/plugins/files/src/lib.rs:217
MediaFormat
Section titled “MediaFormat”pub enum MediaFormatFormat accepted by a native decoder path. TextCard contains a server SVG;
all other formats are selected from file signatures.
Variants
JpegPngWebPHeifAvifPdfTextCardMovMp4MatroskaAviMpegOggAsfFlvMpegTs
Implements: Clone, Copy, Debug, Eq, PartialEq
MediaFormat::is_image
Section titled “MediaFormat::is_image”pub fn is_image(self) -> boolIs this a still-image format handled by vips?
MediaFormat::is_video
Section titled “MediaFormat::is_video”pub fn is_video(self) -> boolIs this a container accepted by the video playback worker?
MediaFormat::ffmpeg_demuxer
Section titled “MediaFormat::ffmpeg_demuxer”pub fn ffmpeg_demuxer(self) -> Option<&'static str>Fixed FFmpeg demuxer name. User input never selects a demuxer.
Source: crates/plugins/files/src/media.rs:44
PublicLinkPreview
Section titled “PublicLinkPreview”pub enum PublicLinkPreviewWhat a link unfurl (Slack, WhatsApp, iMessage) may say about /s/<slug>.
Unfurlers fetch the page HTML without a password or a session, so this is
what an anonymous visitor may learn BEFORE the link page loads. The rules
are stricter than info, never looser:
- A password-protected link is
Generic: the link page shows nothing before the password, so neither may the unfurl, not even whether it is a file or a folder. - A missing, expired, moved or unreadable link is also
Generic, so an unfurl is not an oracle for which slugs exist or have a password. - A name is given only when the link page itself would show it to anyone: no password, names not hidden, and a view or download permission. The owner and the content are never part of a preview.
Variants
GenericFile { name: Option<String> }Folder { name: Option<String> }
Implements: Clone, Debug, PartialEq, Eq
Source: crates/plugins/files/src/public.rs:997
ShareAccess
Section titled “ShareAccess”pub enum ShareAccessNo doc comment.
Variants
DeniedViewerEditor
Implements: Clone, Copy, Debug, Eq, PartialEq
Source: crates/plugins/files/src/shares.rs:83
Functions
Section titled “Functions”acceptable_slug
Section titled “acceptable_slug”pub fn acceptable_slug(slug: &str) -> boolThe rule for a new or changed slug. ASCII only, so a Unicode confusable
(Cyrillic а, full-width letters) can never imitate another link. Dashes
only between characters, never doubled, so a--b, -a and a- cannot
pass for a-b or a.
Source: crates/plugins/files/src/public.rs:315
acquire_large_media_slot
Section titled “acquire_large_media_slot”pub async fn acquire_large_media_slot() -> MediaSlotPermitAcquire the shared media slot for input above the 64 MiB memfd threshold. Keeping large copies out of the reserved lane prevents them from blocking small thumbnails while preserving the process-wide two-slot maximum.
Source: crates/plugins/files/src/lib.rs:565
acquire_media_slot
Section titled “acquire_media_slot”pub async fn acquire_media_slot() -> MediaSlotPermitAcquire a media slot for input at or below the 64 MiB memfd threshold. Small work uses the reserved slot first and can also fill the shared lane.
Source: crates/plugins/files/src/lib.rs:555
background_image_extension
Section titled “background_image_extension”pub fn background_image_extension(bytes: &[u8]) -> Option<&'static str>Return the safe filename extension for a supported background image. Signature checks reject renamed documents and active SVG content.
Source: crates/plugins/files/src/lib.rs:109
calendar_share_roots
Section titled “calendar_share_roots”pub async fn calendar_share_roots( state: &FilesState, recipient: &str,) -> Result<Vec<CalendarShareRoot>, FilesIndexReadError>Return active incoming Share roots after validating each immutable item ID. Calendar projections use these roots to filter derived Index rows without scanning a shared Home or trusting path-only legacy grants.
Source: crates/plugins/files/src/lib.rs:244
check_media_startup
Section titled “check_media_startup”pub async fn check_media_startup() -> MediaHealthDecode a fixed JPEG through the real wrapper at startup, with no Home access. A tool version probe cannot prove that actual decoding works (#988 / DESIGN §39). Startup continues on failure so Admin can diagnose it; the error is logged loudly.
Source: crates/plugins/files/src/media.rs:345
cleanup_user_deletion
Section titled “cleanup_user_deletion”pub async fn cleanup_user_deletion(state: &FilesState, user_id: &str) -> Result<(), String>Clean Files state after the Home leaves the live tree. Upload rows stay in SQLite until every chunk directory is removed, so a restart can retry a partial cleanup. Large Index tables are deleted in short transactions; this work never holds the instance mutation lock.
Source: crates/plugins/files/src/lib.rs:2404
create_photo_upload
Section titled “create_photo_upload”pub async fn create_photo_upload( state: Arc<FilesState>, principal: PluginRequestContext, headers: HeaderMap, body: Bytes,) -> ResponseStart a Photos-view upload through the existing Tus staging and install path. The Photos plugin supplies the route; Files remains the writer.
Source: crates/plugins/files/src/lib.rs:1277
dimensions_allowed
Section titled “dimensions_allowed”pub fn dimensions_allowed(dimensions: ImageDimensions) -> boolCheck decoder-reported dimensions without allowing multiplication overflow.
Source: crates/plugins/files/src/media.rs:227
ensure_unsplash_sidecar
Section titled “ensure_unsplash_sidecar”pub async fn ensure_unsplash_sidecar( state: &FilesState, owner: &str, path: &str, creator: &str, source_url: &str,) -> Result<(), String>Repair or create an Unsplash XMP Sidecar when a previous selection saved its image but failed before attribution finished (#422).
Source: crates/plugins/files/src/lib.rs:2268
is_background_image
Section titled “is_background_image”pub fn is_background_image(bytes: &[u8]) -> boolCheck the signature of a JPEG, PNG, GIF or WebP background image.
Source: crates/plugins/files/src/lib.rs:133
mark_transfer_destination_stale
Section titled “mark_transfer_destination_stale”pub async fn mark_transfer_destination_stale( state: &FilesState, target_user_id: &str,) -> Result<(), String>Mark the recipient Home root stale after a transfer. The normal folder reconciler discovers the new top-level directory when the recipient next browses it; its child folders are reconciled on demand.
Source: crates/plugins/files/src/lib.rs:2482
migrate_legacy_backgrounds
Section titled “migrate_legacy_backgrounds”pub async fn migrate_legacy_backgrounds(state: &FilesState, owner: &str) -> Result<(), String>Move each legacy hidden background file into Photos/Backgrounds/ while
keeping its Files item ID. Issue #422 requires per-file atomic moves: a
failed move leaves that source file in place, and rerunning this function
safely continues the migration. Empty legacy directories are removed last.
Source: crates/plugins/files/src/lib.rs:1292
migrations
Section titled “migrations”pub fn migrations() -> calternal_db::MigrationSetMigrations for the Files Index. The server owns migration application and includes this set beside the other core Plugin sets during startup.
Source: crates/plugins/files/src/lib.rs:2621
mime_matches_format
Section titled “mime_matches_format”pub fn mime_matches_format(mime: &str, format: MediaFormat) -> boolCheck the Index MIME hint against the signature-selected video container.
Source: crates/plugins/files/src/media.rs:199
parse_vips_dimensions
Section titled “parse_vips_dimensions”pub fn parse_vips_dimensions(output: &str) -> Option<ImageDimensions>Parse exactly one positive width/height pair from vipsheader -a output.
Source: crates/plugins/files/src/media.rs:236
photo_upload_options
Section titled “photo_upload_options”pub async fn photo_upload_options() -> ResponseAdvertise the same Tus extensions on the Photos creation endpoint.
Source: crates/plugins/files/src/lib.rs:2376
public_link_preview
Section titled “public_link_preview”pub async fn public_link_preview(state: &FilesState, slug: &str) -> PublicLinkPreviewThe safe link-unfurl preview for the public link page /s/<slug>; see
PublicLinkPreview for what it may and may not reveal.
Source: crates/plugins/files/src/lib.rs:2580
public_router
Section titled “public_router”pub fn public_router(state: Arc<FilesState>) -> RouterAnonymous public-link routes mounted by the server outside the files prefix.
Source: crates/plugins/files/src/lib.rs:2571
record_agent_data_move
Section titled “record_agent_data_move”pub async fn record_agent_data_move( state: &FilesState, owner: &str, old_path: &str, new_path: &str,) -> Result<(), String>Preserve the Files item ID when the Notes single writer completes a move.
Source: crates/plugins/files/src/lib.rs:391
record_agent_data_write
Section titled “record_agent_data_write”pub async fn record_agent_data_write( state: &FilesState, owner: &str, path: &str, before: Option<calternal_fs::FileFingerprint>,) -> Result<(), String>Index a validated Note, Canvas or Task write from its single writer (§60, #976), then tag its stable Files feed rows with the authenticated Agent turn.
Source: crates/plugins/files/src/lib.rs:335
record_note_write
Section titled “record_note_write”pub async fn record_note_write( state: &FilesState, owner: &str, path: &str, before: calternal_fs::FileFingerprint,) -> Result<(), String>Refresh the Files Index after the Notes plugin commits an atomic Note or Canvas write (DESIGN §60, #976). The Note writer validates scene content. Preserve the immutable item ID only when the indexed fingerprint matched the writer’s pre-write inode. A stale path gets a new item ID.
Source: crates/plugins/files/src/lib.rs:295
revoke_user_access_in
Section titled “revoke_user_access_in”pub async fn revoke_user_access_in( tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, user_id: &str,) -> Result<(), sqlx::Error>Revoke live Files grants in the same transaction that records a pending Home deletion. The Home moves out of the live tree before the request returns; bulk Index, Trash, and upload cleanup runs from the resumable deletion worker.
Source: crates/plugins/files/src/lib.rs:2384
save_unsplash_image
Section titled “save_unsplash_image”pub async fn save_unsplash_image( state: Arc<FilesState>, principal: PluginRequestContext, photo_id: &str, creator: &str, source_url: &str, bytes: Bytes,) -> Result<String, String>Store an Unsplash picture through Tus and the Files Index, then add its photographer credit to an adjacent XMP Sidecar (DESIGN §35, #422). The deterministic image name lets a retry reuse this file and return its ID. Heap-pin the install and Sidecar futures to bound the request worker stack; both remain awaited in order before returning the indexed identity (#422).
Source: crates/plugins/files/src/lib.rs:2228
shared_note_access
Section titled “shared_note_access”pub async fn shared_note_access( state: &FilesState, owner: &str, recipient: &str, path: &str,) -> ShareAccessCheck the current item-ID-bound Share for a Note path. Call on every live frame because a recipient can lose access while a socket stays open.
Source: crates/plugins/files/src/lib.rs:197
sniff_media
Section titled “sniff_media”pub fn sniff_media(bytes: &[u8]) -> Option<MediaFormat>Return one approved format from a bounded prefix, rejecting ambiguous BMFF brands.
Source: crates/plugins/files/src/media.rs:100
thumbnail_supported
Section titled “thumbnail_supported”pub fn thumbnail_supported(path: &str, mime: &str) -> boolWhether a path and the Index MIME type can produce a Files thumbnail. Calendar uses this to expose only previews that this worker can build.
Source: crates/plugins/files/src/lib.rs:100
undo_agent_turn
Section titled “undo_agent_turn”pub async fn undo_agent_turn( state: &FilesState, actor: &str, mutations: &[AgentTurnFileMutation],) -> Result<Vec<AgentFileUndoConflict>, String>Reverse a turn’s Files writes as one preflighted unit. Any later change to a touched path, missing version snapshot, or changed item returns conflicts before the filesystem or Files Index is modified.
Source: crates/plugins/files/src/agent_undo.rs:35
unsplash_background_path
Section titled “unsplash_background_path”pub fn unsplash_background_path(photo_id: &str, extension: &str) -> Result<RelPath, String>Build the visible Photos path for an Unsplash picture from a digest of its remote ID. A remote identifier must never become a Home path component.
Source: crates/plugins/files/src/lib.rs:139
upload_destination_from_headers
Section titled “upload_destination_from_headers”pub fn upload_destination_from_headers(headers: &HeaderMap) -> Result<String, StatusCode>Check the tus version and metadata with the same parsers as upload creation. A caller that does not need the source mtime can use the destination only.
Source: crates/plugins/files/src/uploads.rs:674
valid_new_password
Section titled “valid_new_password”pub fn valid_new_password(password: &str) -> boolThe byte limit on a new public link password, before Argon2 work.
Source: crates/plugins/files/src/public.rs:506
valid_slug
Section titled “valid_slug”pub fn valid_slug(slug: &str) -> boolThe lookup rule: any slug that could ever have been stored. Stored slugs are never re-validated, so a lookup accepts the older, looser shape.
Source: crates/plugins/files/src/public.rs:267
video_dimensions_allowed
Section titled “video_dimensions_allowed”pub fn video_dimensions_allowed(width: u32, height: u32) -> boolParse one FFprobe video stream geometry and enforce the playback cap.
Source: crates/plugins/files/src/media.rs:259
video_header_is_allowed
Section titled “video_header_is_allowed”pub async fn video_header_is_allowed(source: &File, format: MediaFormat) -> boolProbe video stream headers inside the media sandbox from an immutable snapshot. The launcher rejects an original file handle and checks the video byte cap before namespace setup (#501 / DESIGN §39).
Source: crates/plugins/files/src/media.rs:270
Constants
Section titled “Constants”BACKGROUND_IMAGE_MAX_BYTES
Section titled “BACKGROUND_IMAGE_MAX_BYTES”pub const BACKGROUND_IMAGE_MAX_BYTES: u64Maximum size of an image stored for use as a User background.
Source: crates/plugins/files/src/lib.rs:105
MAX_THUMBNAIL_BYTES
Section titled “MAX_THUMBNAIL_BYTES”pub const MAX_THUMBNAIL_BYTES: u64Maximum compressed image input copied to a sealed native thumbnail input.
Source: crates/plugins/files/src/media.rs:27
MAX_VIDEO_BYTES
Section titled “MAX_VIDEO_BYTES”pub const MAX_VIDEO_BYTES: u64Maximum compressed video input copied to a sealed native video input.
Source: crates/plugins/files/src/media.rs:33
VIDEO_CODEC_WHITELIST
Section titled “VIDEO_CODEC_WHITELIST”pub const VIDEO_CODEC_WHITELIST: &strCodecs needed by the video containers supported by Files and Video.