Snacks v2.2.3
Automated Video Library Encoder
Patch release adding first-class Uploading and Downloading work item statuses, multi-master conflict detection, automatic node reset with exponential backoff, a complete "Clear History" reset path, and several dispatch and recovery reliability fixes.
New Features
Uploading & Downloading Work Item Statuses
UploadingandDownloadingadded toWorkItemStatusenum -- work items now carry their own transfer status instead of overloadingProcessingwith aRemoteJobPhasehint. The server returns these statuses in queue and count endpoints, and the frontend renders them with dedicated badge styles and pulsing animations.- Frontend no longer relies on ephemeral SignalR state for transfer items -- previously, uploading/downloading items were only tracked client-side via SignalR events and had to be manually preserved across page refreshes. The server now includes them in the processing group, eliminating the ephemeral item bookkeeping entirely.
- Progress labels simplified -- transfer progress bars now display a clean percentage instead of prefixing "Uploading" or "Downloading" in the label text, since the status badge already communicates the phase.
Multi-Master Conflict Detection
- Handshake rejects second master --
RegisterOrUpdateNodenow returns a rejection tuple when a second master attempts to join the cluster. The handshake endpoint returns409 Conflictand the discovery listener ignores master announcements when a master already exists. - Periodic master tiebreak -- the heartbeat loop detects duplicate masters at runtime using a deterministic NodeId tiebreak. The losing master disables its dispatch timer and pushes a
ClusterWarningto the UI.
Automatic Node Reset with Dispatch Cooldown
- Exponential backoff on dispatch failure -- consecutive dispatch failures to a node apply an exponential cooldown (5s, 10s, 20s, ... up to 60s). After 5 consecutive failures the node is automatically reset.
ResetNodeAsynccancels all in-flight jobs -- sends cancel and file cleanup to the worker, clears all master-side tracking dictionaries, requeues affected work items, and applies a 30-second post-reset cooldown. Exposed viaPOST /api/cluster/nodes/{nodeId}/reset.- Per-node dispatch locks -- a
SemaphoreSlimper node serializes dispatch state changes, preventing double-dispatch races between the dispatch timer and concurrent upload completions.
Full Clear History Reset
ClearHistoryAsyncis now a coordinated multi-step reset -- cancels any active scan, acquires the scan lock, clears all remote cluster state (cancels transfers, notifies workers, purges WAL), kills any active local FFmpeg process, clears the in-memory queue, resets all database statuses, and notifies the UI with aHistoryClearedevent.- Scan cancellation support --
AutoScanServicenow holds aCancellationTokenSourcethat is cancelled on clear history, propagated through ffprobe calls and file enumeration.FfprobeService.ProbeAsyncaccepts an optionalCancellationTokenand kills the ffprobe process on cancellation.
Bug Fixes
Dispatch Reliability
- Stale
RemoteWorkItemIdno longer causes upload resume to wrong node -- before reusing a previous job ID for upload resume, the master now verifies the worker node still has partial data. If the node returns zero bytes, the stale ID is cleared from the database and a fresh upload begins. - Active uploads and dispatch tasks excluded from idle detection -- the heartbeat idle-detection logic now skips nodes with an in-progress
_activeUploadsentry or a pending dispatch task, preventing premature requeue during the upload-to-encode transition window. - Node status updates are now locked --
UpdateNodeStatuswraps all field assignments in alock(node)to prevent torn reads from concurrent heartbeat and dispatch threads. - Dispatch task tracking replaces fire-and-forget --
DispatchToNodeAsynctasks are tracked in_activeDispatchTasksso node availability checks can exclude nodes mid-dispatch, and completed tasks are cleaned up each dispatch cycle.
Download & Failure Handling
- Node failure skips active downloads --
HandleNodeFailureAsyncnow returns early if the job has an active download in progress, letting the download complete or fail on its own rather than racing with requeue logic. - Cancel cleans up worker files --
CancelRemoteJobAsyncnow sends aDELETE /api/cluster/files/{jobId}in addition to the job cancel, preventing orphaned partial files on worker nodes. - Retry counter cleanup on cancel and failure -- both
CancelRemoteJobAsyncandHandleNodeFailureAsyncnow clear all retry-related keys (_downloadRetryCounts, idle grace, validation) for the job.
Recovery Path
- Recovery creates CancellationTokenSources -- recovered encoding and download jobs now get their own
CancellationTokenSourceregistered in_jobCts, so they can be properly cancelled instead of running unsupervised. - Recovery uploads set
Uploadingstatus -- re-dispatched recovery uploads now setWorkItemStatus.Uploadingand transition the node toBusyafter upload completes, matching the normal dispatch path. - Node transitions to
Busyafter upload -- both normal dispatch and recovery now callUpdateNodeStatus(Busy)when the upload completes and encoding begins, rather than leaving the node inUploadingstate.
Queue Restore on Startup
- Lazy probe on startup restore --
RestoreToQueueAsyncbuilds work items from persisted database fields without running ffprobe, dramatically speeding up startup with large queues. The probe is performed lazily when the item is picked up for local processing or cluster dispatch. AddFileAsyncacceptsCancellationToken-- the scan-initiated add path now propagates the scan cancellation token through to ffprobe.
Encoding
- Corrupt file header check widened -- the output file header validation now checks four zero bytes (
0x00000000) instead of two, reducing false positives on valid files that happen to start with two null bytes. - 4K skip threshold relaxed by 20% -- the "already target codec" 4K skip check now uses
fourKTarget * 1.2instead of an exact match, preventing unnecessary re-encodes of files slightly above the target bitrate. - Low-bitrate 4K H.264 uses proportional compression -- 4K H.264 files with bitrates below the 4K target now compress to 70% of their original bitrate (matching the non-4K behavior) instead of inflating to the full 4K target.
Frontend
- New CSS badge styles for
UploadingandDownloading-- blue and sky-blue badges with pulsing animation, consistent with the existing status badge pattern. - Cancel and stop buttons shown for transfer states -- items in
UploadingorDownloadingstatus now show the same cancel/stop controls asProcessingitems. - Error messages logged in WorkItemUpdated -- the SignalR
WorkItemUpdatedconsole log now includes the error message when present, aiding debugging.
Files Changed
New Files
build-and-export-dev.bat-- development build script
Modified Files
Snacks/Models/WorkItem.cs-- addedUploadingandDownloadingtoWorkItemStatusenumSnacks/Controllers/ClusterController.cs-- handshake rejection, node reset endpoint, corrupt header check, upload status fixSnacks/Controllers/HomeController.cs-- version bump, processing filter includes new statusesSnacks/Data/MediaFileRepository.cs--ClearRemoteWorkItemIdAsync,ClearAllTransitionsAsyncSnacks/Services/AutoScanService.cs-- coordinated clear history, scan cancellation, lazy restore pathSnacks/Services/ClusterDiscoveryService.cs-- version bump, multi-master rejection in discovery and handshake,RegisterOrUpdateNodereturns rejection tupleSnacks/Services/ClusterFileTransferService.cs-- upload setsWorkItemStatus.UploadingSnacks/Services/ClusterService.cs-- dispatch cooldown/reset, per-node locks, dispatch task tracking, locked node status updates, full remote state clear, master conflict detection, recovery CTS creation, download/failure cleanupSnacks/Services/FfprobeService.cs--CancellationTokensupport with process killSnacks/Services/TranscodingService.cs--RestoreToQueueAsync,ClearAllInMemoryState, lazy probe, 4K bitrate logic, cancellation token propagation, status filter updatesSnacks/wwwroot/css/site.css-- uploading/downloading badge stylesSnacks/wwwroot/js/transcoding.js-- status enum mapping, ephemeral item removal, progress label simplification, transfer status rendering.gitignore-- addeddata/directoryREADME.md-- version bumpelectron-app/package.json-- version bumpelectron-app/package-lock.json-- version bump
Full documentation: README.md