Skip to content

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

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).
pub struct AgentFileUndoConflict

A 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

pub struct CalendarShareRoot

A 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

pub struct DavFilesProvider

The 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

pub fn new(state: Arc<FilesState>) -> Self

No doc comment.

Source: crates/plugins/files/src/dav.rs:67

pub struct FilesState

No doc comment.

Fields

  • pub root: Root
  • pub db: Db

Implements: Clone

pub fn new(root: Root, db: Db) -> Self

No doc comment.

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.

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.

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.

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.

pub fn with_clock(mut self, clock: Arc<dyn calternal_db::Clock>) -> Self

Replace the time source for upload leases (tests use a fake clock).

pub fn with_trusted_proxies(mut self, trusted_proxies: Vec<ipnet::IpNet>) -> Self

Apply the same trusted proxy boundary used by authentication.

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.

pub async fn cleanup_expired_uploads(&self) -> Result<(), String>

Remove expired tus state and its reserved chunks after a scheduled job.

pub async fn restore_upload_reservations(&self) -> Result<(), String>

Restore durable upload reservations before the server accepts writes.

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.

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).

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).

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).

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.

pub async fn generate_thumbnail(&self, payload: serde_json::Value) -> Result<(), String>

No doc comment.

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

pub struct ImageDimensions

Dimensions reported by a header-only metadata probe.

Fields

  • pub width: u32
  • pub height: u32

Implements: Clone, Copy, Debug, Eq, PartialEq

Source: crates/plugins/files/src/media.rs:94

pub struct MediaSlotPermit

Hold 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

pub struct ReadableFile

A 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

pub enum FilesIndexReadError

Why 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

pub enum MediaFormat

Format accepted by a native decoder path. TextCard contains a server SVG; all other formats are selected from file signatures.

Variants

  • Jpeg
  • Png
  • WebP
  • Heif
  • Avif
  • Pdf
  • TextCard
  • MovMp4
  • Matroska
  • Avi
  • Mpeg
  • Ogg
  • Asf
  • Flv
  • MpegTs

Implements: Clone, Copy, Debug, Eq, PartialEq

pub fn is_image(self) -> bool

Is this a still-image format handled by vips?

pub fn is_video(self) -> bool

Is this a container accepted by the video playback worker?

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

pub enum PublicLinkPreview

What 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

  • Generic
  • File { name: Option<String> }
  • Folder { name: Option<String> }

Implements: Clone, Debug, PartialEq, Eq

Source: crates/plugins/files/src/public.rs:997

pub enum ShareAccess

No doc comment.

Variants

  • Denied
  • Viewer
  • Editor

Implements: Clone, Copy, Debug, Eq, PartialEq

Source: crates/plugins/files/src/shares.rs:83

pub fn acceptable_slug(slug: &str) -> bool

The 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

pub async fn acquire_large_media_slot() -> MediaSlotPermit

Acquire 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

pub async fn acquire_media_slot() -> MediaSlotPermit

Acquire 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

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

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

pub async fn check_media_startup() -> MediaHealth

Decode 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

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

pub async fn create_photo_upload(
state: Arc<FilesState>,
principal: PluginRequestContext,
headers: HeaderMap,
body: Bytes,
) -> Response

Start 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

pub fn dimensions_allowed(dimensions: ImageDimensions) -> bool

Check decoder-reported dimensions without allowing multiplication overflow.

Source: crates/plugins/files/src/media.rs:227

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

pub fn is_background_image(bytes: &[u8]) -> bool

Check the signature of a JPEG, PNG, GIF or WebP background image.

Source: crates/plugins/files/src/lib.rs:133

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

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

pub fn migrations() -> calternal_db::MigrationSet

Migrations 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

pub fn mime_matches_format(mime: &str, format: MediaFormat) -> bool

Check the Index MIME hint against the signature-selected video container.

Source: crates/plugins/files/src/media.rs:199

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

pub async fn photo_upload_options() -> Response

Advertise the same Tus extensions on the Photos creation endpoint.

Source: crates/plugins/files/src/lib.rs:2376

pub async fn public_link_preview(state: &FilesState, slug: &str) -> PublicLinkPreview

The 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

pub fn public_router(state: Arc<FilesState>) -> Router

Anonymous public-link routes mounted by the server outside the files prefix.

Source: crates/plugins/files/src/lib.rs:2571

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

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

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

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

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

pub async fn shared_note_access(
state: &FilesState,
owner: &str,
recipient: &str,
path: &str,
) -> ShareAccess

Check 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

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

pub fn thumbnail_supported(path: &str, mime: &str) -> bool

Whether 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

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

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

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

pub fn valid_new_password(password: &str) -> bool

The byte limit on a new public link password, before Argon2 work.

Source: crates/plugins/files/src/public.rs:506

pub fn valid_slug(slug: &str) -> bool

The 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

pub fn video_dimensions_allowed(width: u32, height: u32) -> bool

Parse one FFprobe video stream geometry and enforce the playback cap.

Source: crates/plugins/files/src/media.rs:259

pub async fn video_header_is_allowed(source: &File, format: MediaFormat) -> bool

Probe 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

pub const BACKGROUND_IMAGE_MAX_BYTES: u64

Maximum size of an image stored for use as a User background.

Source: crates/plugins/files/src/lib.rs:105

pub const MAX_THUMBNAIL_BYTES: u64

Maximum compressed image input copied to a sealed native thumbnail input.

Source: crates/plugins/files/src/media.rs:27

pub const MAX_VIDEO_BYTES: u64

Maximum compressed video input copied to a sealed native video input.

Source: crates/plugins/files/src/media.rs:33

pub const VIDEO_CODEC_WHITELIST: &str

Codecs needed by the video containers supported by Files and Video.

Source: crates/plugins/files/src/media.rs:39