Quality Order can now be change on per Quality Profile. Quality Title used in Renaming can now be changed by the user. Both options require Advanced Settings to be enabled.

pull/4/head
Taloth Saldono 10 years ago committed by Taloth
parent 47a8d93c18
commit c90791b266

@ -107,7 +107,6 @@ namespace NzbDrone.Api.Test.MappingTests
[Test]
public void should_map_qualityprofile()
{
var profileResource = new QualityProfileResource
{
Allowed = Builder<QualityResource>.CreateListOfSize(1).Build().ToList(),

@ -1,5 +1,6 @@
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.EpisodeFiles

@ -4,6 +4,7 @@ using NzbDrone.Api.Episodes;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.History;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.History

@ -2,6 +2,7 @@
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Indexers

@ -166,8 +166,8 @@
<Compile Include="NzbDroneApiModule.cs" />
<Compile Include="Qualities\QualityProfileResource.cs" />
<Compile Include="Qualities\QualityProfileModule.cs" />
<Compile Include="Qualities\QualitySizeResource.cs" />
<Compile Include="Qualities\QualitySizeModule.cs" />
<Compile Include="Qualities\QualityDefinitionResource.cs" />
<Compile Include="Qualities\QualityDefinitionModule.cs" />
<Compile Include="Extensions\ReqResExtensions.cs" />
<Compile Include="Config\SettingsModule.cs" />
<Compile Include="System\SystemModule.cs" />

@ -0,0 +1,38 @@
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Mapping;
namespace NzbDrone.Api.Qualities
{
public class QualityDefinitionModule : NzbDroneRestModule<QualityDefinitionResource>
{
private readonly IQualityDefinitionService _qualityDefinitionService;
public QualityDefinitionModule(IQualityDefinitionService qualityDefinitionService)
{
_qualityDefinitionService = qualityDefinitionService;
GetResourceAll = GetAll;
GetResourceById = GetById;
UpdateResource = Update;
}
private void Update(QualityDefinitionResource resource)
{
var model = resource.InjectTo<QualityDefinition>();
_qualityDefinitionService.Update(model);
}
private QualityDefinitionResource GetById(int id)
{
return _qualityDefinitionService.Get((Quality)id).InjectTo<QualityDefinitionResource>();
}
private List<QualityDefinitionResource> GetAll()
{
return ToListResource(_qualityDefinitionService.All);
}
}
}

@ -0,0 +1,18 @@
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Qualities
{
public class QualityDefinitionResource : RestResource
{
public Quality Quality { get; set; }
public String Title { get; set; }
public Int32 Weight { get; set; }
public Int32 MinSize { get; set; }
public Int32 MaxSize { get; set; }
}
}

@ -9,11 +9,14 @@ namespace NzbDrone.Api.Qualities
public class QualityProfileModule : NzbDroneRestModule<QualityProfileResource>
{
private readonly IQualityProfileService _qualityProfileService;
private readonly IQualityDefinitionService _qualityDefinitionService;
public QualityProfileModule(IQualityProfileService qualityProfileService)
public QualityProfileModule(IQualityProfileService qualityProfileService,
IQualityDefinitionService qualityDefinitionService)
: base("/qualityprofiles")
{
_qualityProfileService = qualityProfileService;
_qualityDefinitionService = qualityDefinitionService;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Cutoff).NotNull();
@ -44,38 +47,47 @@ namespace NzbDrone.Api.Qualities
private void Update(QualityProfileResource resource)
{
var model = resource.InjectTo<QualityProfile>();
var model = _qualityProfileService.Get(resource.Id);
model.Name = resource.Name;
model.Cutoff = (Quality)resource.Cutoff.Id;
model.Allowed = resource.Allowed.Select(p => (Quality)p.Id).ToList();
_qualityProfileService.Update(model);
}
private QualityProfileResource GetById(int id)
{
return QualityToResource(_qualityProfileService.Get(id));
return MapToResource(_qualityProfileService.Get(id));
}
private List<QualityProfileResource> GetAll()
{
var allProfiles = _qualityProfileService.All();
var profiles = allProfiles.Select(QualityToResource).ToList();
var profiles = _qualityProfileService.All().Select(MapToResource).ToList();
return profiles;
}
private static QualityProfileResource QualityToResource(QualityProfile profile)
private QualityProfileResource MapToResource(QualityProfile profile)
{
return new QualityProfileResource
{
Cutoff = profile.Cutoff.InjectTo<QualityResource>(),
Available = Quality.All()
.Where(c => !profile.Allowed.Any(q => c.Id == q.Id))
.InjectTo<List<QualityResource>>(),
Allowed = profile.Allowed.InjectTo<List<QualityResource>>(),
Cutoff = MapToResource(_qualityDefinitionService.Get(profile.Cutoff)),
Available = _qualityDefinitionService.All()
.Where(c => !profile.Allowed.Any(q => c.Quality == q))
.Select(MapToResource).ToList(),
Allowed = profile.Allowed.Select(_qualityDefinitionService.Get).Select(MapToResource).ToList(),
Name = profile.Name,
Id = profile.Id
};
}
private QualityResource MapToResource(QualityDefinition config)
{
return new QualityResource
{
Id = config.Quality.Id,
Name = config.Quality.Name,
Weight = config.Weight
};
}
}
}

@ -14,7 +14,8 @@ namespace NzbDrone.Api.Qualities
public class QualityResource : RestResource
{
public Int32 Weight { get; set; }
public String Name { get; set; }
public Int32 Weight { get; set; }
}
}

@ -7,9 +7,13 @@ namespace NzbDrone.Api.Qualities
{
public class QualityProfileSchemaModule : NzbDroneRestModule<QualityProfileResource>
{
public QualityProfileSchemaModule()
private readonly IQualityDefinitionService _qualityDefinitionService;
public QualityProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
: base("/qualityprofiles/schema")
{
_qualityDefinitionService = qualityDefinitionService;
GetResourceAll = GetAll;
}
@ -19,21 +23,30 @@ namespace NzbDrone.Api.Qualities
profile.Cutoff = Quality.Unknown;
profile.Allowed = new List<Quality>();
return new List<QualityProfileResource>{ QualityToResource(profile)};
return new List<QualityProfileResource> { QualityToResource(profile) };
}
private static QualityProfileResource QualityToResource(QualityProfile profile)
private QualityProfileResource QualityToResource(QualityProfile profile)
{
return new QualityProfileResource
{
Available = Quality.All()
.Where(c => !profile.Allowed.Any(q => c.Id == q.Id))
.InjectTo<List<QualityResource>>(),
Allowed = profile.Allowed.InjectTo<List<QualityResource>>(),
Name = profile.Name,
Id = profile.Id
};
{
Cutoff = QualityToResource(_qualityDefinitionService.Get(profile.Cutoff)),
Available = _qualityDefinitionService.All().Select(QualityToResource).ToList(),
Allowed = profile.Allowed.Select(_qualityDefinitionService.Get).Select(QualityToResource).ToList(),
Name = profile.Name,
Id = profile.Id
};
}
private QualityResource QualityToResource(QualityDefinition config)
{
return new QualityResource
{
Id = config.Quality.Id,
Name = config.Quality.Name,
Weight = config.Weight
};
}
}
}

@ -1,38 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Mapping;
namespace NzbDrone.Api.Qualities
{
public class QualitySizeModule : NzbDroneRestModule<QualitySizeResource>
{
private readonly IQualitySizeService _qualityTypeProvider;
public QualitySizeModule(IQualitySizeService qualityTypeProvider)
{
_qualityTypeProvider = qualityTypeProvider;
GetResourceAll = GetAll;
GetResourceById = GetById;
UpdateResource = Update;
}
private void Update(QualitySizeResource resource)
{
var model = resource.InjectTo<QualitySize>();
_qualityTypeProvider.Update(model);
}
private QualitySizeResource GetById(int id)
{
return _qualityTypeProvider.Get(id).InjectTo<QualitySizeResource>();
}
private List<QualitySizeResource> GetAll()
{
return ToListResource(_qualityTypeProvider.All);
}
}
}

@ -1,13 +0,0 @@
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Qualities
{
public class QualitySizeResource : RestResource
{
public Int32 QualityId { get; set; }
public String Name { get; set; }
public Int32 MinSize { get; set; }
public Int32 MaxSize { get; set; }
}
}

@ -1,5 +1,6 @@
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Queue

@ -75,6 +75,7 @@ namespace NzbDrone.Core.Test.Datastore
public void one_to_one()
{
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(c => c.Quality = new QualityModel())
.BuildNew();
Db.Insert(episodeFile);

@ -81,25 +81,25 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
[Test]
public void should_read_existing_indexes()
{
var indexes = _subject.GetIndexes("QualitySizes");
var indexes = _subject.GetIndexes("QualityDefinitions");
indexes.Should().NotBeEmpty();
indexes.Should().OnlyContain(c => c != null);
indexes.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Column));
indexes.Should().OnlyContain(c => c.Table == "QualitySizes");
indexes.Should().OnlyContain(c => c.Table == "QualityDefinitions");
indexes.Should().OnlyContain(c => c.Unique);
}
[Test]
public void should_add_indexes_when_creating_new_table()
{
var columns = _subject.GetColumns("QualitySizes");
var indexes = _subject.GetIndexes("QualitySizes");
var columns = _subject.GetColumns("QualityDefinitions");
var indexes = _subject.GetIndexes("QualityDefinitions");
_subject.CreateTable("QualityB", columns.Values, indexes);
_subject.CreateTable("QualityDefinitionsB", columns.Values, indexes);
var newIndexes = _subject.GetIndexes("QualityB");
var newIndexes = _subject.GetIndexes("QualityDefinitionsB");
newIndexes.Should().HaveSameCount(indexes);
newIndexes.Select(c=>c.Column).Should().BeEquivalentTo(indexes.Select(c=>c.Column));

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private RemoteEpisode parseResultSingle;
private Series series30minutes;
private Series series60minutes;
private QualitySize qualityType;
private QualityDefinition qualityType;
[SetUp]
public void Setup()
@ -47,10 +47,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(c => c.Runtime = 60)
.Build();
qualityType = Builder<QualitySize>.CreateNew()
qualityType = Builder<QualityDefinition>.CreateNew()
.With(q => q.MinSize = 0)
.With(q => q.MaxSize = 10)
.With(q => q.QualityId = 1)
.With(q => q.Quality = Quality.SDTV)
.Build();
}
@ -61,7 +61,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series30minutes;
parseResultSingle.Release.Size = 184572800;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series60minutes;
parseResultSingle.Release.Size = 368572800;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -99,7 +99,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series30minutes;
parseResultSingle.Release.Size = 1.Gigabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series60minutes;
parseResultSingle.Release.Size = 1.Gigabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -135,7 +135,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultMulti.Series = series30minutes;
parseResultMulti.Release.Size = 184572800;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultMulti.Series = series60minutes;
parseResultMulti.Release.Size = 368572800;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -173,7 +173,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultMulti.Series = series30minutes;
parseResultMulti.Release.Size = 1.Gigabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -192,7 +192,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultMulti.Series = series60minutes;
parseResultMulti.Release.Size = 10.Gigabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -211,7 +211,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series30minutes;
parseResultSingle.Release.Size = 184572800;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -230,7 +230,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series60minutes;
parseResultSingle.Release.Size = 368572800;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -249,7 +249,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series30minutes;
parseResultSingle.Release.Size = 1.Gigabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -270,7 +270,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Series = series60minutes;
parseResultSingle.Release.Size = 10.Gigabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -292,7 +292,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Release.Size = 18457280000;
qualityType.MaxSize = 0;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -314,7 +314,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultSingle.Release.Size = 36857280000;
qualityType.MaxSize = 0;
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -338,7 +338,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
qualityType.MaxSize = (int)600.Megabytes();
Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
Mocker.GetMock<IEpisodeService>().Setup(
s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@ -374,7 +374,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(parseResult, null).Should().BeFalse();
Mocker.GetMock<IQualitySizeService>().Verify(c=>c.Get(It.IsAny<int>()),Times.Never());
Mocker.GetMock<IQualityDefinitionService>().Verify(c => c.Get(It.IsAny<Quality>()), Times.Never());
}
}
}

@ -13,35 +13,35 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_return_true_if_current_episode_is_less_than_cutoff()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p },
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p, Allowed = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.DVD, true)).Should().BeTrue();
}
[Test]
public void should_return_false_if_current_episode_is_equal_to_cutoff()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Allowed = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.HDTV720p, true)).Should().BeFalse();
}
[Test]
public void should_return_false_if_current_episode_is_greater_than_cutoff()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Allowed = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
}
[Test]
public void should_return_true_when_new_episode_is_proper_but_existing_is_not()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Allowed = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.HDTV720p, false), new QualityModel(Quality.HDTV720p, true)).Should().BeTrue();
}
[Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Allowed = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.HDTV720p, true), new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
}
}

@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
};
_fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_parseResultMulti = new RemoteEpisode
@ -62,8 +62,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_upgradableQuality = new QualityModel(Quality.SDTV, false);
_notupgradableQuality = new QualityModel(Quality.HDTV1080p, true);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_notupgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_notupgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(3)).Returns<QualityModel>(null);
@ -132,7 +130,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing()
{
_fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p };
_fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p, Allowed = Qualities.QualityFixture.GetDefaultQualities() };
_parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false);
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, false);

@ -26,7 +26,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew().Build();
_series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_episode = Builder<Episode>.CreateNew()
.With(e => e.SeriesId = _series.Id)

@ -1,4 +1,5 @@
using FluentAssertions;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Qualities;
@ -23,6 +24,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
new object[] { Quality.SDTV, false, Quality.SDTV, true, Quality.SDTV, true },
new object[] { Quality.WEBDL1080p, false, Quality.WEBDL1080p, false, Quality.WEBDL1080p, false }
};
[SetUp]
public void Setup()
{
}
private void GivenAutoDownloadPropers(bool autoDownloadPropers)
{
@ -36,7 +43,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenAutoDownloadPropers(true);
Subject.IsUpgradable(new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
var qualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() };
Subject.IsUpgradable(qualityProfile, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
.Should().Be(expected);
}
@ -45,8 +54,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenAutoDownloadPropers(false);
Subject.IsUpgradable(new QualityModel(Quality.DVD, true),
new QualityModel(Quality.DVD, false)).Should().BeFalse();
var qualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() };
Subject.IsUpgradable(qualityProfile, new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false))
.Should().BeFalse();
}
}
}

@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
var fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_parseResultMulti = new RemoteEpisode

@ -37,6 +37,10 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
remoteEpisode.Release = new ReleaseInfo();
remoteEpisode.Release.PublishDate = DateTime.UtcNow;
remoteEpisode.Series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
return remoteEpisode;
}

@ -37,6 +37,10 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
remoteEpisode.Release.PublishDate = DateTime.Now.AddDays(-Age);
remoteEpisode.Release.Size = size;
remoteEpisode.Series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
return remoteEpisode;
}

@ -87,7 +87,6 @@ namespace NzbDrone.Core.Test.Download
{
Mocker.GetMock<IDownloadClient>().Setup(c => c.IsConfigured).Returns(false);
Subject.DownloadReport(_parseResult);
Mocker.GetMock<IDownloadClient>().Verify(c => c.DownloadNzb(It.IsAny<RemoteEpisode>()), Times.Never());

@ -80,6 +80,20 @@ namespace NzbDrone.Core.Test.MediaFiles
.Returns(true);
Subject.Execute(new DownloadedEpisodesScanCommand());
VerifyNoImport();
}
[Test]
public void should_skip_if_no_series_found()
{
Mocker.GetMock<IParsingService>().Setup(c => c.GetSeries("foldername")).Returns((Series)null);
Subject.Execute(new DownloadedEpisodesScanCommand());
Mocker.GetMock<IMakeImportDecision>()
.Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<bool>(), It.IsAny<Core.Qualities.QualityModel>()),
Times.Never());
VerifyNoImport();
}

@ -13,6 +13,7 @@ using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using FizzWare.NBuilder;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{
@ -63,7 +64,10 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
_fail3.Setup(c => c.RejectionReason).Returns("_fail3");
_videoFiles = new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
_series = new Series();
_series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_quality = new QualityModel(Quality.DVD);
_localEpisode = new LocalEpisode
{
@ -80,7 +84,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<int>()))
.Returns(_videoFiles);
}
private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] mocks)
@ -162,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<int>()))
.Returns(_videoFiles);
Subject.GetImportDecisions(_videoFiles, new Series(), false);
Subject.GetImportDecisions(_videoFiles, _series, false);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetEpisodes(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count));
@ -176,7 +179,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false, null);
var result = Subject.GetImportDecisions(_videoFiles, _series, false, null);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}
@ -187,7 +190,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false, new QualityModel(Quality.SDTV));
var result = Subject.GetImportDecisions(_videoFiles, _series, false, new QualityModel(Quality.SDTV));
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}
@ -198,7 +201,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = new QualityModel(Quality.Bluray1080p);
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false, expectedQuality);
var result = Subject.GetImportDecisions(_videoFiles, _series, false, expectedQuality);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}

@ -22,13 +22,15 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(s => s.SeriesType = SeriesTypes.Standard)
.With(s => s.SeriesType = SeriesTypes.Standard)
.With(e => e.QualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_localEpisode = new LocalEpisode
{
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
Quality = new QualityModel(Quality.HDTV720p, false)
Quality = new QualityModel(Quality.HDTV720p, false),
Series = _series
};
}

@ -29,6 +29,7 @@ namespace NzbDrone.Core.Test.MediaFiles
_approvedDecisions = new List<ImportDecision>();
var series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Allowed = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
var episodes = Builder<Episode>.CreateListOfSize(5)

@ -182,7 +182,7 @@
<Compile Include="ParserTests\ParsingServiceTests\MapFixture.cs" />
<Compile Include="ParserTests\SeriesTitleInfoFixture.cs" />
<Compile Include="Providers\XemProxyFixture.cs" />
<Compile Include="Qualities\QualitySizeRepositoryFixture.cs" />
<Compile Include="Qualities\QualityDefinitionRepositoryFixture.cs" />
<Compile Include="Qualities\QualityProfileRepositoryFixture.cs" />
<Compile Include="RootFolderTests\FreeSpaceOnDrivesFixture.cs" />
<Compile Include="Qualities\QualityFixture.cs" />
@ -217,14 +217,14 @@
<Compile Include="TvTests\SeriesServiceTests\UpdateSeriesFixture.cs" />
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
<Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
<Compile Include="Qualities\QualitySizeServiceFixture.cs" />
<Compile Include="Qualities\QualityDefinitionServiceFixture.cs" />
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
<Compile Include="FluentTest.cs" />
<Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" />
<Compile Include="OrganizerTests\GetNewFilenameFixture.cs" />
<Compile Include="DecisionEngineTests\MonitoredEpisodeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\DownloadDecisionMakerFixture.cs" />
<Compile Include="TvTests\QualityModelFixture.cs" />
<Compile Include="Qualities\QualityModelComparerFixture.cs" />
<Compile Include="RootFolderTests\RootFolderServiceFixture.cs" />
<Compile Include="HistoryTests\HistoryRepositoryFixture.cs" />
<Compile Include="MediaFiles\MediaFileServiceTest.cs" />

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
@ -50,6 +51,10 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Build();
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "DRONE" };
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}
private void GivenProper()

@ -1,4 +1,5 @@
using System;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
@ -150,9 +151,9 @@ namespace NzbDrone.Core.Test.ParserTests
}
[Test, TestCaseSource("SelfQualityParserCases")]
public void parsing_our_own_quality_enum(Quality quality)
public void parsing_our_own_quality_enum_name(Quality quality)
{
var fileName = String.Format("My series S01E01 [{0}]", quality);
var fileName = String.Format("My series S01E01 [{0}]", quality.Name);
var result = Parser.QualityParser.ParseQuality(fileName);
result.Quality.Should().Be(quality);
}

@ -4,25 +4,27 @@ using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using FluentAssertions;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.Qualities
{
[TestFixture]
public class QualitySizeRepositoryFixture : DbTest<QualitySizeRepository, QualitySize>
public class QualityDefinitionRepositoryFixture : DbTest<QualityDefinitionRepository, QualityDefinition>
{
[SetUp]
public void Setup()
{
Mocker.SetConstant<IQualitySizeRepository>(Subject);
Mocker.Resolve<QualitySizeService>().Handle(new ApplicationStartedEvent());
foreach (var qualityDefault in Quality.DefaultQualityDefinitions)
{
qualityDefault.Id = 0;
Storage.Insert(qualityDefault);
}
}
[Test]
public void should_get_quality_size_by_id()
public void should_get_qualitydefinition_by_id()
{
var size = Subject.GetByQualityId(Quality.Bluray1080p.Id);
var size = Subject.GetByQualityId((int)Quality.Bluray1080p);
size.Should().NotBeNull();
}

@ -0,0 +1,79 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Qualities
{
[TestFixture]
public class QualityDefinitionServiceFixture : CoreTest<QualityDefinitionService>
{
[Test]
public void init_should_add_all_definitions()
{
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IQualityDefinitionRepository>()
.Verify(v => v.Insert(It.IsAny<QualityDefinition>()), Times.Exactly(Quality.All.Count));
}
[Test]
public void init_should_insert_any_missing_definitions()
{
Mocker.GetMock<IQualityDefinitionRepository>()
.Setup(s => s.All())
.Returns(new List<QualityDefinition>
{
new QualityDefinition(Quality.SDTV) { Weight = 1, MinSize = 0, MaxSize = 100, Id = 20 }
});
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IQualityDefinitionRepository>()
.Verify(v => v.Insert(It.IsAny<QualityDefinition>()), Times.Exactly(Quality.All.Count - 1));
}
[Test]
public void init_should_insert_missing_definitions_preserving_weight()
{
// User moved HDTV1080p to a higher weight.
var currentQualities = new List<QualityDefinition>
{
new QualityDefinition(Quality.SDTV) { Id = 5, Title = "SDTV", Weight = 1, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL720p) { Id = 2, Title = "720p WEB-DL", Weight = 2, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.HDTV1080p) { Id = 4, Title = "1080p HDTV", Weight = 3, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL1080p) { Id = 8, Title = "1080p WEB-DL", Weight = 4, MinSize=0, MaxSize=100 },
};
// Expected to insert Bluray720p above HDTV1080p.
// Expected to insert Bluray1080p above WEBDL1080p.
var addBluray1080p = new List<QualityDefinition>
{
new QualityDefinition(Quality.SDTV) { Title = "SDTV", Weight = 1, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.HDTV1080p) { Title = "1080p HDTV", Weight = 2, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL720p) { Title = "720p WEB-DL", Weight = 3, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.Bluray720p) { Title = "720p BluRay", Weight = 4, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL1080p) { Title = "1080p WEB-DL", Weight = 5, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.Bluray1080p) { Title = "1080p BluRay", Weight = 6, MinSize=0, MaxSize=100 }
};
Mocker.GetMock<IQualityDefinitionRepository>()
.Setup(v => v.All())
.Returns(currentQualities);
Subject.InsertMissingDefinitions(addBluray1080p);
Mocker.GetMock<IQualityDefinitionRepository>()
.Verify(v => v.Insert(It.Is<QualityDefinition>(p => p.Quality == Quality.Bluray720p && p.Weight == 4)), Times.Once());
Mocker.GetMock<IQualityDefinitionRepository>()
.Verify(v => v.Update(It.Is<QualityDefinition>(p => p.Quality == Quality.WEBDL1080p && p.Weight == 5)), Times.Once());
Mocker.GetMock<IQualityDefinitionRepository>()
.Verify(v => v.Insert(It.Is<QualityDefinition>(p => p.Quality == Quality.Bluray1080p && p.Weight == 6)), Times.Once());
}
}
}

@ -1,4 +1,6 @@
using FluentAssertions;
using System.Linq;
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
@ -42,81 +44,21 @@ namespace NzbDrone.Core.Test.Qualities
i.Should().Be(expected);
}
[Test]
public void Icomparer_greater_test()
{
var first = Quality.DVD;
var second = Quality.Bluray1080p;
second.Should().BeGreaterThan(first);
}
[Test]
public void Icomparer_lesser()
{
var first = Quality.DVD;
var second = Quality.Bluray1080p;
first.Should().BeLessThan(second);
}
[Test]
public void equal_operand()
{
var first = Quality.Bluray1080p;
var second = Quality.Bluray1080p;
(first == second).Should().BeTrue();
(first >= second).Should().BeTrue();
(first <= second).Should().BeTrue();
}
[Test]
public void equal_operand_false()
{
var first = Quality.Bluray1080p;
var second = Quality.Unknown;
(first == second).Should().BeFalse();
}
[Test]
public void not_equal_operand()
{
var first = Quality.Bluray1080p;
var second = Quality.Bluray1080p;
(first != second).Should().BeFalse();
}
[Test]
public void not_equal_operand_false()
public static List<Quality> GetDefaultQualities()
{
var first = Quality.Bluray1080p;
var second = Quality.Unknown;
(first != second).Should().BeTrue();
}
[Test]
public void greater_operand()
{
var first = Quality.DVD;
var second = Quality.Bluray1080p;
(first < second).Should().BeTrue();
(first <= second).Should().BeTrue();
}
[Test]
public void lesser_operand()
{
var first = Quality.DVD;
var second = Quality.Bluray1080p;
(second > first).Should().BeTrue();
(second >= first).Should().BeTrue();
return new List<Quality>
{
Quality.SDTV,
Quality.WEBDL480p,
Quality.DVD,
Quality.HDTV720p,
Quality.HDTV1080p,
Quality.RAWHD,
Quality.WEBDL720p,
Quality.Bluray720p,
Quality.WEBDL1080p,
Quality.Bluray1080p
};
}
}
}

@ -0,0 +1,117 @@
using System.Linq;
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Qualities
{
[TestFixture]
public class QualityModelComparerFixture : CoreTest
{
public QualityModelComparer Subject { get; set; }
private void GivenDefaultQualityProfile()
{
Subject = new QualityModelComparer(new QualityProfile { Allowed = QualityFixture.GetDefaultQualities() });
}
private void GivenCustomQualityProfile()
{
Subject = new QualityModelComparer(new QualityProfile { Allowed = new List<Quality> { Quality.Bluray720p, Quality.DVD } });
}
[Test]
public void Icomparer_greater_test()
{
GivenDefaultQualityProfile();
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
var compare = Subject.Compare(second, first);
compare.Should().BeGreaterThan(0);
}
[Test]
public void Icomparer_greater_proper()
{
GivenDefaultQualityProfile();
var first = new QualityModel(Quality.Bluray1080p, false);
var second = new QualityModel(Quality.Bluray1080p, true);
var compare = Subject.Compare(second, first);
compare.Should().BeGreaterThan(0);
}
[Test]
public void Icomparer_lesser()
{
GivenDefaultQualityProfile();
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
var compare = Subject.Compare(first, second);
compare.Should().BeLessThan(0);
}
[Test]
public void Icomparer_lesser_proper()
{
GivenDefaultQualityProfile();
var first = new QualityModel(Quality.DVD, false);
var second = new QualityModel(Quality.DVD, true);
var compare = Subject.Compare(first, second);
compare.Should().BeLessThan(0);
}
[Test]
public void Icomparer_greater_custom_order()
{
GivenCustomQualityProfile();
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray720p, true);
var compare = Subject.Compare(first, second);
compare.Should().BeGreaterThan(0);
}
[Test]
public void Icomparer_missing_custom_order()
{
GivenCustomQualityProfile();
var first = new QualityModel(Quality.Bluray720p, true);
var second = new QualityModel(Quality.Bluray1080p, true);
var compare = Subject.Compare(first, second);
compare.Should().BeGreaterThan(0);
}
[Test]
public void Icomparer_missing_both_custom_order()
{
GivenCustomQualityProfile();
var first = new QualityModel(Quality.SDTV, true);
var second = new QualityModel(Quality.Bluray1080p, true);
var compare = Subject.Compare(first, second);
compare.Should().Be(0);
}
}
}

@ -7,7 +7,6 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Qualities
{
[TestFixture]
public class QualityProfileRepositoryFixture : DbTest<QualityProfileRepository, QualityProfile>
{
[Test]

@ -1,39 +0,0 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Qualities
{
[TestFixture]
public class QualitySizeServiceFixture : CoreTest<QualitySizeService>
{
[Test]
public void Init_should_add_all_sizes()
{
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IQualitySizeRepository>()
.Verify(v => v.Insert(It.IsAny<QualitySize>()), Times.Exactly(Quality.All().Count));
}
[Test]
public void Init_should_insert_any_missing_sizes()
{
Mocker.GetMock<IQualitySizeRepository>()
.Setup(s => s.All())
.Returns(new List<QualitySize>
{
new QualitySize { QualityId = 1, Name = "SDTV", MinSize = 0, MaxSize = 100 }
});
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IQualitySizeRepository>()
.Verify(v => v.Insert(It.IsAny<QualitySize>()), Times.Exactly(Quality.All().Count - 1));
}
}
}

@ -1,125 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.TvTests
{
[TestFixture]
public class QualityModelFixture : CoreTest
{
[Test]
public void Icomparer_greater_test()
{
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
second.Should().BeGreaterThan(first);
}
[Test]
public void Icomparer_greater_proper()
{
var first = new QualityModel(Quality.Bluray1080p, false);
var second = new QualityModel(Quality.Bluray1080p, true);
second.Should().BeGreaterThan(first);
}
[Test]
public void Icomparer_lesser()
{
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
first.Should().BeLessThan(second);
}
[Test]
public void Icomparer_lesser_proper()
{
var first = new QualityModel(Quality.DVD, false);
var second = new QualityModel(Quality.DVD, true);
first.Should().BeLessThan(second);
}
[Test]
public void equal_operand()
{
var first = new QualityModel(Quality.Bluray1080p, true);
var second = new QualityModel(Quality.Bluray1080p, true);
(first == second).Should().BeTrue();
(first >= second).Should().BeTrue();
(first <= second).Should().BeTrue();
}
[Test]
public void equal_operand_false()
{
var first = new QualityModel(Quality.Bluray1080p, true);
var second = new QualityModel(Quality.Unknown, true);
(first == second).Should().BeFalse();
}
[Test]
public void equal_operand_false_proper()
{
var first = new QualityModel(Quality.Bluray1080p, true);
var second = new QualityModel(Quality.Bluray1080p, false);
(first == second).Should().BeFalse();
}
[Test]
public void not_equal_operand()
{
var first = new QualityModel(Quality.Bluray1080p, true);
var second = new QualityModel(Quality.Bluray1080p, true);
(first != second).Should().BeFalse();
}
[Test]
public void not_equal_operand_false()
{
var first = new QualityModel(Quality.Bluray1080p, true);
var second = new QualityModel(Quality.Unknown, true);
(first != second).Should().BeTrue();
}
[Test]
public void not_equal_operand_false_proper()
{
var first = new QualityModel(Quality.Bluray1080p, true);
var second = new QualityModel(Quality.Bluray1080p, false);
(first != second).Should().BeTrue();
}
[Test]
public void greater_operand()
{
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
(first < second).Should().BeTrue();
(first <= second).Should().BeTrue();
}
[Test]
public void lesser_operand()
{
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
(second > first).Should().BeTrue();
(second >= first).Should().BeTrue();
}
}
}

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Blacklisting

@ -2,6 +2,8 @@
using Marr.Data.Converters;
using Marr.Data.Mapping;
using NzbDrone.Core.Qualities;
using System.Collections.Generic;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Datastore.Converters
{
@ -26,7 +28,7 @@ namespace NzbDrone.Core.Datastore.Converters
public object ToDB(object clrValue)
{
if(clrValue == null) return 0;
if(clrValue == DBNull.Value) return 0;
if(clrValue as Quality == null)
{

@ -0,0 +1,52 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using NzbDrone.Core.Qualities;
using System.Collections.Generic;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Datastore.Converters
{
public class QualityListConverter : IConverter
{
public object FromDB(ConverterContext context)
{
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
var val = Convert.ToString(context.DbValue);
var qualityList = Json.Deserialize<List<int>>(val).ConvertAll(Quality.FindById);
return qualityList;
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if (clrValue == DBNull.Value) return null;
var qualityList = clrValue as List<Quality>;
if (qualityList == null)
{
throw new InvalidOperationException("Can only store a list of qualities in this database column.");
}
var intList = qualityList.ConvertAll(v => v.Id);
return intList.ToJson();
}
public Type DbType
{
get { return typeof(string); }
}
}
}

@ -0,0 +1,55 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using NzbDrone.Core.Qualities;
using System.Collections.Generic;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Datastore.Converters
{
public class QualityModelConverter : IConverter
{
public object FromDB(ConverterContext context)
{
if (context.DbValue == DBNull.Value)
{
return new QualityModel();
}
var val = Convert.ToString(context.DbValue);
var jsonObject = Json.Deserialize<Dictionary<string, object>>(val);
return new QualityModel((Quality)Convert.ToInt32(jsonObject["id"]), Convert.ToBoolean(jsonObject["proper"]));
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if (clrValue == DBNull.Value)
clrValue = new QualityModel();
var qualityModel = clrValue as QualityModel;
if (qualityModel == null)
{
throw new InvalidOperationException("Can only store a QualityModel in this database column.");
}
var jsonObject = new Dictionary<string, object>();
jsonObject["id"] = (int)qualityModel.Quality;
jsonObject["proper"] = qualityModel.Proper;
return jsonObject.ToJson();
}
public Type DbType
{
get { return typeof(string); }
}
}
}

@ -0,0 +1,97 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
using System.Linq;
using System;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Qualities;
using System.Collections.Generic;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(36)]
public class update_with_quality_converters : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ConvertQualityProfiles);
Execute.WithConnection(ConvertQualityModels);
}
private void ConvertQualityProfiles(IDbConnection conn, IDbTransaction tran)
{
var qualityListConverter = new NzbDrone.Core.Datastore.Converters.QualityListConverter();
// Convert 'Allowed' column in QualityProfiles from Json List<object> to Json List<int> (int = Quality)
using (IDbCommand qualityProfileCmd = conn.CreateCommand())
{
qualityProfileCmd.Transaction = tran;
qualityProfileCmd.CommandText = @"SELECT Id, Allowed FROM QualityProfiles";
using (IDataReader qualityProfileReader = qualityProfileCmd.ExecuteReader())
{
while (qualityProfileReader.Read())
{
var id = qualityProfileReader.GetInt32(0);
var allowedJson = qualityProfileReader.GetString(1);
var allowed = Json.Deserialize<List<Quality>>(allowedJson);
var allowedNewJson = qualityListConverter.ToDB(allowed);
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE QualityProfiles SET Allowed = ? WHERE Id = ?";
updateCmd.AddParameter(allowedNewJson);
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
private void ConvertQualityModels(IDbConnection conn, IDbTransaction tran)
{
// Converts the QualityModel JSON objects to their new format (only storing the QualityId instead of the entire object)
ConvertQualityModel(conn, tran, "Blacklist");
ConvertQualityModel(conn, tran, "EpisodeFiles");
ConvertQualityModel(conn, tran, "History");
}
private void ConvertQualityModel(IDbConnection conn, IDbTransaction tran, string tableName)
{
var qualityModelConverter = new NzbDrone.Core.Datastore.Converters.QualityModelConverter();
using (IDbCommand qualityModelCmd = conn.CreateCommand())
{
qualityModelCmd.Transaction = tran;
qualityModelCmd.CommandText = @"SELECT Id, Quality FROM " + tableName;
using (IDataReader qualityModelReader = qualityModelCmd.ExecuteReader())
{
while (qualityModelReader.Read())
{
var id = qualityModelReader.GetInt32(0);
var qualityJson = qualityModelReader.GetString(1);
var quality = Json.Deserialize<QualityModel>(qualityJson);
var qualityNewJson = qualityModelConverter.ToDB(quality);
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE " + tableName + " SET Quality = ? WHERE Id = ?";
updateCmd.AddParameter(qualityNewJson);
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

@ -0,0 +1,63 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
using System.Linq;
using System;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Qualities;
using System.Collections.Generic;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(37)]
public class add_configurable_qualities : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.TableForModel("QualityDefinitions")
.WithColumn("Quality").AsInt32().Unique()
.WithColumn("Title").AsString().Unique()
.WithColumn("Weight").AsInt32().Unique()
.WithColumn("MinSize").AsInt32()
.WithColumn("MaxSize").AsInt32();
Execute.WithConnection(ConvertQualities);
Delete.Table("QualitySizes");
}
private void ConvertQualities(IDbConnection conn, IDbTransaction tran)
{
// Convert QualitySizes to a more generic QualityDefinitions table.
using (IDbCommand qualitySizeCmd = conn.CreateCommand())
{
qualitySizeCmd.Transaction = tran;
qualitySizeCmd.CommandText = @"SELECT QualityId, MinSize, MaxSize FROM QualitySizes";
using (IDataReader qualitySizeReader = qualitySizeCmd.ExecuteReader())
{
while (qualitySizeReader.Read())
{
var qualityId = qualitySizeReader.GetInt32(0);
var minSize = qualitySizeReader.GetInt32(1);
var maxSize = qualitySizeReader.GetInt32(2);
var defaultConfig = Quality.DefaultQualityDefinitions.Single(p => (int)p.Quality == qualityId);
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "INSERT INTO QualityDefinitions (Quality, Title, Weight, MinSize, MaxSize) VALUES (?, ?, ?, ?, ?)";
updateCmd.AddParameter(qualityId);
updateCmd.AddParameter(defaultConfig.Title);
updateCmd.AddParameter(defaultConfig.Weight);
updateCmd.AddParameter(minSize);
updateCmd.AddParameter(maxSize);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

@ -10,5 +10,11 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
}
public static void AddParameter(this System.Data.IDbCommand command, object value)
{
var parameter = command.CreateParameter();
parameter.Value = value;
command.Parameters.Add(parameter);
}
}
}

@ -60,7 +60,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
Mapper.Entity<QualitySize>().RegisterModel("QualitySizes");
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions");
Mapper.Entity<Log>().RegisterModel("Logs");
@ -81,6 +81,8 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(Boolean), new BooleanIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<Quality>), new QualityListConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new QualityModelConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
}

@ -6,7 +6,7 @@ namespace NzbDrone.Core.DecisionEngine
{
public interface IQualityUpgradableSpecification
{
bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null);
bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality);
}
@ -20,11 +20,12 @@ namespace NzbDrone.Core.DecisionEngine
_logger = logger;
}
public bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null)
public bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
if (newQuality != null)
{
if (currentQuality >= newQuality)
int compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality);
if (compare <= 0)
{
_logger.Trace("existing item has better or equal quality. skipping");
return false;
@ -41,7 +42,9 @@ namespace NzbDrone.Core.DecisionEngine
public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
if (currentQuality.Quality >= profile.Cutoff)
int compare = new QualityModelComparer(profile).Compare(currentQuality.Quality, profile.Cutoff);
if (compare >= 0)
{
if (newQuality != null && IsProperUpgrade(currentQuality, newQuality))
{
@ -57,7 +60,9 @@ namespace NzbDrone.Core.DecisionEngine
public bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality)
{
if (currentQuality.Quality == newQuality.Quality && newQuality > currentQuality)
int compare = newQuality.Proper.CompareTo(currentQuality.Proper);
if (currentQuality.Quality == newQuality.Quality && compare > 0)
{
_logger.Trace("New quality is a proper for existing quality");
return true;

@ -9,13 +9,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AcceptableSizeSpecification : IDecisionEngineSpecification
{
private readonly IQualitySizeService _qualityTypeProvider;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public AcceptableSizeSpecification(IQualitySizeService qualityTypeProvider, IEpisodeService episodeService, Logger logger)
public AcceptableSizeSpecification(IQualityDefinitionService qualityDefinitionService, IEpisodeService episodeService, Logger logger)
{
_qualityTypeProvider = qualityTypeProvider;
_qualityDefinitionService = qualityDefinitionService;
_episodeService = episodeService;
_logger = logger;
}
@ -44,15 +44,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
var qualityType = _qualityTypeProvider.Get(quality.Id);
var qualityDefinition = _qualityDefinitionService.Get(quality);
if (qualityType.MaxSize == 0)
if (qualityDefinition.MaxSize == 0)
{
_logger.Trace("Max size is 0 (unlimited) - skipping check.");
return true;
}
var maxSize = qualityType.MaxSize.Megabytes();
var maxSize = qualityDefinition.MaxSize.Megabytes();
//Multiply maxSize by Series.Runtime
maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count;

@ -4,6 +4,8 @@ using NLog;
using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
@ -44,7 +46,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<RemoteEpisode> queue)
{
var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id);
var matchingSeriesAndQuality = matchingSeries.Where(q => q.ParsedEpisodeInfo.Quality >= newEpisode.ParsedEpisodeInfo.Quality);
var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.QualityProfile).Compare(q.ParsedEpisodeInfo.Quality, newEpisode.ParsedEpisodeInfo.Quality) >= 0);
return matchingSeriesAndQuality.Any(q => q.Episodes.Select(e => e.Id).Intersect(newEpisode.Episodes.Select(e => e.Id)).Any());
}

@ -63,7 +63,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (bestQualityInHistory != null)
{
_logger.Trace("Comparing history quality with report. History is {0}", bestQualityInHistory);
if (!_qualityUpgradableSpecification.IsUpgradable(bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
return false;
}
}

@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
_logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality);
if (!_qualityUpgradableSpecification.IsUpgradable(file.Quality, subject.ParsedEpisodeInfo.Quality))
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
{
return false;
}

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Download
{
@ -16,7 +18,7 @@ namespace NzbDrone.Core.Download
private readonly IDownloadService _downloadService;
private readonly Logger _logger;
public DownloadApprovedReports(IDownloadService downloadService, Logger logger)
public DownloadApprovedReports(IDownloadService downloadService, Logger logger)
{
_downloadService = downloadService;
_logger = logger;
@ -57,11 +59,13 @@ namespace NzbDrone.Core.Download
public List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> decisions)
{
return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any())
.OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality)
.ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
.ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
.ThenBy(c => c.RemoteEpisode.Release.Age)
.ToList();
.GroupBy(c => c.RemoteEpisode.Series.Id, (i,s) => s
.OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile))
.ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
.ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
.ThenBy(c => c.RemoteEpisode.Release.Age))
.SelectMany(c => c)
.ToList();
}
}
}

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Download

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.History

@ -4,6 +4,7 @@ using System.Linq;
using Marr.Data.QGen;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.History

@ -6,6 +6,7 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.History

@ -10,6 +10,7 @@ using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles

@ -1,5 +1,6 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles

@ -7,6 +7,8 @@ using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
@ -40,8 +42,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownload = false)
{
var qualifiedImports = decisions.Where(c => c.Approved)
.OrderByDescending(c => c.LocalEpisode.Quality)
.ThenByDescending(c => c.LocalEpisode.Size);
.GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s
.OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.QualityProfile))
.ThenByDescending(c => c.LocalEpisode.Size))
.SelectMany(c => c)
.ToList();
var imported = new List<ImportDecision>();

@ -7,6 +7,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
@ -60,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
if (parsedEpisode != null)
{
if (quality != null && quality > parsedEpisode.Quality)
if (quality != null && new QualityModelComparer(parsedEpisode.Series.QualityProfile).Compare(quality, parsedEpisode.Quality) > 0)
{
_logger.Trace("Using quality from folder: {0}", quality);
parsedEpisode.Quality = quality;

@ -1,6 +1,8 @@
using System.Linq;
using NLog;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
@ -17,7 +19,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && e.EpisodeFile.Value.Quality > localEpisode.Quality))
var qualityComparer = new QualityModelComparer(localEpisode.Series.QualityProfile);
if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) > 0))
{
_logger.Trace("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path);
return false;

@ -5,6 +5,7 @@ using NLog;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Notifications

@ -145,6 +145,8 @@
<Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
<Compile Include="Datastore\Converters\ProviderSettingConverter.cs" />
<Compile Include="Datastore\Converters\QualityIntConverter.cs" />
<Compile Include="Datastore\Converters\QualityListConverter.cs" />
<Compile Include="Datastore\Converters\QualityModelConverter.cs" />
<Compile Include="Datastore\Converters\Int32Converter.cs" />
<Compile Include="Datastore\Converters\EmbeddedDocumentConverter.cs" />
<Compile Include="Datastore\Converters\UtcConverter.cs" />
@ -191,6 +193,8 @@
<Compile Include="Datastore\Migration\033_add_api_key_to_pushover.cs" />
<Compile Include="Datastore\Migration\034_remove_series_contraints.cs" />
<Compile Include="Datastore\Migration\035_add_series_folder_format_to_naming_config.cs" />
<Compile Include="Datastore\Migration\036_update_with_quality_converters.cs" />
<Compile Include="Datastore\Migration\037_add_configurable_qualities.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@ -456,12 +460,13 @@
<Compile Include="Parser\Parser.cs" />
<Compile Include="Parser\ParsingService.cs" />
<Compile Include="Parser\QualityParser.cs" />
<Compile Include="Qualities\QualityModelComparer.cs" />
<Compile Include="Rest\JsonNetSerializer.cs" />
<Compile Include="RootFolders\RootFolderRepository.cs" />
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
<Compile Include="ThingiProvider\IProvider.cs" />
<Compile Include="Qualities\QualityProfileInUseException.cs" />
<Compile Include="Qualities\QualitySizeRepository.cs" />
<Compile Include="Qualities\QualityDefinitionRepository.cs" />
<Compile Include="Qualities\QualityProfileRepository.cs" />
<Compile Include="Queue\Queue.cs" />
<Compile Include="Queue\UpdateQueueEvent.cs" />
@ -489,7 +494,7 @@
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
<Compile Include="Tv\RefreshEpisodeService.cs" />
<Compile Include="Tv\SeriesRepository.cs" />
<Compile Include="Tv\QualityModel.cs" />
<Compile Include="Qualities\QualityModel.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabAddResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabHistoryItem.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabHistory.cs" />
@ -575,7 +580,7 @@
<Compile Include="Qualities\QualityProfileService.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Qualities\QualitySizeService.cs">
<Compile Include="Qualities\QualityDefinitionService.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="RootFolders\UnmappedFolder.cs" />
@ -597,7 +602,7 @@
<Compile Include="Tv\Episode.cs" />
<Compile Include="Instrumentation\Log.cs" />
<Compile Include="History\History.cs" />
<Compile Include="Qualities\QualitySize.cs" />
<Compile Include="Qualities\QualityDefinition.cs" />
<Compile Include="Qualities\QualityProfile.cs" />
<Compile Include="RootFolders\RootFolder.cs" />
<Compile Include="Tv\Series.cs" />

@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Organizer
@ -22,6 +23,7 @@ namespace NzbDrone.Core.Organizer
public class FileNameBuilder : IBuildFileNames
{
private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly ICached<EpisodeFormat> _patternCache;
private readonly Logger _logger;
@ -43,10 +45,12 @@ namespace NzbDrone.Core.Organizer
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public FileNameBuilder(INamingConfigService namingConfigService,
IQualityDefinitionService qualityDefinitionService,
ICacheManger cacheManger,
Logger logger)
{
_namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService;
_patternCache = cacheManger.GetCache<EpisodeFormat>(GetType());
_logger = logger;
}
@ -87,12 +91,10 @@ namespace NzbDrone.Core.Organizer
sortedEpisodes.First().Title
};
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance)
{
{"{Series Title}", series.Title},
{"Original Title", episodeFile.SceneName}
};
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
tokenValues.Add("{Series Title}", series.Title);
tokenValues.Add("{Original Title}", episodeFile.SceneName);
tokenValues.Add("{Release Group}", episodeFile.ReleaseGroup);
if (series.SeriesType == SeriesTypes.Daily)
@ -146,7 +148,7 @@ namespace NzbDrone.Core.Organizer
}
tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
tokenValues.Add("{Quality Title}", episodeFile.Quality.ToString());
tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
return CleanFilename(ReplaceTokens(pattern, tokenValues).Trim());
@ -341,6 +343,14 @@ namespace NzbDrone.Core.Organizer
return String.Join(" + ", episodeTitles.Select(Parser.Parser.CleanupEpisodeTitle).Distinct());
}
private string GetQualityTitle(QualityModel quality)
{
if (quality.Proper)
return _qualityDefinitionService.Get(quality.Quality).Title + " Proper";
else
return _qualityDefinitionService.Get(quality.Quality).Title;
}
}
public enum MultiEpisodeStyle

@ -1,7 +1,8 @@
using System;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using System.Linq;
namespace NzbDrone.Core.Parser.Model
{

@ -1,5 +1,6 @@
using System;
using System.Linq;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser.Model

@ -2,65 +2,24 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Converters;
namespace NzbDrone.Core.Qualities
{
public class Quality : IComparable<Quality>, IEmbeddedDocument
public class Quality : IEmbeddedDocument, IEquatable<Quality>
{
public int Id { get; set; }
public string Name { get; set; }
public int Weight { get; set; }
public int CompareTo(Quality other)
{
if (other.Weight > Weight)
return -1;
if (other.Weight < Weight)
return 1;
if (other.Weight == Weight)
return 0;
return 0;
}
public static bool operator !=(Quality x, Quality y)
public Quality()
{
return !(x == y);
}
public static bool operator ==(Quality x, Quality y)
{
var xObj = (Object)x;
var yObj = (object)y;
if (xObj == null || yObj == null)
{
return xObj == yObj;
}
return x.CompareTo(y) == 0;
}
public static bool operator >(Quality x, Quality y)
private Quality(int id, string name)
{
return x.CompareTo(y) > 0;
}
public static bool operator <(Quality x, Quality y)
{
return x.CompareTo(y) < 0;
}
public static bool operator <=(Quality x, Quality y)
{
return x.CompareTo(y) <= 0;
}
public static bool operator >=(Quality x, Quality y)
{
return x.CompareTo(y) >= 0;
Id = id;
Name = name;
}
public override string ToString()
@ -70,110 +29,96 @@ namespace NzbDrone.Core.Qualities
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + Weight.GetHashCode();
return hash;
}
return Id.GetHashCode();
}
public bool Equals(Quality other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Weight, Weight);
return Id.Equals(other.Id);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(Quality)) return false;
return Equals((Quality)obj);
}
public static Quality Unknown
{
get { return new Quality { Id = 0, Name = "Unknown", Weight = 0 }; }
return Equals(obj as Quality);
}
public static Quality SDTV
public static bool operator ==(Quality left, Quality right)
{
get { return new Quality { Id = 1, Name = "SDTV", Weight = 1 }; }
return Equals(left, right);
}
public static Quality WEBDL480p
public static bool operator !=(Quality left, Quality right)
{
get { return new Quality { Id = 8, Name = "WEBDL-480p", Weight = 2 }; }
return !Equals(left, right);
}
public static Quality DVD
public static Quality Unknown { get { return new Quality(0, "Unknown"); } }
public static Quality SDTV { get { return new Quality(1, "SDTV"); } }
public static Quality DVD { get { return new Quality(2, "DVD"); } }
public static Quality WEBDL1080p { get { return new Quality(3, "WEBDL-1080p"); } }
public static Quality HDTV720p { get { return new Quality(4, "HDTV-720p"); } }
public static Quality WEBDL720p { get { return new Quality(5, "WEBDL-720p"); } }
public static Quality Bluray720p { get { return new Quality(6, "Bluray-720p"); } }
public static Quality Bluray1080p { get { return new Quality(7, "Bluray-1080p"); } }
public static Quality WEBDL480p { get { return new Quality(8, "WEBDL-480p"); } }
public static Quality HDTV1080p { get { return new Quality(9, "HDTV-1080p"); } }
public static Quality RAWHD { get { return new Quality(10, "Raw-HD"); } }
public static Quality HDTV480p { get { return new Quality(11, "HDTV-480p"); } }
public static List<Quality> All
{
get { return new Quality { Id = 2, Name = "DVD", Weight = 3 }; }
}
public static Quality HDTV720p
{
get { return new Quality { Id = 4, Name = "HDTV-720p", Weight = 4 }; }
}
public static Quality HDTV1080p
{
get { return new Quality { Id = 9, Name = "HDTV-1080p", Weight = 5 }; }
}
public static Quality RAWHD
{
get { return new Quality { Id = 10, Name = "Raw-HD", Weight = 6 }; }
}
public static Quality WEBDL720p
{
get { return new Quality { Id = 5, Name = "WEBDL-720p", Weight = 7 }; }
}
public static Quality Bluray720p
{
get { return new Quality { Id = 6, Name = "Bluray720p", Weight = 8 }; }
}
public static Quality WEBDL1080p
{
get { return new Quality { Id = 3, Name = "WEBDL-1080p", Weight = 9 }; }
}
public static Quality Bluray1080p
{
get { return new Quality { Id = 7, Name = "Bluray1080p", Weight = 10 }; }
get
{
return new List<Quality>
{
SDTV,
DVD,
WEBDL1080p,
HDTV720p,
WEBDL720p,
Bluray720p,
Bluray1080p,
WEBDL480p,
HDTV1080p,
RAWHD
};
}
}
public static List<Quality> All()
public static HashSet<QualityDefinition> DefaultQualityDefinitions
{
return new List<Quality>
{
SDTV,
WEBDL480p,
DVD,
HDTV720p,
HDTV1080p,
RAWHD,
WEBDL720p,
WEBDL1080p,
Bluray720p,
Bluray1080p
};
get
{
return new HashSet<QualityDefinition>
{
new QualityDefinition(Quality.SDTV) { /*Title = "SDTV", */ Weight = 1, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL480p) { /*Title = "WEB-DL", */ Weight = 2, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.DVD) { /*Title = "DVD", */ Weight = 3, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.HDTV720p) { /*Title = "720p HDTV", */ Weight = 4, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.HDTV1080p) { /*Title = "1080p HDTV", */ Weight = 5, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.RAWHD) { /*Title = "RawHD", */ Weight = 6, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL720p) { /*Title = "720p WEB-DL", */ Weight = 7, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.Bluray720p) { /*Title = "720p BluRay", */ Weight = 8, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.WEBDL1080p) { /*Title = "1080p WEB-DL",*/ Weight = 9, MinSize=0, MaxSize=100 },
new QualityDefinition(Quality.Bluray1080p) { /*Title = "1080p BluRay",*/ Weight = 10, MinSize=0, MaxSize=100 }
};
}
}
public static Quality FindById(int id)
{
if (id == 0) return Unknown;
var quality = All().SingleOrDefault(q => q.Id == id);
Quality quality = All.FirstOrDefault(v => v.Id == id);
if (quality == null)
throw new ArgumentException("ID does not match a known quality", "id");
return quality;
}

@ -0,0 +1,33 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Qualities
{
public class QualityDefinition : ModelBase
{
public Quality Quality { get; set; }
public string Title { get; set; }
public int Weight { get; set; }
public int MinSize { get; set; }
public int MaxSize { get; set; }
public QualityDefinition()
{
}
public QualityDefinition(Quality quality)
{
Quality = quality;
Title = quality.Name;
}
public override string ToString()
{
return Quality.Name;
}
}
}

@ -0,0 +1,33 @@
using System;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Qualities
{
public interface IQualityDefinitionRepository : IBasicRepository<QualityDefinition>
{
QualityDefinition GetByQualityId(int qualityId);
}
public class QualityDefinitionRepository : BasicRepository<QualityDefinition>, IQualityDefinitionRepository
{
public QualityDefinitionRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public QualityDefinition GetByQualityId(int qualityId)
{
try
{
return Query.Single(q => (int)q.Quality == qualityId);
}
catch (InvalidOperationException e)
{
throw new ModelNotFoundException(typeof(QualityDefinition), qualityId);
}
}
}
}

@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
using System;
namespace NzbDrone.Core.Qualities
{
public interface IQualityDefinitionService
{
void Update(QualityDefinition qualityDefinition);
List<QualityDefinition> All();
QualityDefinition Get(Quality quality);
}
public class QualityDefinitionService : IQualityDefinitionService, IHandle<ApplicationStartedEvent>
{
private readonly IQualityDefinitionRepository _qualityDefinitionRepository;
private readonly Logger _logger;
public QualityDefinitionService(IQualityDefinitionRepository qualityDefinitionRepository, Logger logger)
{
_qualityDefinitionRepository = qualityDefinitionRepository;
_logger = logger;
}
public void Update(QualityDefinition qualityDefinition)
{
_qualityDefinitionRepository.Update(qualityDefinition);
}
public List<QualityDefinition> All()
{
return _qualityDefinitionRepository.All().ToList();
}
public QualityDefinition Get(Quality quality)
{
if (quality == Quality.Unknown)
return new QualityDefinition(Quality.Unknown);
return _qualityDefinitionRepository.GetByQualityId((int)quality);
}
public void InsertMissingDefinitions(List<QualityDefinition> allDefinitions)
{
allDefinitions.OrderBy(v => v.Weight).ToList();
var existingDefinitions = _qualityDefinitionRepository.All().OrderBy(v => v.Weight).ToList();
// Try insert each item intelligently to merge the lists preserving the Weight the user set.
for (int i = 0; i < allDefinitions.Count;i++)
{
// Skip if this definition isn't missing.
if (existingDefinitions.Any(v => v.Quality == allDefinitions[i].Quality))
continue;
int targetIndexMinimum = 0;
for (int j = 0; j < i; j++)
targetIndexMinimum = Math.Max(targetIndexMinimum, existingDefinitions.FindIndex(v => v.Quality == allDefinitions[j].Quality) + 1);
int targetIndexMaximum = existingDefinitions.Count;
for (int j = i + 1; j < allDefinitions.Count; j++)
{
var index = existingDefinitions.FindIndex(v => v.Quality == allDefinitions[j].Quality);
if (index != -1)
targetIndexMaximum = Math.Min(targetIndexMaximum, index);
}
// Rounded down average sounds reasonable.
int targetIndex = (targetIndexMinimum + targetIndexMaximum) / 2;
existingDefinitions.Insert(targetIndex, allDefinitions[i]);
}
// Update all Weights.
List<QualityDefinition> insertList = new List<QualityDefinition>();
List<QualityDefinition> updateList = new List<QualityDefinition>();
for (int i = 0; i < existingDefinitions.Count; i++)
{
if (existingDefinitions[i].Id == 0)
{
existingDefinitions[i].Weight = i + 1;
_qualityDefinitionRepository.Insert(existingDefinitions[i]);
}
else if (existingDefinitions[i].Weight != i + 1)
{
existingDefinitions[i].Weight = i + 1;
_qualityDefinitionRepository.Update(existingDefinitions[i]);
}
}
}
public void Handle(ApplicationStartedEvent message)
{
_logger.Debug("Setting up default quality config");
InsertMissingDefinitions(Quality.DefaultQualityDefinitions.ToList());
}
}
}

@ -0,0 +1,73 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Qualities
{
public class QualityModel : IEmbeddedDocument, IEquatable<QualityModel>
{
public Quality Quality { get; set; }
public Boolean Proper { get; set; }
public QualityModel()
: this(Quality.Unknown)
{
}
public QualityModel(Quality quality, Boolean proper = false)
{
Quality = quality;
Proper = proper;
}
public override string ToString()
{
string result = Quality.ToString();
if (Proper)
{
result += " Proper";
}
return result;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + Proper.GetHashCode();
hash = hash * 23 + Quality.GetHashCode();
return hash;
}
}
public bool Equals(QualityModel other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Quality.Equals(Quality) && other.Proper.Equals(Proper);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj as QualityModel);
}
public static bool operator ==(QualityModel left, QualityModel right)
{
return Equals(left, right);
}
public static bool operator !=(QualityModel left, QualityModel right)
{
return !Equals(left, right);
}
}
}

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.EnsureThat;
namespace NzbDrone.Core.Qualities
{
public class QualityModelComparer : IComparer<Quality>, IComparer<QualityModel>
{
private readonly QualityProfile _qualityProfile;
public QualityModelComparer(QualityProfile qualityProfile)
{
Ensure.That(qualityProfile, () => qualityProfile).IsNotNull();
Ensure.That(qualityProfile.Allowed, () => qualityProfile.Allowed).HasItems();
_qualityProfile = qualityProfile;
}
public int Compare(Quality left, Quality right)
{
int leftIndex = _qualityProfile.Allowed.IndexOf(left);
int rightIndex = _qualityProfile.Allowed.IndexOf(right);
return leftIndex.CompareTo(rightIndex);
}
public int Compare(QualityModel left, QualityModel right)
{
int result = Compare(left.Quality, right.Quality);
if (result == 0)
result = left.Proper.CompareTo(right.Proper);
return result;
}
/*
public string GetName(Quality quality)
{
QualityDefinition qualityDefinition = _qualityDefinitionService.Get(quality);
return qualityDefinition.Name;
}
public string GetName(QualityModel quality)
{
QualityDefinition qualityDefinition = _qualityDefinitionService.Get(quality.Quality);
if (quality.Proper)
return qualityDefinition.Name + " Proper";
else
return qualityDefinition.Name;
}
public string GetSceneName(QualityModel quality)
{
QualityDefinition qualityDefinition = _qualityDefinitionService.Get(quality.Quality);
if (quality.Proper)
return qualityDefinition.SceneName + " PROPER";
else
return qualityDefinition.SceneName;
}*/
}
}

@ -1,18 +0,0 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Qualities
{
public class QualitySize : ModelBase
{
public int QualityId { get; set; }
public string Name { get; set; }
public int MinSize { get; set; }
public int MaxSize { get; set; }
public override string ToString()
{
return Name;
}
}
}

@ -1,33 +0,0 @@
using System;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Qualities
{
public interface IQualitySizeRepository : IBasicRepository<QualitySize>
{
QualitySize GetByQualityId(int qualityId);
}
public class QualitySizeRepository : BasicRepository<QualitySize>, IQualitySizeRepository
{
public QualitySizeRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public QualitySize GetByQualityId(int qualityId)
{
try
{
return Query.Single(q => q.QualityId == qualityId);
}
catch (InvalidOperationException e)
{
throw new ModelNotFoundException(typeof(QualitySize), qualityId);
}
}
}
}

@ -1,64 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Qualities
{
public interface IQualitySizeService
{
void Update(QualitySize qualitySize);
List<QualitySize> All();
QualitySize Get(int qualityId);
}
public class QualitySizeService : IQualitySizeService, IHandle<ApplicationStartedEvent>
{
private readonly IQualitySizeRepository _qualitySizeRepository;
private readonly Logger _logger;
public QualitySizeService(IQualitySizeRepository qualitySizeRepository, Logger logger)
{
_qualitySizeRepository = qualitySizeRepository;
_logger = logger;
}
public virtual void Update(QualitySize qualitySize)
{
_qualitySizeRepository.Update(qualitySize);
}
public virtual List<QualitySize> All()
{
return _qualitySizeRepository.All().ToList();
}
public virtual QualitySize Get(int qualityId)
{
return _qualitySizeRepository.GetByQualityId(qualityId);
}
public void Handle(ApplicationStartedEvent message)
{
var existing = All();
_logger.Debug("Setting up default quality sizes");
foreach (var quality in Quality.All())
{
if (!existing.Any(s => s.QualityId == quality.Id))
{
_qualitySizeRepository.Insert(new QualitySize
{
QualityId = quality.Id,
Name = quality.Name,
MinSize = 0,
MaxSize = 100
});
}
}
}
}
}

@ -1,5 +1,6 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Queue

@ -1,120 +0,0 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Tv
{
public class QualityModel : IComparable<QualityModel>, IEmbeddedDocument
{
public Quality Quality { get; set; }
public Boolean Proper { get; set; }
public QualityModel()
: this(Quality.Unknown)
{
}
public QualityModel(Quality quality, Boolean proper = false)
{
Quality = quality;
Proper = proper;
}
public int CompareTo(QualityModel other)
{
if (other.Quality > Quality)
return -1;
if (other.Quality < Quality)
return 1;
if (other.Quality == Quality && other.Proper == Proper)
return 0;
if (Proper && !other.Proper)
return 1;
if (!Proper && other.Proper)
return -1;
return 0;
}
public static bool operator !=(QualityModel x, QualityModel y)
{
return !(x == y);
}
public static bool operator ==(QualityModel x, QualityModel y)
{
var xObj = (Object)x;
var yObj = (object)y;
if (xObj == null || yObj == null)
{
return xObj == yObj;
}
return x.CompareTo(y) == 0;
}
public static bool operator >(QualityModel x, QualityModel y)
{
return x.CompareTo(y) > 0;
}
public static bool operator <(QualityModel x, QualityModel y)
{
return x.CompareTo(y) < 0;
}
public static bool operator <=(QualityModel x, QualityModel y)
{
return x.CompareTo(y) <= 0;
}
public static bool operator >=(QualityModel x, QualityModel y)
{
return x.CompareTo(y) >= 0;
}
public override string ToString()
{
string result = Quality.ToString();
if (Proper)
{
result += " Proper";
}
return result;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + Proper.GetHashCode();
hash = hash * 23 + Quality.GetHashCode();
return hash;
}
}
public bool Equals(QualityModel other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Quality, Quality) && other.Proper.Equals(Proper);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(QualityModel)) return false;
return Equals((QualityModel)obj);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,11 @@
'use strict';
define(
[
'backbone',
'Quality/QualityDefinitionModel'
], function (Backbone, QualityDefinitionModel) {
return Backbone.Collection.extend({
model: QualityDefinitionModel,
url : window.NzbDrone.ApiRoot + '/qualitydefinition'
});
});

@ -9,10 +9,10 @@ define(
baseInitialize: ModelBase.prototype.initialize,
initialize: function () {
var name = this.get('name');
var name = this.get('quality').name;
this.successMessage = 'Saved ' + name + ' size settings';
this.errorMessage = 'Couldn\'t save ' + name + ' size settings';
this.successMessage = 'Saved ' + name + ' quality settings';
this.errorMessage = 'Couldn\'t save ' + name + ' quality settings';
this.baseInitialize.call(this);
}

@ -1,11 +0,0 @@
'use strict';
define(
[
'backbone',
'Quality/QualitySizeModel'
], function (Backbone, QualitySizeModel) {
return Backbone.Collection.extend({
model: QualitySizeModel,
url : window.NzbDrone.ApiRoot + '/qualitysize'
});
});

@ -0,0 +1,16 @@
<fieldset>
<legend>Quality Definitions</legend>
<div class="span11">
<div id="quality-definition-list">
<div class="x-header">
<div class="row">
<span class="span2">Quality</span>
<span class="span2">Title</span>
<span class="offset1 span4">Size Limit</span>
</div>
</div>
<div class="x-rows">
</div>
</div>
</div>
</fieldset>

@ -0,0 +1,17 @@
'use strict';
define(
[
'marionette',
'backgrid',
'Settings/Quality/Definition/QualityDefinitionView'
], function (Marionette, Backgrid, QualityDefinitionView) {
return Marionette.CompositeView.extend({
template: 'Settings/Quality/Definition/QualityDefinitionCollectionTemplate',
itemViewContainer: ".x-rows",
itemView: QualityDefinitionView
});
});

@ -0,0 +1,31 @@
 <span class="span2">
{{quality.name}}
</span>
<span class="span2">
<input type="text" class="x-title input-block-level" value="{{title}}">
</span>
<span class="offset1 span4">
<div class="x-slider"></div>
<div class="size-label-wrapper">
<div class="pull-left">
<span class="label label-warning x-min-thirty"
name="thirtyMinuteMinSize"
title="Minimum size for a 30 minute episode">
</span>
<span class="label label-info x-min-sixty"
name="sixtyMinuteMinSize"
title="Minimum size for a 60 minute episode">
</span>
</div>
<div class="pull-right">
<span class="label label-warning x-max-thirty"
name="thirtyMinuteMaxSize"
title="Maximum size for a 30 minute episode">
</span>
<span class="label label-info x-max-sixty"
name="sixtyMinuteMaxSize"
title="Maximum size for a 60 minute episode">
</span>
</div>
</div>
</span>

@ -0,0 +1,86 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'filesize',
'jquery-ui'
], function (Marionette, AsModelBoundView, fileSize) {
var view = Marionette.ItemView.extend({
template: 'Settings/Quality/Definition/QualityDefinitionTemplate',
className: 'row',
ui: {
title : '.x-title',
sizeSlider : '.x-slider',
thirtyMinuteMinSize: '.x-min-thirty',
sixtyMinuteMinSize : '.x-min-sixty',
thirtyMinuteMaxSize: '.x-max-thirty',
sixtyMinuteMaxSize : '.x-max-sixty'
},
events: {
'change .x-title': '_updateTitle',
'slide .x-slider': '_updateSize'
},
initialize: function (options) {
this.qualityProfileCollection = options.qualityProfiles;
this.filesize = fileSize;
},
onRender: function () {
this.ui.sizeSlider.slider({
range : true,
min : 0,
max : 200,
values : [ this.model.get('minSize'), this.model.get('maxSize') ],
});
this._changeSize();
},
_updateTitle: function() {
this.model.set('title', this.ui.title.val());
},
_updateSize: function (event, ui) {
this.model.set('minSize', ui.values[0]);
this.model.set('maxSize', ui.values[1]);
this._changeSize();
},
_changeSize: function () {
var minSize = this.model.get('minSize');
var maxSize = this.model.get('maxSize');
{
var minBytes = minSize * 1024 * 1024;
var minThirty = fileSize(minBytes * 30, 1, false);
var minSixty = fileSize(minBytes * 60, 1, false);
this.ui.thirtyMinuteMinSize.html(minThirty);
this.ui.sixtyMinuteMinSize.html(minSixty);
}
{
var maxBytes = maxSize * 1024 * 1024;
var maxThirty = fileSize(maxBytes * 30, 1, false);
var maxSixty = fileSize(maxBytes * 60, 1, false);
this.ui.thirtyMinuteMaxSize.html(maxThirty);
this.ui.sixtyMinuteMaxSize.html(maxSixty);
}
/*if (parseInt(maxSize, 10) === 0) {
thirty = 'No Limit';
sixty = 'No Limit';
}*/
}
});
return AsModelBoundView.call(view);
});

@ -0,0 +1,9 @@
'use strict';
define(
[
'marionette'
], function (Marionette) {
return Marionette.ItemView.extend({
template : 'Settings/Quality/Profile/EditQualityProfileItemViewTemplate'
});
});

@ -0,0 +1,5 @@
<i class="x-moveleft-handle pull-left icon-chevron-left" />
<i class="x-drag-handle pull-right icon-resize-vertical advanced-setting" />
<i class="x-moveright-handle pull-right icon-chevron-right" />
<span>{{name}}</span>

@ -29,25 +29,15 @@
</div>
</div>
<div>
<div class="offset1 span3">
<div class="offset1 span2">
<h3>Available</h3>
<select multiple="multiple" class="x-available-list">
{{#each available}}
<option value="{{id}}">{{name}}</option>
{{/each}}
</select>
<ul class="x-available-list">
</ul>
</div>
<div class="span3">
<div class="control-group">
<div class="controls">
<h3>Allowed</h3>
<select multiple="multiple" class="x-allowed-list" validation-name="allowed">
{{#each allowed}}
<option value="{{id}}">{{name}}</option>
{{/each}}
</select>
</div>
</div>
<div class="span2">
<h3>Allowed</h3>
<ul class="x-allowed-list" validation-name="allowed">
</ul>
</div>
</div>
</div>

@ -4,63 +4,113 @@ define(
'vent',
'marionette',
'backbone',
'backbone.collectionview',
'Settings/Quality/Profile/EditQualityProfileItemView',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView',
'underscore'
], function (vent, Marionette, Backbone, AsModelBoundView, AsValidatedView, _) {
], function (vent, Marionette, Backbone, BackboneSortableCollectionView, EditQualityProfileItemView, AsModelBoundView, AsValidatedView, _) {
var view = Marionette.ItemView.extend({
template: 'Settings/Quality/Profile/EditQualityProfileTemplate',
ui: {
cutoff: '.x-cutoff'
available: '.x-available-list',
allowed : '.x-allowed-list',
cutoff : '.x-cutoff'
},
events: {
'click .x-save' : '_saveQualityProfile',
'dblclick .x-available-list': '_moveQuality',
'dblclick .x-allowed-list' : '_moveQuality'
//'click .x-qualityitem' : '_moveQuality',
},
initialize: function (options) {
this.profileCollection = options.profileCollection;
this.availableCollection = new Backbone.Collection(this.model.get('available'));
this.availableCollection.comparator = function (model) { return -model.get('weight'); };
this.availableCollection.sort();
this.allowedCollection = new Backbone.Collection(this.model.get('allowed').reverse());
},
onRender: function() {
var listViewAvailable = new BackboneSortableCollectionView( {
el : this.ui.available,
modelView : EditQualityProfileItemView,
selectable: false,
sortable : false,
collection: this.availableCollection
});
listViewAvailable.render();
var listViewAllowed = new BackboneSortableCollectionView( {
el : this.ui.allowed,
modelView : EditQualityProfileItemView,
selectable: false,
sortable : true,
sortableOptions : {
handle: ".x-drag-handle"
},
collection : this.allowedCollection
} );
listViewAllowed.render();
this.listenTo(listViewAvailable, "doubleClick", this._moveQuality);
this.listenTo(listViewAllowed, "doubleClick", this._moveQuality);
this.listenTo(listViewAllowed, "sortStop", this._updateModel);
},
_moveQuality: function (event) {
var quality;
var qualityId = event.target.value;
var availableCollection = new Backbone.Collection(this.model.get('available'));
availableCollection.comparator = function (model) {
return model.get('weight');
};
var allowedCollection = new Backbone.Collection(this.model.get('allowed'));
allowedCollection.comparator = function (model) {
return model.get('weight');
};
if (availableCollection.get(qualityId)) {
quality = availableCollection.get(qualityId);
availableCollection.remove(quality);
allowedCollection.add(quality);
var qualityId = event.get('id');
if (this.availableCollection.get(qualityId)) {
quality = this.availableCollection.get(qualityId);
var idealIndex = 0;
var idealMismatches = 1000;
// Insert it at the best possible spot.
for (var i = 0; i <= this.allowedCollection.length; i++) {
var mismatches = 0;
for (var j = 0; j < i; j++) {
if (this.allowedCollection.at(j).get('weight') < quality.get('weight'))
mismatches++;
}
for (j = i; j < this.allowedCollection.length; j++) {
if (this.allowedCollection.at(j).get('weight') > quality.get('weight'))
mismatches++;
}
if (mismatches <= idealMismatches) {
idealIndex = i;
idealMismatches = mismatches;
}
}
this.availableCollection.remove(quality);
this.allowedCollection.add(quality, {at: idealIndex});
}
else if (allowedCollection.get(qualityId)) {
quality = allowedCollection.get(qualityId);
else if (this.allowedCollection.get(qualityId)) {
quality = this.allowedCollection.get(qualityId);
allowedCollection.remove(quality);
availableCollection.add(quality);
this.allowedCollection.remove(quality);
this.availableCollection.add(quality);
}
else {
throw 'couldnt find quality id ' + qualityId;
}
this.model.set('available', availableCollection.toJSON());
this.model.set('allowed', allowedCollection.toJSON());
this._updateModel();
},
_updateModel: function() {
this.model.set('available', this.availableCollection.toJSON().reverse());
this.model.set('allowed', this.allowedCollection.toJSON().reverse());
this.render();
},
_saveQualityProfile: function () {
var self = this;
var cutoff = _.findWhere(this.model.get('allowed'), { id: parseInt(this.ui.cutoff.val(), 10)});

@ -5,27 +5,27 @@ define(
'marionette',
'Quality/QualityProfileCollection',
'Settings/Quality/Profile/QualityProfileCollectionView',
'Quality/QualitySizeCollection',
'Settings/Quality/Size/QualitySizeCollectionView'
], function (Marionette, QualityProfileCollection, QualityProfileCollectionView, QualitySizeCollection, QualitySizeCollectionView) {
'Quality/QualityDefinitionCollection',
'Settings/Quality/Definition/QualityDefinitionCollectionView'
], function (Marionette, QualityProfileCollection, QualityProfileCollectionView, QualityDefinitionCollection, QualityDefinitionCollectionView) {
return Marionette.Layout.extend({
template: 'Settings/Quality/QualityLayoutTemplate',
regions: {
qualityProfile : '#quality-profile',
qualitySize : '#quality-size'
qualityProfile : '#quality-profile',
qualityDefinition : '#quality-definition'
},
initialize: function (options) {
this.settings = options.settings;
QualityProfileCollection.fetch();
this.qualitySizeCollection = new QualitySizeCollection();
this.qualitySizeCollection.fetch();
this.qualityDefinitionCollection = new QualityDefinitionCollection();
this.qualityDefinitionCollection.fetch();
},
onShow: function () {
this.qualityProfile.show(new QualityProfileCollectionView({collection: QualityProfileCollection}));
this.qualitySize.show(new QualitySizeCollectionView({collection: this.qualitySizeCollection}));
this.qualityDefinition.show(new QualityDefinitionCollectionView({collection: this.qualityDefinitionCollection}));
}
});
});

@ -5,5 +5,5 @@
<br/>
<div class="row advanced-setting">
<div class="span12" id="quality-size"/>
<div class="span12" id="quality-definition"/>
</div>

@ -1,4 +0,0 @@
<fieldset>
<legend>Quality Size Limits</legend>
<ul class="quality-sizes"/>
</fieldset>

@ -1,9 +0,0 @@
'use strict';
define(['marionette', 'Settings/Quality/Size/QualitySizeView'], function (Marionette, QualitySizeView) {
return Marionette.CompositeView.extend({
itemView : QualitySizeView,
itemViewContainer: '.quality-sizes',
template : 'Settings/Quality/Size/QualitySizeCollectionTemplate'
});
});

@ -1,20 +0,0 @@
<div class="quality-size-item">
<h3 class="center-block">{{name}}</h3>
<div class="size">
<div class="size-value-wrapper">
<div>
<span class="label label-large label-warning x-size-thirty"
name="thirtyMinuteSize"
title="Maximum size for a 30 minute episode">
</span>
</div>
<div>
<span class="label label-large label-info x-size-sixty"
name="sixtyMinuteSize"
title="Maximum size for a 60 minute episode">
</span>
</div>
</div>
<input type="text" name="maxSize" class="knob x-knob" />
</div>
</div>

@ -1,61 +0,0 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'filesize',
'jquery.knob'
], function (Marionette, AsModelBoundView, fileSize) {
var view = Marionette.ItemView.extend({
template: 'Settings/Quality/Size/QualitySizeTemplate',
tagName : 'li',
ui: {
knob : '.x-knob',
thirtyMinuteSize: '.x-size-thirty',
sixtyMinuteSize : '.x-size-sixty'
},
events: {
'change .x-knob': '_changeMaxSize'
},
initialize: function (options) {
this.qualityProfileCollection = options.qualityProfiles;
this.filesize = fileSize;
},
onRender: function () {
this.ui.knob.knob({
min : 0,
max : 200,
step : 1,
cursor : 25,
width : 150,
stopper : true,
displayInput: false
});
this._changeMaxSize();
},
_changeMaxSize: function () {
var maxSize = this.model.get('maxSize');
var bytes = maxSize * 1024 * 1024;
var thirty = fileSize(bytes * 30, 1, false);
var sixty = fileSize(bytes * 60, 1, false);
if (parseInt(maxSize, 10) === 0) {
thirty = 'No Limit';
sixty = 'No Limit';
}
this.ui.thirtyMinuteSize.html(thirty);
this.ui.sixtyMinuteSize.html(sixty);
}
});
return AsModelBoundView.call(view);
});

@ -1,6 +1,7 @@
@import "../../Shared/Styles/card";
@import "../../Content/Bootstrap/mixins";
.quality-profiles, .quality-sizes {
.quality-profiles {
li {
display: inline-block;
vertical-align: top;
@ -35,41 +36,121 @@
}
}
.quality-size-item {
.card;
text-align: center;
width: 200px;
height: 210px;
padding: 10px 15px;
h3 {
margin-top: 0px;
ul.x-available-list, ul.x-allowed-list {
min-height: 100px;
.user-select(none);
margin: 0;
padding: 0;
list-style-type: none;
outline: none;
cursor: pointer;
li {
margin: 2px;
padding: 2px;
line-height: 20px;
border: 1px solid #AAA;
border-radius: 4px; /* may need vendor varients */
background: #FAFAFA;
&:hover {
border-color: #888;
background: #EEE;
}
.x-drag-handle, .x-moveleft-handle, .x-moveright-handle {
opacity: 0.0;
line-height: 20px;
}
}
}
.size {
position: relative;
height: 100px;
margin: 10px;
text-align: center;
ul.x-available-list li {
.x-moveright-handle {
opacity: 0.2;
}
.x-drag-handle {
display: none;
}
&:hover .x-moveright-handle {
opacity: 1.0;
}
}
.knob {
box-shadow: none;
ul.x-allowed-list li {
.x-drag-handle, .x-moveleft-handle {
opacity: 0.2;
}
.x-drag-handle:hover {
opacity: 1.0;
cursor: pointer;
}
&:hover .x-moveleft-handle {
opacity: 1.0;
}
}
.size-value-wrapper {
position: absolute;
top: 50px;
width: 100%;
#quality-definition-list {
div {
margin-top: 2px;
.x-header .row {
font-weight: bold;
line-height: 40px;
}
.x-rows .row {
line-height: 30px;
border-top: 1px solid #ddd;
vertical-align: middle;
padding: 5px;
input {
margin-bottom: 0px;
}
.size-label-wrapper {
line-height: 20px;
}
.label {
min-width: 70px;
text-align: center;
margin: 0px 1px;
padding: 1px 4px;
}
.ui-slider {
position: relative;
text-align: left;
background-color: #f5f5f5;
border-radius: 3px;
border: 1px solid #ccc;
height: 8px;
.ui-slider-range {
position: absolute;
display: block;
background-color: #ddd;
height: 100%;
}
.ui-slider-handle {
position: absolute;
z-index: 2;
width: 6px;
height: 12px;
cursor: default;
background-color: #ccc;
border: 1px solid #aaa;
border-radius: 3px;
top: -3px;
}
}
}
}
#quality-size {
overflow: hidden;
}

@ -1,6 +1,7 @@
'use strict';
define(
[
'jquery',
'vent',
'marionette',
'backbone',
@ -17,7 +18,8 @@ define(
'Settings/General/GeneralView',
'Shared/LoadingView',
'Config'
], function (vent,
], function ($,
vent,
Marionette,
Backbone,
SettingsModel,
@ -196,7 +198,7 @@ define(
this.ui.advancedSettings.prop('checked', checked);
if (checked) {
this.$el.addClass('show-advanced-settings');
$('body').addClass('show-advanced-settings');
}
},
@ -205,11 +207,11 @@ define(
Config.setValue('advancedSettings', checked);
if (checked) {
this.$el.addClass('show-advanced-settings');
$('body').addClass('show-advanced-settings');
}
else {
this.$el.removeClass('show-advanced-settings');
$('body').removeClass('show-advanced-settings');
}
}
});

@ -2,29 +2,31 @@
require.config({
paths: {
'backbone' : 'JsLibraries/backbone',
'moment' : 'JsLibraries/moment',
'filesize' : 'JsLibraries/filesize',
'handlebars' : 'JsLibraries/handlebars.runtime',
'handlebars.helpers' : 'JsLibraries/handlebars.helpers',
'bootstrap' : 'JsLibraries/bootstrap',
'backbone.deepmodel' : 'JsLibraries/backbone.deep.model',
'backbone.pageable' : 'JsLibraries/backbone.pageable',
'backbone.validation' : 'JsLibraries/backbone.validation',
'backbone.modelbinder': 'JsLibraries/backbone.modelbinder',
'backgrid' : 'JsLibraries/backbone.backgrid',
'backgrid.paginator' : 'JsLibraries/backbone.backgrid.paginator',
'backgrid.selectall' : 'JsLibraries/backbone.backgrid.selectall',
'fullcalendar' : 'JsLibraries/fullcalendar',
'backstrech' : 'JsLibraries/jquery.backstretch',
'underscore' : 'JsLibraries/lodash.underscore',
'marionette' : 'JsLibraries/backbone.marionette',
'signalR' : 'JsLibraries/jquery.signalR',
'jquery.knob' : 'JsLibraries/jquery.knob',
'jquery.dotdotdot' : 'JsLibraries/jquery.dotdotdot',
'messenger' : 'JsLibraries/messenger',
'jquery' : 'JsLibraries/jquery',
'libs' : 'JsLibraries/',
'backbone' : 'JsLibraries/backbone',
'moment' : 'JsLibraries/moment',
'filesize' : 'JsLibraries/filesize',
'handlebars' : 'JsLibraries/handlebars.runtime',
'handlebars.helpers' : 'JsLibraries/handlebars.helpers',
'bootstrap' : 'JsLibraries/bootstrap',
'backbone.deepmodel' : 'JsLibraries/backbone.deep.model',
'backbone.pageable' : 'JsLibraries/backbone.pageable',
'backbone.validation' : 'JsLibraries/backbone.validation',
'backbone.modelbinder' : 'JsLibraries/backbone.modelbinder',
'backbone.collectionview' : 'JsLibraries/backbone.collectionview',
'backgrid' : 'JsLibraries/backbone.backgrid',
'backgrid.paginator' : 'JsLibraries/backbone.backgrid.paginator',
'backgrid.selectall' : 'JsLibraries/backbone.backgrid.selectall',
'fullcalendar' : 'JsLibraries/fullcalendar',
'backstrech' : 'JsLibraries/jquery.backstretch',
'underscore' : 'JsLibraries/lodash.underscore',
'marionette' : 'JsLibraries/backbone.marionette',
'signalR' : 'JsLibraries/jquery.signalR',
'jquery-ui' : 'JsLibraries/jquery-ui',
'jquery.knob' : 'JsLibraries/jquery.knob',
'jquery.dotdotdot' : 'JsLibraries/jquery.dotdotdot',
'messenger' : 'JsLibraries/messenger',
'jquery' : 'JsLibraries/jquery',
'libs' : 'JsLibraries/',
'api': 'Require/require.api'
},
@ -105,6 +107,12 @@ require.config({
}
},
'jquery-ui' : {
deps:
[
'jquery'
]
},
'jquery.knob' : {
deps:
[
@ -143,6 +151,14 @@ require.config({
'backbone'
]
},
'backbone.collectionview': {
deps:
[
'backbone',
'jquery-ui'
],
exports: 'Backbone.CollectionView'
},
backgrid : {
deps:
[

Loading…
Cancel
Save