Upload date in UI; extract HTML_UI; Twitch clips mode; DB connection pool
Four nice-to-have items, plus surfacing the upload date in both UIs
since the user explicitly asked.
Upload date display
- Desktop: card meta row now includes a `YYYY-MM-DD` cell between the
ID and the duration; detail panel header shows it as `📅 YYYY-MM-DD`.
- Web: video player modal title gains a date+duration subline so the
viewer has context without opening the metadata pane (card meta
already shows the date).
- New `format_upload_date()` helper on the desktop side; the web side
reuses the existing `fmtDate()` JS.
Extract HTML_UI to include_str!
- `LOGIN_HTML` and `HTML_UI` moved out of `src/web.rs` into
`src/web_ui/login.html` and `src/web_ui/index.html`. The Rust file
drops from 2915 lines to 1806; editors now syntax-highlight the
HTML/CSS/JS, and UI diffs no longer overwhelm the Rust diff view.
- `include_str!` bakes them into the binary at compile time, so there
is no runtime fs lookup and no extra deploy artifacts.
Twitch clips-only mode
- `classify_twitch` now recognises `twitch.tv/<user>/clips` as a
Channel URL (was Unknown), so yt-dlp's TwitchClips extractor is
reachable from our normal download pipeline.
- Both UIs gain a "Clips only" toggle visible only when the URL is
a Twitch channel (not a specific VOD/clip). On submit, the URL is
rewritten to `twitch.tv/<user>/clips` before dispatch.
- 1 new platform test covers the clips-listing classification.
DB connection pool (r2d2_sqlite)
- `Database` now wraps an `r2d2`-managed SQLite pool. File-backed
databases get up to 4 connections; the in-memory fallback is capped
at 1 (since each in-memory connection is a fresh empty DB by default
in SQLite).
- `WebState.db` drops its outer `Mutex` — concurrent handlers each
check out their own connection from the pool. Static-file fetches
through `/files/` no longer serialize on the watched/positions/
password lookups that the auth middleware performs.
- Pool init failures are converted to `rusqlite::Error` so the
existing `Result<T>` return types still work.
47 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8d1c274075
commit
375a24262c
8 changed files with 1428 additions and 1151 deletions
|
|
@ -230,9 +230,11 @@ fn classify_tiktok(url: &str) -> UrlKind {
|
|||
}
|
||||
|
||||
fn classify_twitch(url: &str) -> UrlKind {
|
||||
// twitch.tv/<user> → Channel
|
||||
// twitch.tv/<user> → Channel (bare profile)
|
||||
// twitch.tv/<user>/clips → Channel (clips-only listing)
|
||||
// twitch.tv/<user>/videos → Channel (VOD listing)
|
||||
// twitch.tv/videos/<id> → Video (VOD)
|
||||
// twitch.tv/<user>/clip/<id> → Video (clip)
|
||||
// twitch.tv/<user>/clip/<id> → Video (single clip)
|
||||
if let Some(rest) = url.split("twitch.tv/").nth(1) {
|
||||
let first = rest.split('/').next().unwrap_or("").trim_end_matches('?');
|
||||
if first.is_empty() { return UrlKind::Unknown; }
|
||||
|
|
@ -240,10 +242,16 @@ fn classify_twitch(url: &str) -> UrlKind {
|
|||
if rest.contains("/clip/") || rest.contains("/video/") {
|
||||
return UrlKind::Video;
|
||||
}
|
||||
// No nested path means a bare channel page.
|
||||
let nested = rest.trim_start_matches(first).trim_start_matches('/');
|
||||
let nested = nested.split('?').next().unwrap_or("");
|
||||
if nested.is_empty() || nested == "videos" || nested == "about" {
|
||||
// Bare channel + channel-scoped listing pages all collapse to Channel.
|
||||
// yt-dlp's extractors recognise each variant and pull the right
|
||||
// subset (clips/highlights/uploads/all) without further hints.
|
||||
if nested.is_empty()
|
||||
|| nested == "videos"
|
||||
|| nested == "about"
|
||||
|| nested == "clips"
|
||||
{
|
||||
return UrlKind::Channel { handle: first.to_string() };
|
||||
}
|
||||
}
|
||||
|
|
@ -423,6 +431,15 @@ mod tests {
|
|||
assert!(matches!(info.kind, UrlKind::Video));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twitch_clips_listing_is_channel() {
|
||||
let info = classify_url("https://www.twitch.tv/streamername/clips");
|
||||
match info.kind {
|
||||
UrlKind::Channel { handle } => assert_eq!(handle, "streamername"),
|
||||
_ => panic!("expected Channel with clips listing"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vimeo_numeric_is_video() {
|
||||
let info = classify_url("https://vimeo.com/123456");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue