mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-14 09:03:49 -07:00
Fixed: Blocklisting pending releases
Closes #2357 Closes #2478 Closes #3247 Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
parent
0a52be5c9e
commit
4ea5c68216
7 changed files with 144 additions and 71 deletions
|
@ -299,6 +299,17 @@ class Queue extends Component {
|
||||||
return !!(item && item.artistId && item.albumId);
|
return !!(item && item.artistId && item.albumId);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
|
allPending={isConfirmRemoveModalOpen && (
|
||||||
|
selectedIds.every((id) => {
|
||||||
|
const item = items.find((i) => i.id === id);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
|
||||||
|
})
|
||||||
|
)}
|
||||||
onRemovePress={this.onRemoveSelectedConfirmed}
|
onRemovePress={this.onRemoveSelectedConfirmed}
|
||||||
onModalClose={this.onConfirmRemoveModalClose}
|
onModalClose={this.onConfirmRemoveModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -394,6 +394,7 @@ class QueueRow extends Component {
|
||||||
isOpen={isRemoveQueueItemModalOpen}
|
isOpen={isRemoveQueueItemModalOpen}
|
||||||
sourceTitle={title}
|
sourceTitle={title}
|
||||||
canIgnore={!!artist}
|
canIgnore={!!artist}
|
||||||
|
isPending={isPending}
|
||||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||||
onModalClose={this.onRemoveQueueItemModalClose}
|
onModalClose={this.onRemoveQueueItemModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -72,7 +72,8 @@ class RemoveQueueItemModal extends Component {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
canIgnore
|
canIgnore,
|
||||||
|
isPending
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { removeFromClient, blocklist, skipRedownload } = this.state;
|
const { removeFromClient, blocklist, skipRedownload } = this.state;
|
||||||
|
@ -95,10 +96,11 @@ class RemoveQueueItemModal extends Component {
|
||||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
Are you sure you want to remove '{sourceTitle}' from the queue?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
isPending ?
|
||||||
|
null :
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||||
{translate('RemoveFromDownloadClient')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
@ -109,6 +111,7 @@ class RemoveQueueItemModal extends Component {
|
||||||
onChange={this.onRemoveFromClientChange}
|
onChange={this.onRemoveFromClientChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
}
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
|
@ -164,6 +167,7 @@ RemoveQueueItemModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
canIgnore: PropTypes.bool.isRequired,
|
canIgnore: PropTypes.bool.isRequired,
|
||||||
|
isPending: PropTypes.bool.isRequired,
|
||||||
onRemovePress: PropTypes.func.isRequired,
|
onRemovePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -73,7 +73,8 @@ class RemoveQueueItemsModal extends Component {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
canIgnore
|
canIgnore,
|
||||||
|
allPending
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { removeFromClient, blocklist, skipRedownload } = this.state;
|
const { removeFromClient, blocklist, skipRedownload } = this.state;
|
||||||
|
@ -96,10 +97,11 @@ class RemoveQueueItemsModal extends Component {
|
||||||
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', [selectedCount]) : translate('RemoveSelectedItemQueueMessageText')}
|
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', [selectedCount]) : translate('RemoveSelectedItemQueueMessageText')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
allPending ?
|
||||||
|
null :
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||||
{translate('RemoveFromDownloadClient')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
@ -110,6 +112,7 @@ class RemoveQueueItemsModal extends Component {
|
||||||
onChange={this.onRemoveFromClientChange}
|
onChange={this.onRemoveFromClientChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
}
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
|
@ -165,6 +168,7 @@ RemoveQueueItemsModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
selectedCount: PropTypes.number.isRequired,
|
selectedCount: PropTypes.number.isRequired,
|
||||||
canIgnore: PropTypes.bool.isRequired,
|
canIgnore: PropTypes.bool.isRequired,
|
||||||
|
allPending: PropTypes.bool.isRequired,
|
||||||
onRemovePress: PropTypes.func.isRequired,
|
onRemovePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Lidarr.Http.REST;
|
||||||
using Lidarr.Http.REST.Attributes;
|
using Lidarr.Http.REST.Attributes;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Blocklisting;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
@ -17,6 +18,7 @@ using NzbDrone.Core.Profiles.Qualities;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Queue;
|
using NzbDrone.Core.Queue;
|
||||||
using NzbDrone.SignalR;
|
using NzbDrone.SignalR;
|
||||||
|
using Sentry.Protocol;
|
||||||
|
|
||||||
namespace Lidarr.Api.V1.Queue
|
namespace Lidarr.Api.V1.Queue
|
||||||
{
|
{
|
||||||
|
@ -32,6 +34,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
private readonly IFailedDownloadService _failedDownloadService;
|
private readonly IFailedDownloadService _failedDownloadService;
|
||||||
private readonly IIgnoredDownloadService _ignoredDownloadService;
|
private readonly IIgnoredDownloadService _ignoredDownloadService;
|
||||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
|
private readonly IBlocklistService _blocklistService;
|
||||||
|
|
||||||
public QueueController(IBroadcastSignalRMessage broadcastSignalRMessage,
|
public QueueController(IBroadcastSignalRMessage broadcastSignalRMessage,
|
||||||
IQueueService queueService,
|
IQueueService queueService,
|
||||||
|
@ -40,7 +43,8 @@ namespace Lidarr.Api.V1.Queue
|
||||||
ITrackedDownloadService trackedDownloadService,
|
ITrackedDownloadService trackedDownloadService,
|
||||||
IFailedDownloadService failedDownloadService,
|
IFailedDownloadService failedDownloadService,
|
||||||
IIgnoredDownloadService ignoredDownloadService,
|
IIgnoredDownloadService ignoredDownloadService,
|
||||||
IProvideDownloadClient downloadClientProvider)
|
IProvideDownloadClient downloadClientProvider,
|
||||||
|
IBlocklistService blocklistService)
|
||||||
: base(broadcastSignalRMessage)
|
: base(broadcastSignalRMessage)
|
||||||
{
|
{
|
||||||
_queueService = queueService;
|
_queueService = queueService;
|
||||||
|
@ -49,6 +53,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
_failedDownloadService = failedDownloadService;
|
_failedDownloadService = failedDownloadService;
|
||||||
_ignoredDownloadService = ignoredDownloadService;
|
_ignoredDownloadService = ignoredDownloadService;
|
||||||
_downloadClientProvider = downloadClientProvider;
|
_downloadClientProvider = downloadClientProvider;
|
||||||
|
_blocklistService = blocklistService;
|
||||||
|
|
||||||
_qualityComparer = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty));
|
_qualityComparer = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty));
|
||||||
}
|
}
|
||||||
|
@ -62,29 +67,62 @@ namespace Lidarr.Api.V1.Queue
|
||||||
[RestDeleteById]
|
[RestDeleteById]
|
||||||
public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false)
|
public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false)
|
||||||
{
|
{
|
||||||
var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload);
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
||||||
|
|
||||||
if (trackedDownload != null)
|
if (pendingRelease != null)
|
||||||
{
|
{
|
||||||
_trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId);
|
Remove(pendingRelease);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var trackedDownload = GetTrackedDownload(id);
|
||||||
|
|
||||||
|
if (trackedDownload == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload);
|
||||||
|
_trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("bulk")]
|
[HttpDelete("bulk")]
|
||||||
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false)
|
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false)
|
||||||
{
|
{
|
||||||
var trackedDownloadIds = new List<string>();
|
var trackedDownloadIds = new List<string>();
|
||||||
|
var pendingToRemove = new List<NzbDrone.Core.Queue.Queue>();
|
||||||
|
var trackedToRemove = new List<TrackedDownload>();
|
||||||
|
|
||||||
foreach (var id in resource.Ids)
|
foreach (var id in resource.Ids)
|
||||||
{
|
{
|
||||||
var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload);
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
||||||
|
|
||||||
|
if (pendingRelease != null)
|
||||||
|
{
|
||||||
|
pendingToRemove.Add(pendingRelease);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackedDownload = GetTrackedDownload(id);
|
||||||
|
|
||||||
if (trackedDownload != null)
|
if (trackedDownload != null)
|
||||||
{
|
{
|
||||||
trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId);
|
trackedToRemove.Add(trackedDownload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var pendingRelease in pendingToRemove.DistinctBy(p => p.Id))
|
||||||
|
{
|
||||||
|
Remove(pendingRelease);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var trackedDownload in trackedToRemove.DistinctBy(t => t.DownloadItem.DownloadId))
|
||||||
|
{
|
||||||
|
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload);
|
||||||
|
trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId);
|
||||||
|
}
|
||||||
|
|
||||||
_trackedDownloadService.StopTracking(trackedDownloadIds);
|
_trackedDownloadService.StopTracking(trackedDownloadIds);
|
||||||
|
|
||||||
return new { };
|
return new { };
|
||||||
|
@ -195,24 +233,14 @@ namespace Lidarr.Api.V1.Queue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackedDownload Remove(int id, bool removeFromClient, bool blocklist, bool skipRedownload)
|
private void Remove(NzbDrone.Core.Queue.Queue pendingRelease)
|
||||||
{
|
|
||||||
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
|
||||||
|
|
||||||
if (pendingRelease != null)
|
|
||||||
{
|
{
|
||||||
|
_blocklistService.Block(pendingRelease.RemoteAlbum, "Pending release manually blocklisted");
|
||||||
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
|
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackedDownload = GetTrackedDownload(id);
|
private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist, bool skipRedownload)
|
||||||
|
|
||||||
if (trackedDownload == null)
|
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeFromClient)
|
if (removeFromClient)
|
||||||
{
|
{
|
||||||
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
||||||
|
|
|
@ -16,6 +16,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||||
{
|
{
|
||||||
bool Blocklisted(int artistId, ReleaseInfo release);
|
bool Blocklisted(int artistId, ReleaseInfo release);
|
||||||
PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec);
|
PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec);
|
||||||
|
void Block(RemoteAlbum remoteAlbum, string message);
|
||||||
void Delete(int id);
|
void Delete(int id);
|
||||||
void Delete(List<int> ids);
|
void Delete(List<int> ids);
|
||||||
}
|
}
|
||||||
|
@ -66,6 +67,30 @@ namespace NzbDrone.Core.Blocklisting
|
||||||
return _blocklistRepository.GetPaged(pagingSpec);
|
return _blocklistRepository.GetPaged(pagingSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Block(RemoteAlbum remoteAlbum, string message)
|
||||||
|
{
|
||||||
|
var blocklist = new Blocklist
|
||||||
|
{
|
||||||
|
ArtistId = remoteAlbum.Artist.Id,
|
||||||
|
AlbumIds = remoteAlbum.Albums.Select(e => e.Id).ToList(),
|
||||||
|
SourceTitle = remoteAlbum.Release.Title,
|
||||||
|
Quality = remoteAlbum.ParsedAlbumInfo.Quality,
|
||||||
|
Date = DateTime.UtcNow,
|
||||||
|
PublishedDate = remoteAlbum.Release.PublishDate,
|
||||||
|
Size = remoteAlbum.Release.Size,
|
||||||
|
Indexer = remoteAlbum.Release.Indexer,
|
||||||
|
Protocol = remoteAlbum.Release.DownloadProtocol,
|
||||||
|
Message = message,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (remoteAlbum.Release is TorrentInfo torrentRelease)
|
||||||
|
{
|
||||||
|
blocklist.TorrentInfoHash = torrentRelease.InfoHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
_blocklistRepository.Insert(blocklist);
|
||||||
|
}
|
||||||
|
|
||||||
public void Delete(int id)
|
public void Delete(int id)
|
||||||
{
|
{
|
||||||
_blocklistRepository.Delete(id);
|
_blocklistRepository.Delete(id);
|
||||||
|
|
|
@ -137,13 +137,6 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ILookup<int, PendingRelease> CreateAlbumLookup(IEnumerable<PendingRelease> alreadyPending)
|
|
||||||
{
|
|
||||||
return alreadyPending.SelectMany(v => v.RemoteAlbum.Albums
|
|
||||||
.Select(d => new { Album = d, PendingRelease = v }))
|
|
||||||
.ToLookup(v => v.Album.Id, v => v.PendingRelease);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ReleaseInfo> GetPending()
|
public List<ReleaseInfo> GetPending()
|
||||||
{
|
{
|
||||||
var releases = _repository.All().Select(p =>
|
var releases = _repository.All().Select(p =>
|
||||||
|
@ -163,13 +156,6 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
return releases;
|
return releases;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ReleaseInfo> FilterBlockedIndexers(List<ReleaseInfo> releases)
|
|
||||||
{
|
|
||||||
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedProviders().Select(v => v.ProviderId));
|
|
||||||
|
|
||||||
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RemoteAlbum> GetPendingRemoteAlbums(int artistId)
|
public List<RemoteAlbum> GetPendingRemoteAlbums(int artistId)
|
||||||
{
|
{
|
||||||
return IncludeRemoteAlbums(_repository.AllByArtistId(artistId)).Select(v => v.RemoteAlbum).ToList();
|
return IncludeRemoteAlbums(_repository.AllByArtistId(artistId)).Select(v => v.RemoteAlbum).ToList();
|
||||||
|
@ -263,6 +249,20 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
.MaxBy(p => p.Release.AgeHours);
|
.MaxBy(p => p.Release.AgeHours);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ILookup<int, PendingRelease> CreateAlbumLookup(IEnumerable<PendingRelease> alreadyPending)
|
||||||
|
{
|
||||||
|
return alreadyPending.SelectMany(v => v.RemoteAlbum.Albums
|
||||||
|
.Select(d => new { Album = d, PendingRelease = v }))
|
||||||
|
.ToLookup(v => v.Album.Id, v => v.PendingRelease);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ReleaseInfo> FilterBlockedIndexers(List<ReleaseInfo> releases)
|
||||||
|
{
|
||||||
|
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedProviders().Select(v => v.ProviderId));
|
||||||
|
|
||||||
|
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private List<PendingRelease> GetPendingReleases()
|
private List<PendingRelease> GetPendingReleases()
|
||||||
{
|
{
|
||||||
return IncludeRemoteAlbums(_repository.All().ToList());
|
return IncludeRemoteAlbums(_repository.All().ToList());
|
||||||
|
@ -354,13 +354,6 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
|
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Func<PendingRelease, bool> MatchingReleasePredicate(ReleaseInfo release)
|
|
||||||
{
|
|
||||||
return p => p.Title == release.Title &&
|
|
||||||
p.Release.PublishDate == release.PublishDate &&
|
|
||||||
p.Release.Indexer == release.Indexer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetDelay(RemoteAlbum remoteAlbum)
|
private int GetDelay(RemoteAlbum remoteAlbum)
|
||||||
{
|
{
|
||||||
var delayProfile = _delayProfileService.AllForTags(remoteAlbum.Artist.Tags).OrderBy(d => d.Order).First();
|
var delayProfile = _delayProfileService.AllForTags(remoteAlbum.Artist.Tags).OrderBy(d => d.Order).First();
|
||||||
|
@ -455,5 +448,12 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
{
|
{
|
||||||
RemoveRejected(message.ProcessedDecisions.Rejected);
|
RemoveRejected(message.ProcessedDecisions.Rejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Func<PendingRelease, bool> MatchingReleasePredicate(ReleaseInfo release)
|
||||||
|
{
|
||||||
|
return p => p.Title == release.Title &&
|
||||||
|
p.Release.PublishDate == release.PublishDate &&
|
||||||
|
p.Release.Indexer == release.Indexer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue