feat(downloads): redesign download manager (#1313)

* docs(downloads): specify manager MVP redesign

* docs(downloads): plan manager MVP implementation

* docs(downloads): tighten manager validation plan

* fix(downloads): keep renderer download state global

* fix(downloads): make active count accessible

* feat(downloads): derive queue and library view model

* test(downloads): close view model coverage gaps

* fix(downloads): stabilize malformed view model data

* refactor(downloads): isolate library navigation

* fix(downloads): report library navigation failures

* feat(downloads): add ready-to-watch library

* feat(downloads): add active download queue

* feat(downloads): finish manager MVP

* docs(downloads): clarify detail-first offline behavior

* docs(downloads): plan detail navigation follow-up

* fix(downloads): open completed movies in details

* test(downloads): cover pending series navigation

* fix(downloads): honor the global cover size

* fix(downloads): prefer local playback in shared details

* fix(downloads): preserve external launch priority

* fix(downloads): prefer local playback in Xtream details

* test(downloads): cover offline detail journey

* docs(downloads): document offline detail behavior

* docs(downloads): format detail navigation plan

* fix(downloads): open Stalker items in provider details

* docs(downloads): clarify Stalker navigation fallback

* fix(xtream): isolate reused detail identities

* fix(xtream): ignore stale VOD positions

* fix(downloads): keep offline Xtream playback available

* docs(downloads): clarify provider playback availability

* docs(downloads): design missing-file recovery

* docs(downloads): plan missing-file recovery

* feat(downloads): derive completed file availability

* feat(downloads): recover missing completed files

* feat(downloads): refresh missing local files

* feat(downloads): separate missing files from ready media

* feat(downloads): surface missing files for recovery

* refactor(downloads): simplify ready cards

* test(downloads): cover missing-file and series journeys

* feat(downloads): finish missing-file recovery

* docs(downloads): design offline detail views

* docs(downloads): plan offline detail views

* feat(downloads): persist offline metadata snapshots

* fix(downloads): complete metadata snapshot bridge contract

* feat(downloads): manage offline metadata snapshots

* fix(downloads): harden metadata snapshot updates

* fix(downloads): restrict snapshot artwork

* fix(downloads): guard restart artwork URL

* fix(downloads): refine artwork URL checks

* feat(downloads): expose offline metadata updates

* fix(downloads): keep metadata service change focused

* fix(downloads): preserve metadata error conventions

* feat(downloads): derive offline detail content

* fix(downloads): preserve unknown episode coordinates

* feat(downloads): add focused offline detail routes

* fix(downloads): ignore fragments in shell route state

* fix(downloads): normalize fragments before queries

* feat(downloads): open ready cards in offline details

* fix(downloads): use native disabled card styles

* feat(downloads): enrich offline detail metadata

* fix(downloads): harden offline metadata resolution

* fix(downloads): preserve stalker provider titles

* fix(downloads): distinguish stalker metadata seeds

* fix(downloads): stabilize offline metadata refresh

* fix(downloads): throttle sparse metadata refreshes

* fix(downloads): type metadata language settings

* feat(downloads): render offline movie and series details

* fix(downloads): harden offline detail interactions

* fix(downloads): close offline detail edge cases

* feat(downloads): hand off to provider-only details

* fix(downloads): preserve stalker provider handoff

* feat(downloads): capture metadata at download time

* fix(downloads): preserve snapshot source semantics

* fix(downloads): preserve episode snapshot identity

* docs(downloads): document offline details flow

* docs(downloads): clarify stalker provider fallback

* test(downloads): cover offline detail journeys

* test(downloads): stabilize offline detail selectors

* style(downloads): format changed files

* docs(downloads): clean design spec formatting

* fix(downloads): preserve offline library ownership

* test(downloads): fix Windows workspace navigation

* test(database): preserve Electron tsconfig resolution

* perf(downloads): avoid blocking file availability probes
This commit is contained in:
4gray authored and GitHub committed 2026-08-01 18:09:31 +02:00
1 parent 46c58f4f57
commit 760099358b
188 files changed
+31245 -2281

No files matched your search

+1
View File
@@ -2,6 +2,7 @@ export * from './lib/channel-drm.interface';
export * from './lib/channel.interface';
export * from './lib/channel.model';
export * from './lib/dev-logger.util';
export * from './lib/download-metadata.interface';
export * from './lib/embedded-mpv-session.interface';
export * from './lib/electron-api.interface';
export * from './lib/epg-channel-metadata.model';
@@ -0,0 +1,37 @@
export interface DownloadMetadataPerson {
tmdbPersonId?: number;
name: string;
role?: string;
profileUrl?: string;
}
export interface DownloadEpisodeMetadata {
title?: string;
plot?: string;
stillUrl?: string;
seasonNumber: number;
episodeNumber: number;
}
export interface DownloadMetadataSnapshot {
version: 1;
language: string;
mediaKind: 'movie' | 'series';
title: string;
originalTitle?: string;
plot?: string;
releaseDate?: string;
year?: number;
durationMinutes?: number;
genres?: string[];
rating?: number;
status?: string;
posterUrl?: string;
backdropUrl?: string;
tmdbId?: number;
providerCategoryId?: string;
cast?: DownloadMetadataPerson[];
creators?: DownloadMetadataPerson[];
episode?: DownloadEpisodeMetadata;
enrichedAt?: string;
}
@@ -4,6 +4,7 @@ import {
EmbeddedMpvSession,
EmbeddedMpvSupport,
} from './embedded-mpv-session.interface';
import { DownloadMetadataSnapshot } from './download-metadata.interface';
import { EpgChannelMetadata } from './epg-channel-metadata.model';
import { EpgProgram } from './epg-program.model';
import { ExternalPlayerSession } from './external-player-session.interface';
@@ -137,6 +138,9 @@ export const ELECTRON_BRIDGE_DOWNLOAD_STATUSES = {
export type ElectronBridgeDownloadStatus =
(typeof ELECTRON_BRIDGE_DOWNLOAD_STATUSES)[keyof typeof ELECTRON_BRIDGE_DOWNLOAD_STATUSES];
export type ElectronDownloadFileAvailability =
'available' | 'missing' | 'not-applicable';
export const ELECTRON_BRIDGE_APP_UPDATE_STATUSES = {
Unsupported: 'unsupported',
Idle: 'idle',
@@ -386,8 +390,7 @@ export interface ElectronBridgePlaylistRow {
}
export type ElectronBridgePlaylistUpsertInput =
| Playlist
| ElectronBridgePlaylistInput;
Playlist | ElectronBridgePlaylistInput;
export interface ElectronBridgeCategoryRow {
id: number;
@@ -514,6 +517,7 @@ export interface ElectronBridgeDownloadStartPayload {
title: string;
url: string;
posterUrl?: string;
metadataSnapshot?: DownloadMetadataSnapshot;
downloadFolder: string;
headers?: ElectronBridgeDownloadHeaders;
seriesXtreamId?: number;
@@ -530,6 +534,10 @@ export interface ElectronBridgeDownloadStartResult extends ElectronBridgeErrorRe
id?: number;
}
export interface ElectronBridgeDownloadRedownloadResult extends ElectronBridgeErrorResult {
recovered?: boolean;
}
export interface ElectronDownloadItem {
id: number;
playlistId: string;
@@ -543,7 +551,9 @@ export interface ElectronDownloadItem {
fileName?: string;
filePath?: string;
posterUrl?: string;
metadataSnapshot?: DownloadMetadataSnapshot;
status: ElectronBridgeDownloadStatus;
fileAvailability: ElectronDownloadFileAvailability;
bytesDownloaded?: number;
totalBytes?: number;
errorMessage?: string;
@@ -928,9 +938,7 @@ export interface ElectronBridgeApi {
playlistId: string,
pins: VodSourcePin[]
) => Promise<ElectronBridgeResult>;
dbClearVodSourcePin: (
matchKeys: string[]
) => Promise<ElectronBridgeResult>;
dbClearVodSourcePin: (matchKeys: string[]) => Promise<ElectronBridgeResult>;
onChannelChange?: (
callback: (data: { direction: 'up' | 'down' }) => void
) => () => void;
@@ -1072,9 +1080,16 @@ export interface ElectronBridgeApi {
downloadId: number,
downloadFolder: string
) => Promise<ElectronBridgeErrorResult>;
downloadsRedownloadMissing: (
downloadId: number
) => Promise<ElectronBridgeDownloadRedownloadResult>;
downloadsRemove: (downloadId: number) => Promise<ElectronBridgeErrorResult>;
downloadsGetList: (playlistId?: string) => Promise<ElectronDownloadItem[]>;
downloadsGet: (downloadId: number) => Promise<ElectronDownloadItem | null>;
downloadsUpdateMetadata: (
downloadId: number,
metadataSnapshot: DownloadMetadataSnapshot
) => Promise<ElectronBridgeErrorResult>;
downloadsGetDefaultFolder: () => Promise<string>;
downloadsSelectFolder: () => Promise<string | null>;
downloadsRevealFile: (