github derekshreds/Snacks v2.2.3
Snacks v2.2.3

9 hours ago

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

  • Uploading and Downloading added to WorkItemStatus enum -- work items now carry their own transfer status instead of overloading Processing with a RemoteJobPhase hint. 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 -- RegisterOrUpdateNode now returns a rejection tuple when a second master attempts to join the cluster. The handshake endpoint returns 409 Conflict and 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 ClusterWarning to 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.
  • ResetNodeAsync cancels 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 via POST /api/cluster/nodes/{nodeId}/reset.
  • Per-node dispatch locks -- a SemaphoreSlim per node serializes dispatch state changes, preventing double-dispatch races between the dispatch timer and concurrent upload completions.

Full Clear History Reset

  • ClearHistoryAsync is 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 a HistoryCleared event.
  • Scan cancellation support -- AutoScanService now holds a CancellationTokenSource that is cancelled on clear history, propagated through ffprobe calls and file enumeration. FfprobeService.ProbeAsync accepts an optional CancellationToken and kills the ffprobe process on cancellation.

Bug Fixes

Dispatch Reliability

  • Stale RemoteWorkItemId no 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 _activeUploads entry or a pending dispatch task, preventing premature requeue during the upload-to-encode transition window.
  • Node status updates are now locked -- UpdateNodeStatus wraps all field assignments in a lock(node) to prevent torn reads from concurrent heartbeat and dispatch threads.
  • Dispatch task tracking replaces fire-and-forget -- DispatchToNodeAsync tasks are tracked in _activeDispatchTasks so 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 -- HandleNodeFailureAsync now 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 -- CancelRemoteJobAsync now sends a DELETE /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 CancelRemoteJobAsync and HandleNodeFailureAsync now 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 CancellationTokenSource registered in _jobCts, so they can be properly cancelled instead of running unsupervised.
  • Recovery uploads set Uploading status -- re-dispatched recovery uploads now set WorkItemStatus.Uploading and transition the node to Busy after upload completes, matching the normal dispatch path.
  • Node transitions to Busy after upload -- both normal dispatch and recovery now call UpdateNodeStatus(Busy) when the upload completes and encoding begins, rather than leaving the node in Uploading state.

Queue Restore on Startup

  • Lazy probe on startup restore -- RestoreToQueueAsync builds 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.
  • AddFileAsync accepts CancellationToken -- 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.2 instead 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 Uploading and Downloading -- 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 Uploading or Downloading status now show the same cancel/stop controls as Processing items.
  • Error messages logged in WorkItemUpdated -- the SignalR WorkItemUpdated console 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 -- added Uploading and Downloading to WorkItemStatus enum
  • Snacks/Controllers/ClusterController.cs -- handshake rejection, node reset endpoint, corrupt header check, upload status fix
  • Snacks/Controllers/HomeController.cs -- version bump, processing filter includes new statuses
  • Snacks/Data/MediaFileRepository.cs -- ClearRemoteWorkItemIdAsync, ClearAllTransitionsAsync
  • Snacks/Services/AutoScanService.cs -- coordinated clear history, scan cancellation, lazy restore path
  • Snacks/Services/ClusterDiscoveryService.cs -- version bump, multi-master rejection in discovery and handshake, RegisterOrUpdateNode returns rejection tuple
  • Snacks/Services/ClusterFileTransferService.cs -- upload sets WorkItemStatus.Uploading
  • Snacks/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 cleanup
  • Snacks/Services/FfprobeService.cs -- CancellationToken support with process kill
  • Snacks/Services/TranscodingService.cs -- RestoreToQueueAsync, ClearAllInMemoryState, lazy probe, 4K bitrate logic, cancellation token propagation, status filter updates
  • Snacks/wwwroot/css/site.css -- uploading/downloading badge styles
  • Snacks/wwwroot/js/transcoding.js -- status enum mapping, ephemeral item removal, progress label simplification, transfer status rendering
  • .gitignore -- added data/ directory
  • README.md -- version bump
  • electron-app/package.json -- version bump
  • electron-app/package-lock.json -- version bump

Full documentation: README.md

Don't miss a new Snacks release

NewReleases is sending notifications on new releases.