1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-04 10:02:40 +01:00

AHD, PTP and HDB support the new indexer flags too now! Indexer flags can be preferred over other releases.

This commit is contained in:
Leonardo Galli 2017-04-17 17:12:09 +02:00 committed by GitHub
parent 10091b9454
commit 433ae019de
21 changed files with 424 additions and 132 deletions

View File

@ -8,6 +8,7 @@ public class IndexerConfigResource : RestResource
public int MinimumAge { get; set; }
public int Retention { get; set; }
public int RssSyncInterval { get; set; }
public bool PreferIndexerFlags { get; set; }
public int AvailabilityDelay { get; set; }
public bool AllowHardcodedSubs { get; set; }
public string WhitelistedHardcodedSubs { get; set; }
@ -22,6 +23,7 @@ public static IndexerConfigResource ToResource(IConfigService model)
MinimumAge = model.MinimumAge,
Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval,
PreferIndexerFlags = model.PreferIndexerFlags,
AvailabilityDelay = model.AvailabilityDelay,
AllowHardcodedSubs = model.AllowHardcodedSubs,
WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs,

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,86 @@
using System;
using System.Linq;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.PTPTests
{
[TestFixture]
public class PTPFixture : CoreTest<PassThePopcorn>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "PTP",
Settings = new PassThePopcornSettings() { Passkey = "fakekey", Username = "asdf", Password = "sad" }
};
}
[TestCase("Files/Indexers/PTP/imdbsearch.json")]
public void should_parse_feed_from_PTP(string fileName)
{
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
System.IO.StringWriter authStream = new System.IO.StringWriter();
Json.Serialize(authResponse, authStream);
var responseJson = ReadAllText(fileName);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST)))
.Returns<HttpRequest>(r => new HttpResponse(r,new HttpHeader(), authStream.ToString()));
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader {ContentType = HttpAccept.Json.Value}, responseJson));
var torrents = Subject.FetchRecent();
torrents.Should().HaveCount(293);
torrents.First().Should().BeOfType<PassThePopcornInfo>();
var first = torrents.First() as TorrentInfo;
first.Guid.Should().Be("PassThePopcorn-483521");
first.Title.Should().Be("The.Night.Of.S01.720p.HDTV.x264-BTN");
first.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
first.DownloadUrl.Should().Be("https://passthepopcorn.me/torrents.php?action=download&id=483521&authkey=00000000000000000000000000000000&torrent_pass=00000000000000000000000000000000");
first.InfoUrl.Should().Be("https://passthepopcorn.me/torrents.php?id=148131&torrentid=483521");
first.PublishDate.Should().Be(DateTime.Parse("2017-04-17T12:13:42+0000").ToUniversalTime());
first.Size.Should().Be(9370933376);
first.InfoHash.Should().BeNullOrEmpty();
first.MagnetUrl.Should().BeNullOrEmpty();
first.Peers.Should().Be(3);
first.Seeders.Should().Be(1);
torrents.Any(t => t.IndexerFlags.HasFlag(IndexerFlags.G_Freeleech)).Should().Be(true);
}
[Test]
public void should_warn_on_wrong_passkey()
{
var responseJson = new { status = 5, message = "Invalid authentication credentials" }.ToJson();
Mocker.GetMock<IHttpClient>()
.Setup(v => v.Execute(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(),
Encoding.UTF8.GetBytes(responseJson)));
var torrents = Subject.FetchRecent();
torrents.Should().BeEmpty();
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

@ -389,6 +389,10 @@
<Content Include="Files\ArabicRomanNumeralDictionary.JSON">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Compile Include="IndexerTests\PTPTests\PTPFixture.cs" />
<None Include="Files\Indexers\PTP\imdbsearch.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
@ -574,6 +578,8 @@
<Folder Include="DataAugmentation\SceneNumbering\" />
<Folder Include="Providers\" />
<Folder Include="ProviderTests\UpdateProviderTests\" />
<Folder Include="IndexerTests\PTPTests\" />
<Folder Include="Files\Indexers\PTP\" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

View File

@ -190,6 +190,13 @@ public bool EnableCompletedDownloadHandling
set { SetValue("EnableCompletedDownloadHandling", value); }
}
public bool PreferIndexerFlags
{
get { return GetValueBoolean("PreferIndexerFlags", false); }
set {SetValue("PreferIndexerFlags", value);}
}
public bool AllowHardcodedSubs
{
get { return GetValueBoolean("AllowHardcodedSubs", false); }

View File

@ -46,6 +46,8 @@ public interface IConfigService
int RssSyncInterval { get; set; }
int MinimumAge { get; set; }
bool PreferIndexerFlags { get; set; }
int AvailabilityDelay { get; set; }
bool AllowHardcodedSubs { get; set; }

View File

@ -4,18 +4,21 @@
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine
{
public class DownloadDecisionComparer : IComparer<DownloadDecision>
{
private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y);
public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y);
public DownloadDecisionComparer(IDelayProfileService delayProfileService)
public DownloadDecisionComparer(IDelayProfileService delayProfileService, IConfigService configService)
{
_delayProfileService = delayProfileService;
_configService = configService;
}
public int Compare(DownloadDecision x, DownloadDecision y)
@ -24,6 +27,7 @@ public int Compare(DownloadDecision x, DownloadDecision y)
{
CompareQuality,
ComparePreferredWords,
CompareIndexerFlags,
CompareProtocol,
ComparePeersIfTorrent,
CompareAgeIfUsenet,
@ -84,7 +88,22 @@ private int ComparePreferredWords(DownloadDecision x, DownloadDecision y)
return num;
});
; }
}
private int CompareIndexerFlags(DownloadDecision x, DownloadDecision y)
{
var releaseX = x.RemoteMovie.Release;
var releaseY = y.RemoteMovie.Release;
if (_configService.PreferIndexerFlags)
{
return CompareBy(x.RemoteMovie.Release, y.RemoteMovie.Release, release => ScoreFlags(release.IndexerFlags));
}
else
{
return 0;
}
}
private int CompareProtocol(DownloadDecision x, DownloadDecision y)
{
@ -185,5 +204,34 @@ private int CompareSize(DownloadDecision x, DownloadDecision y)
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes()));
}
private int ScoreFlags(IndexerFlags flags)
{
var flagValues = Enum.GetValues(typeof(IndexerFlags));
var score = 0;
foreach (IndexerFlags value in flagValues)
{
if ((flags & value) == value)
{
switch (value)
{
case IndexerFlags.G_DoubleUpload:
case IndexerFlags.G_Freeleech:
case IndexerFlags.PTP_Approved:
case IndexerFlags.PTP_Golden:
case IndexerFlags.HDB_Internal:
score += 2;
break;
case IndexerFlags.G_Halfleech:
score += 1;
break;
}
}
}
return score;
}
}
}

View File

@ -1,6 +1,7 @@
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine
{
@ -13,10 +14,12 @@ public interface IPrioritizeDownloadDecision
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
{
private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService)
public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService, IConfigService configService)
{
_delayProfileService = delayProfileService;
_configService = configService;
}
public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions)
@ -24,7 +27,7 @@ public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisio
return decisions.Where(c => c.RemoteEpisode.Series != null)
.GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) =>
{
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService));
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
})
.SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteEpisode.Series == null))
@ -36,7 +39,7 @@ public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision
return decisions.Where(c => c.RemoteMovie.Movie != null)
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
{
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService));
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
})
.SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.Movie == null))

View File

@ -69,6 +69,12 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var id = torrent.Id;
var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}";
IndexerFlags flags = 0;
if (torrent.Freeleech == "1.00")
{
flags |= IndexerFlags.G_Freeleech;
}
torrentInfos.Add(new TorrentInfo()
{
@ -80,7 +86,8 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(),
ImdbId = int.Parse(torrent.ImdbId.Substring(2))
ImdbId = int.Parse(torrent.ImdbId.Substring(2)),
IndexerFlags = flags,
});
}
}

View File

@ -53,6 +53,18 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
var id = result.Id;
var internalRelease = (result.TypeOrigin == 1 ? true : false);
IndexerFlags flags = 0;
if (result.FreeLeech == "yes")
{
flags |= IndexerFlags.G_Freeleech;
}
if (internalRelease)
{
flags |= IndexerFlags.HDB_Internal;
}
torrentInfos.Add(new HDBitsInfo()
{
Guid = string.Format("HDBits-{0}", id),
@ -65,7 +77,8 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(),
Internal = internalRelease,
ImdbId = result.ImdbInfo?.Id ?? 0
ImdbId = result.ImdbInfo?.Id ?? 0,
IndexerFlags = flags
});
}

View File

@ -28,6 +28,7 @@ public class Torrent
public string ReleaseName { get; set; }
public bool Checked { get; set; }
public bool GoldenPopcorn { get; set; }
public string FreeleechType { get; set; }
}
public class Movie

View File

@ -67,6 +67,11 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
flags |= IndexerFlags.PTP_Approved;//title = $"{title} ✔";
}
if (torrent.FreeleechType == "Freeleech")
{
flags |= IndexerFlags.G_Freeleech;
}
// Only add approved torrents
if (_settings.RequireApproved && torrent.Checked)
{

View File

@ -46,6 +46,9 @@ protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInf
torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2));
}
}
torrentInfo.IndexerFlags = GetFlags(item);
return torrentInfo;
}
@ -151,6 +154,32 @@ protected override string GetMagnetUrl(XElement item)
return base.GetPeers(item);
}
protected IndexerFlags GetFlags(XElement item)
{
IndexerFlags flags = 0;
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
if (uploadFactor == 2)
{
flags |= IndexerFlags.G_DoubleUpload;
}
if (downloadFactor == 0.5)
{
flags |= IndexerFlags.G_Halfleech;
}
if (downloadFactor == 0.0)
{
flags |= IndexerFlags.G_Freeleech;
}
return flags;
}
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
{
var attr = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.CurrentCultureIgnoreCase));
@ -160,6 +189,20 @@ protected string TryGetTorznabAttribute(XElement item, string key, string defaul
return attr.Attribute("value").Value;
}
return defaultValue;
}
protected float TryGetFloatTorznabAttribute(XElement item, string key, float defaultValue = 0)
{
var attr = TryGetTorznabAttribute(item, key, defaultValue.ToString());
float result = 0;
if (float.TryParse(attr, out result))
{
return result;
}
return defaultValue;
}
}

View File

@ -97,7 +97,11 @@ public virtual string ToString(string format)
[Flags]
public enum IndexerFlags
{
PTP_Golden = 1, //PTP
PTP_Approved = 2, //PTP
G_Freeleech = 1, //General
G_Halfleech = 2, //General, only 1/2 of download counted
G_DoubleUpload = 4, //General
PTP_Golden = 8, //PTP
PTP_Approved = 16, //PTP
HDB_Internal = 32 //HDBits
}
}

View File

@ -0,0 +1,58 @@
var Backgrid = require('backgrid');
var Marionette = require('marionette');
require('bootstrap');
module.exports = Backgrid.Cell.extend({
className : 'edition-cell',
//template : 'Cells/EditionCellTemplate',
render : function() {
var flags = this.model.get("indexerFlags");
if (!flags) {
return this;
}
var html = "";
if (flags) {
_.each(flags, function(flag){
var addon = "";
var title = "";
switch (flag) {
case "G_Freeleech":
addon = "⬇";
title = "Freeleech";
break;
case "G_Halfleech":
addon = "⇩";
title = "50% Freeleech";
break;
case "G_DoubleUpload":
addon = "⬆";
title = "Double upload";
break;
case "PTP_Golden":
addon = "🍿";
title = "Golden";
break;
case "PTP_Approved":
addon = "✔";
title = "Approved by PTP"
case "HDB_Internal":
addon = "⭐️";
title = "HDBits Internal";
break;
}
if (addon != "") {
html += "<span title='{0}'>{1}</span> ".format(title, addon);
}
});
}
this.$el.html(html);
return this;
}
});

View File

@ -10,22 +10,7 @@ module.exports = NzbDroneCell.extend({
var infoUrl = this.model.get('infoUrl');
var flags = this.model.get("indexerFlags");
if (flags) {
_.each(flags, function(flag){
var addon = "";
switch (flag) {
case "PTP_Golden":
addon = "🍿";
break;
case "PTP_Approved":
addon = "✔";
break;
}
title += addon;
});
}
if (infoUrl) {
this.$el.html('<a href="{0}">{1}</a>'.format(infoUrl, title));

View File

@ -9,6 +9,7 @@ var AgeCell = require('../../Release/AgeCell');
var ProtocolCell = require('../../Release/ProtocolCell');
var PeersCell = require('../../Release/PeersCell');
var EditionCell = require('../../Cells/EditionCell');
var IndexerFlagsCell = require('../../Cells/IndexerFlagsCell');
module.exports = Marionette.Layout.extend({
template : 'Movies/Search/ManualLayoutTemplate',
@ -39,6 +40,11 @@ module.exports = Marionette.Layout.extend({
cell : EditionCell,
title : "Edition",
},
{
name : 'flags',
label : 'Flags',
cell : IndexerFlagsCell,
},
{
name : 'indexer',
label : 'Indexer',

View File

@ -33,6 +33,33 @@ var Collection = PagableCollection.extend({
"edition" : {
sortKey : "edition"
},
"flags" : {
sortValue : function(model) {
var flags = model.get("indexerFlags");
var weight = 0;
if (flags) {
_.each(flags, function(flag){
var addon = "";
var title = "";
switch (flag) {
case "G_Halfleech":
weight += 1;
break;
case "G_Freeleech":
case "G_DoubleUpload":
case "PTP_Approved":
case "PTP_Golden":
case "HDB_Internal":
weight += 2;
break;
}
});
}
return weight;
}
},
'download' : {
sortKey : 'releaseWeight'
},

View File

@ -8,7 +8,7 @@ var QualityCell = require('../Cells/QualityCell');
var ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
var LoadingView = require('../Shared/LoadingView');
var EditionCell = require('../Cells/EditionCell');
var ReleaseTitleCell = require("./ReleaseTitleCell");
var ReleaseTitleCell = require("../Cells/ReleaseTitleCell");
module.exports = Marionette.Layout.extend({
template : 'Release/ReleaseLayoutTemplate',

View File

@ -1,33 +0,0 @@
var _ = require('underscore');
var Backgrid = require('backgrid');
var FormatHelpers = require('../Shared/FormatHelpers');
module.exports = Backgrid.Cell.extend({
className : 'title-cell',
render : function() {
debugger;
var title = this.model.get('title');
var flags = this.model.get("indexerFlags");
if (flags) {
_.each(flags, function(flag){
var addon = "";
debugger;
switch (flag) {
case "PTP_Golden":
addon = "🍿";
break;
case "PTP_Approved":
addon = "✔";
break;
}
title += addon;
});
}
this.$el.html(title);
return this;
}
});

View File

@ -25,6 +25,27 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Prefer Special Indexer Flags</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-info" title="If set to yes, the more indexer flags (such as Golden, Approved, Internal, Freeleech, Double upload, etc.) a release has the more priorized it will be. Quality and Preferred words would still come first."/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="preferIndexerFlags" class="x-completed-download-handling"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
</div>
<div class="form-group advanced-setting">
<label class="col-sm-3 control-label">RSS Sync Interval</label>