mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
feat(downloads): separate missing files from ready media
This commit is contained in:
1 parent
2899091b74
commit
596f5a8d4d
2 files changed
+141
-5
No files matched your search
@@ -489,6 +489,124 @@ describe('buildDownloadManagerViewModel', () => {
|
||||
expect(libraryItemIds(result.library)).toEqual([6]);
|
||||
});
|
||||
|
||||
it('moves missing completed movies to attention instead of the ready library', () => {
|
||||
const result = build([
|
||||
download(1, { fileAvailability: 'available' }),
|
||||
download(2, { fileAvailability: 'missing' }),
|
||||
]);
|
||||
|
||||
expect(rowIds(result.attention)).toEqual([2]);
|
||||
expect(result.attention[0].attentionReason).toBe('file-missing');
|
||||
expect(libraryItemIds(result.library)).toEqual([1]);
|
||||
});
|
||||
|
||||
it('keeps available series members grouped and exposes each missing episode', () => {
|
||||
const seriesEpisode = (
|
||||
id: number,
|
||||
fileAvailability: DownloadItem['fileAvailability']
|
||||
) =>
|
||||
download(id, {
|
||||
contentType: 'episode',
|
||||
episodeNumber: id,
|
||||
fileAvailability,
|
||||
seasonNumber: 1,
|
||||
seriesXtreamId: 70,
|
||||
});
|
||||
const result = build([
|
||||
seriesEpisode(1, 'available'),
|
||||
seriesEpisode(2, 'missing'),
|
||||
seriesEpisode(3, 'available'),
|
||||
]);
|
||||
|
||||
expect(rowIds(result.attention)).toEqual([2]);
|
||||
expect(result.attention[0].attentionReason).toBe('file-missing');
|
||||
expect(result.library).toHaveLength(1);
|
||||
expect(result.library[0].kind).toBe('series');
|
||||
if (result.library[0].kind === 'series') {
|
||||
expect(result.library[0].members.map(({ id }) => id)).toEqual([
|
||||
1, 3,
|
||||
]);
|
||||
}
|
||||
expect(result.counts).toEqual({
|
||||
all: 2,
|
||||
movie: 0,
|
||||
series: 2,
|
||||
inProgress: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a fully missing series out of Ready to watch', () => {
|
||||
const result = build([
|
||||
download(1, {
|
||||
contentType: 'episode',
|
||||
fileAvailability: 'missing',
|
||||
seriesXtreamId: 70,
|
||||
}),
|
||||
download(2, {
|
||||
contentType: 'episode',
|
||||
fileAvailability: 'missing',
|
||||
seriesXtreamId: 70,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(result.library).toEqual([]);
|
||||
expect(rowIds(result.attention)).toEqual([1, 2]);
|
||||
expect(
|
||||
result.attention.map(({ attentionReason }) => attentionReason)
|
||||
).toEqual(['file-missing', 'file-missing']);
|
||||
});
|
||||
|
||||
it('filters and searches missing rows without exposing source metadata in the library', () => {
|
||||
const downloads = [
|
||||
download(1, {
|
||||
fileAvailability: 'missing',
|
||||
playlistId: 'playlist-b',
|
||||
}),
|
||||
download(2, {
|
||||
contentType: 'episode',
|
||||
fileAvailability: 'missing',
|
||||
playlistId: 'playlist-b',
|
||||
}),
|
||||
];
|
||||
|
||||
expect(
|
||||
rowIds(
|
||||
build(downloads, {
|
||||
filter: 'movie',
|
||||
searchTerm: 'beta source',
|
||||
}).attention
|
||||
)
|
||||
).toEqual([1]);
|
||||
expect(
|
||||
rowIds(
|
||||
build(downloads, {
|
||||
filter: 'series',
|
||||
searchTerm: 'beta source',
|
||||
}).attention
|
||||
)
|
||||
).toEqual([2]);
|
||||
expect(build(downloads).counts).toEqual({
|
||||
all: 2,
|
||||
movie: 1,
|
||||
series: 1,
|
||||
inProgress: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not mutate missing-file inputs while partitioning them', () => {
|
||||
const available = Object.freeze(
|
||||
download(1, { fileAvailability: 'available' })
|
||||
);
|
||||
const missing = Object.freeze(
|
||||
download(2, { fileAvailability: 'missing' })
|
||||
);
|
||||
const downloads = Object.freeze([available, missing]);
|
||||
|
||||
expect(() => build(downloads)).not.toThrow();
|
||||
expect(downloads).toEqual([available, missing]);
|
||||
expect(missing).not.toHaveProperty('attentionReason');
|
||||
});
|
||||
|
||||
it('excludes paused rows from activeCount independently of filter and search', () => {
|
||||
const result = build(
|
||||
[
|
||||
|
||||
@@ -19,7 +19,10 @@ export type {
|
||||
|
||||
export type DownloadFilterId = 'all' | 'movie' | 'series' | 'in-progress';
|
||||
|
||||
export type DownloadAttentionReason = 'file-missing' | 'transfer';
|
||||
|
||||
export interface DownloadListItemViewModel extends DownloadLibraryRow {
|
||||
readonly attentionReason: DownloadAttentionReason;
|
||||
readonly seriesTitle: string;
|
||||
}
|
||||
|
||||
@@ -95,6 +98,14 @@ function needsAttention(item: DownloadItem): boolean {
|
||||
return item.status === 'failed' || item.status === 'canceled';
|
||||
}
|
||||
|
||||
function isMissingCompletedFile(item: DownloadItem): boolean {
|
||||
return item.status === 'completed' && item.fileAvailability === 'missing';
|
||||
}
|
||||
|
||||
function isReady(item: DownloadItem): boolean {
|
||||
return item.status === 'completed' && item.fileAvailability !== 'missing';
|
||||
}
|
||||
|
||||
function compareQueued(
|
||||
left: DownloadListItemViewModel,
|
||||
right: DownloadListItemViewModel
|
||||
@@ -161,17 +172,21 @@ export function buildDownloadManagerViewModel({
|
||||
scopePlaylistId === undefined || item.playlistId === scopePlaylistId
|
||||
);
|
||||
const scopedRows = scopedItems.map((item) => ({
|
||||
attentionReason: isMissingCompletedFile(item)
|
||||
? ('file-missing' as const)
|
||||
: ('transfer' as const),
|
||||
item,
|
||||
episodeLabel: episodeLabel(item),
|
||||
seriesTitle: seriesTitle(item),
|
||||
sourceName: sourceNames.get(item.playlistId) ?? '',
|
||||
}));
|
||||
const scopedActive = scopedRows.filter(({ item }) => isActive(item));
|
||||
const scopedAttention = scopedRows.filter(({ item }) =>
|
||||
needsAttention(item)
|
||||
const scopedAttention = scopedRows.filter(
|
||||
({ item }) =>
|
||||
needsAttention(item) || isMissingCompletedFile(item)
|
||||
);
|
||||
const scopedLibrary = buildDownloadLibrary(
|
||||
scopedRows.filter(({ item }) => item.status === 'completed')
|
||||
scopedRows.filter(({ item }) => isReady(item))
|
||||
);
|
||||
const activeFilter = normalizeDownloadFilter(filter);
|
||||
const displayed = scopedRows
|
||||
@@ -181,10 +196,13 @@ export function buildDownloadManagerViewModel({
|
||||
.filter(({ item }) => isActive(item))
|
||||
.sort(compareQueued);
|
||||
const attention = displayed
|
||||
.filter(({ item }) => needsAttention(item))
|
||||
.filter(
|
||||
({ item }) =>
|
||||
needsAttention(item) || isMissingCompletedFile(item)
|
||||
)
|
||||
.sort(compareQueued);
|
||||
const library = buildDownloadLibrary(
|
||||
displayed.filter(({ item }) => item.status === 'completed')
|
||||
displayed.filter(({ item }) => isReady(item))
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user