Skip to content
Open
72 changes: 28 additions & 44 deletions docs/IMPROVEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,60 @@

Track usability, DX, and architecture improvements. Focus: user-friendliness, easy setup, easy diagnosis.

Each entry below links to a dedicated page in [`docs/improvements/`](./improvements/) with full details.

---

## Gitignore
- DB file `data/arm.db` is not gitignored — should be added so test/seed data isn't committed.
- [gitignore.md](./improvements/gitignore.md) — DB file `data/arm.db` not gitignored

## Data / Persistence
- **Migrate from `DateTime` to `DateTimeOffset`** — All model timestamps (`Job.StartTime`, `Job.StopTime`, `Notification.Timestamp`, `DiscMetadata.CreatedAt`/`LastUsedAt`, etc.) use `DateTime`. EF Core reads these back as `DateTimeKind.Unspecified` from SQLite, losing the UTC context. `.ToLocalTime()` in views works but is a band-aid. Switching to `DateTimeOffset` stores the offset with the value and makes timezone intent explicit. Requires touching models, EF mappings, all view comparisons, and serialization.
- [data-persistence.md](./improvements/data-persistence.md) — Migrate from `DateTime` to `DateTimeOffset`

## Configuration & Setup
- **Add 4K UHD disc type with separate settings** — Currently `ArmSettings` and `ConfigSnapshot` only have `HbArgsDvd` / `HbPresetDvd` and `HbArgsBd` / `HbPresetBd`. 4K UHD discs need different handling (ffmpeg passthrough to preserve HDR, no HandBrake re-encode). Add `HbArgsUhd` / `HbPresetUhd` and `FfmpegPostFileArgsUhd` properties, a `DiscType.Uhd` enum value, and wire them through `HandBrakeService.GetHbSettings()` and the `Conductor` pipeline. For 4K UHD, the typical workflow is `USE_FFMPEG: true` with `-c:v copy -c:a ac3 -b:a 640k` to preserve HDR metadata losslessly. Users currently have to swap `arm.yaml` manually when switching between 1080p and 4K discs — automatic disc-type detection would eliminate this.
- No `appsettings.Production.json` or env-aware config profiles. Would help users separate sensitive keys (API keys) from path config.
- Seed data scripts exist in `scripts/` but require manual run. Consider auto-seeding on first launch for demo/testing.
- [configuration-4k-uhd.md](./improvements/configuration-4k-uhd.md) — Add 4K UHD disc type with separate settings
- [configuration-appsettings.md](./improvements/configuration-appsettings.md) — Environment-aware config profiles
- [configuration-seed-data.md](./improvements/configuration-seed-data.md) — Auto-seed data on first launch

## Disc Type Detection
- [disc-type-audiobook-mp3.md](./improvements/disc-type-audiobook-mp3.md) — MP3 audiobook / audio CD-ROM classification

## UI / User Experience
- **Restart from last successful stage** — add a "retry from failure" action that resumes the pipeline at the last failed stage instead of restarting from scratch. Requires each stage to checkpoint its completion state in the DB (e.g. a `Stages` table or bitfield on `Job`).
- [ui-restart-last-stage.md](./improvements/ui-restart-last-stage.md) — Restart from last successful stage

## SignalR
- `SignalRNotificationBroadcaster` is wired via `INotificationBroadcaster` interface. Works but the broadcaster is a singleton while the hub context is scoped per connection. Should verify no lifetime issues.
- [signalr.md](./improvements/signalr.md) — Broadcaster lifetime concerns

## Pages / Views
- **Redesign Identification section** — improve the layout of the Identification section on the Job detail page.
- **DVD/Blu-ray detection workflow** — the Settings page has a "Detect Disc" / "Scan Drives" button but the actual udev-based monitoring workflow from the original ARM isn't replicated. Should add a "Start Monitoring" action that runs the Conductor/IdentifyService loop.
- **Home dashboard** — core metrics displayed. Could add charts (job success rate over time, rips per day) or sparkline trends.
- Batch actions on Active Rips page (abandon all, retry all).
- [pages-identification.md](./improvements/pages-identification.md) — Redesign Identification section
- [pages-disc-detection.md](./improvements/pages-disc-detection.md) — DVD/Blu-ray detection workflow
- [pages-dashboard.md](./improvements/pages-dashboard.md) — Home dashboard enhancements
- [pages-batch-actions.md](./improvements/pages-batch-actions.md) — Batch actions on Active Rips page

## MusicBrainz
- **Investigate moving off XML where possible** — MusicBrainz XML parsing is fragile (manual XElement traversal, namespace handling). If MusicBrainz offers a JSON endpoint, prefer it.
- [musicbrainz.md](./improvements/musicbrainz.md) — Investigate moving off XML

## Dependency Injection
- WebUi now has full DI wiring. However many services are registered as `Scoped` when they're effectively stateless. `CliProcessRunner` is singleton. Review lifetime choices — some could be singletons or transient.
- [dependency-injection.md](./improvements/dependency-injection.md) — Review lifetime choices

## Startup & Recovery
- **Resume in-progress rips on restart** — currently, if the app is restarted while a job is ripping (VideoRipping, TranscodeActive, etc.), the background task is lost and the job stays stuck. On startup, scan for jobs in non-terminal states and resume them: re-attach the MakeMKV/HandBrake process if still running, or restart the rip/transcode stage from where it left off. Requires stage-level checkpointing (which stage completed, which files were produced) so the system can pick up without re-doing completed work.
- [startup-recovery.md](./improvements/startup-recovery.md) — Resume in-progress rips on restart

## Testing
- **Audio CD test:** Deferred — low priority. Needs abcde conf and audio disc in drive.
- **Data disc test:** Deferred — needs data disc for testing.
- **Error recovery tests:** Deferred — needs dirty/scratched discs for edge case testing.
- No CRC64 test with real DVD data (uses synthetic directory).
- No SignalR hub tests.
- [testing.md](./improvements/testing.md) — Deferred / missing tests

## Security
- `LogsController.Reader` uses `Path.GetFileName` for sanitization but could use `Path.GetFullPath` + prefix validation as defense-in-depth.
- [security.md](./improvements/security.md) — LogsController sanitization

## MCP (Model Context Protocol)
- ✅ **MCP server implemented** using the C# SDK (`ModelContextProtocol.AspNetCore` v1.4.1) with HTTP (Streamable HTTP) transport at `/mcp`.
- Exposed tools:
- `get_jobs` — list jobs with optional status filter, `offset`, and `limit` pagination.
- `get_logs` — read job log files with `offset` (line number) and `pageSize` for efficient browsing of long logs.
- `get_config` — returns current ARM Sharp configuration (API key presence is shown as booleans, values are never exposed).
- 🔲 Future tools: `update_config`, `eject_drive`, `trigger_identify`, log streaming via SSE.
- [mcp.md](./improvements/mcp.md) — Exposed tools & future tools

## Container / Deployment
- Docker image is ~2GB with full .NET SDK. Switch to self-contained publish with runtime-only image to reduce size.
- GitHub Actions CI has QEMU set up but only builds `linux/amd64`. Add `linux/arm64` multi-arch build once ARM64 runners or cross-compilation are available.
- **HandBrake nvdec support** — current `arm-dependencies:1.7.3` base image compiles HandBrake without `--enable-nvdec`. The devcontainer has a custom rebuild with nvdec working, but the production Dockerfile still uses the base image's build (no hw-decoding). Need to either fork and rebuild `arm-dependencies`, or add a multi-stage HandBrake build step to the production Dockerfile.
- Docker buildx warning — migrate from legacy builder to BuildKit.
- [container-deployment.md](./improvements/container-deployment.md) — Image size, multi-arch, nvdec, buildkit

## Disc Databases (Track Identification)
- **thediscdb.com integration** — Encrypted BDs often return 0 tracks from `makemkvcon info --robot`. thediscdb.com stores disc IDs mapped to known track layouts. Adding a lookup step would let us skip the expensive `makemkvcon info` scan for known discs and identify the correct main feature track without guessing by filesize. API is simple REST — define a `DiscDatabaseService` client, cache results locally, and plug into `IdentifyService`.
- [disc-databases.md](./improvements/disc-databases.md) — thediscdb.com integration

## Notifications (Low Priority)
Pushbullet, IFTTT, JSON webhook, and Bash script notifications are already implemented in `NotificationService.SendRemoteNotificationsAsync()`. Two additional channels remain:

### Pushover
- **API:** `POST https://api.pushover.net/1/messages.json` with `token` (app key), `user` (user key), `message`, `title`, `sound`, etc.
- **Config keys:** `PoUserKey` / `PO_USER_KEY` already exist in `ArmSettings` and `ConfigSnapshot`, mapped from YAML. Missing: a `PoAppToken` key for the application token.
- **Implementation:** ~20 lines in `NotificationService` — `SendPushoverAsync(client, appToken, userKey, title, body, ct)`.
- **Settings UI:** Apprise tab currently read-only; would need editable form fields.

### Apprise
- **CLI:** `apprise` is a command-line tool supporting 80+ notification services (Slack, Discord, Telegram, email, etc.). Original Python ARM invokes it via subprocess.
- **Config key:** `Apprise` / `APPRISE` already exist in `ArmSettings` and `ConfigSnapshot`.
- **Implementation:** ~30 lines in `NotificationService` — call `apprise -b "body" -t "title"` via `CliProcessRunner`.
- **Settings UI:** Same as Pushover — needs editable form on Apprise tab.
- [notifications-pushover.md](./improvements/notifications-pushover.md) — Pushover integration
- [notifications-apprise.md](./improvements/notifications-apprise.md) — Apprise integration
7 changes: 7 additions & 0 deletions docs/improvements/configuration-4k-uhd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Configuration & Setup — Add 4K UHD Disc Type with Separate Settings

Currently `ArmSettings` and `ConfigSnapshot` only have `HbArgsDvd` / `HbPresetDvd` and `HbArgsBd` / `HbPresetBd`. 4K UHD discs need different handling (ffmpeg passthrough to preserve HDR, no HandBrake re-encode).

Add `HbArgsUhd` / `HbPresetUhd` and `FfmpegPostFileArgsUhd` properties, a `DiscType.Uhd` enum value, and wire them through `HandBrakeService.GetHbSettings()` and the `Conductor` pipeline. For 4K UHD, the typical workflow is `USE_FFMPEG: true` with `-c:v copy -c:a ac3 -b:a 640k` to preserve HDR metadata losslessly.

Users currently have to swap `arm.yaml` manually when switching between 1080p and 4K discs — automatic disc-type detection would eliminate this.
3 changes: 3 additions & 0 deletions docs/improvements/configuration-appsettings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Configuration & Setup — Environment-Aware Config Profiles

No `appsettings.Production.json` or env-aware config profiles exists. Would help users separate sensitive keys (API keys) from path config.
3 changes: 3 additions & 0 deletions docs/improvements/configuration-seed-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Configuration & Setup — Auto-Seed Data

Seed data scripts exist in `scripts/` but require manual run. Consider auto-seeding on first launch for demo/testing.
13 changes: 13 additions & 0 deletions docs/improvements/container-deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Container / Deployment

## Docker Image Size
Docker image is ~2GB with full .NET SDK. Switch to self-contained publish with runtime-only image to reduce size.

## Multi-Arch Builds
GitHub Actions CI has QEMU set up but only builds `linux/amd64`. Add `linux/arm64` multi-arch build once ARM64 runners or cross-compilation are available.

## HandBrake nvdec Support
Current `arm-dependencies:1.7.3` base image compiles HandBrake without `--enable-nvdec`. The devcontainer has a custom rebuild with nvdec working, but the production Dockerfile still uses the base image's build (no hw-decoding). Need to either fork and rebuild `arm-dependencies`, or add a multi-stage HandBrake build step to the production Dockerfile.

## BuildKit Migration
Docker buildx warning — migrate from legacy builder to BuildKit.
5 changes: 5 additions & 0 deletions docs/improvements/data-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Data / Persistence — Migrate from `DateTime` to `DateTimeOffset`

All model timestamps (`Job.StartTime`, `Job.StopTime`, `Notification.Timestamp`, `DiscMetadata.CreatedAt`/`LastUsedAt`, etc.) use `DateTime`. EF Core reads these back as `DateTimeKind.Unspecified` from SQLite, losing the UTC context. `.ToLocalTime()` in views works but is a band-aid.

Switching to `DateTimeOffset` stores the offset with the value and makes timezone intent explicit. Requires touching models, EF mappings, all view comparisons, and serialization.
3 changes: 3 additions & 0 deletions docs/improvements/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Dependency Injection — Review Lifetime Choices

WebUi now has full DI wiring. However many services are registered as `Scoped` when they're effectively stateless. `CliProcessRunner` is singleton. Review lifetime choices — some could be singletons or transient.
5 changes: 5 additions & 0 deletions docs/improvements/disc-databases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Disc Databases — thediscdb.com Integration

Encrypted BDs often return 0 tracks from `makemkvcon info --robot`. thediscdb.com stores disc IDs mapped to known track layouts. Adding a lookup step would let us skip the expensive `makemkvcon info` scan for known discs and identify the correct main feature track without guessing by filesize.

API is simple REST — define a `DiscDatabaseService` client, cache results locally, and plug into `IdentifyService`.
34 changes: 34 additions & 0 deletions docs/improvements/disc-type-audiobook-mp3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Disc Type Detection — MP3 Audiobook / Audio CD-ROM Support

## Context

Discovered while investigating job 607: an "Angels & Demons" audiobook disc mounted as an iso9660 filesystem containing `.mp3` files at the root — no `VIDEO_TS`, `BDMV`, or `CDA` directories present.

## The Problem

`GetDiscType()` in `IdentifyService.cs` only recognizes three disc structures:

| Check | Pattern | Detects |
|-------|---------|---------|
| `Directory.Exists("VIDEO_TS")` | DVD | ✅ |
| `Directory.Exists("BDMV")` | Blu-ray | ✅ |
| `FindOnDisc("CDA")` | Audio CD | ✅ |
| *(everything else)* | `DiscType.Unknown` | ❌ Fails |

An MP3 audiobook disc has none of these structures, so the Conductor hits the `default` case and the job fails with:

> `Couldn't identify the disc type. Exiting without any action.`

## What Should Happen

The system already has a `DiscType.Data` case in the Conductor that calls `RipDataAsync`, and an existing `DiscType.Music` path using MusicBrainZ + `RipMusicAsync`. We should add MP3/audio file detection to `GetDiscType()`:

- Scan the root of the mounted disc for common audio file extensions (`.mp3`, `.flac`, `.ogg`, `.wav`, `.m4a`, `.aac`)
- If audio files are found, classify as `DiscType.Music` so the existing music pipeline can handle it
- Alternatively, classify as `DiscType.Data` as a fallback for discs with files but no known video/audio directory structure

## Open Questions

- Should `RipMusicAsync` / the music pipeline handle MP3 files, or should this be a generic file copy (data disc) operation?
- Would MusicBrainZ identification work for an audiobook with no CDDA tracks?
- Should we add a new `DiscType.Audiobook` for dedicated handling?
3 changes: 3 additions & 0 deletions docs/improvements/gitignore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Gitignore

DB file `data/arm.db` is not gitignored — should be added so test/seed data isn't committed.
16 changes: 16 additions & 0 deletions docs/improvements/mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# MCP (Model Context Protocol)

✅ **MCP server implemented** using the C# SDK (`ModelContextProtocol.AspNetCore` v1.4.1) with HTTP (Streamable HTTP) transport at `/mcp`.

## Exposed Tools

- `get_jobs` — list jobs with optional status filter, `offset`, and `limit` pagination.
- `get_logs` — read job log files with `offset` (line number) and `pageSize` for efficient browsing of long logs.
- `get_config` — returns current ARM Sharp configuration (API key presence is shown as booleans, values are never exposed).

## Future Tools (🔲)

- `update_config`
- `eject_drive`
- `trigger_identify`
- Log streaming via SSE
3 changes: 3 additions & 0 deletions docs/improvements/musicbrainz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# MusicBrainz — Investigate Moving Off XML

MusicBrainz XML parsing is fragile (manual XElement traversal, namespace handling). If MusicBrainz offers a JSON endpoint, prefer it.
6 changes: 6 additions & 0 deletions docs/improvements/notifications-apprise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Notifications — Apprise

- **CLI:** `apprise` is a command-line tool supporting 80+ notification services (Slack, Discord, Telegram, email, etc.). Original Python ARM invokes it via subprocess.
- **Config key:** `Apprise` / `APPRISE` already exist in `ArmSettings` and `ConfigSnapshot`.
- **Implementation:** ~30 lines in `NotificationService` — call `apprise -b "body" -t "title"` via `CliProcessRunner`.
- **Settings UI:** Same as Pushover — needs editable form on Apprise tab.
6 changes: 6 additions & 0 deletions docs/improvements/notifications-pushover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Notifications — Pushover

- **API:** `POST https://api.pushover.net/1/messages.json` with `token` (app key), `user` (user key), `message`, `title`, `sound`, etc.
- **Config keys:** `PoUserKey` / `PO_USER_KEY` already exist in `ArmSettings` and `ConfigSnapshot`, mapped from YAML. Missing: a `PoAppToken` key for the application token.
- **Implementation:** ~20 lines in `NotificationService` — `SendPushoverAsync(client, appToken, userKey, title, body, ct)`.
- **Settings UI:** Apprise tab currently read-only; would need editable form fields.
3 changes: 3 additions & 0 deletions docs/improvements/pages-batch-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pages / Views — Batch Actions on Active Rips Page

Add batch actions (abandon all, retry all) on the Active Rips page.
3 changes: 3 additions & 0 deletions docs/improvements/pages-dashboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pages / Views — Home Dashboard Enhancements

Core metrics displayed. Could add charts (job success rate over time, rips per day) or sparkline trends.
3 changes: 3 additions & 0 deletions docs/improvements/pages-disc-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pages / Views — DVD/Blu-ray Detection Workflow

The Settings page has a "Detect Disc" / "Scan Drives" button but the actual udev-based monitoring workflow from the original ARM isn't replicated. Should add a "Start Monitoring" action that runs the Conductor/IdentifyService loop.
3 changes: 3 additions & 0 deletions docs/improvements/pages-identification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pages / Views — Redesign Identification Section

Improve the layout of the Identification section on the Job detail page.
3 changes: 3 additions & 0 deletions docs/improvements/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Security — LogsController Sanitization

`LogsController.Reader` uses `Path.GetFileName` for sanitization but could use `Path.GetFullPath` + prefix validation as defense-in-depth.
3 changes: 3 additions & 0 deletions docs/improvements/signalr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SignalR — Broadcaster Lifetime Concerns

`SignalRNotificationBroadcaster` is wired via `INotificationBroadcaster` interface. Works but the broadcaster is a singleton while the hub context is scoped per connection. Should verify no lifetime issues.
3 changes: 3 additions & 0 deletions docs/improvements/startup-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Startup & Recovery — Resume In-Progress Rips on Restart

Currently, if the app is restarted while a job is ripping (VideoRipping, TranscodeActive, etc.), the background task is lost and the job stays stuck. On startup, scan for jobs in non-terminal states and resume them: re-attach the MakeMKV/HandBrake process if still running, or restart the rip/transcode stage from where it left off. Requires stage-level checkpointing (which stage completed, which files were produced) so the system can pick up without re-doing completed work.
7 changes: 7 additions & 0 deletions docs/improvements/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Testing — Deferred / Missing Tests

- **Audio CD test:** Deferred — low priority. Needs abcde conf and audio disc in drive.
- **Data disc test:** Deferred — needs data disc for testing.
- **Error recovery tests:** Deferred — needs dirty/scratched discs for edge case testing.
- No CRC64 test with real DVD data (uses synthetic directory).
- No SignalR hub tests.
3 changes: 3 additions & 0 deletions docs/improvements/ui-restart-last-stage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# UI / User Experience — Restart from Last Successful Stage

Add a "retry from failure" action that resumes the pipeline at the last failed stage instead of restarting from scratch. Requires each stage to checkpoint its completion state in the DB (e.g. a `Stages` table or bitfield on `Job`).
Loading