mirror of
https://github.com/Sonarr/Sonarr
synced 2025-12-06 16:32:24 +01:00
New: Show grabbed/blocklisted releases in Interactive Search
Closes #3955
This commit is contained in:
parent
10964b625e
commit
e8c63405d2
13 changed files with 270 additions and 71 deletions
|
|
@ -26,6 +26,7 @@ import {
|
||||||
faArrowCircleRight as fasArrowCircleRight,
|
faArrowCircleRight as fasArrowCircleRight,
|
||||||
faAsterisk as fasAsterisk,
|
faAsterisk as fasAsterisk,
|
||||||
faBackward as fasBackward,
|
faBackward as fasBackward,
|
||||||
|
faBan as fasBan,
|
||||||
faBars as fasBars,
|
faBars as fasBars,
|
||||||
faBolt as fasBolt,
|
faBolt as fasBolt,
|
||||||
faBookmark as fasBookmark,
|
faBookmark as fasBookmark,
|
||||||
|
|
@ -124,6 +125,7 @@ export const ADVANCED_SETTINGS = fasCog;
|
||||||
export const ARROW_LEFT = fasArrowCircleLeft;
|
export const ARROW_LEFT = fasArrowCircleLeft;
|
||||||
export const ARROW_RIGHT = fasArrowCircleRight;
|
export const ARROW_RIGHT = fasArrowCircleRight;
|
||||||
export const BACKUP = farFileArchive;
|
export const BACKUP = farFileArchive;
|
||||||
|
export const BLOCKLIST = fasBan;
|
||||||
export const BUG = fasBug;
|
export const BUG = fasBug;
|
||||||
export const CALENDAR = fasCalendarAlt;
|
export const CALENDAR = fasCalendarAlt;
|
||||||
export const CALENDAR_O = farCalendar;
|
export const CALENDAR_O = farCalendar;
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,16 @@
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocklistIconContainer {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
.size {
|
.size {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'age': string;
|
'age': string;
|
||||||
|
'blocklistIconContainer': string;
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'download': string;
|
'download': string;
|
||||||
'downloadIcon': string;
|
'downloadIcon': string;
|
||||||
|
'history': string;
|
||||||
'indexer': string;
|
'indexer': string;
|
||||||
'indexerFlags': string;
|
'indexerFlags': string;
|
||||||
'interactiveIcon': string;
|
'interactiveIcon': string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
|
|
@ -78,6 +78,7 @@ interface InteractiveSearchRowProps extends Release {
|
||||||
function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||||
const {
|
const {
|
||||||
decision,
|
decision,
|
||||||
|
history,
|
||||||
parsedInfo,
|
parsedInfo,
|
||||||
release,
|
release,
|
||||||
publishDate,
|
publishDate,
|
||||||
|
|
@ -129,6 +130,12 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||||
const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false);
|
const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false);
|
||||||
const { isGrabbing, isGrabbed, grabError, grabRelease } = useGrabRelease();
|
const { isGrabbing, isGrabbed, grabError, grabRelease } = useGrabRelease();
|
||||||
|
|
||||||
|
const isBlocklisted = useMemo(() => {
|
||||||
|
return (
|
||||||
|
decision.rejections.findIndex((r) => r.reason === 'blocklisted') >= 0
|
||||||
|
);
|
||||||
|
}, [decision]);
|
||||||
|
|
||||||
const handleGrabPress = useCallback(() => {
|
const handleGrabPress = useCallback(() => {
|
||||||
if (downloadAllowed) {
|
if (downloadAllowed) {
|
||||||
grabRelease({
|
grabRelease({
|
||||||
|
|
@ -206,6 +213,56 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||||
|
|
||||||
<TableRowCell className={styles.indexer}>{indexer}</TableRowCell>
|
<TableRowCell className={styles.indexer}>{indexer}</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.history}>
|
||||||
|
{history ? (
|
||||||
|
<Icon
|
||||||
|
name={icons.DOWNLOADING}
|
||||||
|
kind={history.failed ? kinds.DANGER : kinds.DEFAULT}
|
||||||
|
title={`${
|
||||||
|
history.failed
|
||||||
|
? translate('FailedAt', {
|
||||||
|
date: formatDateTime(
|
||||||
|
history.failed,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat,
|
||||||
|
{ includeSeconds: true }
|
||||||
|
),
|
||||||
|
})
|
||||||
|
: translate('GrabbedAt', {
|
||||||
|
date: formatDateTime(
|
||||||
|
history.grabbed,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat,
|
||||||
|
{ includeSeconds: true }
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isBlocklisted ? (
|
||||||
|
<Icon
|
||||||
|
containerClassName={
|
||||||
|
history ? styles.blocklistIconContainer : undefined
|
||||||
|
}
|
||||||
|
name={icons.BLOCKLIST}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={
|
||||||
|
history?.failed
|
||||||
|
? `${translate('BlockListedAt', {
|
||||||
|
date: formatDateTime(
|
||||||
|
history.failed,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat,
|
||||||
|
{ includeSeconds: true }
|
||||||
|
),
|
||||||
|
})}`
|
||||||
|
: translate('Blocklisted')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.size}>{formatBytes(size)}</TableRowCell>
|
<TableRowCell className={styles.size}>{formatBytes(size)}</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.peers}>
|
<TableRowCell className={styles.peers}>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,13 @@ const { useOptions, useOption, getOptions, getOption, setOptions, setOption } =
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'history',
|
||||||
|
label: translate('History'),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: 'ascending',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: () => translate('Size'),
|
label: () => translate('Size'),
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export interface Release extends ModelBase {
|
||||||
parsedInfo: ParsedInfo;
|
parsedInfo: ParsedInfo;
|
||||||
release: ReleaseInfo;
|
release: ReleaseInfo;
|
||||||
decision: Decision;
|
decision: Decision;
|
||||||
|
history?: ReleaseHistory;
|
||||||
qualityWeight: number;
|
qualityWeight: number;
|
||||||
languages: Language[];
|
languages: Language[];
|
||||||
mappedSeriesId?: number;
|
mappedSeriesId?: number;
|
||||||
|
|
@ -104,6 +105,11 @@ export interface Decision {
|
||||||
rejections: Rejection[];
|
rejections: Rejection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReleaseHistory {
|
||||||
|
grabbed: string;
|
||||||
|
failed: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const FILTERS: Filter[] = [
|
export const FILTERS: Filter[] = [
|
||||||
{
|
{
|
||||||
key: 'all',
|
key: 'all',
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
|
|
@ -109,62 +110,12 @@ public void Delete(List<int> ids)
|
||||||
|
|
||||||
private bool SameNzb(Blocklist item, ReleaseInfo release)
|
private bool SameNzb(Blocklist item, ReleaseInfo release)
|
||||||
{
|
{
|
||||||
if (item.PublishedDate == release.PublishDate)
|
return ReleaseComparer.SameNzb(new ReleaseComparerModel(item), release);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HasSameIndexer(item, release.Indexer) &&
|
|
||||||
HasSamePublishedDate(item, release.PublishDate) &&
|
|
||||||
HasSameSize(item, release.Size))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool SameTorrent(Blocklist item, TorrentInfo release)
|
private bool SameTorrent(Blocklist item, TorrentInfo release)
|
||||||
{
|
{
|
||||||
if (release.InfoHash.IsNotNullOrWhiteSpace())
|
return ReleaseComparer.SameTorrent(new ReleaseComparerModel(item), release);
|
||||||
{
|
|
||||||
return release.InfoHash.Equals(item.TorrentInfoHash, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HasSameIndexer(item, release.Indexer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasSameIndexer(Blocklist item, string indexer)
|
|
||||||
{
|
|
||||||
if (item.Indexer.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.Indexer.Equals(indexer, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasSamePublishedDate(Blocklist item, DateTime publishedDate)
|
|
||||||
{
|
|
||||||
if (!item.PublishedDate.HasValue)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.PublishedDate.Value.AddMinutes(-2) <= publishedDate &&
|
|
||||||
item.PublishedDate.Value.AddMinutes(2) >= publishedDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasSameSize(Blocklist item, long size)
|
|
||||||
{
|
|
||||||
if (!item.Size.HasValue)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var difference = Math.Abs(item.Size.Value - size);
|
|
||||||
|
|
||||||
return difference <= 2.Megabytes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(ClearBlocklistCommand message)
|
public void Execute(ClearBlocklistCommand message)
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@
|
||||||
"BlackholeWatchFolder": "Watch Folder",
|
"BlackholeWatchFolder": "Watch Folder",
|
||||||
"BlackholeWatchFolderHelpText": "Folder from which {appName} should import completed downloads",
|
"BlackholeWatchFolderHelpText": "Folder from which {appName} should import completed downloads",
|
||||||
"Blocklist": "Blocklist",
|
"Blocklist": "Blocklist",
|
||||||
|
"BlockListedAt": "Blocklisted at {date}",
|
||||||
"BlocklistAndSearch": "Blocklist and Search",
|
"BlocklistAndSearch": "Blocklist and Search",
|
||||||
"BlocklistAndSearchHint": "Start a search for a replacement after blocklisting",
|
"BlocklistAndSearchHint": "Start a search for a replacement after blocklisting",
|
||||||
"BlocklistAndSearchMultipleHint": "Start searches for replacements after blocklisting",
|
"BlocklistAndSearchMultipleHint": "Start searches for replacements after blocklisting",
|
||||||
|
|
@ -706,6 +707,7 @@
|
||||||
"ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
|
"ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
|
||||||
"ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'",
|
"ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'",
|
||||||
"Failed": "Failed",
|
"Failed": "Failed",
|
||||||
|
"FailedAt": "Failed at: {date}",
|
||||||
"FailedToFetchSettings": "Failed to fetch settings",
|
"FailedToFetchSettings": "Failed to fetch settings",
|
||||||
"FailedToFetchUpdates": "Failed to fetch updates",
|
"FailedToFetchUpdates": "Failed to fetch updates",
|
||||||
"FailedToLoadCustomFiltersFromApi": "Failed to load custom filters from API",
|
"FailedToLoadCustomFiltersFromApi": "Failed to load custom filters from API",
|
||||||
|
|
@ -795,6 +797,7 @@
|
||||||
"GrabReleaseUnknownSeriesOrEpisodeMessageText": "{appName} was unable to determine which series and episode this release was for. {appName} may be unable to automatically import this release. Do you want to grab '{title}'?",
|
"GrabReleaseUnknownSeriesOrEpisodeMessageText": "{appName} was unable to determine which series and episode this release was for. {appName} may be unable to automatically import this release. Do you want to grab '{title}'?",
|
||||||
"GrabSelected": "Grab Selected",
|
"GrabSelected": "Grab Selected",
|
||||||
"Grabbed": "Grabbed",
|
"Grabbed": "Grabbed",
|
||||||
|
"GrabbedAt": "Grabbed at: {date}",
|
||||||
"Group": "Group",
|
"Group": "Group",
|
||||||
"HardlinkCopyFiles": "Hardlink/Copy Files",
|
"HardlinkCopyFiles": "Hardlink/Copy Files",
|
||||||
"HasMissingSeason": "Has Missing Season",
|
"HasMissingSeason": "Has Missing Season",
|
||||||
|
|
|
||||||
32
src/NzbDrone.Core/Parser/Model/ReleaseComparerModel.cs
Normal file
32
src/NzbDrone.Core/Parser/Model/ReleaseComparerModel.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Blocklisting;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
public class ReleaseComparerModel
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string TorrentInfoHash { get; set; }
|
||||||
|
public DateTime? PublishedDate { get; set; }
|
||||||
|
public string Indexer { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
|
||||||
|
public ReleaseComparerModel(Blocklist blocklist)
|
||||||
|
{
|
||||||
|
Title = blocklist.SourceTitle;
|
||||||
|
TorrentInfoHash = blocklist.TorrentInfoHash;
|
||||||
|
PublishedDate = blocklist.PublishedDate;
|
||||||
|
Indexer = blocklist.Indexer;
|
||||||
|
Size = blocklist.Size ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReleaseComparerModel(EpisodeHistory history)
|
||||||
|
{
|
||||||
|
Title = history.SourceTitle;
|
||||||
|
PublishedDate = history.Date;
|
||||||
|
Indexer = history.Data.GetValueOrDefault("indexer");
|
||||||
|
Size = long.Parse(history.Data.GetValueOrDefault("size", "0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/NzbDrone.Core/Parser/ReleaseComparer.cs
Normal file
68
src/NzbDrone.Core/Parser/ReleaseComparer.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Parser;
|
||||||
|
|
||||||
|
public static class ReleaseComparer
|
||||||
|
{
|
||||||
|
public static bool SameNzb(ReleaseComparerModel item, ReleaseInfo release)
|
||||||
|
{
|
||||||
|
if (item.PublishedDate == release.PublishDate)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HasSameIndexer(item, release.Indexer) &&
|
||||||
|
HasSamePublishedDate(item, release.PublishDate) &&
|
||||||
|
HasSameSize(item, release.Size))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool SameTorrent(ReleaseComparerModel item, TorrentInfo release)
|
||||||
|
{
|
||||||
|
if (release.InfoHash.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return release.InfoHash.Equals(item.TorrentInfoHash, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HasSameIndexer(item, release.Indexer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasSameIndexer(ReleaseComparerModel item, string indexer)
|
||||||
|
{
|
||||||
|
if (item.Indexer.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.Indexer.Equals(indexer, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasSamePublishedDate(ReleaseComparerModel item, DateTime publishedDate)
|
||||||
|
{
|
||||||
|
if (!item.PublishedDate.HasValue)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.PublishedDate.Value.AddMinutes(-2) <= publishedDate &&
|
||||||
|
item.PublishedDate.Value.AddMinutes(2) >= publishedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasSameSize(ReleaseComparerModel item, long size)
|
||||||
|
{
|
||||||
|
if (item.Size == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var difference = Math.Abs(item.Size - size);
|
||||||
|
|
||||||
|
return difference <= 2.Megabytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Exceptions;
|
using NzbDrone.Core.Exceptions;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.IndexerSearch;
|
using NzbDrone.Core.IndexerSearch;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
|
@ -31,6 +32,7 @@ public class ReleaseController : RestController<ReleaseResource>
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
|
private readonly IHistoryService _historyService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly QualityProfile _qualityProfile;
|
private readonly QualityProfile _qualityProfile;
|
||||||
|
|
@ -44,6 +46,7 @@ public ReleaseController(IFetchAndParseRss rssFetcherAndParser,
|
||||||
ISeriesService seriesService,
|
ISeriesService seriesService,
|
||||||
IEpisodeService episodeService,
|
IEpisodeService episodeService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
IHistoryService historyService,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IQualityProfileService qualityProfileService,
|
IQualityProfileService qualityProfileService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
|
|
@ -56,6 +59,7 @@ public ReleaseController(IFetchAndParseRss rssFetcherAndParser,
|
||||||
_seriesService = seriesService;
|
_seriesService = seriesService;
|
||||||
_episodeService = episodeService;
|
_episodeService = episodeService;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
|
_historyService = historyService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_qualityProfile = qualityProfileService.GetDefaultProfile(string.Empty);
|
_qualityProfile = qualityProfileService.GetDefaultProfile(string.Empty);
|
||||||
|
|
@ -204,8 +208,9 @@ private async Task<List<ReleaseResource>> GetEpisodeReleases(int episodeId)
|
||||||
{
|
{
|
||||||
var decisions = await _releaseSearchService.EpisodeSearch(episodeId, true, true);
|
var decisions = await _releaseSearchService.EpisodeSearch(episodeId, true, true);
|
||||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
||||||
|
var history = _historyService.FindByEpisodeId(episodeId);
|
||||||
|
|
||||||
return MapDecisions(prioritizedDecisions);
|
return MapDecisions(prioritizedDecisions, history);
|
||||||
}
|
}
|
||||||
catch (SearchFailedException ex)
|
catch (SearchFailedException ex)
|
||||||
{
|
{
|
||||||
|
|
@ -224,8 +229,9 @@ private async Task<List<ReleaseResource>> GetSeasonReleases(int seriesId, int se
|
||||||
{
|
{
|
||||||
var decisions = await _releaseSearchService.SeasonSearch(seriesId, seasonNumber, false, false, true, true);
|
var decisions = await _releaseSearchService.SeasonSearch(seriesId, seasonNumber, false, false, true, true);
|
||||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
||||||
|
var history = _historyService.GetBySeason(seriesId, seasonNumber, null);
|
||||||
|
|
||||||
return MapDecisions(prioritizedDecisions);
|
return MapDecisions(prioritizedDecisions, history);
|
||||||
}
|
}
|
||||||
catch (SearchFailedException ex)
|
catch (SearchFailedException ex)
|
||||||
{
|
{
|
||||||
|
|
@ -244,7 +250,7 @@ private async Task<List<ReleaseResource>> GetRss()
|
||||||
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
|
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
|
||||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
||||||
|
|
||||||
return MapDecisions(prioritizedDecisions);
|
return MapDecisions(prioritizedDecisions, new List<EpisodeHistory>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCacheKey(ReleaseResource resource)
|
private string GetCacheKey(ReleaseResource resource)
|
||||||
|
|
@ -257,7 +263,7 @@ private string GetCacheKey(ReleaseGrabResource resource)
|
||||||
return string.Concat(resource.IndexerId, "_", resource.Guid);
|
return string.Concat(resource.IndexerId, "_", resource.Guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision> decisions)
|
private List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision> decisions, List<EpisodeHistory> history)
|
||||||
{
|
{
|
||||||
var result = new List<ReleaseResource>();
|
var result = new List<ReleaseResource>();
|
||||||
|
|
||||||
|
|
@ -265,9 +271,70 @@ private List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision> decisio
|
||||||
{
|
{
|
||||||
var release = downloadDecision.MapDecision(result.Count, _qualityProfile);
|
var release = downloadDecision.MapDecision(result.Count, _qualityProfile);
|
||||||
|
|
||||||
|
release.History = AddHistory(downloadDecision.RemoteEpisode.Release, history);
|
||||||
|
|
||||||
result.Add(release);
|
result.Add(release);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReleaseHistoryResource? AddHistory(ReleaseInfo release, List<EpisodeHistory> history)
|
||||||
|
{
|
||||||
|
var grabbed = history.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed &&
|
||||||
|
h.Data.TryGetValue("guid", out var guid) &&
|
||||||
|
guid == release.Guid);
|
||||||
|
|
||||||
|
if (grabbed == null && release.DownloadProtocol == DownloadProtocol.Torrent)
|
||||||
|
{
|
||||||
|
if (release is not TorrentInfo torrentInfo)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrentInfo.InfoHash.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
grabbed = history.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed &&
|
||||||
|
ReleaseComparer.SameTorrent(new ReleaseComparerModel(h),
|
||||||
|
torrentInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grabbed == null)
|
||||||
|
{
|
||||||
|
grabbed = history.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed &&
|
||||||
|
h.SourceTitle == release.Title &&
|
||||||
|
(DownloadProtocol)Convert.ToInt32(
|
||||||
|
h.Data.GetValueOrDefault("protocol")) ==
|
||||||
|
DownloadProtocol.Torrent &&
|
||||||
|
ReleaseComparer.SameTorrent(new ReleaseComparerModel(h),
|
||||||
|
torrentInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (grabbed == null)
|
||||||
|
{
|
||||||
|
grabbed = history.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed &&
|
||||||
|
ReleaseComparer.SameNzb(new ReleaseComparerModel(h),
|
||||||
|
release));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grabbed != null)
|
||||||
|
{
|
||||||
|
var resource = new ReleaseHistoryResource
|
||||||
|
{
|
||||||
|
Grabbed = grabbed.Date,
|
||||||
|
};
|
||||||
|
|
||||||
|
var failedHistory = history.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.DownloadFailed &&
|
||||||
|
h.DownloadId == grabbed.DownloadId);
|
||||||
|
|
||||||
|
if (failedHistory != null)
|
||||||
|
{
|
||||||
|
resource.Failed = failedHistory.Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
src/Sonarr.Api.V5/Release/ReleaseHistoryResource.cs
Normal file
7
src/Sonarr.Api.V5/Release/ReleaseHistoryResource.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Sonarr.Api.V5.Release;
|
||||||
|
|
||||||
|
public class ReleaseHistoryResource
|
||||||
|
{
|
||||||
|
public DateTime? Grabbed { get; set; }
|
||||||
|
public DateTime? Failed { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ public class ReleaseResource : RestResource
|
||||||
public ParsedEpisodeInfoResource? ParsedInfo { get; set; }
|
public ParsedEpisodeInfoResource? ParsedInfo { get; set; }
|
||||||
public ReleaseInfoResource? Release { get; set; }
|
public ReleaseInfoResource? Release { get; set; }
|
||||||
public ReleaseDecisionResource? Decision { get; set; }
|
public ReleaseDecisionResource? Decision { get; set; }
|
||||||
|
public ReleaseHistoryResource? History { get; set; }
|
||||||
public int QualityWeight { get; set; }
|
public int QualityWeight { get; set; }
|
||||||
public List<Language> Languages { get; set; } = [];
|
public List<Language> Languages { get; set; } = [];
|
||||||
public int? MappedSeasonNumber { get; set; }
|
public int? MappedSeasonNumber { get; set; }
|
||||||
|
|
@ -56,20 +57,6 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<ReleaseResource> MapDecisions(this IEnumerable<DownloadDecision> decisions, QualityProfile profile)
|
|
||||||
{
|
|
||||||
var result = new List<ReleaseResource>();
|
|
||||||
|
|
||||||
foreach (var downloadDecision in decisions)
|
|
||||||
{
|
|
||||||
var release = MapDecision(downloadDecision, result.Count, profile);
|
|
||||||
|
|
||||||
result.Add(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReleaseResource MapDecision(this DownloadDecision decision, int initialWeight, QualityProfile profile)
|
public static ReleaseResource MapDecision(this DownloadDecision decision, int initialWeight, QualityProfile profile)
|
||||||
{
|
{
|
||||||
var release = decision.ToResource();
|
var release = decision.ToResource();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue