Skip to content

Dispatcharr Phase 1: native API source loading #201

Description

@kristofferR

Part of #200. Fully shippable on its own (load + scan Dispatcharr source streams, read-only).

New src-tauri/src/engine/dispatcharr.rs (register in engine/mod.rs)

  • DispatcharrClient — normalize base URL like normalize_xtream_server (playlist.rs:775); reqwest client with the same knobs as the playlist downloader. ensure_token(): API-key header if set, else JWT login → cache access token (expiry now+25 min) → refresh on expiry → full re-login on 401; request() retries once on 401.
  • Serde-tolerant DTOs: DispatcharrChannel { id, uuid, channel_number, name, channel_group, tvg_id, logo, streams }, DispatcharrStream { id, name, url, m3u_account, is_custom, tvg_id, is_stale, stream_stats, ... }, DispatcharrGroup.
  • fetch_all<T>() handling both bare arrays and DRF {count, next, results} pagination (untagged enum), rewriting next host onto the base URL (reverse-proxy gotcha), hard page ceiling (500). Fetchers: channels, per-channel streams, groups, m3u accounts (accounts tolerate failure).
  • build_m3u(...): one #EXTINF per stream; name = channel name, or "{name} [{order+1}/{count}] {account-or-stream-name}" for multi-stream channels; attrs tvg-id, tvg-chno, tvg-logo ({base}/api/channels/logos/{id}/cache/), group-title, plus the five x-dispatcharr-* attrs (escape quotes). URL line = provider stream.url.
  • dispatcharr_ids_from_extinf(&str) -> Option<DispatcharrStreamRef> built on parse_extinf_attributes.
  • Session registry: register_session(identity, Arc<DispatcharrClient>) / get_session(identity) (same pattern as SERVER_LOCATION_CACHE).
  • Write-op signatures used later by Dispatcharr Phase 2: scan source streams #202: patch_stream_stats, set_channel_streams, bulk_delete_streams.

Modified src-tauri/src/commands/playlist.rs

  • DispatcharrOpenRequest { server, username, password, api_key } (next to XtreamOpenRequest).
  • open_playlist_dispatcharr + _inner mirroring open_playlist_xtream_inner: validate (api_key OR user+pass) → source key dispatcharr:{host}|{user} (mirror build_xtream_source_key) → emit playlist://load-progress stages → fetch channels/groups/accounts via tokio::join!, per-channel streams with buffer_unordered(8)build_m3uwrite_bytes_to_cacheparse_playlist_with_progress → set preview.file_name = "{host} (Dispatcharr)" and preview.source_identityregister_session.

Saved/recent/wiring

  • SavedPlaylistSource::Dispatcharr { server, username?, password?, api_key? } variant + SavedPlaylistKind arm (models/saved_playlist.rs); arms in commands/saved.rs (default_display_name, sanitize_saved_playlist_entry, open_saved_playlist with identity override saved:{id}); RecentPlaylistKind::Dispatcharr in commands/recent.rs (secrets only if "remember" checked); register the command in lib.rs invoke_handler.

Frontend

  • src/lib/types.ts + src/lib/tauri.ts: request type + openPlaylistDispatcharr wrapper.
  • src/components/OpenSourceDialog.tsx: new "Dispatcharr" tab (server URL, API-key vs username/password toggle, remember-credentials); autofill server from pasted /proxy/ts/stream/ or /output/m3u URLs (mirror parseXtreamM3ULink).
  • src/App.tsx: openPlaylistDispatcharrValue callback modeled on openPlaylistXtreamValue.
  • SavedPlaylistEditorDialog.tsx + src/lib/savedPlaylists.ts: dispatcharr form branch.

Tests

  • engine/dispatcharr.rs unit tests using the spawn_http_server pattern from checker.rs, extended to capture method+body: JWT login/refresh/401-retry, API-key header, pagination (paginated + bare array + host rewrite).
  • Pure tests: build_m3uparser::parse_m3udispatcharr_ids_from_extinf round-trip (the load-bearing invariant); attr escaping.
  • SavedPlaylistSource::Dispatcharr serde round-trip; legacy JSON still parses.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions