No site selected

Import Media

Queue cloud-sync handover batches; the background worker consumes them.

About this section: Import MediaQueue cloud-sync handover bundles; the worker imports them in the background.

Purpose

Consume the upstream cloud-sync handover bundles from /mnt/s3-sync/assets/sync/ into the global library (asset, asset_gallery, asset_collection) per Decision 639. The CGI never imports inline; it queues batches into import_job for the background worker (Decision 640).

Flow

  • Scan: walks the mount for .ready sentinels and records new ingest_batch rows.
  • Pre-flight: validates the manifest shape, sentinel pair, and per-item decisions; no library writes.
  • Queue: auto-preflights if needed, then inserts an import_job row. Live progress shows on Import Tasks.
  • Bulk queue: select multiple batches and queue them in one click; the worker drains the queue in priority + FIFO order.

Example

Scan finds 37e428d2-a4f6-4a31-bacf-0c835fb24084.ready. Tick its checkbox (or several at once), click Queue selected. The worker claims the next queued job within seconds and writes job_* events as it progresses.

Consequences

  • The library is site-agnostic. Sites surface library content via asset_site_membership, gallery/collection membership, content allowlists, and access packages.
  • Cancel is allowed only while a job is queued; once running, only systemctl stop gpu-admin-panel-import-worker stops it.
  • Action remove only soft-deletes rows via deleted_at; nothing is hard-deleted.
  • Frosted, blurred, and vivid_blurred derivatives are intentionally not persisted.
Deeper dive

Sourced from gpu-floor/MIGRATION-INTRO.md and the queued-worker decision in DECISION_LOG.md.

Worker contract

A long-running import-worker.pl daemon claims queued jobs using SELECT ... FOR UPDATE SKIP LOCKED (MariaDB 10.6+). It updates heartbeat_at every few items; if a worker dies, its running jobs are reset to queued after the configured stale threshold (default 15 minutes).

Sentinel protocol

A bundle is consumable only when both <sync_id>.json and <sync_id>.ready exist and the JSON has status = completed and verification.passed = true. The importer recomputes manifest_sha256 at consumption time so audit history records what we actually ingested.

Source-of-truth mapping

  • source/source.json -> asset_source (cold provenance).
  • source/data/photo.manifest.json + photo.analysis.json + photo.meaning.json + photo.family.json -> asset_ai_run rows with explicit analysis_kind; unknown filenames are captured with source_data:<file>.
  • Bundled cover_hash -> asset_cover linked to the parent asset.
  • Containers of type gallery -> asset_gallery + asset_gallery_item junctions.

Idempotency + integrity

Every multi-row write is wrapped in AdminDB::transaction. Hash-based UNIQUE keys make re-running the same batch a no-op. Per-item failures isolate to ingest_batch_item.decision = failed with last_error; the batch becomes partial when any siblings still succeeded.

Read-only mount + ledger boundary

The /mnt/s3-sync mount is read-only by configuration. The importer never writes to upstream or deletes .ready sentinels; all consumer state lives in ingest_batch / ingest_batch_item / ingest_event / import_job in admin_panel.

Batch bc02ed61-9d39-4538-952a-ed4ced680e96

Status: imported; action: update

Type

asset

Action

update

Status

imported

Items

4

Bytes

583.4 MB

Source host

flux-1

Manifest sha256

68e352fc3e27524e...

Started

2026-04-30 00:32:18

Completed

2026-04-30 00:32:25

Manifest summary

  • created_at: 2026-04-12T21:19:43Z
  • completed_at: 2026-04-12T21:22:15Z
  • verification.passed: true
  • verification.total_files: 551
  • prior_sync_id: none

Job history 1

Newest first. The active job (if any) is at the top.

idstaterequested_byqueuedclaimedworkerprogressfinishedlast_error
2doneadmin2026-04-30 00:32:192026-04-30 00:32:22vultr/pid:3550594/42026-04-30 00:32:25-

Items 4

Per-item decisions captured by preflight and updated by import. Re-running pre-flight is safe.

asset_typeasset_hashcover_hashdecisiontarget idslast_errorupdated_at
video212a6c7a8629cd00...-createdasset:66-2026-04-30 00:32:23
cover707a5b21ccd44b54...-createdasset:66-2026-04-30 00:32:24
video66c6d940e2db2715...-createdasset:67-2026-04-30 00:32:24
coverefcfc61a54e141eb...-createdasset:67-2026-04-30 00:32:25

Recent events

Append-only audit ledger for this batch (latest 50).

event_typepayloadcreated_at
job_finished{"error":null,"job_id":"2","phase":"job_finish","summary":{"items_seen":4,"ok":1,"status":"imported","summary":{"ok":1,"remaining":0,"status":"imported","tally":{"created":4,"deferred":0,"failed":0,"l...2026-04-30 00:32:25
import_finished{"phase":"import","remaining":0,"skipped_terminal":0,"status":"imported","tally":{"created":4,"deferred":0,"failed":0,"linked":0,"skipped":0,"updated":0}}2026-04-30 00:32:25
item_created{"cover_hash":null,"error":null,"hash":"efcfc61a54e141eba32b3497937c7385","phase":"item_import","target_ids":{"asset_id":"67","cover_hash":"efcfc61a54e141eba32b3497937c7385","parent_hash":"66c6d940e2d...2026-04-30 00:32:25
item_created{"cover_hash":null,"error":null,"hash":"66c6d940e2db27158de3a5b6202d7470","phase":"item_import","target_ids":{"asset_id":"67"},"type":"video"}2026-04-30 00:32:24
item_created{"cover_hash":null,"error":null,"hash":"707a5b21ccd44b548145b286df183baa","phase":"item_import","target_ids":{"asset_id":"66","cover_hash":"707a5b21ccd44b548145b286df183baa","parent_hash":"212a6c7a862...2026-04-30 00:32:24
item_created{"cover_hash":null,"error":null,"hash":"212a6c7a8629cd00baae3940aa717e6c","phase":"item_import","target_ids":{"asset_id":"66"},"type":"video"}2026-04-30 00:32:23
import_started{"action":"update","already_terminal_count":0,"container_count":1,"item_count":4,"manifest_sha256":"68e352fc3e27524e81e734634007a85225a09ac58ce50f7b1580258e8327ed6c","max_items":0,"phase":"import"}2026-04-30 00:32:23
job_claimed{"job_id":"2","phase":"job_claim","priority":100,"worker_id":"vultr/pid:355059"}2026-04-30 00:32:22
job_queued{"auto_preflighted":1,"items_total":"4","job_id":2,"phase":"job_queue","requested_by":"admin"}2026-04-30 00:32:19
preflight_finished{"counts":{"will_create":4,"will_fail":0,"will_link":0,"will_skip":0,"will_update":0},"failure_samples":[],"item_count":4,"item_type_counts":{"cover":2,"video":2},"ok":1,"phase":"preflight"}2026-04-30 00:32:19
preflight_started{"action":"update","container_count":1,"item_count":4,"item_type_counts":{"cover":2,"video":2},"manifest_sha256":"68e352fc3e27524e81e734634007a85225a09ac58ce50f7b1580258e8327ed6c","phase":"preflight",...2026-04-30 00:32:18
scan{"source": "filesystem", "manifest_sha256": "68e352fc3e27524e81e734634007a85225a09ac58ce50f7b1580258e8327ed6c", "sync_type": "asset", "action": "update", "item_count": "4", "total_bytes": "611706911",...2026-04-30 00:29:20