auto-ytdlp 1.1.2

Download videos with yt-dlp automatically. You can even download multiple videos at the same time!
# src/ Architecture

## Threading Model

- **Main thread**: Runs TUI event loop (100ms tick rate)
- **AppState message processor**: Background thread spawned on `AppState::new()` - processes all state mutations via channel
- **Download controller**: Spawned when downloads start, manages N worker threads
- **Worker threads**: One per concurrent download, check shutdown flags to handle termination

## Core Files

### app_state.rs
Thread-safe state manager using message passing. All mutations go through `StateMessage` enum sent via `state.send()`. Never access internal mutexes directly from outside - use the public API methods.

Key pattern:
```rust
state.send(StateMessage::SetPaused(true))?;  // Async mutation
let is_paused = state.is_paused()?;          // Sync read
```

The `UiSnapshot` struct captures all UI state in one lock acquisition per frame.

### errors.rs
Uses `thiserror`. The `Result<T>` type alias wraps `AppError`. Mutex poison errors convert to `AppError::Lock`.

### args.rs
CLI argument parsing with `clap`. Defines `-c` (concurrent), `-d` (download dir), `-f` (archive file), `--auto` (no TUI).

## Data Flow

1. URLs loaded from `links.txt``StateMessage::LoadLinks` → queue
2. Worker pops URL with `state.pop_queue()` → spawns yt-dlp subprocess
3. Progress updates sent via `StateMessage::UpdateDownloadProgress`
4. On completion, URL removed from `links.txt` and `StateMessage::IncrementCompleted` sent