From 74a38415cf19093dee2afbc177e2861fbf43a535 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 8 Jun 2014 01:22:55 -0700 Subject: [PATCH] Profiles Indexes are created with the same uniqueness when copying a table New: Non-English episode support New: Renamed Quality Profiles to Profiles and made them more powerful New: Configurable wait time before grabbing a release to wait for a better quality --- .../MappingTests/ResourceMappingFixture.cs | 17 +- src/NzbDrone.Api/Indexers/ReleaseModule.cs | 6 +- src/NzbDrone.Api/Indexers/ReleaseResource.cs | 2 + src/NzbDrone.Api/NzbDrone.Api.csproj | 11 +- .../Profiles/Languages/LanguageModule.cs | 39 + .../Profiles/Languages/LanguageResource.cs | 14 + .../Profiles/LegacyProfileModule.cs | 36 + src/NzbDrone.Api/Profiles/ProfileModule.cs | 67 + src/NzbDrone.Api/Profiles/ProfileResource.cs | 25 + .../ProfileSchemaModule.cs} | 23 +- .../ProfileValidation.cs} | 8 +- .../Qualities/QualityProfileModule.cs | 60 - .../Qualities/QualityProfileResource.cs | 20 - src/NzbDrone.Api/Queue/QueueModule.cs | 23 +- src/NzbDrone.Api/Series/SeriesModule.cs | 5 +- src/NzbDrone.Api/Series/SeriesResource.cs | 22 +- .../Datastore/MarrDataLazyLoadingFixture.cs | 19 +- .../AlterFixture.cs | 19 + .../DuplicateFixture.cs | 4 +- .../CutoffSpecificationFixture.cs | 11 +- .../HistorySpecificationFixture.cs | 17 +- .../LanguageSpecificationFixture.cs | 44 +- .../NotInQueueSpecificationFixture.cs | 3 +- .../PrioritizeDownloadDecisionFixture.cs | 4 +- ...ityAllowedByProfileSpecificationFixture.cs | 7 +- .../QualityUpgradeSpecificationFixture.cs | 9 +- .../RssSync/DelaySpecificationFixture.cs | 253 +++ .../RssSync/ProperSpecificationFixture.cs | 3 +- .../UpgradeDiskSpecificationFixture.cs | 3 +- .../DownloadApprovedFixture.cs | 26 +- .../ProcessFixture.cs | 165 ++ .../HistoryTests/HistoryServiceFixture.cs | 9 +- .../CleanupOrphanedBlacklistFixture.cs | 42 + .../CleanupOrphanedPendingReleasesFixture.cs | 42 + .../NzbSearchServiceFixture.cs | 7 +- .../ImportDecisionMakerFixture.cs | 3 +- .../UpgradeSpecificationFixture.cs | 5 +- .../ImportApprovedEpisodesFixture.cs | 3 +- .../NzbDrone.Core.Test.csproj | 11 +- .../ProfileRepositoryFixture.cs} | 14 +- .../ProfileServiceFixture.cs} | 27 +- .../Qualities/QualityFixture.cs | 5 +- .../Qualities/QualityModelComparerFixture.cs | 19 +- .../EpisodeProviderTest.cs | 1519 ----------------- .../EpisodesWhereCutoffUnmetFixture.cs | 17 +- ...yFixture.cs => SeriesRepositoryFixture.cs} | 10 +- .../UpdateMultipleSeriesFixture.cs | 2 +- .../Extensions/RelationshipExtensions.cs | 5 - .../036_update_with_quality_converters.cs | 3 +- .../Migration/054_rename_profiles.cs | 31 + .../Migration/055_drop_old_profile_columns.cs | 14 + .../Migration/Framework/SQLiteIndex.cs | 7 +- .../Framework/SQLiteMigrationHelper.cs | 6 - src/NzbDrone.Core/Datastore/TableMapping.cs | 16 +- .../{Specifications => }/DownloadDecision.cs | 25 +- .../DecisionEngine/DownloadDecisionMaker.cs | 16 +- .../DownloadDecisionPriorizationService.cs | 2 +- .../IDecisionEngineSpecification.cs | 5 +- .../DecisionEngine/IRejectWithReason.cs | 2 +- .../QualityUpgradableSpecification.cs | 9 +- src/NzbDrone.Core/DecisionEngine/Rejection.cs | 21 + .../DecisionEngine/RejectionType.cs | 8 + .../AcceptableSizeSpecification.cs | 7 +- .../Specifications/BlacklistSpecification.cs | 2 + .../Specifications/CutoffSpecification.cs | 4 +- .../Specifications/LanguageSpecification.cs | 12 +- .../Specifications/NotInQueueSpecification.cs | 4 +- .../NotRestrictedReleaseSpecification.cs | 2 + .../Specifications/NotSampleSpecification.cs | 2 + .../QualityAllowedByProfileSpecification.cs | 4 +- .../Specifications/RetentionSpecification.cs | 2 + .../Specifications/RetrySpecification.cs | 2 + .../RssSync/DelaySpecification.cs | 116 ++ .../RssSync/HistorySpecification.cs | 6 +- .../RssSync/MonitoredEpisodeSpecification.cs | 2 + .../RssSync/ProperSpecification.cs | 2 + .../Search/DailyEpisodeMatchSpecification.cs | 3 + .../Search/EpisodeRequestedSpecification.cs | 2 + .../Search/SeasonMatchSpecification.cs | 2 + .../Search/SeriesSpecification.cs | 2 + .../SingleEpisodeSearchMatchSpecification.cs | 2 + .../UpgradeDiskSpecification.cs | 4 +- .../Download/DownloadApprovedReports.cs | 67 - .../Download/DownloadClientItem.cs | 3 - .../Download/Pending/PendingRelease.cs | 18 + .../Pending/PendingReleaseRepository.cs | 31 + .../Download/Pending/PendingReleaseService.cs | 219 +++ .../Pending/PendingReleasesUpdatedEvent.cs | 8 + .../Download/ProcessDownloadDecisions.cs | 103 ++ .../Download/ProcessedDecisions.cs | 17 + src/NzbDrone.Core/History/HistoryService.cs | 7 +- .../CleanupOrphanedPendingReleases.cs | 31 + .../IndexerSearch/EpisodeSearchService.cs | 19 +- .../IndexerSearch/SeasonSearchService.cs | 10 +- .../IndexerSearch/SeriesSearchService.cs | 8 +- .../Indexers/RssSyncCompleteEvent.cs | 8 + src/NzbDrone.Core/Indexers/RssSyncService.cs | 38 +- .../EpisodeImport/ImportApprovedEpisodes.cs | 2 +- .../EpisodeImport/ImportDecisionMaker.cs | 3 +- .../Specifications/UpgradeSpecification.cs | 3 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 30 +- src/NzbDrone.Core/Parser/Language.cs | 25 +- src/NzbDrone.Core/Profiles/GrabDelayMode.cs | 9 + src/NzbDrone.Core/Profiles/Profile.cs | 18 + .../Profiles/ProfileInUseException.cs | 13 + .../Profiles/ProfileQualityItem.cs | 11 + .../Profiles/ProfileRepository.cs | 18 + src/NzbDrone.Core/Profiles/ProfileService.cs | 106 ++ .../Qualities/QualityModelComparer.cs | 21 +- src/NzbDrone.Core/Qualities/QualityProfile.cs | 12 - .../Qualities/QualityProfileInUseException.cs | 13 - .../Qualities/QualityProfileItem.cs | 11 - .../Qualities/QualityProfileRepository.cs | 19 - .../Qualities/QualityProfileService.cs | 105 -- src/NzbDrone.Core/Queue/QueueService.cs | 29 +- src/NzbDrone.Core/Tv/EpisodeCutoffService.cs | 17 +- src/NzbDrone.Core/Tv/EpisodeRepository.cs | 2 +- src/NzbDrone.Core/Tv/Series.cs | 6 +- .../Validation/LangaugeValidator.cs | 21 + .../Validation/RuleBuilderExtensions.cs | 6 + .../EpisodeIntegrationTests.cs | 2 +- .../NzbDrone.Integration.Test.csproj | 1 - .../QualityProfileIntegrationTest.cs | 10 - .../SeriesEditorIntegrationTest.cs | 6 +- .../SeriesIntegrationTest.cs | 4 +- src/UI/AddSeries/AddSeriesLayout.js | 6 +- src/UI/AddSeries/SearchResultView.js | 36 +- .../AddSeries/SearchResultViewTemplate.html | 4 +- src/UI/Cells/Edit/QualityCellEditor.js | 14 +- .../{QualityProfileCell.js => ProfileCell.js} | 10 +- src/UI/Config.js | 2 +- .../Summary/EpisodeSummaryLayoutTemplate.html | 2 +- src/UI/Handlebars/Helpers/Quality.js | 10 +- src/UI/Handlebars/Helpers/String.js | 9 + .../backbone.marionette.templates.js | 2 +- src/UI/History/Queue/QueueStatusCell.js | 5 + src/UI/History/Queue/TimeleftCell.js | 11 +- src/UI/Profile/ProfileCollection.js | 18 + .../ProfileModel.js} | 0 .../ProfileSelectionPartial.html} | 2 +- src/UI/Quality/QualityProfileCollection.js | 18 - src/UI/Series/Details/InfoViewTemplate.html | 2 +- src/UI/Series/Edit/EditSeriesView.js | 12 +- .../Series/Edit/EditSeriesViewTemplate.html | 6 +- .../Series/Editor/SeriesEditorFooterView.js | 18 +- .../SeriesEditorFooterViewTemplate.html | 6 +- src/UI/Series/Editor/SeriesEditorLayout.js | 52 +- .../SeriesOverviewItemViewTemplate.html | 2 +- src/UI/Series/Index/SeriesIndexLayout.js | 12 +- .../{Quality => }/Profile/AllowedLabeler.js | 1 + .../DeleteProfileView.js} | 2 +- .../DeleteProfileViewTemplate.html} | 0 .../Edit/EditProfileItemView.js} | 2 +- .../Edit/EditProfileItemViewTemplate.html} | 0 .../Edit/EditProfileLayout.js} | 28 +- .../Edit/EditProfileLayoutTemplate.html} | 0 .../Settings/Profile/Edit/EditProfileView.js | 61 + .../Profile/Edit/EditProfileViewTemplate.html | 71 + .../Edit/QualitySortableCollectionView.js | 22 + .../Profile/Language/LanguageCollection.js | 18 + .../Profile/Language/LanguageModel.js | 10 + src/UI/Settings/Profile/LanguageLabel.js | 20 + .../ProfileCollectionTemplate.html} | 6 +- .../ProfileCollectionView.js} | 14 +- src/UI/Settings/Profile/ProfileLayout.js | 27 + .../Profile/ProfileLayoutTemplate.html | 3 + .../Profile/ProfileSchemaCollection.js | 13 + .../ProfileView.js} | 7 +- .../Settings/Profile/ProfileViewTemplate.html | 16 + src/UI/Settings/Profile/profile.less | 31 + .../Definition/QualityDefinitionView.js | 2 +- .../Profile/Edit/EditQualityProfileView.js | 26 - .../Edit/EditQualityProfileViewTemplate.html | 23 - .../Edit/QualitySortableCollectionView.js | 22 - .../Profile/QualityProfileSchemaCollection.js | 13 - .../Profile/QualityProfileViewTemplate.html | 9 - src/UI/Settings/Quality/QualityLayout.js | 7 +- .../Quality/QualityLayoutTemplate.html | 6 - src/UI/Settings/Quality/quality.less | 24 - src/UI/Settings/SettingsLayout.js | 18 + src/UI/Settings/SettingsLayoutTemplate.html | 2 + src/UI/Settings/settings.less | 1 + 182 files changed, 2493 insertions(+), 2433 deletions(-) create mode 100644 src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs create mode 100644 src/NzbDrone.Api/Profiles/Languages/LanguageResource.cs create mode 100644 src/NzbDrone.Api/Profiles/LegacyProfileModule.cs create mode 100644 src/NzbDrone.Api/Profiles/ProfileModule.cs create mode 100644 src/NzbDrone.Api/Profiles/ProfileResource.cs rename src/NzbDrone.Api/{Qualities/QualityProfileSchemaModule.cs => Profiles/ProfileSchemaModule.cs} (50%) rename src/NzbDrone.Api/{Qualities/QualityProfileValidation.cs => Profiles/ProfileValidation.cs} (69%) delete mode 100644 src/NzbDrone.Api/Qualities/QualityProfileModule.cs delete mode 100644 src/NzbDrone.Api/Qualities/QualityProfileResource.cs create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs create mode 100644 src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/ProcessFixture.cs create mode 100644 src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs create mode 100644 src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs rename src/NzbDrone.Core.Test/{Qualities/QualityProfileRepositoryFixture.cs => Profiles/ProfileRepositoryFixture.cs} (52%) rename src/NzbDrone.Core.Test/{Qualities/QualityProfileServiceFixture.cs => Profiles/ProfileServiceFixture.cs} (61%) delete mode 100644 src/NzbDrone.Core.Test/TvTests/EpisodeProviderTests/EpisodeProviderTest.cs rename src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/{QualityProfileRepositoryFixture.cs => SeriesRepositoryFixture.cs} (74%) create mode 100644 src/NzbDrone.Core/Datastore/Migration/054_rename_profiles.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/055_drop_old_profile_columns.cs rename src/NzbDrone.Core/DecisionEngine/{Specifications => }/DownloadDecision.cs (53%) create mode 100644 src/NzbDrone.Core/DecisionEngine/Rejection.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/RejectionType.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs delete mode 100644 src/NzbDrone.Core/Download/DownloadApprovedReports.cs create mode 100644 src/NzbDrone.Core/Download/Pending/PendingRelease.cs create mode 100644 src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs create mode 100644 src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs create mode 100644 src/NzbDrone.Core/Download/Pending/PendingReleasesUpdatedEvent.cs create mode 100644 src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs create mode 100644 src/NzbDrone.Core/Download/ProcessedDecisions.cs create mode 100644 src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs create mode 100644 src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs create mode 100644 src/NzbDrone.Core/Profiles/GrabDelayMode.cs create mode 100644 src/NzbDrone.Core/Profiles/Profile.cs create mode 100644 src/NzbDrone.Core/Profiles/ProfileInUseException.cs create mode 100644 src/NzbDrone.Core/Profiles/ProfileQualityItem.cs create mode 100644 src/NzbDrone.Core/Profiles/ProfileRepository.cs create mode 100644 src/NzbDrone.Core/Profiles/ProfileService.cs delete mode 100644 src/NzbDrone.Core/Qualities/QualityProfile.cs delete mode 100644 src/NzbDrone.Core/Qualities/QualityProfileInUseException.cs delete mode 100644 src/NzbDrone.Core/Qualities/QualityProfileItem.cs delete mode 100644 src/NzbDrone.Core/Qualities/QualityProfileRepository.cs delete mode 100644 src/NzbDrone.Core/Qualities/QualityProfileService.cs create mode 100644 src/NzbDrone.Core/Validation/LangaugeValidator.cs delete mode 100644 src/NzbDrone.Integration.Test/QualityProfileIntegrationTest.cs rename src/UI/Cells/{QualityProfileCell.js => ProfileCell.js} (51%) create mode 100644 src/UI/Handlebars/Helpers/String.js create mode 100644 src/UI/Profile/ProfileCollection.js rename src/UI/{Quality/QualityProfileModel.js => Profile/ProfileModel.js} (100%) rename src/UI/{Quality/QualityProfileSelectionPartial.html => Profile/ProfileSelectionPartial.html} (53%) delete mode 100644 src/UI/Quality/QualityProfileCollection.js rename src/UI/Settings/{Quality => }/Profile/AllowedLabeler.js (99%) rename src/UI/Settings/{Quality/Profile/DeleteQualityProfileView.js => Profile/DeleteProfileView.js} (86%) rename src/UI/Settings/{Quality/Profile/DeleteQualityProfileViewTemplate.html => Profile/DeleteProfileViewTemplate.html} (100%) rename src/UI/Settings/{Quality/Profile/Edit/EditQualityProfileItemView.js => Profile/Edit/EditProfileItemView.js} (61%) rename src/UI/Settings/{Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html => Profile/Edit/EditProfileItemViewTemplate.html} (100%) rename src/UI/Settings/{Quality/Profile/Edit/EditQualityProfileLayout.js => Profile/Edit/EditProfileLayout.js} (83%) rename src/UI/Settings/{Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html => Profile/Edit/EditProfileLayoutTemplate.html} (100%) create mode 100644 src/UI/Settings/Profile/Edit/EditProfileView.js create mode 100644 src/UI/Settings/Profile/Edit/EditProfileViewTemplate.html create mode 100644 src/UI/Settings/Profile/Edit/QualitySortableCollectionView.js create mode 100644 src/UI/Settings/Profile/Language/LanguageCollection.js create mode 100644 src/UI/Settings/Profile/Language/LanguageModel.js create mode 100644 src/UI/Settings/Profile/LanguageLabel.js rename src/UI/Settings/{Quality/Profile/QualityProfileCollectionTemplate.html => Profile/ProfileCollectionTemplate.html} (64%) rename src/UI/Settings/{Quality/Profile/QualityProfileCollectionView.js => Profile/ProfileCollectionView.js} (69%) create mode 100644 src/UI/Settings/Profile/ProfileLayout.js create mode 100644 src/UI/Settings/Profile/ProfileLayoutTemplate.html create mode 100644 src/UI/Settings/Profile/ProfileSchemaCollection.js rename src/UI/Settings/{Quality/Profile/QualityProfileView.js => Profile/ProfileView.js} (81%) create mode 100644 src/UI/Settings/Profile/ProfileViewTemplate.html create mode 100644 src/UI/Settings/Profile/profile.less delete mode 100644 src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js delete mode 100644 src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html delete mode 100644 src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js delete mode 100644 src/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js delete mode 100644 src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html diff --git a/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs b/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs index 00c52d90e..1e02507f0 100644 --- a/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs +++ b/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs @@ -11,15 +11,16 @@ using NzbDrone.Api.History; using NzbDrone.Api.Indexers; using NzbDrone.Api.Logs; using NzbDrone.Api.Mapping; -using NzbDrone.Api.Qualities; +using NzbDrone.Api.Profiles; using NzbDrone.Api.RootFolders; using NzbDrone.Api.Series; -using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Indexers; using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.RootFolders; using NzbDrone.Core.Tv; @@ -41,8 +42,8 @@ namespace NzbDrone.Api.Test.MappingTests [TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))] [TestCase(typeof(DownloadDecision), typeof(ReleaseResource))] [TestCase(typeof(Core.History.History), typeof(HistoryResource))] - [TestCase(typeof(QualityProfile), typeof(QualityProfileResource))] - [TestCase(typeof(QualityProfileItem), typeof(QualityProfileItemResource))] + [TestCase(typeof(Profile), typeof(ProfileResource))] + [TestCase(typeof(ProfileQualityItem), typeof(ProfileQualityItemResource))] [TestCase(typeof(Log), typeof(LogResource))] [TestCase(typeof(Command), typeof(CommandResource))] public void matching_fields(Type modelType, Type resourceType) @@ -105,16 +106,16 @@ namespace NzbDrone.Api.Test.MappingTests [Test] - public void should_map_qualityprofile() + public void should_map_profile() { - var profileResource = new QualityProfileResource + var profileResource = new ProfileResource { Cutoff = Quality.WEBDL1080p, - Items = new List { new QualityProfileItemResource { Quality = Quality.WEBDL1080p, Allowed = true } } + Items = new List { new ProfileQualityItemResource { Quality = Quality.WEBDL1080p, Allowed = true } } }; - profileResource.InjectTo(); + profileResource.InjectTo(); } diff --git a/src/NzbDrone.Api/Indexers/ReleaseModule.cs b/src/NzbDrone.Api/Indexers/ReleaseModule.cs index 0e01fada3..a64222b0b 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseModule.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseModule.cs @@ -5,7 +5,6 @@ using Nancy; using NLog; using NzbDrone.Api.Mapping; using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Indexers; @@ -15,7 +14,6 @@ using Omu.ValueInjecter; using System.Linq; using Nancy.ModelBinding; using NzbDrone.Api.Extensions; -using NzbDrone.Core.Qualities; namespace NzbDrone.Api.Indexers { @@ -106,14 +104,14 @@ namespace NzbDrone.Api.Indexers release.InjectFrom(downloadDecision.RemoteEpisode.Release); release.InjectFrom(downloadDecision.RemoteEpisode.ParsedEpisodeInfo); release.InjectFrom(downloadDecision); - release.Rejections = downloadDecision.Rejections.ToList(); + release.Rejections = downloadDecision.Rejections.Select(r => r.Reason).ToList(); release.DownloadAllowed = downloadDecision.RemoteEpisode.DownloadAllowed; release.ReleaseWeight = result.Count; if (downloadDecision.RemoteEpisode.Series != null) { - release.QualityWeight = downloadDecision.RemoteEpisode.Series.QualityProfile.Value.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 2; + release.QualityWeight = downloadDecision.RemoteEpisode.Series.Profile.Value.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 2; } if (!release.Quality.Proper) diff --git a/src/NzbDrone.Api/Indexers/ReleaseResource.cs b/src/NzbDrone.Api/Indexers/ReleaseResource.cs index bb6f53379..a07861eb0 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseResource.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseResource.cs @@ -28,6 +28,8 @@ namespace NzbDrone.Api.Indexers public int[] EpisodeNumbers { get; set; } public int[] AbsoluteEpisodeNumbers { get; set; } public Boolean Approved { get; set; } + public Boolean TemporarilyRejected { get; set; } + public Boolean Rejected { get; set; } public Int32 TvRageId { get; set; } public IEnumerable Rejections { get; set; } public DateTime PublishDate { get; set; } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 75878901e..80bb065fb 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -150,6 +150,9 @@ + + + @@ -175,7 +178,7 @@ - + @@ -183,7 +186,7 @@ - + @@ -202,8 +205,8 @@ - - + + diff --git a/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs b/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs new file mode 100644 index 000000000..147bc69aa --- /dev/null +++ b/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Api.Profiles.Languages +{ + public class LanguageModule : NzbDroneRestModule + { + public LanguageModule() + { + GetResourceAll = GetAll; + GetResourceById = GetById; + } + + private LanguageResource GetById(int id) + { + var language = (Language)id; + + return new LanguageResource + { + Id = (int)language, + Name = language.ToString() + }; + } + + private List GetAll() + { + return ((Language[])Enum.GetValues(typeof (Language))) + .Select(l => new LanguageResource + { + Id = (int) l, + Name = l.ToString() + }) + .OrderBy(l => l.Name) + .ToList(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Profiles/Languages/LanguageResource.cs b/src/NzbDrone.Api/Profiles/Languages/LanguageResource.cs new file mode 100644 index 000000000..e51d3f555 --- /dev/null +++ b/src/NzbDrone.Api/Profiles/Languages/LanguageResource.cs @@ -0,0 +1,14 @@ +using System; +using Newtonsoft.Json; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Profiles.Languages +{ + public class LanguageResource : RestResource + { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)] + public Int32 Id { get; set; } + public String Name { get; set; } + public String NameLower { get { return Name.ToLowerInvariant(); } } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Profiles/LegacyProfileModule.cs b/src/NzbDrone.Api/Profiles/LegacyProfileModule.cs new file mode 100644 index 000000000..23f76fc3c --- /dev/null +++ b/src/NzbDrone.Api/Profiles/LegacyProfileModule.cs @@ -0,0 +1,36 @@ +using System; +using System.Text; +using Nancy; +using Nancy.Responses; + +namespace NzbDrone.Api.Profiles +{ + class LegacyProfileModule : NzbDroneApiModule + { + public LegacyProfileModule() + : base("qualityprofile") + { + Get["/"] = x => + { + string queryString = ConvertQueryParams(Request.Query); + var url = String.Format("/api/profile?{0}", queryString); + + return Response.AsRedirect(url, RedirectResponse.RedirectType.Permanent); + }; + } + + private string ConvertQueryParams(DynamicDictionary query) + { + var sb = new StringBuilder(); + + foreach (var key in query) + { + var value = query[key]; + + sb.AppendFormat("&{0}={1}", key, value); + } + + return sb.ToString().Trim('&'); + } + } +} diff --git a/src/NzbDrone.Api/Profiles/ProfileModule.cs b/src/NzbDrone.Api/Profiles/ProfileModule.cs new file mode 100644 index 000000000..413d99281 --- /dev/null +++ b/src/NzbDrone.Api/Profiles/ProfileModule.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using FluentValidation; +using NzbDrone.Api.Mapping; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Api.Profiles +{ + public class ProfileModule : NzbDroneRestModule + { + private readonly IProfileService _profileService; + + public ProfileModule(IProfileService profileService) + { + _profileService = profileService; + SharedValidator.RuleFor(c => c.Name).NotEmpty(); + SharedValidator.RuleFor(c => c.Cutoff).NotNull(); + SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality(); + SharedValidator.RuleFor(c => c.Language).ValidLanguage(); + + GetResourceAll = GetAll; + GetResourceById = GetById; + UpdateResource = Update; + CreateResource = Create; + DeleteResource = DeleteProfile; + } + + private int Create(ProfileResource resource) + { + var model = resource.InjectTo(); + model = _profileService.Add(model); + return model.Id; + } + + private void DeleteProfile(int id) + { + _profileService.Delete(id); + } + + private void Update(ProfileResource resource) + { + var model = _profileService.Get(resource.Id); + + model.Name = resource.Name; + model.Cutoff = (Quality)resource.Cutoff.Id; + model.Items = resource.Items.InjectTo>(); + model.Language = resource.Language; + model.GrabDelay = resource.GrabDelay; + model.GrabDelayMode = resource.GrabDelayMode; + + _profileService.Update(model); + } + + private ProfileResource GetById(int id) + { + return _profileService.Get(id).InjectTo(); + } + + private List GetAll() + { + var profiles = _profileService.All().InjectTo>(); + + return profiles; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Profiles/ProfileResource.cs b/src/NzbDrone.Api/Profiles/ProfileResource.cs new file mode 100644 index 000000000..432569460 --- /dev/null +++ b/src/NzbDrone.Api/Profiles/ProfileResource.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Api.REST; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Api.Profiles +{ + public class ProfileResource : RestResource + { + public String Name { get; set; } + public Quality Cutoff { get; set; } + public List Items { get; set; } + public Language Language { get; set; } + public Int32 GrabDelay { get; set; } + public GrabDelayMode GrabDelayMode { get; set; } + } + + public class ProfileQualityItemResource : RestResource + { + public Quality Quality { get; set; } + public bool Allowed { get; set; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs b/src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs similarity index 50% rename from src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs rename to src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs index 9fb05c48a..baa115cfe 100644 --- a/src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs +++ b/src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs @@ -1,34 +1,37 @@ using System.Collections.Generic; -using NzbDrone.Core.Qualities; -using NzbDrone.Api.Mapping; using System.Linq; +using NzbDrone.Api.Mapping; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; -namespace NzbDrone.Api.Qualities +namespace NzbDrone.Api.Profiles { - public class QualityProfileSchemaModule : NzbDroneRestModule + public class ProfileSchemaModule : NzbDroneRestModule { private readonly IQualityDefinitionService _qualityDefinitionService; - public QualityProfileSchemaModule(IQualityDefinitionService qualityDefinitionService) - : base("/qualityprofile/schema") + public ProfileSchemaModule(IQualityDefinitionService qualityDefinitionService) + : base("/profile/schema") { _qualityDefinitionService = qualityDefinitionService; GetResourceAll = GetAll; } - private List GetAll() + private List GetAll() { var items = _qualityDefinitionService.All() .OrderBy(v => v.Weight) - .Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = false }) + .Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = false }) .ToList(); - var profile = new QualityProfile(); + var profile = new Profile(); profile.Cutoff = Quality.Unknown; profile.Items = items; + profile.Language = Language.English; - return new List { profile.InjectTo() }; + return new List { profile.InjectTo() }; } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs b/src/NzbDrone.Api/Profiles/ProfileValidation.cs similarity index 69% rename from src/NzbDrone.Api/Qualities/QualityProfileValidation.cs rename to src/NzbDrone.Api/Profiles/ProfileValidation.cs index c90ebda61..003c96f39 100644 --- a/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs +++ b/src/NzbDrone.Api/Profiles/ProfileValidation.cs @@ -3,11 +3,11 @@ using System.Linq; using FluentValidation; using FluentValidation.Validators; -namespace NzbDrone.Api.Qualities +namespace NzbDrone.Api.Profiles { - public static class QualityProfileValidation + public static class ProfileValidation { - public static IRuleBuilderOptions> MustHaveAllowedQuality(this IRuleBuilder> ruleBuilder) + public static IRuleBuilderOptions> MustHaveAllowedQuality(this IRuleBuilder> ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); @@ -25,7 +25,7 @@ namespace NzbDrone.Api.Qualities protected override bool IsValid(PropertyValidatorContext context) { - var list = context.PropertyValue as IList; + var list = context.PropertyValue as IList; if (list == null) { diff --git a/src/NzbDrone.Api/Qualities/QualityProfileModule.cs b/src/NzbDrone.Api/Qualities/QualityProfileModule.cs deleted file mode 100644 index 6cb1b16df..000000000 --- a/src/NzbDrone.Api/Qualities/QualityProfileModule.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.Qualities; -using NzbDrone.Api.Mapping; -using FluentValidation; - -namespace NzbDrone.Api.Qualities -{ - public class QualityProfileModule : NzbDroneRestModule - { - private readonly IQualityProfileService _qualityProfileService; - - public QualityProfileModule(IQualityProfileService qualityProfileService) - { - _qualityProfileService = qualityProfileService; - SharedValidator.RuleFor(c => c.Name).NotEmpty(); - SharedValidator.RuleFor(c => c.Cutoff).NotNull(); - SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality();//.SetValidator(new AllowedValidator()); - - GetResourceAll = GetAll; - GetResourceById = GetById; - UpdateResource = Update; - CreateResource = Create; - DeleteResource = DeleteProfile; - } - - private int Create(QualityProfileResource resource) - { - var model = resource.InjectTo(); - model = _qualityProfileService.Add(model); - return model.Id; - } - - private void DeleteProfile(int id) - { - _qualityProfileService.Delete(id); - } - - private void Update(QualityProfileResource resource) - { - var model = _qualityProfileService.Get(resource.Id); - - model.Name = resource.Name; - model.Cutoff = (Quality)resource.Cutoff.Id; - model.Items = resource.Items.InjectTo>(); - _qualityProfileService.Update(model); - } - - private QualityProfileResource GetById(int id) - { - return _qualityProfileService.Get(id).InjectTo(); - } - - private List GetAll() - { - var profiles = _qualityProfileService.All().InjectTo>(); - - return profiles; - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Api/Qualities/QualityProfileResource.cs b/src/NzbDrone.Api/Qualities/QualityProfileResource.cs deleted file mode 100644 index dbeff0fa0..000000000 --- a/src/NzbDrone.Api/Qualities/QualityProfileResource.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using NzbDrone.Api.REST; -using NzbDrone.Core.Qualities; - -namespace NzbDrone.Api.Qualities -{ - public class QualityProfileResource : RestResource - { - public String Name { get; set; } - public Quality Cutoff { get; set; } - public List Items { get; set; } - } - - public class QualityProfileItemResource : RestResource - { - public Quality Quality { get; set; } - public bool Allowed { get; set; } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Api/Queue/QueueModule.cs b/src/NzbDrone.Api/Queue/QueueModule.cs index 148a2210f..1c735050a 100644 --- a/src/NzbDrone.Api/Queue/QueueModule.cs +++ b/src/NzbDrone.Api/Queue/QueueModule.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Linq; using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Queue; @@ -7,25 +9,40 @@ using NzbDrone.Core.Queue; namespace NzbDrone.Api.Queue { public class QueueModule : NzbDroneRestModuleWithSignalR, - IHandle + IHandle, IHandle { private readonly IQueueService _queueService; + private readonly IPendingReleaseService _pendingReleaseService; - public QueueModule(ICommandExecutor commandExecutor, IQueueService queueService) + public QueueModule(ICommandExecutor commandExecutor, IQueueService queueService, IPendingReleaseService pendingReleaseService) : base(commandExecutor) { _queueService = queueService; + _pendingReleaseService = pendingReleaseService; GetResourceAll = GetQueue; } private List GetQueue() { - return ToListResource(_queueService.GetQueue); + return ToListResource(GetQueueItems); + } + + private IEnumerable GetQueueItems() + { + var queue = _queueService.GetQueue(); + var pending = _pendingReleaseService.GetPendingQueue(); + + return queue.Concat(pending); } public void Handle(UpdateQueueEvent message) { BroadcastResourceChange(ModelAction.Sync); } + + public void Handle(PendingReleasesUpdatedEvent message) + { + BroadcastResourceChange(ModelAction.Sync); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index 1c5806549..d80bf60ff 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -15,7 +15,6 @@ using NzbDrone.Api.Mapping; using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Validation.Paths; using NzbDrone.Core.DataAugmentation.Scene; -using Omu.ValueInjecter; namespace NzbDrone.Api.Series { @@ -27,7 +26,6 @@ namespace NzbDrone.Api.Series IHandle { - private readonly ICommandExecutor _commandExecutor; private readonly ISeriesService _seriesService; private readonly ISeriesStatisticsService _seriesStatisticsService; private readonly ISceneMappingService _sceneMappingService; @@ -47,7 +45,6 @@ namespace NzbDrone.Api.Series ) : base(commandExecutor) { - _commandExecutor = commandExecutor; _seriesService = seriesService; _seriesStatisticsService = seriesStatisticsService; _sceneMappingService = sceneMappingService; @@ -60,7 +57,7 @@ namespace NzbDrone.Api.Series UpdateResource = UpdateSeries; DeleteResource = DeleteSeries; - SharedValidator.RuleFor(s => s.QualityProfileId).ValidId(); + SharedValidator.RuleFor(s => s.ProfileId).ValidId(); SharedValidator.RuleFor(s => s.Path) .Cascade(CascadeMode.StopOnFirstFailure) diff --git a/src/NzbDrone.Api/Series/SeriesResource.cs b/src/NzbDrone.Api/Series/SeriesResource.cs index b6b980b03..39ee26e0a 100644 --- a/src/NzbDrone.Api/Series/SeriesResource.cs +++ b/src/NzbDrone.Api/Series/SeriesResource.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Api.Series { //Todo: Sorters should be done completely on the client //Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing? - //Todo: We should get the entire QualityProfile instead of ID and Name separately + //Todo: We should get the entire Profile instead of ID and Name separately //View Only public String Title { get; set; } @@ -32,7 +32,7 @@ namespace NzbDrone.Api.Series public Int32 EpisodeCount { get; set; } public Int32 EpisodeFileCount { get; set; } public SeriesStatusType Status { get; set; } - public String QualityProfileName { get; set; } + public String ProfileName { get; set; } public String Overview { get; set; } public DateTime? NextAiring { get; set; } public DateTime? PreviousAiring { get; set; } @@ -46,7 +46,7 @@ namespace NzbDrone.Api.Series //View & Edit public String Path { get; set; } - public Int32 QualityProfileId { get; set; } + public Int32 ProfileId { get; set; } //Editing Only public Boolean SeasonFolder { get; set; } @@ -65,5 +65,21 @@ namespace NzbDrone.Api.Series public String RootFolderPath { get; set; } public String Certification { get; set; } public List Genres { get; set; } + + //Used to support legacy consumers + public Int32 QualityProfileId + { + get + { + return ProfileId; + } + set + { + if (value > 0 && ProfileId == 0) + { + ProfileId = value; + } + } + } } } diff --git a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs index 32aee7226..8cf59717b 100644 --- a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs @@ -1,6 +1,7 @@ using FizzWare.NBuilder; using NUnit.Framework; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Core.Qualities; @@ -15,19 +16,19 @@ namespace NzbDrone.Core.Test.Datastore [SetUp] public void Setup() { - var qualityProfile = new NzbDrone.Core.Qualities.QualityProfile + var profile = new Profile { Name = "Test", Cutoff = Quality.WEBDL720p, - Items = NzbDrone.Core.Test.Qualities.QualityFixture.GetDefaultQualities() + Items = Qualities.QualityFixture.GetDefaultQualities() }; - qualityProfile = Db.Insert(qualityProfile); + profile = Db.Insert(profile); var series = Builder.CreateListOfSize(1) .All() - .With(v => v.QualityProfileId = qualityProfile.Id) + .With(v => v.ProfileId = profile.Id) .BuildListOfNew(); Db.InsertMany(series); @@ -50,7 +51,7 @@ namespace NzbDrone.Core.Test.Datastore } [Test] - public void should_lazy_load_qualityprofile_if_not_joined() + public void should_lazy_load_profile_if_not_joined() { var db = Mocker.Resolve(); var DataMapper = db.GetDataMapper(); @@ -62,7 +63,7 @@ namespace NzbDrone.Core.Test.Datastore foreach (var episode in episodes) { Assert.IsNotNull(episode.Series); - Assert.IsFalse(episode.Series.QualityProfile.IsLoaded); + Assert.IsFalse(episode.Series.Profile.IsLoaded); } } @@ -84,20 +85,20 @@ namespace NzbDrone.Core.Test.Datastore } [Test] - public void should_explicit_load_qualityprofile_if_joined() + public void should_explicit_load_profile_if_joined() { var db = Mocker.Resolve(); var DataMapper = db.GetDataMapper(); var episodes = DataMapper.Query() .Join(Marr.Data.QGen.JoinType.Inner, v => v.Series, (l, r) => l.SeriesId == r.Id) - .Join(Marr.Data.QGen.JoinType.Inner, v => v.QualityProfile, (l, r) => l.QualityProfileId == r.Id) + .Join(Marr.Data.QGen.JoinType.Inner, v => v.Profile, (l, r) => l.ProfileId == r.Id) .ToList(); foreach (var episode in episodes) { Assert.IsNotNull(episode.Series); - Assert.IsTrue(episode.Series.QualityProfile.IsLoaded); + Assert.IsTrue(episode.Series.Profile.IsLoaded); } } diff --git a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs index 17b45da97..2db04935f 100644 --- a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs @@ -6,6 +6,7 @@ using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using System.Linq; +using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests { @@ -119,5 +120,23 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests newColumns.Values.Should().HaveSameCount(columns.Values); newIndexes.Should().Contain(i=>i.Column == "AirTime"); } + + [Test] + public void should_create_indexes_with_the_same_uniqueness() + { + var columns = _subject.GetColumns("Series"); + var indexes = _subject.GetIndexes("Series"); + + var tempIndexes = indexes.JsonClone(); + + tempIndexes[0].Unique = false; + tempIndexes[1].Unique = true; + + _subject.CreateTable("Series_New", columns.Values, tempIndexes); + var newIndexes = _subject.GetIndexes("Series_New"); + + newIndexes.Should().HaveSameCount(tempIndexes); + newIndexes.ShouldAllBeEquivalentTo(tempIndexes, options => options.Excluding(o => o.IndexName).Excluding(o => o.Table)); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs index d193fb1ec..c7e8e2ad3 100644 --- a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs @@ -25,12 +25,12 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests { var series = Builder.CreateListOfSize(10) .Random(3) - .With(c => c.QualityProfileId = 100) + .With(c => c.ProfileId = 100) .BuildListOfNew(); Db.InsertMany(series); - var duplicates = _subject.GetDuplicates("series", "QualityProfileId").ToList(); + var duplicates = _subject.GetDuplicates("series", "ProfileId").ToList(); duplicates.Should().HaveCount(1); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs index 861d39d32..d7ae1643c 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs @@ -1,5 +1,6 @@ using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; @@ -13,35 +14,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, Items = Qualities.QualityFixture.GetDefaultQualities() }, + Subject.CutoffNotMet(new Profile { Cutoff = Quality.Bluray1080p, Items = 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, Items = Qualities.QualityFixture.GetDefaultQualities() }, + Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = 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, Items = Qualities.QualityFixture.GetDefaultQualities() }, + Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = 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, Items = Qualities.QualityFixture.GetDefaultQualities() }, + Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = 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, Items = Qualities.QualityFixture.GetDefaultQualities() }, + Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() }, new QualityModel(Quality.HDTV720p, true), new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse(); } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs index 0fc46ab1f..f26cdf7ea 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Download.Clients.Sabnzbd; using NzbDrone.Core.History; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; @@ -42,7 +43,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests }; _fakeSeries = Builder.CreateNew() - .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _parseResultMulti = new RemoteEpisode @@ -62,9 +63,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _upgradableQuality = new QualityModel(Quality.SDTV, false); _notupgradableQuality = new QualityModel(Quality.HDTV1080p, true); - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 1)).Returns(_notupgradableQuality); - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 2)).Returns(_notupgradableQuality); - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 3)).Returns(null); + Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 1)).Returns(_notupgradableQuality); + Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 2)).Returns(_notupgradableQuality); + Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 3)).Returns(null); Mocker.GetMock() .Setup(c => c.GetDownloadClients()) @@ -73,12 +74,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests private void WithFirstReportUpgradable() { - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 1)).Returns(_upgradableQuality); + Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 1)).Returns(_upgradableQuality); } private void WithSecondReportUpgradable() { - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 2)).Returns(_upgradableQuality); + Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 2)).Returns(_upgradableQuality); } private void GivenSabnzbdDownloadClient() @@ -132,11 +133,11 @@ 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, Items = Qualities.QualityFixture.GetDefaultQualities() }; + _fakeSeries.Profile = new Profile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }; _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false); _upgradableQuality = new QualityModel(Quality.WEBDL1080p, false); - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 1)).Returns(_upgradableQuality); + Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(It.IsAny(), 1)).Returns(_upgradableQuality); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse(); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs index 10ffdc806..37cde0cd2 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs @@ -1,9 +1,12 @@ using FluentAssertions; +using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -11,28 +14,35 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public class LanguageSpecificationFixture : CoreTest { - private RemoteEpisode parseResult; + private RemoteEpisode _remoteEpisode; + + [SetUp] + public void Setup() + { + _remoteEpisode = new RemoteEpisode + { + ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Language = Language.English + }, + Series = new Series + { + Profile = new LazyLoaded(new Profile + { + Language = Language.English + }) + } + }; + } private void WithEnglishRelease() { - parseResult = new RemoteEpisode - { - ParsedEpisodeInfo = new ParsedEpisodeInfo - { - Language = Language.English - } - }; + _remoteEpisode.ParsedEpisodeInfo.Language = Language.English; } private void WithGermanRelease() { - parseResult = new RemoteEpisode - { - ParsedEpisodeInfo = new ParsedEpisodeInfo - { - Language = Language.German - } - }; + _remoteEpisode.ParsedEpisodeInfo.Language = Language.German; } [Test] @@ -40,7 +50,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { WithEnglishRelease(); - Mocker.Resolve().IsSatisfiedBy(parseResult, null).Should().BeTrue(); + Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); } [Test] @@ -48,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { WithGermanRelease(); - Mocker.Resolve().IsSatisfiedBy(parseResult, null).Should().BeFalse(); + Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs index ff0457b43..be385ae90 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; @@ -27,7 +28,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void Setup() { _series = Builder.CreateNew() - .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _episode = Builder.CreateNew() diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index b91614795..1f8119b68 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Tv; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.DecisionEngine; @@ -38,7 +40,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests remoteEpisode.Release.Size = size; remoteEpisode.Series = Builder.CreateNew() - .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); return remoteEpisode; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs index d8d46f3c0..02f017d7a 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs @@ -5,6 +5,7 @@ using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; @@ -35,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void Setup() { var fakeSeries = Builder.CreateNew() - .With(c => c.QualityProfile = (LazyLoaded)new QualityProfile { Cutoff = Quality.Bluray1080p }) + .With(c => c.Profile = (LazyLoaded)new Profile { Cutoff = Quality.Bluray1080p }) .Build(); remoteEpisode = new RemoteEpisode @@ -49,7 +50,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_allow_if_quality_is_defined_in_profile(Quality qualityType) { remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType; - remoteEpisode.Series.QualityProfile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); + remoteEpisode.Series.Profile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); Subject.IsSatisfiedBy(remoteEpisode, null).Should().BeTrue(); } @@ -58,7 +59,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_not_allow_if_quality_is_not_defined_in_profile(Quality qualityType) { remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType; - remoteEpisode.Series.QualityProfile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); + remoteEpisode.Series.Profile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); Subject.IsSatisfiedBy(remoteEpisode, null).Should().BeFalse(); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs index e91771776..b8596840f 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs @@ -2,6 +2,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; @@ -43,9 +44,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenAutoDownloadPropers(true); - var qualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }; + var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; - Subject.IsUpgradable(qualityProfile, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper)) + Subject.IsUpgradable(profile, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper)) .Should().Be(expected); } @@ -54,9 +55,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenAutoDownloadPropers(false); - var qualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }; + var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; - Subject.IsUpgradable(qualityProfile, new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false)) + Subject.IsUpgradable(profile, new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false)) .Should().BeFalse(); } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs new file mode 100644 index 000000000..29ce17057 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Marr.Data; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine.Specifications.RssSync; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.History; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync +{ + [TestFixture] + public class DelaySpecificationFixture : CoreTest + { + private Profile _profile; + private RemoteEpisode _remoteEpisode; + + [SetUp] + public void Setup() + { + _profile = Builder.CreateNew() + .Build(); + + var series = Builder.CreateNew() + .With(s => s.Profile = _profile) + .Build(); + + _remoteEpisode = Builder.CreateNew() + .With(r => r.Series = series) + .Build(); + + _profile.Items = new List(); + _profile.Items.Add(new ProfileQualityItem { Allowed = true, Quality = Quality.HDTV720p }); + _profile.Items.Add(new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL720p }); + _profile.Items.Add(new ProfileQualityItem { Allowed = true, Quality = Quality.Bluray720p }); + + _profile.Cutoff = Quality.WEBDL720p; + + _profile.GrabDelayMode = GrabDelayMode.Always; + + _remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); + _remoteEpisode.Release = new ReleaseInfo(); + + _remoteEpisode.Episodes = Builder.CreateListOfSize(1).Build().ToList(); + } + + private void GivenExistingFile(QualityModel quality) + { + _remoteEpisode.Episodes[0].EpisodeFile = new LazyLoaded(new EpisodeFile + { + Quality = quality + }); + } + + [Test] + public void should_be_true_when_search() + { + Subject.IsSatisfiedBy(new RemoteEpisode(), new SingleEpisodeSearchCriteria()).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_profile_does_not_have_a_delay() + { + _profile.GrabDelay = 0; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_quality_is_last_allowed_in_profile() + { + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p); + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_release_is_older_than_delay() + { + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow.AddHours(-10); + + _profile.GrabDelay = 1; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_false_when_release_is_younger_than_delay() + { + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.SDTV); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _profile.GrabDelay = 12; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_be_true_when_release_is_proper_for_existing_episode() + { + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, true); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + GivenExistingFile(new QualityModel(Quality.HDTV720p)); + + _profile.GrabDelay = 12; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_false_when_release_is_proper_and_no_existing_episode() + { + + } + + [Test] + public void should_be_true_when_release_meets_cutoff_and_mode_is_cutoff() + { + _profile.GrabDelayMode = GrabDelayMode.Cutoff; + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _profile.GrabDelay = 12; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_release_exceeds_cutoff_and_mode_is_cutoff() + { + _profile.GrabDelayMode = GrabDelayMode.Cutoff; + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _profile.GrabDelay = 12; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_false_when_release_is_below_cutoff_and_mode_is_cutoff() + { + _profile.GrabDelayMode = GrabDelayMode.Cutoff; + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _profile.GrabDelay = 12; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_be_false_when_release_is_proper_for_existing_episode_of_different_quality() + { + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, true); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + GivenExistingFile(new QualityModel(Quality.SDTV)); + + _profile.GrabDelay = 12; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_be_false_when_release_is_first_detected_and_mode_is_first() + { + _profile.GrabDelayMode = GrabDelayMode.First; + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _profile.GrabDelay = 12; + + Mocker.GetMock() + .Setup(s => s.GetPendingRemoteEpisodes(It.IsAny())) + .Returns(new List()); + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_be_false_when_release_is_not_first_but_oldest_has_not_expired_and_type_is_first() + { + _profile.GrabDelayMode = GrabDelayMode.First; + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _profile.GrabDelay = 12; + + var pendingRemoteEpisode = _remoteEpisode.JsonClone(); + + Mocker.GetMock() + .Setup(s => s.GetPendingRemoteEpisodes(It.IsAny())) + .Returns(new List { _remoteEpisode.JsonClone() }); + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_be_true_when_existing_pending_release_expired_and_mode_is_first() + { + _profile.GrabDelayMode = GrabDelayMode.First; + + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + _profile.GrabDelay = 12; + + var pendingRemoteEpisode = _remoteEpisode.JsonClone(); + pendingRemoteEpisode.Release.PublishDate = DateTime.UtcNow.AddHours(-15); + + Mocker.GetMock() + .Setup(s => s.GetPendingRemoteEpisodes(It.IsAny())) + .Returns(new List { pendingRemoteEpisode }); + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_one_existing_pending_release_is_expired_and_mode_is_first() + { + _profile.GrabDelayMode = GrabDelayMode.First; + + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL720p); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + _profile.GrabDelay = 12; + + var pendingRemoteEpisode1 = _remoteEpisode.JsonClone(); + pendingRemoteEpisode1.Release.PublishDate = DateTime.UtcNow.AddHours(-15); + + var pendingRemoteEpisode2 = _remoteEpisode.JsonClone(); + pendingRemoteEpisode2.Release.PublishDate = DateTime.UtcNow.AddHours(5); + + Mocker.GetMock() + .Setup(s => s.GetPendingRemoteEpisodes(It.IsAny())) + .Returns(new List { pendingRemoteEpisode1, pendingRemoteEpisode2 }); + + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs index b95925ced..f377b80d5 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs @@ -8,6 +8,7 @@ using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; @@ -37,7 +38,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync var doubleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; var fakeSeries = Builder.CreateNew() - .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p }) + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p }) .Build(); _parseResultMulti = new RemoteEpisode diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs index c9e03d256..e21e10d12 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; @@ -38,7 +39,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var doubleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; var fakeSeries = Builder.CreateNew() - .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _parseResultMulti = new RemoteEpisode diff --git a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs index ed80e2c3c..529c1e355 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs @@ -4,19 +4,19 @@ using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -using NzbDrone.Core.DecisionEngine; namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests { [TestFixture] - public class DownloadApprovedFixture : CoreTest + public class DownloadApprovedFixture : CoreTest { [SetUp] public void SetUp() @@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests remoteEpisode.Release.PublishDate = DateTime.UtcNow; remoteEpisode.Series = Builder.CreateNew() - .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); return remoteEpisode; @@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode)); - Subject.DownloadApproved(decisions); + Subject.ProcessDecisions(decisions); Mocker.GetMock().Verify(v => v.DownloadReport(It.IsAny()), Times.Once()); } @@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests decisions.Add(new DownloadDecision(remoteEpisode)); decisions.Add(new DownloadDecision(remoteEpisode)); - Subject.DownloadApproved(decisions); + Subject.ProcessDecisions(decisions); Mocker.GetMock().Verify(v => v.DownloadReport(It.IsAny()), Times.Once()); } @@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - Subject.DownloadApproved(decisions); + Subject.ProcessDecisions(decisions); Mocker.GetMock().Verify(v => v.DownloadReport(It.IsAny()), Times.Once()); } @@ -110,7 +110,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode)); - Subject.DownloadApproved(decisions).Should().HaveCount(1); + Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(1); } [Test] @@ -130,7 +130,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - Subject.DownloadApproved(decisions).Should().HaveCount(2); + Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2); } [Test] @@ -156,7 +156,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests decisions.Add(new DownloadDecision(remoteEpisode2)); decisions.Add(new DownloadDecision(remoteEpisode3)); - Subject.DownloadApproved(decisions).Should().HaveCount(2); + Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2); } [Test] @@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests decisions.Add(new DownloadDecision(remoteEpisode)); Mocker.GetMock().Setup(s => s.DownloadReport(It.IsAny())).Throws(new Exception()); - Subject.DownloadApproved(decisions).Should().BeEmpty(); + Subject.ProcessDecisions(decisions).Grabbed.Should().BeEmpty(); ExceptionVerification.ExpectedWarns(1); } @@ -177,8 +177,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests public void should_return_an_empty_list_when_none_are_appproved() { var decisions = new List(); - decisions.Add(new DownloadDecision(null, "Failure!")); - decisions.Add(new DownloadDecision(null, "Failure!")); + decisions.Add(new DownloadDecision(null, new Rejection("Failure!"))); + decisions.Add(new DownloadDecision(null, new Rejection("Failure!"))); Subject.GetQualifiedReports(decisions).Should().BeEmpty(); } diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/ProcessFixture.cs new file mode 100644 index 000000000..a913b62b1 --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/ProcessFixture.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using FluentAssertions; +using Marr.Data; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests +{ + [TestFixture] + public class ProcessFixture : CoreTest + { + private DownloadDecision _temporarilyRejected; + private Series _series; + private Episode _episode; + private Profile _profile; + private ReleaseInfo _release; + private ParsedEpisodeInfo _parsedEpisodeInfo; + private RemoteEpisode _remoteEpisode; + + [SetUp] + public void Setup() + { + _series = Builder.CreateNew() + .Build(); + + _episode = Builder.CreateNew() + .Build(); + + _profile = new Profile + { + Name = "Test", + Cutoff = Quality.HDTV720p, + GrabDelay = 1, + Items = new List + { + new ProfileQualityItem { Allowed = true, Quality = Quality.HDTV720p }, + new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL720p }, + new ProfileQualityItem { Allowed = true, Quality = Quality.Bluray720p } + }, + }; + + _series.Profile = new LazyLoaded(_profile); + + _release = Builder.CreateNew().Build(); + + _parsedEpisodeInfo = Builder.CreateNew().Build(); + _parsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p); + + _remoteEpisode = new RemoteEpisode(); + _remoteEpisode.Episodes = new List{ _episode }; + _remoteEpisode.Series = _series; + _remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo; + _remoteEpisode.Release = _release; + + _temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary)); + + Mocker.GetMock() + .Setup(s => s.All()) + .Returns(new List()); + + Mocker.GetMock() + .Setup(s => s.GetSeries(It.IsAny())) + .Returns(_series); + + Mocker.GetMock() + .Setup(s => s.GetEpisodes(It.IsAny(), _series, true, null)) + .Returns(new List {_episode}); + + Mocker.GetMock() + .Setup(s => s.PrioritizeDecisions(It.IsAny>())) + .Returns((List d) => d); + } + + private void GivenHeldRelease(String title, String indexer, DateTime publishDate) + { + var release = _release.JsonClone(); + release.Indexer = indexer; + release.PublishDate = publishDate; + + + var heldReleases = Builder.CreateListOfSize(1) + .All() + .With(h => h.SeriesId = _series.Id) + .With(h => h.Title = title) + .With(h => h.Release = release) + .Build(); + + Mocker.GetMock() + .Setup(s => s.All()) + .Returns(heldReleases); + } + + [Test] + public void should_add() + { + Subject.Add(_temporarilyRejected); + + VerifyInsert(); + } + + [Test] + public void should_not_add_if_it_is_the_same_release_from_the_same_indexer() + { + GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate); + + Subject.Add(_temporarilyRejected); + + VerifyNoInsert(); + } + + [Test] + public void should_add_if_title_is_different() + { + GivenHeldRelease(_release.Title + "-RP", _release.Indexer, _release.PublishDate); + + Subject.Add(_temporarilyRejected); + + VerifyInsert(); + } + + [Test] + public void should_add_if_indexer_is_different() + { + GivenHeldRelease(_release.Title, "AnotherIndexer", _release.PublishDate); + + Subject.Add(_temporarilyRejected); + + VerifyInsert(); + } + [Test] + public void should_add_if_publish_date_is_different() + { + GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate.AddHours(1)); + + Subject.Add(_temporarilyRejected); + + VerifyInsert(); + } + + private void VerifyInsert() + { + Mocker.GetMock() + .Verify(v => v.Insert(It.IsAny()), Times.Once()); + } + + private void VerifyNoInsert() + { + Mocker.GetMock() + .Verify(v => v.Insert(It.IsAny()), Times.Never()); + } + } +} diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index 717f9f915..c7ea148ef 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.History; using NzbDrone.Core.Qualities; @@ -10,14 +11,14 @@ namespace NzbDrone.Core.Test.HistoryTests { public class HistoryServiceFixture : CoreTest { - private QualityProfile _profile; - private QualityProfile _profileCustom; + private Profile _profile; + private Profile _profileCustom; [SetUp] public void Setup() { - _profile = new QualityProfile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities() }; - _profileCustom = new QualityProfile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities(Quality.DVD) }; + _profile = new Profile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities() }; + _profileCustom = new Profile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities(Quality.DVD) }; } [Test] diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs new file mode 100644 index 000000000..350471a1d --- /dev/null +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs @@ -0,0 +1,42 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Blacklisting; +using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.Housekeeping.Housekeepers +{ + [TestFixture] + public class CleanupOrphanedBlacklistFixture : DbTest + { + [Test] + public void should_delete_orphaned_blacklist_items() + { + var blacklist = Builder.CreateNew() + .BuildNew(); + + Db.Insert(blacklist); + Subject.Clean(); + AllStoredModels.Should().BeEmpty(); + } + + [Test] + public void should_not_delete_unorphaned_blacklist_items() + { + var series = Builder.CreateNew().BuildNew(); + + Db.Insert(series); + + var blacklist = Builder.CreateNew() + .With(b => b.SeriesId = series.Id) + .BuildNew(); + + Db.Insert(blacklist); + + Subject.Clean(); + AllStoredModels.Should().HaveCount(1); + } + } +} diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs new file mode 100644 index 000000000..34965efae --- /dev/null +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs @@ -0,0 +1,42 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.Housekeeping.Housekeepers +{ + [TestFixture] + public class CleanupOrphanedPendingReleasesFixture : DbTest + { + [Test] + public void should_delete_orphaned_pending_items() + { + var pendingRelease = Builder.CreateNew() + .BuildNew(); + + Db.Insert(pendingRelease); + Subject.Clean(); + AllStoredModels.Should().BeEmpty(); + } + + [Test] + public void should_not_delete_unorphaned_pending_items() + { + var series = Builder.CreateNew().BuildNew(); + + Db.Insert(series); + + var pendingRelease = Builder.CreateNew() + .With(h => h.SeriesId = series.Id) + .BuildNew(); + + Db.Insert(pendingRelease); + + Subject.Clean(); + AllStoredModels.Should().HaveCount(1); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs index c0e7c0e1c..9570fbfc4 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Test.Framework; using FizzWare.NBuilder; @@ -30,9 +31,9 @@ namespace NzbDrone.Core.Test.IndexerSearchTests .Setup(s => s.GetAvailableProviders()) .Returns(new List { indexer.Object }); - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.GetSearchDecision(It.IsAny>(), It.IsAny())) - .Returns(new List()); + .Returns(new List()); _xemSeries = Builder.CreateNew() .With(v => v.UseSceneNumbering = true) diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs index 0dea398ea..ccaf18d11 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport _videoFiles = new List { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" }; _series = Builder.CreateNew() - .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _quality = new QualityModel(Quality.DVD); diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs index a0bff8424..a217610af 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -22,8 +23,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications public void Setup() { _series = Builder.CreateNew() - .With(s => s.SeriesType = SeriesTypes.Standard) - .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(s => s.SeriesType = SeriesTypes.Standard) + .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _localEpisode = new LocalEpisode diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index 904a0e5c7..251e02c31 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -29,7 +30,7 @@ namespace NzbDrone.Core.Test.MediaFiles _approvedDecisions = new List(); var series = Builder.CreateNew() - .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); var episodes = Builder.CreateListOfSize(5) diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index a36695aa6..b04fdc1b6 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -111,6 +111,7 @@ + @@ -123,6 +124,7 @@ + @@ -141,6 +143,8 @@ + + @@ -209,7 +213,7 @@ - + @@ -242,7 +246,7 @@ - + @@ -260,11 +264,10 @@ - - + diff --git a/src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs similarity index 52% rename from src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs rename to src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs index 10b8ece92..3c9003da7 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; -namespace NzbDrone.Core.Test.Qualities +namespace NzbDrone.Core.Test.Profiles { [TestFixture] - public class QualityProfileRepositoryFixture : DbTest + public class ProfileRepositoryFixture : DbTest { [Test] public void should_be_able_to_read_and_write() { - var profile = new QualityProfile + var profile = new Profile { - Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), + Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), Cutoff = Quality.Bluray1080p, Name = "TestProfile" }; @@ -23,8 +23,8 @@ namespace NzbDrone.Core.Test.Qualities StoredModel.Name.Should().Be(profile.Name); StoredModel.Cutoff.Should().Be(profile.Cutoff); - - StoredModel.Items.Should().Equal(profile.Items, (a,b) => a.Quality == b.Quality && a.Allowed == b.Allowed); + + StoredModel.Items.Should().Equal(profile.Items, (a, b) => a.Quality == b.Quality && a.Allowed == b.Allowed); } diff --git a/src/NzbDrone.Core.Test/Qualities/QualityProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs similarity index 61% rename from src/NzbDrone.Core.Test/Qualities/QualityProfileServiceFixture.cs rename to src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs index 9390925c1..7e33072af 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityProfileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs @@ -3,23 +3,24 @@ using FizzWare.NBuilder; using Moq; using NUnit.Framework; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; -namespace NzbDrone.Core.Test.Qualities +namespace NzbDrone.Core.Test.Profiles { [TestFixture] - public class QualityProfileServiceFixture : CoreTest + public class ProfileServiceFixture : CoreTest { [Test] public void Init_should_add_two_profiles() { Subject.Handle(new ApplicationStartedEvent()); - Mocker.GetMock() - .Verify(v => v.Insert(It.IsAny()), Times.Exactly(4)); + Mocker.GetMock() + .Verify(v => v.Insert(It.IsAny()), Times.Exactly(4)); } [Test] @@ -27,14 +28,14 @@ namespace NzbDrone.Core.Test.Qualities //We don't want to keep adding them back if a user deleted them on purpose. public void Init_should_skip_if_any_profiles_already_exist() { - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.All()) - .Returns(Builder.CreateListOfSize(2).Build().ToList()); + .Returns(Builder.CreateListOfSize(2).Build().ToList()); Subject.Handle(new ApplicationStartedEvent()); - Mocker.GetMock() - .Verify(v => v.Insert(It.IsAny()), Times.Never()); + Mocker.GetMock() + .Verify(v => v.Insert(It.IsAny()), Times.Never()); } @@ -43,15 +44,15 @@ namespace NzbDrone.Core.Test.Qualities { var seriesList = Builder.CreateListOfSize(3) .Random(1) - .With(c => c.QualityProfileId = 2) + .With(c => c.ProfileId = 2) .Build().ToList(); Mocker.GetMock().Setup(c => c.GetAllSeries()).Returns(seriesList); - Assert.Throws(() => Subject.Delete(2)); + Assert.Throws(() => Subject.Delete(2)); - Mocker.GetMock().Verify(c => c.Delete(It.IsAny()), Times.Never()); + Mocker.GetMock().Verify(c => c.Delete(It.IsAny()), Times.Never()); } @@ -61,7 +62,7 @@ namespace NzbDrone.Core.Test.Qualities { var seriesList = Builder.CreateListOfSize(3) .All() - .With(c => c.QualityProfileId = 2) + .With(c => c.ProfileId = 2) .Build().ToList(); @@ -69,7 +70,7 @@ namespace NzbDrone.Core.Test.Qualities Subject.Delete(1); - Mocker.GetMock().Verify(c => c.Delete(1), Times.Once()); + Mocker.GetMock().Verify(c => c.Delete(1), Times.Once()); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs index ddab8f19b..673ca0ca9 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs +++ b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -44,7 +45,7 @@ namespace NzbDrone.Core.Test.Qualities i.Should().Be(expected); } - public static List GetDefaultQualities(params Quality[] allowed) + public static List GetDefaultQualities(params Quality[] allowed) { var qualities = new List { @@ -66,7 +67,7 @@ namespace NzbDrone.Core.Test.Qualities var items = qualities .Except(allowed) .Concat(allowed) - .Select(v => new QualityProfileItem { Quality = v, Allowed = allowed.Contains(v) }).ToList(); + .Select(v => new ProfileQualityItem { Quality = v, Allowed = allowed.Contains(v) }).ToList(); return items; } diff --git a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs index 187455abb..78262f0a5 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs +++ b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; @@ -13,20 +14,20 @@ namespace NzbDrone.Core.Test.Qualities { public QualityModelComparer Subject { get; set; } - private void GivenDefaultQualityProfile() + private void GivenDefaultProfile() { - Subject = new QualityModelComparer(new QualityProfile { Items = QualityFixture.GetDefaultQualities() }); + Subject = new QualityModelComparer(new Profile { Items = QualityFixture.GetDefaultQualities() }); } - private void GivenCustomQualityProfile() + private void GivenCustomProfile() { - Subject = new QualityModelComparer(new QualityProfile { Items = QualityFixture.GetDefaultQualities(Quality.Bluray720p, Quality.DVD) }); + Subject = new QualityModelComparer(new Profile { Items = QualityFixture.GetDefaultQualities(Quality.Bluray720p, Quality.DVD) }); } [Test] public void Icomparer_greater_test() { - GivenDefaultQualityProfile(); + GivenDefaultProfile(); var first = new QualityModel(Quality.DVD, true); var second = new QualityModel(Quality.Bluray1080p, true); @@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Qualities [Test] public void Icomparer_greater_proper() { - GivenDefaultQualityProfile(); + GivenDefaultProfile(); var first = new QualityModel(Quality.Bluray1080p, false); var second = new QualityModel(Quality.Bluray1080p, true); @@ -52,7 +53,7 @@ namespace NzbDrone.Core.Test.Qualities [Test] public void Icomparer_lesser() { - GivenDefaultQualityProfile(); + GivenDefaultProfile(); var first = new QualityModel(Quality.DVD, true); var second = new QualityModel(Quality.Bluray1080p, true); @@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Qualities [Test] public void Icomparer_lesser_proper() { - GivenDefaultQualityProfile(); + GivenDefaultProfile(); var first = new QualityModel(Quality.DVD, false); var second = new QualityModel(Quality.DVD, true); @@ -78,7 +79,7 @@ namespace NzbDrone.Core.Test.Qualities [Test] public void Icomparer_greater_custom_order() { - GivenCustomQualityProfile(); + GivenCustomProfile(); var first = new QualityModel(Quality.DVD, true); var second = new QualityModel(Quality.Bluray720p, true); diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeProviderTests/EpisodeProviderTest.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeProviderTests/EpisodeProviderTest.cs deleted file mode 100644 index 6ed79d4bc..000000000 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeProviderTests/EpisodeProviderTest.cs +++ /dev/null @@ -1,1519 +0,0 @@ -//TODO: Alrighty then... We should delete this or uncomment some of these tests... - -/* - - -using System; -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Download; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Model; -using NzbDrone.Core.Providers; -using NzbDrone.Core.Providers.Core; -using NzbDrone.Core.Test.Framework; - -using TvdbLib.Data; - -namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests -{ - [TestFixture] - - public class EpisodeProviderTest : ObjectDbTest - { - [Test] - public void GetEpisodes_exists() - { - - - var fakeSeries = Builder.CreateNew().Build(); - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - - - var episode = Mocker.Resolve().GetEpisode(1); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series, e => e.EpisodeFile).EqualTo(fakeEpisodes.First()); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - } - - [Test] - public void GetEpisodes_by_season_episode_exists() - { - - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 1) - .Build(); - var fakeEpisodes = Builder.CreateNew() - .With(e => e.SeriesId = 1) - .With(e => e.EpisodeNumber = 1) - .And(e => e.SeasonNumber = 2).Build(); - - Db.Insert(fakeSeries); - Db.Insert(fakeEpisodes); - - - var episode = Mocker.Resolve().GetEpisode(fakeSeries.Id, 2, 1); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series).EqualTo(fakeEpisodes); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - } - - [Test] - public void GetEpisodes_by_season_episode_doesnt_exists() - { - - - - var episode = Mocker.Resolve().GetEpisode(1, 1, 1); - - - episode.Should().BeNull(); - } - - [Test] - public void GetEpisode_with_EpisodeFile() - { - - - var fakeSeries = Builder.CreateNew().Build(); - var fakeFile = Builder.CreateNew().With(f => f.Id).With(c => c.Quality = Quality.SDTV).Build(); - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).TheFirst(1).With(e => e.EpisodeFile = new EpisodeFile { Id = 1 }).With(e => e.EpisodeFile = fakeFile).Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - Db.Insert(fakeFile); - - - var episode = Mocker.Resolve().GetEpisode(1); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series, e => e.EpisodeFile).EqualTo(fakeEpisodes.First()); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - episode.EpisodeFile.Should().NotBeNull(); - } - - [Test] - [ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "Sequence contains no elements")] - public void GetEpisodes_invalid_series() - { - - - Mocker.Resolve(); - - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).Build(); - - - Db.InsertMany(fakeEpisodes); - - - - Mocker.Resolve().GetEpisode(1); - } - - [Test] - public void GetEpisodesBySeason_success() - { - - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 12) - .Build(); - - var episodes = Builder.CreateListOfSize(10) - .All().With(c => c.SeriesId = 12).And(c => c.SeasonNumber = 2) - .TheFirst(5).With(c => c.SeasonNumber = 1) - .Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(episodes); - - - var seasonEposodes = Mocker.Resolve().GetEpisodesBySeason(12, 2); - - - Db.Fetch().Should().HaveCount(10); - seasonEposodes.Should().HaveCount(5); - } - - [Test] - public void RefreshEpisodeInfo_emptyRepo() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 10; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(episodeCount). - All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var actualCount = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList().Count; - Mocker.GetMock().VerifyAll(); - actualCount.Should().Be(episodeCount); - } - - [Test] - public void RefreshEpisodeInfo_should_set_older_than_1900_to_null() - { - //Arrange - const int seriesId = 71663; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(10). - All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")).And(e => e.FirstAired = DateTime.Now) - .TheFirst(7).With(e => e.FirstAired = new DateTime(1800, 1, 1)) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var storedEpisodes = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - storedEpisodes.Should().HaveCount(10); - storedEpisodes.Where(e => e.AirDate == null).Should().HaveCount(7); - storedEpisodes.Where(e => e.AirDate != null).Should().HaveCount(3); - } - - [Test] - public void RefreshEpisodeInfo_should_set_older_than_1900_to_null_for_existing_episodes() - { - //Arrange - const int seriesId = 71663; - - var fakeEpisode = Builder.CreateNew() - .With(e => e.TvDbEpisodeId = 12345) - .With(e => e.AirDate = DateTime.Today) - .Build(); - - var fakeTvDbEpisodes = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(1) - .All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")).And(e => e.FirstAired = DateTime.Now) - .TheFirst(1).With(e => e.FirstAired = new DateTime(1800, 1, 1)) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - Db.Insert(fakeSeries); - Db.Insert(fakeEpisode); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeTvDbEpisodes); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var storedEpisodes = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - storedEpisodes.Should().HaveCount(1); - storedEpisodes.Where(e => e.AirDate == null).Should().HaveCount(1); - } - - [Test] - public void RefreshEpisodeInfo_ignore_episode_zero() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 10; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(episodeCount). - All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) - .TheFirst(1) - .With(e => e.EpisodeNumber = 0) - .With(e => e.SeasonNumber = 15) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - Mocker.GetMock().VerifyAll(); - result.Should().HaveCount(episodeCount); - result.Where(e => e.EpisodeNumber == 0 && e.SeasonNumber == 15).Single().Ignored.Should().BeTrue(); - } - - [Test] - public void RefreshEpisodeInfo_should_skip_future_episodes_with_no_title() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 10; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). - All() - .With(a => c.FirstAired = DateTime.Now.AddDays(-2)) - .With(e => e.EpisodeName = "Something") - .TheFirst(3) - .With(e => e.EpisodeName = "") - .With(e => e.FirstAired = DateTime.Now.AddDays(10)) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - result.Should().HaveCount(episodeCount - 3); - result.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Title) || c.AirDate < DateTime.Now); - } - - [Test] - public void RefreshEpisodeInfo_should_skip_older_than_1900_year_episodes_with_no_title() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 10; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). - All() - .With(a => c.FirstAired = DateTime.Now.AddDays(-2)) - .With(e => e.EpisodeName = "Something") - .TheFirst(3) - .With(e => e.EpisodeName = "") - .With(e => e.FirstAired = new DateTime(1889, 1, 1)) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - result.Should().HaveCount(episodeCount - 3); - result.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Title) || c.AirDate < DateTime.Now); - } - - [Test] - public void RefreshEpisodeInfo_should_add_future_episodes_with_title() - { - const int seriesId = 71663; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = new List(Builder.CreateListOfSize(10). - All() - .With(a => a.FirstAired = DateTime.Now.AddDays(10)) - .With(e => e.EpisodeName = "Something") - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - result.Should().HaveSameCount(fakeEpisodes.Episodes); - } - - [Test] - public void RefreshEpisodeInfo_should_add_old_episodes_with_no_title() - { - const int seriesId = 71663; - - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = new List(Builder.CreateListOfSize(10). - All() - .With(a => a.FirstAired = DateTime.Now.AddDays(-10)) - .With(e => e.EpisodeName = string.Empty) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - result.Should().HaveSameCount(fakeEpisodes.Episodes); - } - - [Test] - public void RefreshEpisodeInfo_ignore_season_zero() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 10; - - var fakeEpisodes = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(episodeCount). - All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) - .With(e => e.SeasonNumber = 0) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeEpisodes); - - Mocker.GetMock() - .Setup(s => s.IsIgnored(seriesId, 0)) - .Returns(true); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - Mocker.GetMock().VerifyAll(); - result.Should().HaveCount(episodeCount); - result.Where(e => e.Ignored).Should().HaveCount(episodeCount); - } - - [Test] - public void new_episodes_only_calls_Insert() - { - const int seriesId = 71663; - var tvdbSeries = Builder.CreateNew() - .With(c => c.Episodes = new List(Builder.CreateListOfSize(5).Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - var currentEpisodes = new List(); - - Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(currentEpisodes); - - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - Mocker.GetMock().Verify(c => c.InsertMany(It.Is>(l => l.Count() == 5)), Times.Once()); - Mocker.GetMock().Verify(c => c.Update(It.IsAny>()), Times.Never()); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void existing_episodes_only_calls_Update() - { - const int seriesId = 71663; - var tvdbSeries = Builder.CreateNew() - .With(c => c.Episodes = new List(Builder.CreateListOfSize(5).Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - var currentEpisodes = new List(); - foreach (var tvDbEpisode in tvdbSeries.Episodes) - { - currentEpisodes.Add(new Episode { TvDbEpisodeId = tvDbEpisode.Id, Series = fakeSeries }); - } - - Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(currentEpisodes); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - Mocker.GetMock().Verify(c => c.InsertMany(It.Is>(l => l.Count() == 0)), Times.Once()); - Mocker.GetMock().Verify(c => c.UpdateMany(It.Is>(l => l.Count() == 5)), Times.Once()); - Mocker.VerifyAllMocks(); - } - - [Test] - public void should_try_to_get_existing_episode_using_tvdbid_first() - { - const int seriesId = 71663; - var fakeTvDbResult = Builder.CreateNew() - .With(c => c.Id = seriesId) - .With(c => c.Episodes = new List( - Builder.CreateListOfSize(1) - .All().With(g => g.Id = 99) - .Build()) - ) - .Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - var fakeEpisodeList = new List { new Episode { TvDbEpisodeId = 99, SeasonNumber = 10, EpisodeNumber = 10, Series = fakeSeries } }; - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(fakeEpisodeList); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(fakeTvDbResult); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - Mocker.VerifyAllMocks(); - Mocker.GetMock().Verify(c => c.UpdateMany(fakeEpisodeList), Times.Once()); - } - - [Test] - public void should_try_to_get_existing_episode_using_tvdbid_first_then_season_episode() - { - const int seriesId = 71663; - var tvdbSeries = Builder.CreateNew() - .With(c => c.Id = seriesId) - .With(c => c.Episodes = new List{ - Builder.CreateNew() - .With(g => g.Id = 99) - .With(g => g.SeasonNumber = 4) - .With(g => g.EpisodeNumber = 15) - .With(g=>g.SeriesId = seriesId) - .Build() - }) - .Build(); - - var localEpisode = Builder.CreateNew() - .With(c => c.SeriesId = seriesId) - .With(c => c.SeasonNumber = 4) - .With(c => c.EpisodeNumber = 15) - .Build(); - - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(new List { localEpisode }); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - Mocker.VerifyAllMocks(); - Mocker.GetMock().Verify(c => c.UpdateMany(new List { localEpisode }), Times.Once()); - } - - [Test] - public void existing_episodes_keep_their_episodeId_file_id() - { - const int seriesId = 71663; - var tvdbSeries = Builder.CreateNew() - .With(c => c.Episodes = new List(Builder.CreateListOfSize(5).Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - var currentEpisodes = new List(); - foreach (var tvDbEpisode in tvdbSeries.Episodes) - { - currentEpisodes.Add(new Episode - { - TvDbEpisodeId = tvDbEpisode.Id, - Id = 99, - EpisodeFile = new EpisodeFile { Id = 69 }, - Ignored = true, - Series = fakeSeries, - EpisodeNumber = tvDbEpisode.EpisodeNumber, - SeasonNumber = tvDbEpisode.SeasonNumber - }); - } - - Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - var updatedEpisodes = new List(); - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(currentEpisodes); - - Mocker.GetMock() - .Setup(c => c.UpdateMany(It.IsAny>())) - .Callback>(ep => updatedEpisodes = ep.ToList()); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - updatedEpisodes.Should().HaveSameCount(tvdbSeries.Episodes); - updatedEpisodes.Should().OnlyContain(c => c.Id == 99); - updatedEpisodes.Should().OnlyContain(c => c.EpisodeFileId == 69); - updatedEpisodes.Should().OnlyContain(c => c.Ignored == true); - } - - [Test] - public void existing_episodes_remote_their_episodeId_file_id_when_episode_number_doesnt_match_tvdbid() - { - const int seriesId = 71663; - var tvdbSeries = Builder.CreateNew() - .With(c => c.Episodes = new List(Builder.CreateListOfSize(5).Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - var currentEpisodes = new List(); - foreach (var tvDbEpisode in tvdbSeries.Episodes) - { - currentEpisodes.Add(new Episode - { - TvDbEpisodeId = tvDbEpisode.Id, - Id = 99, - EpisodeFile = new EpisodeFile { Id = 69 }, - Ignored = true, - Series = fakeSeries, - EpisodeNumber = tvDbEpisode.EpisodeNumber + 1, - SeasonNumber = tvDbEpisode.SeasonNumber - }); - } - - Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - var updatedEpisodes = new List(); - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(currentEpisodes); - - Mocker.GetMock() - .Setup(c => c.UpdateMany(It.IsAny>())) - .Callback>(ep => updatedEpisodes = ep.ToList()); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - updatedEpisodes.Should().OnlyContain(c => c.EpisodeFileId == 0); - } - - [Test] - public void existing_episodes_remote_their_episodeId_file_id_when_season_number_doesnt_match_tvdbid() - { - const int seriesId = 71663; - var tvdbSeries = Builder.CreateNew() - .With(c => c.Episodes = new List(Builder.CreateListOfSize(5).Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - var currentEpisodes = new List(); - foreach (var tvDbEpisode in tvdbSeries.Episodes) - { - currentEpisodes.Add(new Episode - { - TvDbEpisodeId = tvDbEpisode.Id, - Id = 99, - EpisodeFile = new EpisodeFile { Id = 69 }, - Ignored = true, - Series = fakeSeries, - EpisodeNumber = tvDbEpisode.EpisodeNumber, - SeasonNumber = tvDbEpisode.SeasonNumber + 1 - }); - } - - Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - var updatedEpisodes = new List(); - - Mocker.GetMock() - .Setup(d => d.Fetch(It.IsAny(), It.IsAny())) - .Returns(currentEpisodes); - - Mocker.GetMock() - .Setup(c => c.UpdateMany(It.IsAny>())) - .Callback>(ep => updatedEpisodes = ep.ToList()); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - updatedEpisodes.Should().OnlyContain(c => c.EpisodeFileId == 0); - } - - [Test] - public void RefreshEpisodeInfo_should_ignore_new_episode_for_ignored_season() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 2; - - var fakeEpisode = Builder.CreateNew() - .With(e => e.SeasonNumber = 5) - .With(e => e.EpisodeNumber = 1) - .With(e => e.TvDbEpisodeId = 11) - .With(e => e.SeriesId = seriesId) - .With(e => e.Ignored = true) - .Build(); - - var tvdbSeries = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(episodeCount). - All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) - .With(e => e.SeasonNumber = 5) - .TheFirst(1) - .With(e => e.EpisodeNumber = 1) - .With(e => e.Id = 11) - .TheNext(1) - .With(e => e.EpisodeNumber = 2) - .With(e => e.Id = 22) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - Db.Insert(fakeEpisode); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - Mocker.GetMock() - .Setup(s => s.IsIgnored(seriesId, It.IsAny())) - .Returns(true); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - Mocker.GetMock().VerifyAll(); - result.Should().HaveCount(episodeCount); - result.Where(e => e.Ignored).Should().HaveCount(episodeCount); - } - - [Test] - [Explicit] - public void Add_daily_show_episodes() - { - - Mocker.Resolve(); - - Mocker.GetMock() - .Setup(e => e.DefaultQualityProfile).Returns(1); - - Db.Insert(Builder.CreateNew().Build()); - - var seriesRepo = Mocker.Resolve(); - - const int tvDbSeriesId = 71256; - - var seriesProvider = Mocker.Resolve(); - - seriesProvider.AddSeries("Test Series", "c:\\test\\", tvDbSeriesId, 1, null); - - - - var episodeProvider = Mocker.Resolve(); - episodeProvider.RefreshEpisodeInfo(seriesRepo.Get(tvDbSeriesId)); - - - var episodes = episodeProvider.GetEpisodeBySeries(tvDbSeriesId); - episodes.Should().NotBeEmpty(); - } - - [Test] - public void GetEpisode_by_Season_Episode_none_existing() - { - - - - var episode = Mocker.Resolve().GetEpisode(1, 1, 1); - - - episode.Should().BeNull(); - } - - [Test] - public void GetEpisode_by_Season_Episode_with_EpisodeFile() - { - - - var fakeSeries = Builder.CreateNew().Build(); - var fakeFile = Builder.CreateNew().With(f => f.Id).With(c => c.Quality = Quality.SDTV).Build(); - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).TheFirst(1).With(c => c.EpisodeFile = new EpisodeFile { Id = 1 }).With(e => e.EpisodeFile = fakeFile).Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - Db.Insert(fakeFile); - - - var episode = Mocker.Resolve().GetEpisode(1, 1, 1); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series, e => e.EpisodeFile).EqualTo(fakeEpisodes.First()); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - episode.EpisodeFile.Should().NotBeNull(); - } - - [Test] - public void GetEpisode_by_Season_Episode_without_EpisodeFile() - { - - - var fakeSeries = Builder.CreateNew().Build(); - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).TheFirst(1).Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - - - var episode = Mocker.Resolve().GetEpisode(1, 1, 1); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series).EqualTo(fakeEpisodes.First()); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - episode.EpisodeFile.Should().BeNull(); - } - - [Test] - public void GetEpisode_by_AirDate_with_EpisodeFile() - { - - - var fakeSeries = Builder.CreateNew().Build(); - var fakeFile = Builder.CreateNew().With(f => f.Id).With(c => c.Quality = Quality.SDTV).Build(); - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).TheFirst(1).With(e => e.EpisodeFile = new EpisodeFile { Id = 1 }).With(e => e.EpisodeFile = fakeFile).Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - Db.Insert(fakeFile); - - - var episode = Mocker.Resolve().GetEpisode(1, fakeEpisodes[0].AirDate.Value); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series, e => e.EpisodeFile).EqualTo(fakeEpisodes.First()); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - episode.EpisodeFile.Should().NotBeNull(); - } - - [Test] - public void GetEpisode_by_AirDate_without_EpisodeFile() - { - - - var fakeSeries = Builder.CreateNew().Build(); - var fakeEpisodes = Builder.CreateListOfSize(5) - .All().With(e => e.SeriesId = 1).TheFirst(1).With(e => e.EpisodeFile = new EpisodeFile { Id = 1 }).Build(); - - Db.InsertMany(fakeEpisodes); - Db.Insert(fakeSeries); - - - var episode = Mocker.Resolve().GetEpisode(1, fakeEpisodes[0].AirDate.Value); - - - episode.ShouldHave().AllPropertiesBut(e => e.Series).EqualTo(fakeEpisodes.First()); - episode.Series.ShouldHave().AllPropertiesBut(s => s.EpisodeCount, s => s.EpisodeFileCount, s => s.SeasonCount, s => s.NextAiring).EqualTo(fakeSeries); - episode.EpisodeFile.Should().BeNull(); - } - - [Test] - public void MarkEpisodeAsFetched() - { - - var fakeEpisodes = Builder.CreateListOfSize(2) - .All().With(e => e.GrabDate = null) - .Build(); - - var parseResult = new EpisodeParseResult() { Episodes = fakeEpisodes }; - - Mocker.Resolve().Handle(new EpisodeGrabbedEvent(parseResult)); - - Mocker.GetMock().Verify(c=>c.Update(fakeEpisodes[0]),Times.Once()); - Mocker.GetMock().Verify(c=>c.Update(fakeEpisodes[1]),Times.Once()); - } - - [Test] - public void AddEpisode_episode_is_ignored_when_full_season_is_ignored() - { - - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.Ignored = true) - .Build().ToList(); - - episodes.ForEach(c => Db.Insert(c)); - - var newEpisode = Builder.CreateNew() - .With(e => e.SeriesId = 10) - .With(e => e.SeasonNumber = 1) - .With(e => e.EpisodeNumber = 8) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - Mocker.GetMock() - .Setup(s => s.IsIgnored(newEpisode.SeriesId, newEpisode.SeasonNumber)) - .Returns(true); - - - Mocker.Resolve().AddEpisode(newEpisode); - - - var episodesInDb = Db.Fetch(@"SELECT * FROM Episodes"); - - episodesInDb.Should().HaveCount(5); - episodesInDb.Should().OnlyContain(e => e.Ignored); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void AddEpisode_episode_is_not_ignored_when_full_season_is_not_ignored() - { - - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.Ignored = false) - .Build().ToList(); - - episodes.ForEach(c => Db.Insert(c)); - - var newEpisode = Builder.CreateNew() - .With(e => e.SeriesId = 10) - .With(e => e.SeasonNumber = 1) - .With(e => e.EpisodeNumber = 8) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - - Mocker.Resolve().AddEpisode(newEpisode); - - - var episodesInDb = Db.Fetch(@"SELECT * FROM Episodes"); - - episodesInDb.Should().HaveCount(5); - episodesInDb.Should().OnlyContain(e => e.Ignored == false); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void AddEpisode_episode_is_not_ignored_when_not_full_season_is_not_ignored() - { - - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .And(c => c.SeasonNumber = 1) - .And(c => c.Ignored = true) - .TheFirst(2) - .With(c => c.Ignored = false) - .Build().ToList(); - - episodes.ForEach(c => Db.Insert(c)); - - var newEpisode = Builder.CreateNew() - .With(e => e.SeriesId = 10) - .With(e => e.SeasonNumber = 1) - .With(e => e.EpisodeNumber = 8) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - - Mocker.Resolve().AddEpisode(newEpisode); - - - var episodesInDb = Db.Fetch(@"SELECT * FROM Episodes"); - - episodesInDb.Should().HaveCount(5); - episodesInDb.Where(e => e.EpisodeNumber == 8 && !e.Ignored).Should().HaveCount(1); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void IgnoreEpisode_Ignore() - { - - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.Ignored = false) - .Build().ToList(); - - episodes.ForEach(c => Db.Insert(c)); - - - Mocker.Resolve().SetEpisodeIgnore(1, true); - - - var episodesInDb = Db.Fetch(@"SELECT * FROM Episodes"); - - episodesInDb.Should().HaveCount(4); - episodesInDb.Where(e => e.Ignored).Should().HaveCount(1); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void IgnoreEpisode_RemoveIgnore() - { - - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.Ignored = true) - .Build().ToList(); - - episodes.ForEach(c => Db.Insert(c)); - - - Mocker.Resolve().SetEpisodeIgnore(1, false); - - - var episodesInDb = Db.Fetch(@"SELECT * FROM Episodes"); - - episodesInDb.Should().HaveCount(4); - episodesInDb.Where(e => !e.Ignored).Should().HaveCount(1); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void EpisodesWithoutFiles_no_specials() - { - - - var series = Builder.CreateNew() - .With(s => s.Id = 10) - .Build(); - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.AirDate = DateTime.Today.AddDays(-4)) - .With(c => c.Ignored = true) - .TheFirst(2) - .Section(1, 2) - .With(c => c.Ignored = false) - .Build().ToList(); - - var specials = Builder.CreateListOfSize(2) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 0) - .With(c => c.AirDate = DateTime.Today.AddDays(-4)) - .With(c => c.Ignored = false) - .TheFirst(1).With(c => c.Ignored = true) - .Build().ToList(); - - Db.Insert(series); - Db.InsertMany(episodes); - Db.InsertMany(specials); - - - var missingFiles = Mocker.Resolve().EpisodesWithoutFiles(false); - - - missingFiles.Should().HaveCount(1); - missingFiles.Where(e => e.EpisodeFileId == 0).Should().HaveCount(1); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void EpisodesWithoutFiles_with_specials() - { - - - var series = Builder.CreateNew() - .With(s => s.Id = 10) - .Build(); - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.AirDate = DateTime.Today.AddDays(-4)) - .With(c => c.Ignored = true) - .TheFirst(2) - .Section(1, 2) - .With(c => c.Ignored = false) - .Build().ToList(); - - var specials = Builder.CreateListOfSize(2) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 0) - .With(c => c.AirDate = DateTime.Today.AddDays(-4)) - .With(c => c.Ignored = false) - .TheFirst(1) - .With(c => c.Ignored = true) - .Build().ToList(); - - Db.Insert(series); - Db.InsertMany(episodes); - Db.InsertMany(specials); - - - var missingFiles = Mocker.Resolve().EpisodesWithoutFiles(true); - - - missingFiles.Should().HaveCount(2); - missingFiles.Where(e => e.EpisodeFileId == 0).Should().HaveCount(2); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void EpisodesWithFiles_success() - { - - - var series = Builder.CreateNew() - .With(s => s.Id = 10) - .Build(); - - var episodeFile = Builder.CreateNew() - .With(c => c.Id = 1) - .With(c => c.Quality = Quality.SDTV) - .Build(); - - var episodes = Builder.CreateListOfSize(2) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.AirDate = DateTime.Today.AddDays(-4)) - .With(c => c.Ignored = true) - .With(c => c.EpisodeFile = episodeFile) - .Build().ToList(); - - Db.Insert(series); - Db.Insert(episodeFile); - Db.InsertMany(episodes); - - - var withFiles = Mocker.Resolve().EpisodesWithFiles(); - - - withFiles.Should().HaveCount(2); - withFiles.Where(e => e.EpisodeFileId == 0).Should().HaveCount(0); - withFiles.Where(e => e.EpisodeFile == null).Should().HaveCount(0); - - foreach (var withFile in withFiles) - { - withFile.EpisodeFile.Should().NotBeNull(); - withFile.Series.Title.Should().NotBeNullOrEmpty(); - } - - Mocker.VerifyAllMocks(); - } - - [Test] - public void EpisodesWithFiles_no_files() - { - - - var series = Builder.CreateNew() - .With(s => s.Id = 10) - .Build(); - - var episodes = Builder.CreateListOfSize(2) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.AirDate = DateTime.Today.AddDays(-4)) - .With(c => c.Ignored = true) - .Build().ToList(); - - Db.Insert(series); - Db.InsertMany(episodes); - - - var withFiles = Mocker.Resolve().EpisodesWithFiles(); - - - withFiles.Should().HaveCount(0); - - Mocker.VerifyAllMocks(); - } - - [Test] - public void GetEpisodesByFileId_multi_episodes() - { - - - var series = Builder.CreateNew() - .With(s => s.Id = 10) - .Build(); - - var fakeEpisodes = Builder.CreateListOfSize(2) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.EpisodeFile = new EpisodeFile { Id = 12345 }) - .Build(); - - Db.Insert(series); - Db.InsertMany(fakeEpisodes); - - - var episodes = Mocker.Resolve().GetEpisodesByFileId(12345); - - - episodes.Should().HaveCount(2); - Mocker.VerifyAllMocks(); - } - - [Test] - public void GetEpisodesByFileId_single_episode() - { - - - var series = Builder.CreateNew() - .With(s => s.Id = 10) - .Build(); - - var fakeEpisode = Builder.CreateNew() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .With(c => c.EpisodeFile = new EpisodeFile { Id = 12345 }) - .Build(); - - Db.Insert(series); - Db.Insert(fakeEpisode); - - - var episodes = Mocker.Resolve().GetEpisodesByFileId(12345); - - - episodes.Should().HaveCount(1); - episodes.First().ShouldHave().AllPropertiesBut(e => e.Series).EqualTo(fakeEpisode); - Mocker.VerifyAllMocks(); - } - - [Test] - public void IsFirstOrLastEpisodeInSeason_false() - { - - - var fakeEpisodes = Builder.CreateListOfSize(10) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .Build(); - - Db.InsertMany(fakeEpisodes); - - - var result = Mocker.Resolve().IsFirstOrLastEpisodeOfSeason(10, 1, 5); - - - result.Should().BeFalse(); - } - - [Test] - public void IsFirstOrLastEpisodeInSeason_true_first() - { - - - var fakeEpisodes = Builder.CreateListOfSize(10) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .Build(); - - Db.InsertMany(fakeEpisodes); - - - var result = Mocker.Resolve().IsFirstOrLastEpisodeOfSeason(10, 1, 1); - - - result.Should().BeFalse(); - } - - [Test] - public void IsFirstOrLastEpisodeInSeason_true_last() - { - - - var fakeEpisodes = Builder.CreateListOfSize(10) - .All() - .With(c => c.SeriesId = 10) - .With(c => c.SeasonNumber = 1) - .Build(); - - Db.InsertMany(fakeEpisodes); - - - var result = Mocker.Resolve().IsFirstOrLastEpisodeOfSeason(10, 1, 10); - - - result.Should().BeFalse(); - } - - [TestCase("The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Unpacking, 1)] - [TestCase("The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Failed, 1)] - [TestCase("The Office (US) - S01E01E02 - Episode Title", PostDownloadStatusType.Unpacking, 2)] - [TestCase("The Office (US) - S01E01E02 - Episode Title", PostDownloadStatusType.Failed, 2)] - [TestCase("The Office (US) - Season 01 - Episode Title", PostDownloadStatusType.Unpacking, 10)] - [TestCase("The Office (US) - Season 01 - Episode Title", PostDownloadStatusType.Failed, 10)] - public void SetPostDownloadStatus(string folderName, PostDownloadStatusType postDownloadStatus, int episodeCount) - { - - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 12345) - .With(s => s.CleanTitle = "officeus") - .Build(); - - var fakeEpisodes = Builder.CreateListOfSize(episodeCount) - .All() - .With(c => c.SeriesId = 12345) - .With(c => c.SeasonNumber = 1) - .With(c => c.PostDownloadStatus = PostDownloadStatusType.Unknown) - .Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - - Mocker.GetMock().Setup(s => s.GetByTitle("officeus")).Returns(fakeSeries); - - - Mocker.Resolve().SetPostDownloadStatus(fakeEpisodes.Select(e => e.Id).ToList(), postDownloadStatus); - - - var result = Db.Fetch(); - result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(episodeCount); - } - - [Test] - public void SetPostDownloadStatus_Invalid_EpisodeId() - { - - - var postDownloadStatus = PostDownloadStatusType.Failed; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 12345) - .With(s => s.CleanTitle = "officeus") - .Build(); - - var fakeEpisodes = Builder.CreateListOfSize(1) - .All() - .With(c => c.SeriesId = 12345) - .With(c => c.SeasonNumber = 1) - .With(c => c.PostDownloadStatus = PostDownloadStatusType.Unknown) - .Build(); - - Db.Insert(fakeSeries); - Db.InsertMany(fakeEpisodes); - - Mocker.GetMock().Setup(s => s.GetByTitle("officeus")).Returns(fakeSeries); - - - Mocker.Resolve().SetPostDownloadStatus(new List { 300 }, postDownloadStatus); - - - var result = Db.Fetch(); - result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(0); - } - - [Test] - [ExpectedException(typeof(ArgumentException))] - public void SetPostDownloadStatus_should_throw_if_episode_list_is_empty() - { - Mocker.Resolve().SetPostDownloadStatus(new List(), PostDownloadStatusType.Failed); - } - - [Test] - public void RefreshEpisodeInfo_should_ignore_episode_zero_except_if_season_one() - { - //Arrange - const int seriesId = 71663; - const int episodeCount = 5; - - var tvdbSeries = Builder.CreateNew().With( - c => c.Episodes = - new List(Builder.CreateListOfSize(episodeCount). - All() - .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) - .With(e => e.EpisodeNumber = 0) - .TheFirst(1) - .With(e => e.SeasonNumber = 1) - .TheNext(1) - .With(e => e.SeasonNumber = 2) - .TheNext(1) - .With(e => e.SeasonNumber = 3) - .TheNext(1) - .With(e => e.SeasonNumber = 4) - .TheNext(1) - .With(e => e.SeasonNumber = 5) - .Build()) - ).With(c => c.Id = seriesId).Build(); - - var fakeSeries = Builder.CreateNew().With(c => c.Id = seriesId).Build(); - - - - Db.Insert(fakeSeries); - - Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false)) - .Returns(tvdbSeries); - - - Mocker.Resolve().RefreshEpisodeInfo(fakeSeries); - - - var result = Mocker.Resolve().GetEpisodeBySeries(seriesId).ToList(); - result.Should().HaveCount(episodeCount); - result.Where(e => e.Ignored).Should().HaveCount(episodeCount - 1); - result.Single(e => e.SeasonNumber == 1).Ignored.Should().BeFalse(); - } - - - } -} -*/ diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs index 5a3f2e1fc..edd9c8369 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs @@ -4,6 +4,7 @@ using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Core.Qualities; @@ -22,15 +23,15 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests [SetUp] public void Setup() { - var qualityProfile = new QualityProfile + var profile = new Profile { Id = 1, Cutoff = Quality.WEBDL480p, - Items = new List + Items = new List { - new QualityProfileItem { Allowed = true, Quality = Quality.SDTV }, - new QualityProfileItem { Allowed = true, Quality = Quality.WEBDL480p }, - new QualityProfileItem { Allowed = true, Quality = Quality.RAWHD } + new ProfileQualityItem { Allowed = true, Quality = Quality.SDTV }, + new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL480p }, + new ProfileQualityItem { Allowed = true, Quality = Quality.RAWHD } } }; @@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(s => s.Runtime = 30) .With(s => s.Monitored = true) .With(s => s.TitleSlug = "Title3") - .With(s => s.Id = qualityProfile.Id) + .With(s => s.Id = profile.Id) .BuildNew(); _unmonitoredSeries = Builder.CreateNew() @@ -47,7 +48,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(s => s.Runtime = 30) .With(s => s.Monitored = false) .With(s => s.TitleSlug = "Title2") - .With(s => s.Id = qualityProfile.Id) + .With(s => s.Id = profile.Id) .BuildNew(); _monitoredSeries.Id = Db.Insert(_monitoredSeries).Id; @@ -63,7 +64,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests _qualitiesBelowCutoff = new List { - new QualitiesBelowCutoff(qualityProfile.Id, new[] {Quality.SDTV.Id}) + new QualitiesBelowCutoff(profile.Id, new[] {Quality.SDTV.Id}) }; var qualityMet = new EpisodeFile { Path = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p } }; diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs similarity index 74% rename from src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs rename to src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs index 0685ccc0f..bbd18e7e1 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests [Test] public void should_lazyload_quality_profile() { - var profile = new QualityProfile + var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), @@ -24,15 +24,15 @@ namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests }; - Mocker.Resolve().Insert(profile); + Mocker.Resolve().Insert(profile); var series = Builder.CreateNew().BuildNew(); - series.QualityProfileId = profile.Id; + series.ProfileId = profile.Id; Subject.Insert(series); - StoredModel.QualityProfile.Should().NotBeNull(); + StoredModel.Profile.Should().NotBeNull(); } diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs index 86cbe7320..f0585131d 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests { _series = Builder.CreateListOfSize(5) .All() - .With(s => s.QualityProfileId = 1) + .With(s => s.ProfileId = 1) .With(s => s.Monitored) .With(s => s.SeasonFolder) .With(s => s.Path = @"C:\Test\name".AsOsAgnostic()) diff --git a/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs index 2e40accd4..90be4a920 100644 --- a/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs +++ b/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs @@ -17,7 +17,6 @@ namespace NzbDrone.Core.Datastore.Extensions query: (db, parent) => db.Query().SingleOrDefault(c => c.Id == childIdSelector(parent)), condition: parent => childIdSelector(parent) > 0 ); - } public static RelationshipBuilder Relationship(this ColumnMapBuilder mapBuilder) @@ -25,16 +24,12 @@ namespace NzbDrone.Core.Datastore.Extensions return mapBuilder.Relationships.AutoMapComplexTypeProperties(); } - - public static RelationshipBuilder HasMany(this RelationshipBuilder relationshipBuilder, Expression>> portalExpression, Func childIdSelector) where TParent : ModelBase where TChild : ModelBase { return relationshipBuilder.For(portalExpression.GetMemberName()) .LazyLoad((db, parent) => db.Query().Where(c => c.Id == childIdSelector(parent)).ToList()); - - } private static string GetMemberName(this Expression> member) diff --git a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs index ea6f38fdd..78ffb967d 100644 --- a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs +++ b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs @@ -3,6 +3,7 @@ using NzbDrone.Core.Datastore.Migration.Framework; using System.Data; using System.Linq; using NzbDrone.Common.Serializer; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using System.Collections.Generic; using NzbDrone.Core.Datastore.Converters; @@ -41,7 +42,7 @@ namespace NzbDrone.Core.Datastore.Migration var allowed = Json.Deserialize>(allowedJson); - var items = Quality.DefaultQualityDefinitions.OrderBy(v => v.Weight).Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }).ToList(); + var items = Quality.DefaultQualityDefinitions.OrderBy(v => v.Weight).Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }).ToList(); var allowedNewJson = qualityProfileItemConverter.ToDB(items); diff --git a/src/NzbDrone.Core/Datastore/Migration/054_rename_profiles.cs b/src/NzbDrone.Core/Datastore/Migration/054_rename_profiles.cs new file mode 100644 index 000000000..e665c14a4 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/054_rename_profiles.cs @@ -0,0 +1,31 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(54)] + public class rename_profiles : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Rename.Table("QualityProfiles").To("Profiles"); + + Alter.Table("Profiles").AddColumn("Language").AsInt32().Nullable(); + Alter.Table("Profiles").AddColumn("GrabDelay").AsInt32().Nullable(); + Alter.Table("Profiles").AddColumn("GrabDelayMode").AsInt32().Nullable(); + Execute.Sql("UPDATE Profiles SET Language = 1, GrabDelay = 0, GrabDelayMode = 0"); + + //Rename QualityProfileId in Series + Alter.Table("Series").AddColumn("ProfileId").AsInt32().Nullable(); + Execute.Sql("UPDATE Series SET ProfileId = QualityProfileId"); + + //Add HeldReleases + Create.TableForModel("PendingReleases") + .WithColumn("SeriesId").AsInt32() + .WithColumn("Title").AsString() + .WithColumn("Added").AsDateTime() + .WithColumn("ParsedEpisodeInfo").AsString() + .WithColumn("Release").AsString(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/055_drop_old_profile_columns.cs b/src/NzbDrone.Core/Datastore/Migration/055_drop_old_profile_columns.cs new file mode 100644 index 000000000..349c10ca1 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/055_drop_old_profile_columns.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(55)] + public class drop_old_profile_columns : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + SqLiteAlter.DropColumns("Series", new[] { "QualityProfileId" }); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs index cbf18dbd9..59129eaa0 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs @@ -33,7 +33,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework public string CreateSql(string tableName) { - return string.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName); + if (Unique) + { + return String.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName); + } + + return String.Format(@"CREATE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs index 3bb08dace..fabdefca9 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs @@ -147,14 +147,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework } } - public void DropTable(string tableName) { var dropCommand = BuildCommand("DROP TABLE {0};", tableName); dropCommand.ExecuteNonQuery(); } - public void RenameTable(string tableName, string newName) { var renameCommand = BuildCommand("ALTER TABLE {0} RENAME TO {1};", tableName, newName); @@ -184,7 +182,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework return Convert.ToInt32(countCommand.ExecuteScalar()); } - public SQLiteTransaction BeginTransaction() { return _connection.BeginTransaction(); @@ -197,7 +194,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework return command; } - public void ExecuteNonQuery(string command, params string[] args) { var sqLiteCommand = new SQLiteCommand(string.Format(command, args)) @@ -226,7 +222,5 @@ namespace NzbDrone.Core.Datastore.Migration.Framework } } - - } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index edb84e624..b0f14fc9d 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Indexers; using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Jobs; @@ -17,6 +18,8 @@ using NzbDrone.Core.Metadata; using NzbDrone.Core.Metadata.Files; using NzbDrone.Core.Notifications; using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.RootFolders; using NzbDrone.Core.SeriesStats; @@ -27,7 +30,6 @@ namespace NzbDrone.Core.Datastore { public static class TableMapping { - private static readonly FluentMappings Mapper = new FluentMappings(true); public static void Map() @@ -52,7 +54,7 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("Series") .Ignore(s => s.RootFolderPath) .Relationship() - .HasOne(s => s.QualityProfile, s => s.QualityProfileId); + .HasOne(s => s.Profile, s => s.ProfileId); Mapper.Entity().RegisterModel("Episodes") .Ignore(e => e.SeriesTitle) @@ -64,14 +66,16 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("EpisodeFiles") .Relationships.AutoMapICollectionOrComplexProperties(); - Mapper.Entity().RegisterModel("QualityProfiles"); + Mapper.Entity().RegisterModel("Profiles"); Mapper.Entity().RegisterModel("QualityDefinitions"); Mapper.Entity().RegisterModel("Logs"); Mapper.Entity().RegisterModel("NamingConfig"); Mapper.Entity().MapResultSet(); Mapper.Entity().RegisterModel("Blacklist"); - Mapper.Entity().RegisterModel("MetadataFiles"); + + Mapper.Entity().RegisterModel("PendingReleases") + .Ignore(e => e.RemoteEpisode); } private static void RegisterMappers() @@ -84,11 +88,13 @@ 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), new EmbeddedDocumentConverter(new QualityIntConverter())); + MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new QualityIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); + MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter()); + MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter()); } private static void RegisterProviderSettingConverter() diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecision.cs similarity index 53% rename from src/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs rename to src/NzbDrone.Core/DecisionEngine/DownloadDecision.cs index 22616a735..dfbca6e84 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecision.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Parser.Model; -namespace NzbDrone.Core.DecisionEngine.Specifications +namespace NzbDrone.Core.DecisionEngine { public class DownloadDecision { public RemoteEpisode RemoteEpisode { get; private set; } - public IEnumerable Rejections { get; private set; } + public IEnumerable Rejections { get; private set; } public bool Approved { @@ -17,13 +17,28 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } - public DownloadDecision(RemoteEpisode episode, params string[] rejections) + public bool TemporarilyRejected + { + get + { + return Rejections.Any() && Rejections.All(r => r.Type == RejectionType.Temporary); + } + } + + public bool Rejected + { + get + { + return Rejections.Any() && Rejections.All(r => r.Type == RejectionType.Permanent); + } + } + + public DownloadDecision(RemoteEpisode episode, params Rejection[] rejections) { RemoteEpisode = episode; Rejections = rejections.ToList(); } - - + public override string ToString() { if (Approved) diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 3babd682b..4dd786a90 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -4,7 +4,6 @@ using System.Linq; using NLog; using NzbDrone.Common; using NzbDrone.Common.Serializer; -using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Instrumentation.Extensions; using NzbDrone.Core.Parser; @@ -17,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine { List GetRssDecision(List reports); List GetSearchDecision(List reports, SearchCriteriaBase searchCriteriaBase); + DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null); } public class DownloadDecisionMaker : IMakeDownloadDecision @@ -87,7 +87,7 @@ namespace NzbDrone.Core.DecisionEngine } else { - decision = new DownloadDecision(remoteEpisode, "Unknown Series"); + decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series")); } } } @@ -110,19 +110,19 @@ namespace NzbDrone.Core.DecisionEngine } } - private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null) + public DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null) { var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria)) - .Where(c => !string.IsNullOrWhiteSpace(c)); + .Where(c => c != null); return new DownloadDecision(remoteEpisode, reasons.ToArray()); } - private string EvaluateSpec(IRejectWithReason spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null) + private Rejection EvaluateSpec(IRejectWithReason spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null) { try { - if (string.IsNullOrWhiteSpace(spec.RejectionReason)) + if (spec.RejectionReason.IsNullOrWhiteSpace()) { throw new InvalidOperationException("[Need Rejection Text]"); } @@ -130,7 +130,7 @@ namespace NzbDrone.Core.DecisionEngine var generalSpecification = spec as IDecisionEngineSpecification; if (generalSpecification != null && !generalSpecification.IsSatisfiedBy(remoteEpisode, searchCriteriaBase)) { - return spec.RejectionReason; + return new Rejection(spec.RejectionReason, generalSpecification.Type); } } catch (Exception e) @@ -138,7 +138,7 @@ namespace NzbDrone.Core.DecisionEngine e.Data.Add("report", remoteEpisode.Release.ToJson()); e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); _logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Release.Title, e); - return string.Format("{0}: {1}", spec.GetType().Name, e.Message); + return new Rejection(String.Format("{0}: {1}", spec.GetType().Name, e.Message)); } return null; diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs index 9f01356b2..f9fa820b3 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.DecisionEngine return decisions .Where(c => c.RemoteEpisode.Series != null) .GroupBy(c => c.RemoteEpisode.Series.Id, (i, s) => s - .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile)) + .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.Profile)) .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault()) .ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / Math.Max(1, c.RemoteEpisode.Episodes.Count)) .ThenBy(c => c.RemoteEpisode.Release.Age)) diff --git a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs index 089925c7b..69cbdc894 100644 --- a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -5,6 +6,8 @@ namespace NzbDrone.Core.DecisionEngine { public interface IDecisionEngineSpecification : IRejectWithReason { - bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria); + RejectionType Type { get; } + + Boolean IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DecisionEngine/IRejectWithReason.cs b/src/NzbDrone.Core/DecisionEngine/IRejectWithReason.cs index 503071e02..ee9f843d3 100644 --- a/src/NzbDrone.Core/DecisionEngine/IRejectWithReason.cs +++ b/src/NzbDrone.Core/DecisionEngine/IRejectWithReason.cs @@ -4,4 +4,4 @@ namespace NzbDrone.Core.DecisionEngine { string RejectionReason { get; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs index a37f65ef3..0c83be0e3 100644 --- a/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs @@ -1,12 +1,13 @@ using NLog; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.DecisionEngine { public interface IQualityUpgradableSpecification { - bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null); - bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null); + bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); + bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality); } @@ -19,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine _logger = logger; } - public bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null) + public bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) { if (newQuality != null) { @@ -39,7 +40,7 @@ namespace NzbDrone.Core.DecisionEngine return true; } - public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null) + public bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) { int compare = new QualityModelComparer(profile).Compare(currentQuality.Quality, profile.Cutoff); diff --git a/src/NzbDrone.Core/DecisionEngine/Rejection.cs b/src/NzbDrone.Core/DecisionEngine/Rejection.cs new file mode 100644 index 000000000..315803b38 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Rejection.cs @@ -0,0 +1,21 @@ +using System; + +namespace NzbDrone.Core.DecisionEngine +{ + public class Rejection + { + public String Reason { get; set; } + public RejectionType Type { get; set; } + + public Rejection(string reason, RejectionType type = RejectionType.Permanent) + { + Reason = reason; + Type = type; + } + + public override string ToString() + { + return String.Format("[{0}] {1}", Type, Reason); + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/RejectionType.cs b/src/NzbDrone.Core/DecisionEngine/RejectionType.cs new file mode 100644 index 000000000..f15d810ce --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/RejectionType.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.DecisionEngine +{ + public enum RejectionType + { + Permanent = 0, + Temporary = 1 + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index 1d776f3e1..0983a9a50 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; @@ -21,12 +22,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } - public string RejectionReason + public String RejectionReason { get { return "File size too big or small"; } } - public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + public RejectionType Type { get { return RejectionType.Permanent; } } + + public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Beginning size check for: {0}", subject); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs index 6ca2b588a..3e3f28a60 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs @@ -27,6 +27,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (!_configService.EnableFailedDownloadHandling) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 7ede1ed8d..198e26a39 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -24,6 +24,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) @@ -31,7 +33,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); - if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality)) + if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality)) { _logger.Debug("Cutoff already met, rejecting."); return false; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs index 1ec1eb76d..b8916846f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs @@ -1,6 +1,5 @@ using NLog; using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.DecisionEngine.Specifications @@ -18,16 +17,21 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { get { - return "Not English"; + return "Language is not wanted"; } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { + var wantedLanguage = subject.Series.Profile.Value.Language; + _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language); - if (subject.ParsedEpisodeInfo.Language != Language.English) + + if (subject.ParsedEpisodeInfo.Language != wantedLanguage) { - _logger.Debug("Report Language: {0} rejected because it is not English", subject.ParsedEpisodeInfo.Language); + _logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedEpisodeInfo.Language, wantedLanguage); return false; } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs index 64f19ad72..fa58e4119 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs @@ -28,6 +28,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var queue = _downloadTrackingService.GetQueuedDownloads() @@ -46,7 +48,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable queue) { var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id); - var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.QualityProfile).Compare(q.ParsedEpisodeInfo.Quality, newEpisode.ParsedEpisodeInfo.Quality) >= 0); + var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.Profile).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()); } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs index 7a9e9f65c..6818ab5fa 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs @@ -25,6 +25,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Checking if release contains any restricted terms: {0}", subject); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs index c5e193012..a7568c96a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs @@ -7,7 +7,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class NotSampleSpecification : IDecisionEngineSpecification { private readonly Logger _logger; + public string RejectionReason { get { return "Sample"; } } + public RejectionType Type { get { return RejectionType.Permanent; } } public NotSampleSpecification(Logger logger) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs index 758d9b2d6..b40a1acab 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs @@ -21,10 +21,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality); - if (!subject.Series.QualityProfile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality)) + if (!subject.Series.Profile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality)) { _logger.Debug("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality); return false; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs index 614794b92..5aa7539d2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs @@ -25,6 +25,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var age = subject.Release.Age; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs index c99d24c59..dc964932f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs @@ -30,6 +30,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (!_configService.EnableFailedDownloadHandling) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs new file mode 100644 index 000000000..3d117fc54 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -0,0 +1,116 @@ +using System.Linq; +using NLog; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync +{ + public class DelaySpecification : IDecisionEngineSpecification + { + private readonly IPendingReleaseService _pendingReleaseService; + private readonly Logger _logger; + + public DelaySpecification(IPendingReleaseService pendingReleaseService, Logger logger) + { + _pendingReleaseService = pendingReleaseService; + _logger = logger; + } + + public string RejectionReason + { + get + { + return "Waiting for better quality release"; + } + } + + public RejectionType Type { get { return RejectionType.Temporary; } } + + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + //How do we want to handle drone being off and the automatic search being triggered? + //TODO: Add a flag to the search to state it is a "scheduled" search + + if (searchCriteria != null) + { + _logger.Debug("Ignore delay for searches"); + return true; + } + + var profile = subject.Series.Profile.Value; + + if (profile.GrabDelay == 0) + { + _logger.Debug("Profile does not delay before download"); + return true; + } + + var comparer = new QualityModelComparer(profile); + + if (subject.ParsedEpisodeInfo.Quality.Proper) + { + foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) + { + if (comparer.Compare(subject.ParsedEpisodeInfo.Quality, file.Quality) > 0) + { + var properCompare = subject.ParsedEpisodeInfo.Quality.Proper.CompareTo(file.Quality.Proper); + + if (subject.ParsedEpisodeInfo.Quality.Quality == file.Quality.Quality && properCompare > 0) + { + _logger.Debug("New quality is a proper for existing quality, skipping delay"); + return true; + } + } + } + } + + //If quality meets or exceeds the best allowed quality in the profile accept it immediately + var bestQualityInProfile = new QualityModel(profile.Items.Last(q => q.Allowed).Quality); + var bestCompare = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile); + + if (bestCompare >= 0) + { + _logger.Debug("Quality is highest in profile, will not delay"); + return true; + } + + if (profile.GrabDelayMode == GrabDelayMode.Cutoff) + { + var cutoff = new QualityModel(profile.Cutoff); + var cutoffCompare = comparer.Compare(subject.ParsedEpisodeInfo.Quality, cutoff); + + if (cutoffCompare >= 0) + { + _logger.Debug("Quality meets or exceeds the cutoff, will not delay"); + return true; + } + } + + if (profile.GrabDelayMode == GrabDelayMode.First) + { + var episodeIds = subject.Episodes.Select(e => e.Id); + + var oldest = _pendingReleaseService.GetPendingRemoteEpisodes(subject.Series.Id) + .Where(r => r.Episodes.Select(e => e.Id).Intersect(episodeIds).Any()) + .OrderByDescending(p => p.Release.AgeHours) + .FirstOrDefault(); + + if (oldest != null && oldest.Release.AgeHours > profile.GrabDelay) + { + return true; + } + } + + if (subject.Release.AgeHours < profile.GrabDelay) + { + _logger.Debug("Age ({0}) is less than delay {1}, delaying", subject.Release.AgeHours, profile.GrabDelay); + return false; + } + + return true; + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 9dd1fc5c4..d2e49d18c 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -34,6 +34,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) @@ -63,11 +65,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync foreach (var episode in subject.Episodes) { - var bestQualityInHistory = _historyService.GetBestQualityInHistory(subject.Series.QualityProfile, episode.Id); + var bestQualityInHistory = _historyService.GetBestQualityInHistory(subject.Series.Profile, episode.Id); if (bestQualityInHistory != null) { _logger.Debug("Comparing history quality with report. History is {0}", bestQualityInHistory); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality)) + if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality)) return false; } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs index a0709622f..62f21bceb 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs @@ -22,6 +22,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs index 0346ab28a..a5ce14a84 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -28,6 +28,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs index 4d089ce68..576deaa62 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs @@ -23,6 +23,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return "Episode doesn't match"; } } + + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs index 6df94a6fc..506d0790e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs @@ -23,6 +23,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs index 211870221..2ce482905 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs @@ -21,6 +21,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs index 6e6f146f8..abf15f4b4 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs @@ -21,6 +21,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs index 3960a4e4d..c24dd8821 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs @@ -22,6 +22,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 833f4b9a5..d71bc9225 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -24,13 +24,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + public RejectionType Type { get { return RejectionType.Permanent; } } + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality)) + if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality)) { return false; } diff --git a/src/NzbDrone.Core/Download/DownloadApprovedReports.cs b/src/NzbDrone.Core/Download/DownloadApprovedReports.cs deleted file mode 100644 index c5dd7ef58..000000000 --- a/src/NzbDrone.Core/Download/DownloadApprovedReports.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Qualities; - -namespace NzbDrone.Core.Download -{ - public interface IDownloadApprovedReports - { - List DownloadApproved(List decisions); - } - - public class DownloadApprovedReports : IDownloadApprovedReports - { - private readonly IDownloadService _downloadService; - private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; - private readonly Logger _logger; - - public DownloadApprovedReports(IDownloadService downloadService, IPrioritizeDownloadDecision prioritizeDownloadDecision, Logger logger) - { - _downloadService = downloadService; - _prioritizeDownloadDecision = prioritizeDownloadDecision; - _logger = logger; - } - - public List DownloadApproved(List decisions) - { - var qualifiedReports = GetQualifiedReports(decisions); - var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports); - var downloadedReports = new List(); - - foreach (var report in prioritizedDecisions) - { - var remoteEpisode = report.RemoteEpisode; - - try - { - if (downloadedReports.SelectMany(r => r.RemoteEpisode.Episodes) - .Select(e => e.Id) - .ToList() - .Intersect(remoteEpisode.Episodes.Select(e => e.Id)) - .Any()) - { - continue; - } - - _downloadService.DownloadReport(remoteEpisode); - downloadedReports.Add(report); - } - catch (Exception e) - { - _logger.WarnException("Couldn't add report to download queue. " + remoteEpisode, e); - } - } - - return downloadedReports; - } - - public List GetQualifiedReports(IEnumerable decisions) - { - return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any()).ToList(); - } - } -} diff --git a/src/NzbDrone.Core/Download/DownloadClientItem.cs b/src/NzbDrone.Core/Download/DownloadClientItem.cs index 8b9365c9c..3ef9ca2b2 100644 --- a/src/NzbDrone.Core/Download/DownloadClientItem.cs +++ b/src/NzbDrone.Core/Download/DownloadClientItem.cs @@ -1,8 +1,5 @@ using NzbDrone.Core.Parser.Model; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace NzbDrone.Core.Download { diff --git a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs new file mode 100644 index 000000000..c99e5ac6a --- /dev/null +++ b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs @@ -0,0 +1,18 @@ +using System; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download.Pending +{ + public class PendingRelease : ModelBase + { + public Int32 SeriesId { get; set; } + public String Title { get; set; } + public DateTime Added { get; set; } + public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } + public ReleaseInfo Release { get; set; } + + //Not persisted + public RemoteEpisode RemoteEpisode { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs new file mode 100644 index 000000000..26cfb1d39 --- /dev/null +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Download.Pending +{ + public interface IPendingReleaseRepository : IBasicRepository + { + void DeleteBySeriesId(Int32 seriesId); + List AllBySeriesId(Int32 seriesId); + } + + public class PendingReleaseRepository : BasicRepository, IPendingReleaseRepository + { + public PendingReleaseRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + public void DeleteBySeriesId(Int32 seriesId) + { + Delete(r => r.SeriesId == seriesId); + } + + public List AllBySeriesId(Int32 seriesId) + { + return Query.Where(p => p.SeriesId == seriesId); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs new file mode 100644 index 000000000..d60e84e58 --- /dev/null +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Tv.Events; + +namespace NzbDrone.Core.Download.Pending +{ + public interface IPendingReleaseService + { + void Add(DownloadDecision decision); + void RemoveGrabbed(List grabbed); + void RemoveRejected(List rejected); + List GetPending(); + List GetPendingRemoteEpisodes(Int32 seriesId); + List GetPendingQueue(); + } + + public class PendingReleaseService : IPendingReleaseService, IHandle + { + private readonly IPendingReleaseRepository _repository; + private readonly ISeriesService _seriesService; + private readonly IParsingService _parsingService; + private readonly IDownloadService _downloadService; + private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; + + public PendingReleaseService(IPendingReleaseRepository repository, + ISeriesService seriesService, + IParsingService parsingService, + IDownloadService downloadService, + IEventAggregator eventAggregator, + Logger logger) + { + _repository = repository; + _seriesService = seriesService; + _parsingService = parsingService; + _downloadService = downloadService; + _eventAggregator = eventAggregator; + _logger = logger; + } + + public void Add(DownloadDecision decision) + { + var alreadyPending = GetPendingReleases(); + + var episodeIds = decision.RemoteEpisode.Episodes.Select(e => e.Id); + + var existingReports = alreadyPending.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id) + .Intersect(episodeIds) + .Any()); + + if (existingReports.Any(MatchingReleasePredicate(decision))) + { + _logger.Debug("This release is already pending, not adding again"); + return; + } + + _logger.Debug("Adding release to pending releases"); + Insert(decision); + } + + public void RemoveGrabbed(List grabbed) + { + _logger.Debug("Removing grabbed releases from pending"); + var alreadyPending = GetPendingReleases(); + + foreach (var decision in grabbed) + { + var decisionLocal = decision; + var episodeIds = decisionLocal.RemoteEpisode.Episodes.Select(e => e.Id); + + + var existingReports = alreadyPending.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id) + .Intersect(episodeIds) + .Any()); + + foreach (var existingReport in existingReports) + { + _logger.Debug("Removing previously pending release, as it was grabbed."); + Delete(existingReport); + } + } + } + + public void RemoveRejected(List rejected) + { + _logger.Debug("Removing failed releases from pending"); + var pending = GetPendingReleases(); + + foreach (var rejectedRelease in rejected) + { + var matching = pending.SingleOrDefault(MatchingReleasePredicate(rejectedRelease)); + + if (matching != null) + { + _logger.Debug("Removing previously pending release, as it has now been rejected."); + Delete(matching); + } + } + } + + public List GetPending() + { + return _repository.All().Select(p => p.Release).ToList(); + } + + public List GetPendingRemoteEpisodes(int seriesId) + { + return _repository.AllBySeriesId(seriesId).Select(GetRemoteEpisode).ToList(); + } + + public List GetPendingQueue() + { + var queued = new List(); + + foreach (var pendingRelease in GetPendingReleases()) + { + foreach (var episode in pendingRelease.RemoteEpisode.Episodes) + { + var queue = new Queue.Queue + { + Id = episode.Id ^ (pendingRelease.Id << 16), + Series = pendingRelease.RemoteEpisode.Series, + Episode = episode, + Quality = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Quality, + Title = pendingRelease.Title, + Size = pendingRelease.RemoteEpisode.Release.Size, + Sizeleft = pendingRelease.RemoteEpisode.Release.Size, + Timeleft = + pendingRelease.Release.PublishDate.AddHours( + pendingRelease.RemoteEpisode.Series.Profile.Value.GrabDelay) + .Subtract(DateTime.UtcNow), + Status = "Pending", + RemoteEpisode = pendingRelease.RemoteEpisode + }; + queued.Add(queue); + } + } + + return queued; + } + + private List GetPendingReleases() + { + var result = new List(); + + foreach (var release in _repository.All()) + { + var remoteEpisode = GetRemoteEpisode(release); + + if (remoteEpisode == null) continue; + + release.RemoteEpisode = remoteEpisode; + + result.Add(release); + } + + return result; + } + + private RemoteEpisode GetRemoteEpisode(PendingRelease release) + { + var series = _seriesService.GetSeries(release.SeriesId); + + //Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up) + if (series == null) return null; + + var episodes = _parsingService.GetEpisodes(release.ParsedEpisodeInfo, series, true); + + return new RemoteEpisode + { + Series = series, + Episodes = episodes, + ParsedEpisodeInfo = release.ParsedEpisodeInfo, + Release = release.Release + }; + } + + private void Insert(DownloadDecision decision) + { + _repository.Insert(new PendingRelease + { + SeriesId = decision.RemoteEpisode.Series.Id, + ParsedEpisodeInfo = decision.RemoteEpisode.ParsedEpisodeInfo, + Release = decision.RemoteEpisode.Release, + Title = decision.RemoteEpisode.Release.Title, + Added = DateTime.UtcNow + }); + + _eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent()); + } + + private void Delete(PendingRelease pendingRelease) + { + _repository.Delete(pendingRelease); + _eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent()); + } + + private Func MatchingReleasePredicate(DownloadDecision decision) + { + return p => p.Title == decision.RemoteEpisode.Release.Title && + p.Release.PublishDate == decision.RemoteEpisode.Release.PublishDate && + p.Release.Indexer == decision.RemoteEpisode.Release.Indexer; + } + + public void Handle(SeriesDeletedEvent message) + { + _repository.DeleteBySeriesId(message.Series.Id); + } + } +} diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleasesUpdatedEvent.cs b/src/NzbDrone.Core/Download/Pending/PendingReleasesUpdatedEvent.cs new file mode 100644 index 000000000..fd8c881f1 --- /dev/null +++ b/src/NzbDrone.Core/Download/Pending/PendingReleasesUpdatedEvent.cs @@ -0,0 +1,8 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Download.Pending +{ + public class PendingReleasesUpdatedEvent : IEvent + { + } +} diff --git a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs new file mode 100644 index 000000000..4dab30611 --- /dev/null +++ b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download +{ + public interface IProcessDownloadDecisions + { + ProcessedDecisions ProcessDecisions(List decisions); + } + + public class ProcessDownloadDecisions : IProcessDownloadDecisions + { + private readonly IDownloadService _downloadService; + private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; + private readonly IPendingReleaseService _pendingReleaseService; + private readonly Logger _logger; + + public ProcessDownloadDecisions(IDownloadService downloadService, + IPrioritizeDownloadDecision prioritizeDownloadDecision, + IPendingReleaseService pendingReleaseService, + Logger logger) + { + _downloadService = downloadService; + _prioritizeDownloadDecision = prioritizeDownloadDecision; + _pendingReleaseService = pendingReleaseService; + _logger = logger; + } + + public ProcessedDecisions ProcessDecisions(List decisions) + { + var qualifiedReports = GetQualifiedReports(decisions); + var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports); + var downloadedReports = new List(); + var pendingReports = new List(); + + foreach (var report in prioritizedDecisions) + { + var remoteEpisode = report.RemoteEpisode; + + if (DownloadingOrPending(downloadedReports, pendingReports, remoteEpisode)) + { + continue; + } + + if (report.TemporarilyRejected) + { + _pendingReleaseService.Add(report); + pendingReports.Add(report); + continue; + } + + try + { + _downloadService.DownloadReport(remoteEpisode); + downloadedReports.Add(report); + } + catch (Exception e) + { + //TODO: support for store & forward + _logger.WarnException("Couldn't add report to download queue. " + remoteEpisode, e); + } + } + + return new ProcessedDecisions(downloadedReports, pendingReports); + } + + internal List GetQualifiedReports(IEnumerable decisions) + { + //Process both approved and temporarily rejected + return decisions.Where(c => (c.Approved || c.TemporarilyRejected) && c.RemoteEpisode.Episodes.Any()).ToList(); + } + + private bool DownloadingOrPending(List downloading, List pending, RemoteEpisode remoteEpisode) + { + var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList(); + + if (downloading.SelectMany(r => r.RemoteEpisode.Episodes) + .Select(e => e.Id) + .ToList() + .Intersect(episodeIds) + .Any()) + { + return true; + } + + if (pending.SelectMany(r => r.RemoteEpisode.Episodes) + .Select(e => e.Id) + .ToList() + .Intersect(episodeIds) + .Any()) + { + return true; + } + + return false; + } + } +} diff --git a/src/NzbDrone.Core/Download/ProcessedDecisions.cs b/src/NzbDrone.Core/Download/ProcessedDecisions.cs new file mode 100644 index 000000000..c274b931a --- /dev/null +++ b/src/NzbDrone.Core/Download/ProcessedDecisions.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using NzbDrone.Core.DecisionEngine; + +namespace NzbDrone.Core.Download +{ + public class ProcessedDecisions + { + public List Grabbed { get; set; } + public List Pending { get; set; } + + public ProcessedDecisions(List grabbed, List pending) + { + Grabbed = grabbed; + Pending = pending; + } + } +} diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index e2d359016..ca0fbf3bc 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -7,6 +7,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; @@ -17,7 +18,7 @@ namespace NzbDrone.Core.History List All(); void Purge(); void Trim(); - QualityModel GetBestQualityInHistory(QualityProfile qualityProfile, int episodeId); + QualityModel GetBestQualityInHistory(Profile profile, int episodeId); PagingSpec Paged(PagingSpec pagingSpec); List BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType); List Failed(); @@ -95,9 +96,9 @@ namespace NzbDrone.Core.History _historyRepository.Trim(); } - public QualityModel GetBestQualityInHistory(QualityProfile qualityProfile, int episodeId) + public QualityModel GetBestQualityInHistory(Profile profile, int episodeId) { - var comparer = new QualityModelComparer(qualityProfile); + var comparer = new QualityModelComparer(profile); return _historyRepository.GetBestQualityInHistory(episodeId) .OrderByDescending(q => q, comparer) .FirstOrDefault(); diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs new file mode 100644 index 000000000..83a5e9fd0 --- /dev/null +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs @@ -0,0 +1,31 @@ +using NLog; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Housekeeping.Housekeepers +{ + public class CleanupOrphanedPendingReleases : IHousekeepingTask + { + private readonly IDatabase _database; + private readonly Logger _logger; + + public CleanupOrphanedPendingReleases(IDatabase database, Logger logger) + { + _database = database; + _logger = logger; + } + + public void Clean() + { + _logger.Debug("Running orphaned pending releases cleanup"); + + var mapper = _database.GetDataMapper(); + + mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases + WHERE Id IN ( + SELECT PendingReleases.Id FROM PendingReleases + LEFT OUTER JOIN Series + ON PendingReleases.SeriesId = Series.Id + WHERE Series.Id IS NULL)"); + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs index 4bca174db..6a59c3f12 100644 --- a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs @@ -21,19 +21,19 @@ namespace NzbDrone.Core.IndexerSearch public class MissingEpisodeSearchService : IEpisodeSearchService, IExecute, IExecute { private readonly ISearchForNzb _nzbSearchService; - private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IEpisodeService _episodeService; private readonly IQueueService _queueService; private readonly Logger _logger; public MissingEpisodeSearchService(ISearchForNzb nzbSearchService, - IDownloadApprovedReports downloadApprovedReports, + IProcessDownloadDecisions processDownloadDecisions, IEpisodeService episodeService, IQueueService queueService, Logger logger) { _nzbSearchService = nzbSearchService; - _downloadApprovedReports = downloadApprovedReports; + _processDownloadDecisions = processDownloadDecisions; _episodeService = episodeService; _queueService = queueService; _logger = logger; @@ -52,9 +52,10 @@ namespace NzbDrone.Core.IndexerSearch foreach (var episode in missing) { + //TODO: Add a flag to the search to state it is a "scheduled" search var decisions = _nzbSearchService.EpisodeSearch(episode); - var downloaded = _downloadApprovedReports.DownloadApproved(decisions); - downloadedCount += downloaded.Count; + var processed = _processDownloadDecisions.ProcessDecisions(decisions); + downloadedCount += processed.Grabbed.Count; } _logger.ProgressInfo("Completed search for {0} episodes. {1} reports downloaded.", missing.Count, downloadedCount); @@ -65,9 +66,9 @@ namespace NzbDrone.Core.IndexerSearch foreach (var episodeId in message.EpisodeIds) { var decisions = _nzbSearchService.EpisodeSearch(episodeId); - var downloaded = _downloadApprovedReports.DownloadApproved(decisions); + var processed = _processDownloadDecisions.ProcessDecisions(decisions); - _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", downloaded.Count); + _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", processed.Grabbed.Count); } } @@ -97,8 +98,8 @@ namespace NzbDrone.Core.IndexerSearch { rateGate.WaitToProceed(); var decisions = _nzbSearchService.EpisodeSearch(episode); - var downloaded = _downloadApprovedReports.DownloadApproved(decisions); - downloadedCount += downloaded.Count; + var processed = _processDownloadDecisions.ProcessDecisions(decisions); + downloadedCount += processed.Grabbed.Count; } } diff --git a/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs b/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs index 4c164ceef..85e849e01 100644 --- a/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs @@ -8,24 +8,24 @@ namespace NzbDrone.Core.IndexerSearch public class SeasonSearchService : IExecute { private readonly ISearchForNzb _nzbSearchService; - private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly Logger _logger; public SeasonSearchService(ISearchForNzb nzbSearchService, - IDownloadApprovedReports downloadApprovedReports, + IProcessDownloadDecisions processDownloadDecisions, Logger logger) { _nzbSearchService = nzbSearchService; - _downloadApprovedReports = downloadApprovedReports; + _processDownloadDecisions = processDownloadDecisions; _logger = logger; } public void Execute(SeasonSearchCommand message) { var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber); - var downloaded = _downloadApprovedReports.DownloadApproved(decisions); + var processed = _processDownloadDecisions.ProcessDecisions(decisions); - _logger.ProgressInfo("Season search completed. {0} reports downloaded.", downloaded.Count); + _logger.ProgressInfo("Season search completed. {0} reports downloaded.", processed.Grabbed.Count); } } } diff --git a/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs index e80480ef0..d92675b78 100644 --- a/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs @@ -10,17 +10,17 @@ namespace NzbDrone.Core.IndexerSearch { private readonly ISeriesService _seriesService; private readonly ISearchForNzb _nzbSearchService; - private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly Logger _logger; public SeriesSearchService(ISeriesService seriesService, ISearchForNzb nzbSearchService, - IDownloadApprovedReports downloadApprovedReports, + IProcessDownloadDecisions processDownloadDecisions, Logger logger) { _seriesService = seriesService; _nzbSearchService = nzbSearchService; - _downloadApprovedReports = downloadApprovedReports; + _processDownloadDecisions = processDownloadDecisions; _logger = logger; } @@ -39,7 +39,7 @@ namespace NzbDrone.Core.IndexerSearch } var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber); - downloadedCount += _downloadApprovedReports.DownloadApproved(decisions).Count; + downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; } _logger.ProgressInfo("Series search completed. {0} reports downloaded.", downloadedCount); diff --git a/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs b/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs new file mode 100644 index 000000000..af4fef0ff --- /dev/null +++ b/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs @@ -0,0 +1,8 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Indexers +{ + public class RssSyncCompleteEvent : IEvent + { + } +} diff --git a/src/NzbDrone.Core/Indexers/RssSyncService.cs b/src/NzbDrone.Core/Indexers/RssSyncService.cs index 540b4e52e..9c9ed7f1f 100644 --- a/src/NzbDrone.Core/Indexers/RssSyncService.cs +++ b/src/NzbDrone.Core/Indexers/RssSyncService.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Pending; using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Instrumentation.Extensions; using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Indexers { @@ -20,20 +21,26 @@ namespace NzbDrone.Core.Indexers { private readonly IFetchAndParseRss _rssFetcherAndParser; private readonly IMakeDownloadDecision _downloadDecisionMaker; - private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IEpisodeSearchService _episodeSearchService; + private readonly IPendingReleaseService _pendingReleaseService; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public RssSyncService(IFetchAndParseRss rssFetcherAndParser, IMakeDownloadDecision downloadDecisionMaker, - IDownloadApprovedReports downloadApprovedReports, + IProcessDownloadDecisions processDownloadDecisions, IEpisodeSearchService episodeSearchService, + IPendingReleaseService pendingReleaseService, + IEventAggregator eventAggregator, Logger logger) { _rssFetcherAndParser = rssFetcherAndParser; _downloadDecisionMaker = downloadDecisionMaker; - _downloadApprovedReports = downloadApprovedReports; + _processDownloadDecisions = processDownloadDecisions; _episodeSearchService = episodeSearchService; + _pendingReleaseService = pendingReleaseService; + _eventAggregator = eventAggregator; _logger = logger; } @@ -42,24 +49,35 @@ namespace NzbDrone.Core.Indexers { _logger.ProgressInfo("Starting RSS Sync"); - var reports = _rssFetcherAndParser.Fetch(); + var reports = _rssFetcherAndParser.Fetch().Concat(_pendingReleaseService.GetPending()).ToList(); var decisions = _downloadDecisionMaker.GetRssDecision(reports); - var downloaded = _downloadApprovedReports.DownloadApproved(decisions); + var processed = _processDownloadDecisions.ProcessDecisions(decisions); + _pendingReleaseService.RemoveGrabbed(processed.Grabbed); + _pendingReleaseService.RemoveRejected(decisions.Where(d => d.Rejected).ToList()); - _logger.ProgressInfo("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, downloaded.Count()); + var message = String.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count); - return downloaded; + if (processed.Pending.Any()) + { + message += ", Reports pending: " + processed.Pending.Count; + } + + _logger.ProgressInfo(message); + + return processed.Grabbed.Concat(processed.Pending).ToList(); } public void Execute(RssSyncCommand message) { - var downloaded = Sync(); + var processed = Sync(); if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3) { _logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value); - _episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), downloaded.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id)); + _episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), processed.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id)); } + + _eventAggregator.PublishEvent(new RssSyncCompleteEvent()); } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 598093ff7..e0e6c7263 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s - .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.QualityProfile)) + .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile)) .ThenByDescending(c => c.LocalEpisode.Size)) .SelectMany(c => c) .ToList(); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 3c96c66d5..3836c1ef0 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Parser; @@ -61,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (parsedEpisode != null) { - if (quality != null && new QualityModelComparer(parsedEpisode.Series.QualityProfile).Compare(quality, parsedEpisode.Quality) > 0) + if (quality != null && new QualityModelComparer(parsedEpisode.Series.Profile).Compare(quality, parsedEpisode.Quality) > 0) { _logger.Debug("Using quality from folder: {0}", quality); parsedEpisode.Quality = quality; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs index e8dfbeda9..3f91e7296 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs @@ -2,7 +2,6 @@ using NLog; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications { @@ -19,7 +18,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications public bool IsSatisfiedBy(LocalEpisode localEpisode) { - var qualityComparer = new QualityModelComparer(localEpisode.Series.QualityProfile); + var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile); if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) > 0)) { _logger.Debug("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index f85d0f043..0d87b71f6 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -205,6 +205,8 @@ + + @@ -222,15 +224,18 @@ + + + - + @@ -282,9 +287,14 @@ + + + + + - + @@ -311,6 +321,7 @@ + @@ -344,6 +355,7 @@ + @@ -458,6 +470,12 @@ + + + + + + @@ -562,7 +580,6 @@ - @@ -570,9 +587,7 @@ - - @@ -679,9 +694,6 @@ Code - - Code - Code @@ -705,7 +717,6 @@ - @@ -724,6 +735,7 @@ + diff --git a/src/NzbDrone.Core/Parser/Language.cs b/src/NzbDrone.Core/Parser/Language.cs index 564de13f2..a56766162 100644 --- a/src/NzbDrone.Core/Parser/Language.cs +++ b/src/NzbDrone.Core/Parser/Language.cs @@ -2,17 +2,17 @@ { public enum Language { - English = 0, - French = 1, - Spanish = 2, - German = 3, - Italian = 4, - Danish = 5, - Dutch = 6, - Japanese = 7, - Cantonese = 8, - Mandarin = 9, - Korean = 10, + Unknown = 0, + English = 1, + French = 2, + Spanish = 3, + German = 4, + Italian = 5, + Danish = 6, + Dutch = 7, + Japanese = 8, + Cantonese = 9, + Mandarin = 10, Russian = 11, Polish = 12, Vietnamese = 13, @@ -22,6 +22,7 @@ Turkish = 17, Portuguese = 18, Flemish = 19, - Greek = 20 + Greek = 20, + Korean = 21 } } diff --git a/src/NzbDrone.Core/Profiles/GrabDelayMode.cs b/src/NzbDrone.Core/Profiles/GrabDelayMode.cs new file mode 100644 index 000000000..146e68894 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/GrabDelayMode.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Profiles +{ + public enum GrabDelayMode + { + First = 0, + Cutoff = 1, + Always = 2 + } +} diff --git a/src/NzbDrone.Core/Profiles/Profile.cs b/src/NzbDrone.Core/Profiles/Profile.cs new file mode 100644 index 000000000..914f49815 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Profile.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Profiles +{ + public class Profile : ModelBase + { + public String Name { get; set; } + public Quality Cutoff { get; set; } + public List Items { get; set; } + public Language Language { get; set; } + public Int32 GrabDelay { get; set; } + public GrabDelayMode GrabDelayMode { get; set; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Profiles/ProfileInUseException.cs b/src/NzbDrone.Core/Profiles/ProfileInUseException.cs new file mode 100644 index 000000000..d55523d9a --- /dev/null +++ b/src/NzbDrone.Core/Profiles/ProfileInUseException.cs @@ -0,0 +1,13 @@ +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Profiles +{ + public class ProfileInUseException : NzbDroneException + { + public ProfileInUseException(int profileId) + : base("Profile [{0}] is in use.", profileId) + { + + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Profiles/ProfileQualityItem.cs b/src/NzbDrone.Core/Profiles/ProfileQualityItem.cs new file mode 100644 index 000000000..35c9ce360 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/ProfileQualityItem.cs @@ -0,0 +1,11 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Profiles +{ + public class ProfileQualityItem : IEmbeddedDocument + { + public Quality Quality { get; set; } + public bool Allowed { get; set; } + } +} diff --git a/src/NzbDrone.Core/Profiles/ProfileRepository.cs b/src/NzbDrone.Core/Profiles/ProfileRepository.cs new file mode 100644 index 000000000..424875617 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/ProfileRepository.cs @@ -0,0 +1,18 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Profiles +{ + public interface IProfileRepository : IBasicRepository + { + + } + + public class ProfileRepository : BasicRepository, IProfileRepository + { + public ProfileRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + } +} diff --git a/src/NzbDrone.Core/Profiles/ProfileService.cs b/src/NzbDrone.Core/Profiles/ProfileService.cs new file mode 100644 index 000000000..29fe14634 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/ProfileService.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Profiles +{ + public interface IProfileService + { + Profile Add(Profile profile); + void Update(Profile profile); + void Delete(int id); + List All(); + Profile Get(int id); + } + + public class ProfileService : IProfileService, IHandle + { + private readonly IProfileRepository _profileRepository; + private readonly ISeriesService _seriesService; + private readonly Logger _logger; + + public ProfileService(IProfileRepository profileRepository, ISeriesService seriesService, Logger logger) + { + _profileRepository = profileRepository; + _seriesService = seriesService; + _logger = logger; + } + + public Profile Add(Profile profile) + { + return _profileRepository.Insert(profile); + } + + public void Update(Profile profile) + { + _profileRepository.Update(profile); + } + + public void Delete(int id) + { + if (_seriesService.GetAllSeries().Any(c => c.ProfileId == id)) + { + throw new ProfileInUseException(id); + } + + _profileRepository.Delete(id); + } + + public List All() + { + return _profileRepository.All().ToList(); + } + + public Profile Get(int id) + { + return _profileRepository.Get(id); + } + + private Profile AddDefaultProfile(string name, Quality cutoff, params Quality[] allowed) + { + var items = Quality.DefaultQualityDefinitions + .OrderBy(v => v.Weight) + .Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }) + .ToList(); + + var profile = new Profile { Name = name, Cutoff = cutoff, Items = items, Language = Language.English }; + + return Add(profile); + } + + public void Handle(ApplicationStartedEvent message) + { + if (All().Any()) return; + + _logger.Info("Setting up default quality profiles"); + + AddDefaultProfile("SD", Quality.SDTV, + Quality.SDTV, + Quality.WEBDL480p, + Quality.DVD); + + AddDefaultProfile("HD-720p", Quality.HDTV720p, + Quality.HDTV720p, + Quality.WEBDL720p, + Quality.Bluray720p); + + AddDefaultProfile("HD-1080p", Quality.HDTV1080p, + Quality.HDTV1080p, + Quality.WEBDL1080p, + Quality.Bluray1080p); + + AddDefaultProfile("HD - All", Quality.HDTV720p, + Quality.HDTV720p, + Quality.HDTV1080p, + Quality.WEBDL720p, + Quality.WEBDL1080p, + Quality.Bluray720p, + Quality.Bluray1080p); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs index bb66dcd7f..ed392e229 100644 --- a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs +++ b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs @@ -1,28 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NLog; +using System.Collections.Generic; using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Profiles; namespace NzbDrone.Core.Qualities { public class QualityModelComparer : IComparer, IComparer { - private readonly QualityProfile _qualityProfile; + private readonly Profile _profile; - public QualityModelComparer(QualityProfile qualityProfile) + public QualityModelComparer(Profile profile) { - Ensure.That(qualityProfile, () => qualityProfile).IsNotNull(); - Ensure.That(qualityProfile.Items, () => qualityProfile.Items).HasItems(); + Ensure.That(profile, () => profile).IsNotNull(); + Ensure.That(profile.Items, () => profile.Items).HasItems(); - _qualityProfile = qualityProfile; + _profile = profile; } public int Compare(Quality left, Quality right) { - int leftIndex = _qualityProfile.Items.FindIndex(v => v.Quality == left); - int rightIndex = _qualityProfile.Items.FindIndex(v => v.Quality == right); + int leftIndex = _profile.Items.FindIndex(v => v.Quality == left); + int rightIndex = _profile.Items.FindIndex(v => v.Quality == right); return leftIndex.CompareTo(rightIndex); } diff --git a/src/NzbDrone.Core/Qualities/QualityProfile.cs b/src/NzbDrone.Core/Qualities/QualityProfile.cs deleted file mode 100644 index b578d1962..000000000 --- a/src/NzbDrone.Core/Qualities/QualityProfile.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.Datastore; - -namespace NzbDrone.Core.Qualities -{ - public class QualityProfile : ModelBase - { - public string Name { get; set; } - public Quality Cutoff { get; set; } - public List Items { get; set; } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Qualities/QualityProfileInUseException.cs b/src/NzbDrone.Core/Qualities/QualityProfileInUseException.cs deleted file mode 100644 index 24c50e7c6..000000000 --- a/src/NzbDrone.Core/Qualities/QualityProfileInUseException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using NzbDrone.Common.Exceptions; - -namespace NzbDrone.Core.Qualities -{ - public class QualityProfileInUseException : NzbDroneException - { - public QualityProfileInUseException(int profileId) - : base("QualityProfile [{0}] is in use.", profileId) - { - - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Qualities/QualityProfileItem.cs b/src/NzbDrone.Core/Qualities/QualityProfileItem.cs deleted file mode 100644 index 9d7d839d2..000000000 --- a/src/NzbDrone.Core/Qualities/QualityProfileItem.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.Datastore; - -namespace NzbDrone.Core.Qualities -{ - public class QualityProfileItem : IEmbeddedDocument - { - public Quality Quality { get; set; } - public bool Allowed { get; set; } - } -} diff --git a/src/NzbDrone.Core/Qualities/QualityProfileRepository.cs b/src/NzbDrone.Core/Qualities/QualityProfileRepository.cs deleted file mode 100644 index 408cc009e..000000000 --- a/src/NzbDrone.Core/Qualities/QualityProfileRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Messaging.Events; - - -namespace NzbDrone.Core.Qualities -{ - public interface IQualityProfileRepository : IBasicRepository - { - - } - - public class QualityProfileRepository : BasicRepository, IQualityProfileRepository - { - public QualityProfileRepository(IDatabase database, IEventAggregator eventAggregator) - : base(database, eventAggregator) - { - } - } -} diff --git a/src/NzbDrone.Core/Qualities/QualityProfileService.cs b/src/NzbDrone.Core/Qualities/QualityProfileService.cs deleted file mode 100644 index 13b18afad..000000000 --- a/src/NzbDrone.Core/Qualities/QualityProfileService.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Tv; - - -namespace NzbDrone.Core.Qualities -{ - public interface IQualityProfileService - { - QualityProfile Add(QualityProfile profile); - void Update(QualityProfile profile); - void Delete(int id); - List All(); - QualityProfile Get(int id); - } - - public class QualityProfileService : IQualityProfileService, IHandle - { - private readonly IQualityProfileRepository _qualityProfileRepository; - private readonly ISeriesService _seriesService; - private readonly Logger _logger; - - public QualityProfileService(IQualityProfileRepository qualityProfileRepository, ISeriesService seriesService, Logger logger) - { - _qualityProfileRepository = qualityProfileRepository; - _seriesService = seriesService; - _logger = logger; - } - - public QualityProfile Add(QualityProfile profile) - { - return _qualityProfileRepository.Insert(profile); - } - - public void Update(QualityProfile profile) - { - _qualityProfileRepository.Update(profile); - } - - public void Delete(int id) - { - if (_seriesService.GetAllSeries().Any(c => c.QualityProfileId == id)) - { - throw new QualityProfileInUseException(id); - } - - _qualityProfileRepository.Delete(id); - } - - public List All() - { - return _qualityProfileRepository.All().ToList(); - } - - public QualityProfile Get(int id) - { - return _qualityProfileRepository.Get(id); - } - - private QualityProfile AddDefaultQualityProfile(string name, Quality cutoff, params Quality[] allowed) - { - var items = Quality.DefaultQualityDefinitions - .OrderBy(v => v.Weight) - .Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }) - .ToList(); - - var qualityProfile = new QualityProfile { Name = name, Cutoff = cutoff, Items = items }; - - return Add(qualityProfile); - } - - public void Handle(ApplicationStartedEvent message) - { - if (All().Any()) return; - - _logger.Info("Setting up default quality profiles"); - - AddDefaultQualityProfile("SD", Quality.SDTV, - Quality.SDTV, - Quality.WEBDL480p, - Quality.DVD); - - AddDefaultQualityProfile("HD-720p", Quality.HDTV720p, - Quality.HDTV720p, - Quality.WEBDL720p, - Quality.Bluray720p); - - AddDefaultQualityProfile("HD-1080p", Quality.HDTV1080p, - Quality.HDTV1080p, - Quality.WEBDL1080p, - Quality.Bluray1080p); - - AddDefaultQualityProfile("HD - All", Quality.HDTV720p, - Quality.HDTV720p, - Quality.HDTV1080p, - Quality.WEBDL720p, - Quality.WEBDL1080p, - Quality.Bluray720p, - Quality.Bluray1080p); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index 20b57db4a..f7dac3139 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Collections.Generic; using NLog; using NzbDrone.Core.Download; @@ -39,19 +38,21 @@ namespace NzbDrone.Core.Queue { foreach (var episode in queueItem.DownloadItem.RemoteEpisode.Episodes) { - var queue = new Queue(); - queue.Id = queueItem.DownloadItem.DownloadClientId.GetHashCode() + episode.Id; - queue.Series = queueItem.DownloadItem.RemoteEpisode.Series; - queue.Episode = episode; - queue.Quality = queueItem.DownloadItem.RemoteEpisode.ParsedEpisodeInfo.Quality; - queue.Title = queueItem.DownloadItem.Title; - queue.Size = queueItem.DownloadItem.TotalSize; - queue.Sizeleft = queueItem.DownloadItem.RemainingSize; - queue.Timeleft = queueItem.DownloadItem.RemainingTime; - queue.Status = queueItem.DownloadItem.Status.ToString(); - queue.RemoteEpisode = queueItem.DownloadItem.RemoteEpisode; + var queue = new Queue + { + Id = episode.Id ^ (queueItem.DownloadItem.DownloadClientId.GetHashCode().GetHashCode() << 16), + Series = queueItem.DownloadItem.RemoteEpisode.Series, + Episode = episode, + Quality = queueItem.DownloadItem.RemoteEpisode.ParsedEpisodeInfo.Quality, + Title = queueItem.DownloadItem.Title, + Size = queueItem.DownloadItem.TotalSize, + Sizeleft = queueItem.DownloadItem.RemainingSize, + Timeleft = queueItem.DownloadItem.RemainingTime, + Status = queueItem.DownloadItem.Status.ToString(), + RemoteEpisode = queueItem.DownloadItem.RemoteEpisode + }; - if (queueItem.HasError) + if (queueItem.HasError) { queue.ErrorMessage = queueItem.StatusMessage; } diff --git a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs index f88dfc02e..6747aa87e 100644 --- a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs @@ -2,6 +2,7 @@ using System.Linq; using NLog; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Tv @@ -14,30 +15,30 @@ namespace NzbDrone.Core.Tv public class EpisodeCutoffService : IEpisodeCutoffService { private readonly IEpisodeRepository _episodeRepository; - private readonly IQualityProfileService _qualityProfileService; + private readonly IProfileService _profileService; private readonly Logger _logger; - public EpisodeCutoffService(IEpisodeRepository episodeRepository, IQualityProfileService qualityProfileService, Logger logger) + public EpisodeCutoffService(IEpisodeRepository episodeRepository, IProfileService profileService, Logger logger) { _episodeRepository = episodeRepository; - _qualityProfileService = qualityProfileService; + _profileService = profileService; _logger = logger; } public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec) { var qualitiesBelowCutoff = new List(); - var qualityProfiles = _qualityProfileService.All(); + var profiles = _profileService.All(); //Get all items less than the cutoff - foreach (var qualityProfile in qualityProfiles) + foreach (var profile in profiles) { - var cutoffIndex = qualityProfile.Items.FindIndex(v => v.Quality == qualityProfile.Cutoff); - var belowCutoff = qualityProfile.Items.Take(cutoffIndex).ToList(); + var cutoffIndex = profile.Items.FindIndex(v => v.Quality == profile.Cutoff); + var belowCutoff = profile.Items.Take(cutoffIndex).ToList(); if (belowCutoff.Any()) { - qualitiesBelowCutoff.Add(new QualitiesBelowCutoff(qualityProfile.Id, belowCutoff.Select(i => i.Quality.Id))); + qualitiesBelowCutoff.Add(new QualitiesBelowCutoff(profile.Id, belowCutoff.Select(i => i.Quality.Id))); } } diff --git a/src/NzbDrone.Core/Tv/EpisodeRepository.cs b/src/NzbDrone.Core/Tv/EpisodeRepository.cs index 5ebb8c71d..65f92bf8b 100644 --- a/src/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/src/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -224,7 +224,7 @@ namespace NzbDrone.Core.Tv { foreach (var belowCutoff in profile.QualityIds) { - clauses.Add(String.Format("([t1].[QualityProfileId] = {0} AND [t2].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); + clauses.Add(String.Format("([t1].[ProfileId] = {0} AND [t2].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); } } diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index 2d81a305b..7baf9fb80 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Marr.Data; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Qualities; +using NzbDrone.Core.Profiles; using NzbDrone.Common; @@ -27,7 +27,7 @@ namespace NzbDrone.Core.Tv public string Overview { get; set; } public String AirTime { get; set; } public bool Monitored { get; set; } - public int QualityProfileId { get; set; } + public int ProfileId { get; set; } public bool SeasonFolder { get; set; } public DateTime? LastInfoSync { get; set; } public int Runtime { get; set; } @@ -46,7 +46,7 @@ namespace NzbDrone.Core.Tv public string RootFolderPath { get; set; } public DateTime? FirstAired { get; set; } - public LazyLoaded QualityProfile { get; set; } + public LazyLoaded Profile { get; set; } public List Seasons { get; set; } diff --git a/src/NzbDrone.Core/Validation/LangaugeValidator.cs b/src/NzbDrone.Core/Validation/LangaugeValidator.cs new file mode 100644 index 000000000..678245f36 --- /dev/null +++ b/src/NzbDrone.Core/Validation/LangaugeValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation.Validators; + +namespace NzbDrone.Core.Validation +{ + public class LangaugeValidator : PropertyValidator + { + public LangaugeValidator() + : base("Unknown Language") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return false; + + if ((int) context.PropertyValue == 0) return false; + + return true; + } + } +} diff --git a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs index 9bcc7e18a..39a3826d3 100644 --- a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs +++ b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Validators; +using NzbDrone.Core.Parser; namespace NzbDrone.Core.Validation { @@ -31,5 +32,10 @@ namespace NzbDrone.Core.Validation { return ruleBuilder.SetValidator(new InclusiveBetweenValidator(0, 65535)); } + + public static IRuleBuilderOptions ValidLanguage(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.SetValidator(new LangaugeValidator()); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs b/src/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs index 610d6f8bf..39f267679 100644 --- a/src/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs +++ b/src/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Integration.Test { var newSeries = Series.Lookup("archer").First(); - newSeries.QualityProfileId = 1; + newSeries.ProfileId = 1; newSeries.Path = @"C:\Test\Archer".AsOsAgnostic(); newSeries = Series.Post(newSeries); diff --git a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj index 837beaf72..eb1ca6f15 100644 --- a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj +++ b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj @@ -104,7 +104,6 @@ - diff --git a/src/NzbDrone.Integration.Test/QualityProfileIntegrationTest.cs b/src/NzbDrone.Integration.Test/QualityProfileIntegrationTest.cs deleted file mode 100644 index 2fff89a73..000000000 --- a/src/NzbDrone.Integration.Test/QualityProfileIntegrationTest.cs +++ /dev/null @@ -1,10 +0,0 @@ -using NUnit.Framework; - -namespace NzbDrone.Integration.Test -{ - [TestFixture] - public class QualityProfileIntegrationTest : IntegrationTest - { - - } -} \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs b/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs index c0634ea0c..3667a2247 100644 --- a/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Integration.Test { var newSeries = Series.Lookup(title).First(); - newSeries.QualityProfileId = 1; + newSeries.ProfileId = 1; newSeries.Path = String.Format(@"C:\Test\{0}", title).AsOsAgnostic(); Series.Post(newSeries); @@ -33,13 +33,13 @@ namespace NzbDrone.Integration.Test foreach (var s in series) { - s.QualityProfileId = 2; + s.ProfileId = 2; } var result = Series.Editor(series); result.Should().HaveCount(2); - result.TrueForAll(s => s.QualityProfileId == 2).Should().BeTrue(); + result.TrueForAll(s => s.ProfileId == 2).Should().BeTrue(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs b/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs index eef706a78..9700b898f 100644 --- a/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs @@ -31,7 +31,7 @@ namespace NzbDrone.Integration.Test { var series = Series.Lookup("archer").First(); - series.QualityProfileId = 1; + series.ProfileId = 1; series.Path = @"C:\Test\Archer".AsOsAgnostic(); series = Series.Post(series); @@ -50,7 +50,7 @@ namespace NzbDrone.Integration.Test { var series = Series.Lookup("90210").First(); - series.QualityProfileId = 1; + series.ProfileId = 1; series.Path = @"C:\Test\90210".AsOsAgnostic(); series = Series.Post(series); diff --git a/src/UI/AddSeries/AddSeriesLayout.js b/src/UI/AddSeries/AddSeriesLayout.js index b51245d3a..52e29e070 100644 --- a/src/UI/AddSeries/AddSeriesLayout.js +++ b/src/UI/AddSeries/AddSeriesLayout.js @@ -7,7 +7,7 @@ define( 'AddSeries/RootFolders/RootFolderLayout', 'AddSeries/Existing/AddExistingSeriesCollectionView', 'AddSeries/AddSeriesView', - 'Quality/QualityProfileCollection', + 'Profile/ProfileCollection', 'AddSeries/RootFolders/RootFolderCollection', 'Series/SeriesCollection' ], function (vent, @@ -16,7 +16,7 @@ define( RootFolderLayout, ExistingSeriesCollectionView, AddSeriesView, - QualityProfileCollection, + ProfileCollection, RootFolderCollection) { return Marionette.Layout.extend({ @@ -36,7 +36,7 @@ define( }, initialize: function () { - QualityProfileCollection.fetch(); + ProfileCollection.fetch(); RootFolderCollection.fetch() .done(function () { RootFolderCollection.synced = true; diff --git a/src/UI/AddSeries/SearchResultView.js b/src/UI/AddSeries/SearchResultView.js index cb9e80612..685182b5d 100644 --- a/src/UI/AddSeries/SearchResultView.js +++ b/src/UI/AddSeries/SearchResultView.js @@ -6,7 +6,7 @@ define( 'AppLayout', 'backbone', 'marionette', - 'Quality/QualityProfileCollection', + 'Profile/ProfileCollection', 'AddSeries/RootFolders/RootFolderCollection', 'AddSeries/RootFolders/RootFolderLayout', 'Series/SeriesCollection', @@ -19,7 +19,7 @@ define( AppLayout, Backbone, Marionette, - QualityProfiles, + Profiles, RootFolders, RootFolderLayout, SeriesCollection, @@ -32,7 +32,7 @@ define( template: 'AddSeries/SearchResultViewTemplate', ui: { - qualityProfile : '.x-quality-profile', + profile : '.x-profile', rootFolder : '.x-root-folder', seasonFolder : '.x-season-folder', seriesType : '.x-series-type', @@ -42,11 +42,11 @@ define( }, events: { - 'click .x-add' : '_addSeries', - 'change .x-quality-profile' : '_qualityProfileChanged', - 'change .x-root-folder' : '_rootFolderChanged', - 'change .x-season-folder' : '_seasonFolderChanged', - 'change .x-series-type' : '_seriesTypeChanged' + 'click .x-add' : '_addSeries', + 'change .x-profile' : '_profileChanged', + 'change .x-root-folder' : '_rootFolderChanged', + 'change .x-season-folder' : '_seasonFolderChanged', + 'change .x-series-type' : '_seriesTypeChanged' }, initialize: function () { @@ -65,13 +65,13 @@ define( onRender: function () { - var defaultQuality = Config.getValue(Config.Keys.DefaultQualityProfileId); + var defaultProfile = Config.getValue(Config.Keys.DefaultProfileId); var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId); var useSeasonFolder = Config.getValueBoolean(Config.Keys.UseSeasonFolder, true); var defaultSeriesType = Config.getValueBoolean(Config.Keys.DefaultSeriesType, true); - if (QualityProfiles.get(defaultQuality)) { - this.ui.qualityProfile.val(defaultQuality); + if (Profiles.get(defaultProfile)) { + this.ui.profile.val(defaultProfile); } if (RootFolders.get(defaultRoot)) { @@ -101,7 +101,7 @@ define( this.templateHelpers.existing = existingSeries[0].toJSON(); } - this.templateHelpers.qualityProfiles = QualityProfiles.toJSON(); + this.templateHelpers.profiles = Profiles.toJSON(); if (!this.model.get('isExisting')) { this.templateHelpers.rootFolders = RootFolders.toJSON(); @@ -109,8 +109,8 @@ define( }, _onConfigUpdated: function (options) { - if (options.key === Config.Keys.DefaultQualityProfileId) { - this.ui.qualityProfile.val(options.value); + if (options.key === Config.Keys.DefaultProfileId) { + this.ui.profile.val(options.value); } else if (options.key === Config.Keys.DefaultRootFolderId) { @@ -126,8 +126,8 @@ define( } }, - _qualityProfileChanged: function () { - Config.setValue(Config.Keys.DefaultQualityProfileId, this.ui.qualityProfile.val()); + _profileChanged: function () { + Config.setValue(Config.Keys.DefaultProfileId, this.ui.profile.val()); }, _seasonFolderChanged: function () { @@ -160,14 +160,14 @@ define( var icon = this.ui.addButton.find('icon'); icon.removeClass('icon-plus').addClass('icon-spin icon-spinner disabled'); - var quality = this.ui.qualityProfile.val(); + var profile = this.ui.profile.val(); var rootFolderPath = this.ui.rootFolder.children(':selected').text(); var startingSeason = this.ui.startingSeason.val(); var seriesType = this.ui.seriesType.val(); var seasonFolder = this.ui.seasonFolder.prop('checked'); this.model.set({ - qualityProfileId: quality, + profileId: profile, rootFolderPath: rootFolderPath, seasonFolder: seasonFolder, seriesType: seriesType diff --git a/src/UI/AddSeries/SearchResultViewTemplate.html b/src/UI/AddSeries/SearchResultViewTemplate.html index bb56b167d..b60ebcb62 100644 --- a/src/UI/AddSeries/SearchResultViewTemplate.html +++ b/src/UI/AddSeries/SearchResultViewTemplate.html @@ -40,8 +40,8 @@
- - {{> QualityProfileSelectionPartial qualityProfiles}} + + {{> ProfileSelectionPartial profiles}}
diff --git a/src/UI/Cells/Edit/QualityCellEditor.js b/src/UI/Cells/Edit/QualityCellEditor.js index e676df24a..475f0108d 100644 --- a/src/UI/Cells/Edit/QualityCellEditor.js +++ b/src/UI/Cells/Edit/QualityCellEditor.js @@ -4,8 +4,8 @@ define( 'backgrid', 'marionette', 'underscore', - 'Settings/Quality/Profile/QualityProfileSchemaCollection', - ], function (Backgrid, Marionette, _, QualityProfileSchemaCollection) { + 'Settings/Profile/ProfileSchemaCollection' + ], function (Backgrid, Marionette, _, ProfileSchemaCollection) { return Backgrid.CellEditor.extend({ className: 'quality-cell-editor', @@ -21,12 +21,12 @@ define( render: function () { var self = this; - var qualityProfileSchemaCollection = new QualityProfileSchemaCollection(); - var promise = qualityProfileSchemaCollection.fetch(); + var profileSchemaCollection = new ProfileSchemaCollection(); + var promise = profileSchemaCollection.fetch(); promise.done(function () { var templateName = self.template; - self.schema = qualityProfileSchemaCollection.first(); + self.schema = profileSchemaCollection.first(); var selected = _.find(self.schema.get('items'), function (model) { return model.quality.id === self.model.get(self.column.get('name')).quality.id; @@ -50,13 +50,13 @@ define( var column = this.column; var selected = parseInt(this.$el.val(), 10); - var qualityProfileItem = _.find(this.schema.get('items'), function(model) { + var profileItem = _.find(this.schema.get('items'), function(model) { return model.quality.id === selected; }); var newQuality = { proper : false, - quality: qualityProfileItem.quality + quality: profileItem.quality }; model.set(column.get('name'), newQuality); diff --git a/src/UI/Cells/QualityProfileCell.js b/src/UI/Cells/ProfileCell.js similarity index 51% rename from src/UI/Cells/QualityProfileCell.js rename to src/UI/Cells/ProfileCell.js index 7cd6efd88..d63cb9701 100644 --- a/src/UI/Cells/QualityProfileCell.js +++ b/src/UI/Cells/ProfileCell.js @@ -2,18 +2,18 @@ define( [ 'backgrid', - 'Quality/QualityProfileCollection', + 'Profile/ProfileCollection', 'underscore' - ], function (Backgrid, QualityProfileCollection,_) { + ], function (Backgrid, ProfileCollection,_) { return Backgrid.Cell.extend({ - className: 'quality-profile-cell', + className: 'profile-cell', render: function () { this.$el.empty(); - var qualityProfileId = this.model.get(this.column.get('name')); + var profileId = this.model.get(this.column.get('name')); - var profile = _.findWhere(QualityProfileCollection.models, { id: qualityProfileId }); + var profile = _.findWhere(ProfileCollection.models, { id: profileId }); if (profile) { this.$el.html(profile.get('name')); diff --git a/src/UI/Config.js b/src/UI/Config.js index 245ac59ae..7a0b03862 100644 --- a/src/UI/Config.js +++ b/src/UI/Config.js @@ -8,7 +8,7 @@ define( ConfigUpdatedEvent: 'ConfigUpdatedEvent' }, Keys : { - DefaultQualityProfileId : 'DefaultQualityProfileId', + DefaultProfileId : 'DefaultProfileId', DefaultRootFolderId : 'DefaultRootFolderId', UseSeasonFolder : 'UseSeasonFolder', DefaultSeriesType : 'DefaultSeriesType', diff --git a/src/UI/Episode/Summary/EpisodeSummaryLayoutTemplate.html b/src/UI/Episode/Summary/EpisodeSummaryLayoutTemplate.html index 1263ad98e..87e4b838b 100644 --- a/src/UI/Episode/Summary/EpisodeSummaryLayoutTemplate.html +++ b/src/UI/Episode/Summary/EpisodeSummaryLayoutTemplate.html @@ -1,6 +1,6 @@ 
{{#with series}} - {{qualityProfile qualityProfileId}} + {{profile profileId}} {{network}} {{/with}} {{StartTime airDateUtc}} diff --git a/src/UI/Handlebars/Helpers/Quality.js b/src/UI/Handlebars/Helpers/Quality.js index 292c04937..6d9a579cd 100644 --- a/src/UI/Handlebars/Helpers/Quality.js +++ b/src/UI/Handlebars/Helpers/Quality.js @@ -2,15 +2,15 @@ define( [ 'handlebars', - 'Quality/QualityProfileCollection' - ], function (Handlebars, QualityProfileCollection) { + 'Profile/ProfileCollection' + ], function (Handlebars, ProfileCollection) { - Handlebars.registerHelper('qualityProfile', function (profileId) { + Handlebars.registerHelper('profile', function (profileId) { - var profile = QualityProfileCollection.get(profileId); + var profile = ProfileCollection.get(profileId); if (profile) { - return new Handlebars.SafeString('' + profile.get('name') + ''); + return new Handlebars.SafeString('' + profile.get("name") + ''); } return undefined; diff --git a/src/UI/Handlebars/Helpers/String.js b/src/UI/Handlebars/Helpers/String.js new file mode 100644 index 000000000..93b5715fc --- /dev/null +++ b/src/UI/Handlebars/Helpers/String.js @@ -0,0 +1,9 @@ +'use strict'; +define( + [ + 'handlebars' + ], function (Handlebars) { + Handlebars.registerHelper('TitleCase', function (input) { + return new Handlebars.SafeString(input.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();})); + }); + }); diff --git a/src/UI/Handlebars/backbone.marionette.templates.js b/src/UI/Handlebars/backbone.marionette.templates.js index 8778df684..69d72c4ed 100644 --- a/src/UI/Handlebars/backbone.marionette.templates.js +++ b/src/UI/Handlebars/backbone.marionette.templates.js @@ -11,7 +11,7 @@ define( 'Handlebars/Helpers/Quality', 'Handlebars/Helpers/System', 'Handlebars/Helpers/EachReverse', - 'Handlebars/Helpers/EachReverse', + 'Handlebars/Helpers/String', 'Handlebars/Handlebars.Debug' ], function (Templates) { return function () { diff --git a/src/UI/History/Queue/QueueStatusCell.js b/src/UI/History/Queue/QueueStatusCell.js index 580d904ce..2fda4b6ea 100644 --- a/src/UI/History/Queue/QueueStatusCell.js +++ b/src/UI/History/Queue/QueueStatusCell.js @@ -31,6 +31,11 @@ define( icon = 'icon-inbox'; title = 'Downloaded'; } + + if (status === 'pending') { + icon = 'icon-time'; + title = 'Pending'; + } if (errorMessage !== '') { if (status === 'completed') { diff --git a/src/UI/History/Queue/TimeleftCell.js b/src/UI/History/Queue/TimeleftCell.js index cea11f12f..043efdf52 100644 --- a/src/UI/History/Queue/TimeleftCell.js +++ b/src/UI/History/Queue/TimeleftCell.js @@ -14,12 +14,21 @@ define( if (this.cellValue) { + //If the release is pending we want to use the timeleft as the time it will be processed at + if (this.cellValue.get('status').toLowerCase() === 'pending') { + this.$el.html('-'); + this.$el.attr('title', 'Will be processed again in: {0}'.format(this.cellValue.get('timeleft'))); + this.$el.attr('data-container', 'body'); + + return this; + } + var timeleft = this.cellValue.get('timeleft'); var totalSize = fileSize(this.cellValue.get('size'), 1, false); var remainingSize = fileSize(this.cellValue.get('sizeleft'), 1, false); if (timeleft === undefined) { - this.$el.html("-"); + this.$el.html('-'); } else { this.$el.html('{0}'.format(timeleft, remainingSize, totalSize)); diff --git a/src/UI/Profile/ProfileCollection.js b/src/UI/Profile/ProfileCollection.js new file mode 100644 index 000000000..2b0844cd1 --- /dev/null +++ b/src/UI/Profile/ProfileCollection.js @@ -0,0 +1,18 @@ +'use strict'; +define( + [ + 'backbone', + 'Profile/ProfileModel' + ], function (Backbone, ProfileModel) { + + var ProfileCollection = Backbone.Collection.extend({ + model: ProfileModel, + url : window.NzbDrone.ApiRoot + '/profile' + }); + + var profiles = new ProfileCollection(); + + profiles.fetch(); + + return profiles; + }); diff --git a/src/UI/Quality/QualityProfileModel.js b/src/UI/Profile/ProfileModel.js similarity index 100% rename from src/UI/Quality/QualityProfileModel.js rename to src/UI/Profile/ProfileModel.js diff --git a/src/UI/Quality/QualityProfileSelectionPartial.html b/src/UI/Profile/ProfileSelectionPartial.html similarity index 53% rename from src/UI/Quality/QualityProfileSelectionPartial.html rename to src/UI/Profile/ProfileSelectionPartial.html index 688d1b276..6d979b88a 100644 --- a/src/UI/Quality/QualityProfileSelectionPartial.html +++ b/src/UI/Profile/ProfileSelectionPartial.html @@ -1,4 +1,4 @@ - {{#each this}} {{/each}} diff --git a/src/UI/Quality/QualityProfileCollection.js b/src/UI/Quality/QualityProfileCollection.js deleted file mode 100644 index f9a33b99e..000000000 --- a/src/UI/Quality/QualityProfileCollection.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; -define( - [ - 'backbone', - 'Quality/QualityProfileModel' - ], function (Backbone, QualityProfileModel) { - - var QualityProfileCollection = Backbone.Collection.extend({ - model: QualityProfileModel, - url : window.NzbDrone.ApiRoot + '/qualityprofile' - }); - - var profiles = new QualityProfileCollection(); - - profiles.fetch(); - - return profiles; - }); diff --git a/src/UI/Series/Details/InfoViewTemplate.html b/src/UI/Series/Details/InfoViewTemplate.html index 5c962352c..9a5f81071 100644 --- a/src/UI/Series/Details/InfoViewTemplate.html +++ b/src/UI/Series/Details/InfoViewTemplate.html @@ -1,6 +1,6 @@ 
- {{qualityProfile qualityProfileId}} + {{profile profileId}} {{network}} {{runtime}} minutes {{path}} diff --git a/src/UI/Series/Edit/EditSeriesView.js b/src/UI/Series/Edit/EditSeriesView.js index 76801c137..18522f6bd 100644 --- a/src/UI/Series/Edit/EditSeriesView.js +++ b/src/UI/Series/Edit/EditSeriesView.js @@ -3,17 +3,17 @@ define( [ 'vent', 'marionette', - 'Quality/QualityProfileCollection', + 'Profile/ProfileCollection', 'Mixins/AsModelBoundView', 'Mixins/AsValidatedView', 'Mixins/AutoComplete' - ], function (vent, Marionette, QualityProfiles, AsModelBoundView, AsValidatedView) { + ], function (vent, Marionette, Profiles, AsModelBoundView, AsValidatedView) { var view = Marionette.ItemView.extend({ template: 'Series/Edit/EditSeriesViewTemplate', ui: { - qualityProfile: '.x-quality-profile', + profile: '.x-profile', path : '.x-path' }, @@ -24,14 +24,14 @@ define( initialize: function () { - this.model.set('qualityProfiles', QualityProfiles); + this.model.set('profiles', Profiles); }, _saveSeries: function () { var self = this; - var qualityProfileId = this.ui.qualityProfile.val(); - this.model.set({ qualityProfileId: qualityProfileId}); + var profileId = this.ui.profile.val(); + this.model.set({ profileId: profileId}); this.model.save().done(function () { self.trigger('saved'); diff --git a/src/UI/Series/Edit/EditSeriesViewTemplate.html b/src/UI/Series/Edit/EditSeriesViewTemplate.html index d619ecbc5..4b8d3f3cd 100644 --- a/src/UI/Series/Edit/EditSeriesViewTemplate.html +++ b/src/UI/Series/Edit/EditSeriesViewTemplate.html @@ -58,11 +58,11 @@
- +
- + {{#each profiles.models}} {{/each}} diff --git a/src/UI/Series/Editor/SeriesEditorFooterView.js b/src/UI/Series/Editor/SeriesEditorFooterView.js index a111be913..d37e4e485 100644 --- a/src/UI/Series/Editor/SeriesEditorFooterView.js +++ b/src/UI/Series/Editor/SeriesEditorFooterView.js @@ -5,7 +5,7 @@ define( 'marionette', 'backgrid', 'vent', - 'Quality/QualityProfileCollection', + 'Profile/ProfileCollection', 'AddSeries/RootFolders/RootFolderCollection', 'Shared/Toolbar/ToolbarLayout', 'AddSeries/RootFolders/RootFolderLayout', @@ -15,7 +15,7 @@ define( Marionette, Backgrid, vent, - QualityProfiles, + Profiles, RootFolders, ToolbarLayout, RootFolderLayout, @@ -26,7 +26,7 @@ define( ui: { monitored : '.x-monitored', - qualityProfile : '.x-quality-profiles', + profile : '.x-profiles', seasonFolder : '.x-season-folder', rootFolder : '.x-root-folder', selectedCount : '.x-selected-count', @@ -43,8 +43,8 @@ define( templateHelpers: function () { return { - qualityProfiles: QualityProfiles, - rootFolders : RootFolders.toJSON() + profiles : Profiles, + rootFolders: RootFolders.toJSON() }; }, @@ -68,7 +68,7 @@ define( var selected = this.editorGrid.getSelectedModels(); var monitored = this.ui.monitored.val(); - var profile = this.ui.qualityProfile.val(); + var profile = this.ui.profile.val(); var seasonFolder = this.ui.seasonFolder.val(); var rootFolder = this.ui.rootFolder.val(); @@ -82,7 +82,7 @@ define( } if (profile !== 'noChange') { - model.set('qualityProfileId', parseInt(profile, 10)); + model.set('profileId', parseInt(profile, 10)); } if (seasonFolder === 'true') { @@ -113,7 +113,7 @@ define( if (selectedCount === 0) { this.ui.monitored.attr('disabled', ''); - this.ui.qualityProfile.attr('disabled', ''); + this.ui.profile.attr('disabled', ''); this.ui.seasonFolder.attr('disabled', ''); this.ui.rootFolder.attr('disabled', ''); this.ui.saveButton.attr('disabled', ''); @@ -122,7 +122,7 @@ define( else { this.ui.monitored.removeAttr('disabled', ''); - this.ui.qualityProfile.removeAttr('disabled', ''); + this.ui.profile.removeAttr('disabled', ''); this.ui.seasonFolder.removeAttr('disabled', ''); this.ui.rootFolder.removeAttr('disabled', ''); this.ui.saveButton.removeAttr('disabled', ''); diff --git a/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html b/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html index 3ff399d6a..8d30bb969 100644 --- a/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html +++ b/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html @@ -21,11 +21,11 @@
- + - - {{#each qualityProfiles.models}} + {{#each profiles.models}} {{/each}} diff --git a/src/UI/Series/Editor/SeriesEditorLayout.js b/src/UI/Series/Editor/SeriesEditorLayout.js index 1983444fb..38257a593 100644 --- a/src/UI/Series/Editor/SeriesEditorLayout.js +++ b/src/UI/Series/Editor/SeriesEditorLayout.js @@ -7,7 +7,7 @@ define( 'Series/Index/EmptyView', 'Series/SeriesCollection', 'Cells/SeriesTitleCell', - 'Cells/QualityProfileCell', + 'Cells/ProfileCell', 'Cells/SeriesStatusCell', 'Cells/SeasonFolderCell', 'Shared/Toolbar/ToolbarLayout', @@ -19,7 +19,7 @@ define( EmptyView, SeriesCollection, SeriesTitleCell, - QualityProfileCell, + ProfileCell, SeriesStatusCell, SeasonFolderCell, ToolbarLayout, @@ -33,10 +33,10 @@ define( }, ui: { - monitored : '.x-monitored', - qualityProfiles: '.x-quality-profiles', - rootFolder : '.x-root-folder', - selectedCount : '.x-selected-count' + monitored : '.x-monitored', + profiles : '.x-profiles', + rootFolder : '.x-root-folder', + selectedCount : '.x-selected-count' }, events: { @@ -47,36 +47,36 @@ define( columns: [ { - name : '', - cell : 'select-row', - headerCell: 'select-all', - sortable : false + name : '', + cell : 'select-row', + headerCell : 'select-all', + sortable : false }, { - name : 'statusWeight', - label : '', - cell : SeriesStatusCell + name : 'statusWeight', + label : '', + cell : SeriesStatusCell }, { - name : 'title', - label : 'Title', - cell : SeriesTitleCell, - cellValue : 'this' + name : 'title', + label : 'Title', + cell : SeriesTitleCell, + cellValue : 'this' }, { - name : 'qualityProfileId', - label : 'Quality', - cell : QualityProfileCell + name : 'profileId', + label : 'Profile', + cell : ProfileCell }, { - name : 'seasonFolder', - label : 'Season Folder', - cell : SeasonFolderCell + name : 'seasonFolder', + label : 'Season Folder', + cell : SeasonFolderCell }, { - name : 'path', - label : 'Path', - cell : 'string' + name : 'path', + label : 'Path', + cell : 'string' } ], diff --git a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.html b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.html index 3b9ddf3c1..1eb317ace 100644 --- a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.html +++ b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.html @@ -45,7 +45,7 @@ {{seasonCountHelper}} - {{qualityProfile qualityProfileId}} + {{profile profileId}}
{{> EpisodeProgressPartial }} diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js index f0f773d58..d66c79bdf 100644 --- a/src/UI/Series/Index/SeriesIndexLayout.js +++ b/src/UI/Series/Index/SeriesIndexLayout.js @@ -11,7 +11,7 @@ define( 'Cells/RelativeDateCell', 'Cells/SeriesTitleCell', 'Cells/TemplatedCell', - 'Cells/QualityProfileCell', + 'Cells/ProfileCell', 'Cells/EpisodeProgressCell', 'Cells/SeriesActionsCell', 'Cells/SeriesStatusCell', @@ -29,7 +29,7 @@ define( RelativeDateCell, SeriesTitleCell, TemplatedCell, - QualityProfileCell, + ProfileCell, EpisodeProgressCell, SeriesActionsCell, SeriesStatusCell, @@ -65,9 +65,9 @@ define( cell : 'integer' }, { - name : 'qualityProfileId', - label : 'Quality', - cell : QualityProfileCell + name : 'profileId', + label: 'Profile', + cell : ProfileCell }, { name : 'network', @@ -165,7 +165,7 @@ define( }, { title: 'Quality', - name : 'qualityProfileId' + name : 'profileId' }, { title: 'Network', diff --git a/src/UI/Settings/Quality/Profile/AllowedLabeler.js b/src/UI/Settings/Profile/AllowedLabeler.js similarity index 99% rename from src/UI/Settings/Quality/Profile/AllowedLabeler.js rename to src/UI/Settings/Profile/AllowedLabeler.js index dbad67498..667859345 100644 --- a/src/UI/Settings/Quality/Profile/AllowedLabeler.js +++ b/src/UI/Settings/Profile/AllowedLabeler.js @@ -7,6 +7,7 @@ define( Handlebars.registerHelper('allowedLabeler', function () { var ret = ''; var cutoff = this.cutoff; + _.each(this.items, function (item) { if (item.allowed) { if (item.quality.id === cutoff.id) { diff --git a/src/UI/Settings/Quality/Profile/DeleteQualityProfileView.js b/src/UI/Settings/Profile/DeleteProfileView.js similarity index 86% rename from src/UI/Settings/Quality/Profile/DeleteQualityProfileView.js rename to src/UI/Settings/Profile/DeleteProfileView.js index 0aee00971..e685132f5 100644 --- a/src/UI/Settings/Quality/Profile/DeleteQualityProfileView.js +++ b/src/UI/Settings/Profile/DeleteProfileView.js @@ -6,7 +6,7 @@ define( ], function (vent, Marionette) { return Marionette.ItemView.extend({ - template: 'Settings/Quality/Profile/DeleteQualityProfileViewTemplate', + template: 'Settings/Profile/DeleteProfileViewTemplate', events: { 'click .x-confirm-delete': '_removeProfile' diff --git a/src/UI/Settings/Quality/Profile/DeleteQualityProfileViewTemplate.html b/src/UI/Settings/Profile/DeleteProfileViewTemplate.html similarity index 100% rename from src/UI/Settings/Quality/Profile/DeleteQualityProfileViewTemplate.html rename to src/UI/Settings/Profile/DeleteProfileViewTemplate.html diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js b/src/UI/Settings/Profile/Edit/EditProfileItemView.js similarity index 61% rename from src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js rename to src/UI/Settings/Profile/Edit/EditProfileItemView.js index dbd101a84..5898f6649 100644 --- a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js +++ b/src/UI/Settings/Profile/Edit/EditProfileItemView.js @@ -4,6 +4,6 @@ define( 'marionette' ], function (Marionette) { return Marionette.ItemView.extend({ - template : 'Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate' + template : 'Settings/Profile/Edit/EditProfileItemViewTemplate' }); }); diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html b/src/UI/Settings/Profile/Edit/EditProfileItemViewTemplate.html similarity index 100% rename from src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html rename to src/UI/Settings/Profile/Edit/EditProfileItemViewTemplate.html diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js b/src/UI/Settings/Profile/Edit/EditProfileLayout.js similarity index 83% rename from src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js rename to src/UI/Settings/Profile/Edit/EditProfileLayout.js index 2a5ea7586..e41bbe591 100644 --- a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js +++ b/src/UI/Settings/Profile/Edit/EditProfileLayout.js @@ -6,10 +6,10 @@ define( 'AppLayout', 'marionette', 'backbone', - 'Settings/Quality/Profile/Edit/EditQualityProfileItemView', - 'Settings/Quality/Profile/Edit/QualitySortableCollectionView', - 'Settings/Quality/Profile/Edit/EditQualityProfileView', - 'Settings/Quality/Profile/DeleteQualityProfileView', + 'Settings/Profile/Edit/EditProfileItemView', + 'Settings/Profile/Edit/QualitySortableCollectionView', + 'Settings/Profile/Edit/EditProfileView', + 'Settings/Profile/DeleteProfileView', 'Series/SeriesCollection', 'Config' ], function (_, @@ -17,15 +17,15 @@ define( AppLayout, Marionette, Backbone, - EditQualityProfileItemView, + EditProfileItemView, QualitySortableCollectionView, - EditQualityProfileView, + EditProfileView, DeleteView, SeriesCollection, Config) { return Marionette.Layout.extend({ - template: 'Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate', + template: 'Settings/Profile/Edit/EditProfileLayoutTemplate', regions: { fields : '#x-fields', @@ -37,8 +37,8 @@ define( }, events: { - 'click .x-save' : '_saveQualityProfile', - 'click .x-cancel' : '_cancelQualityProfile', + 'click .x-save' : '_saveProfile', + 'click .x-cancel' : '_cancelProfile', 'click .x-delete' : '_delete' }, @@ -53,7 +53,7 @@ define( }, onShow: function () { - this.fieldsView = new EditQualityProfileView({ model: this.model }); + this.fieldsView = new EditProfileView({ model: this.model }); this._showFieldsView(); this.sortableListView = new QualitySortableCollectionView({ @@ -94,7 +94,7 @@ define( this._showFieldsView(); }, - _saveQualityProfile: function () { + _saveProfile: function () { var self = this; var cutoff = this.fieldsView.getCutoff(); this.model.set('cutoff', cutoff); @@ -109,7 +109,7 @@ define( } }, - _cancelQualityProfile: function () { + _cancelProfile: function () { if (!this.model.has('id')) { vent.trigger(vent.Commands.CloseModalCommand); return; @@ -136,7 +136,7 @@ define( _updateDisableStatus: function () { if (this._isQualityInUse()) { this.ui.deleteButton.addClass('disabled'); - this.ui.deleteButton.attr('title', 'Can\'t delete quality profiles attached to a series.'); + this.ui.deleteButton.attr('title', 'Can\'t delete a profile that is attached to a series.'); } else { this.ui.deleteButton.removeClass('disabled'); @@ -144,7 +144,7 @@ define( }, _isQualityInUse: function () { - return SeriesCollection.where({'qualityProfileId': this.model.id}).length !== 0; + return SeriesCollection.where({'profileId': this.model.id}).length !== 0; } }); }); diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html b/src/UI/Settings/Profile/Edit/EditProfileLayoutTemplate.html similarity index 100% rename from src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html rename to src/UI/Settings/Profile/Edit/EditProfileLayoutTemplate.html diff --git a/src/UI/Settings/Profile/Edit/EditProfileView.js b/src/UI/Settings/Profile/Edit/EditProfileView.js new file mode 100644 index 000000000..046534358 --- /dev/null +++ b/src/UI/Settings/Profile/Edit/EditProfileView.js @@ -0,0 +1,61 @@ +'use strict'; +define( + [ + 'underscore', + 'marionette', + 'Settings/Profile/Language/LanguageCollection', + 'Config', + 'Mixins/AsModelBoundView', + 'Mixins/AsValidatedView' + ], function (_, Marionette, LanguageCollection, Config, AsModelBoundView, AsValidatedView) { + + var view = Marionette.ItemView.extend({ + template: 'Settings/Profile/Edit/EditProfileViewTemplate', + + ui: { + cutoff : '.x-cutoff', + delay : '.x-delay', + delayMode : '.x-delay-mode' + }, + + events: { + 'change .x-delay': 'toggleDelayMode', + 'keyup .x-delay': 'toggleDelayMode' + }, + + templateHelpers: function () { + return { + languages : LanguageCollection.toJSON() + }; + }, + + onShow: function () { + this.toggleDelayMode(); + }, + + getCutoff: function () { + var self = this; + + return _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id: parseInt(self.ui.cutoff.val(), 10)}); + }, + + toggleDelayMode: function () { + var delay = parseInt(this.ui.delay.val(), 10); + + if (isNaN(delay)) { + return; + } + + if (delay > 0 && Config.getValueBoolean(Config.Keys.AdvancedSettings)) { + this.ui.delayMode.show(); + } + + else { + this.ui.delayMode.hide(); + } + } + }); + + AsValidatedView.call(view); + return AsModelBoundView.call(view); + }); diff --git a/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.html b/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.html new file mode 100644 index 000000000..1aef89d07 --- /dev/null +++ b/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.html @@ -0,0 +1,71 @@ +
+ + +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/src/UI/Settings/Profile/Edit/QualitySortableCollectionView.js b/src/UI/Settings/Profile/Edit/QualitySortableCollectionView.js new file mode 100644 index 000000000..9c9041de0 --- /dev/null +++ b/src/UI/Settings/Profile/Edit/QualitySortableCollectionView.js @@ -0,0 +1,22 @@ +'use strict'; +define( + [ + 'backbone.collectionview', + 'Settings/Profile/Edit/EditProfileItemView' + ], function (BackboneSortableCollectionView, EditProfileItemView) { + return BackboneSortableCollectionView.extend({ + + className: 'qualities', + modelView: EditProfileItemView, + + attributes: { + 'validation-name': 'items' + }, + + events: { + 'click li, td' : '_listItem_onMousedown', + 'dblclick li, td' : '_listItem_onDoubleClick', + 'keydown' : '_onKeydown' + } + }); + }); diff --git a/src/UI/Settings/Profile/Language/LanguageCollection.js b/src/UI/Settings/Profile/Language/LanguageCollection.js new file mode 100644 index 000000000..d190024b1 --- /dev/null +++ b/src/UI/Settings/Profile/Language/LanguageCollection.js @@ -0,0 +1,18 @@ +'use strict'; +define( + [ + 'backbone', + 'Settings/Profile/Language/LanguageModel' + ], function (Backbone, LanguageModel) { + + var LanuageCollection = Backbone.Collection.extend({ + model: LanguageModel, + url : window.NzbDrone.ApiRoot + '/language' + }); + + var languages = new LanuageCollection(); + + languages.fetch(); + + return languages; + }); diff --git a/src/UI/Settings/Profile/Language/LanguageModel.js b/src/UI/Settings/Profile/Language/LanguageModel.js new file mode 100644 index 000000000..ad6b13e3e --- /dev/null +++ b/src/UI/Settings/Profile/Language/LanguageModel.js @@ -0,0 +1,10 @@ +'use strict'; +define( + [ + 'backbone' + ], function (Backbone) { + return Backbone.Model.extend({ + + }); + }); + diff --git a/src/UI/Settings/Profile/LanguageLabel.js b/src/UI/Settings/Profile/LanguageLabel.js new file mode 100644 index 000000000..c72abda5a --- /dev/null +++ b/src/UI/Settings/Profile/LanguageLabel.js @@ -0,0 +1,20 @@ +'use strict'; +define( + [ + 'underscore', + 'handlebars', + 'Settings/Profile/Language/LanguageCollection' + ], function (_, Handlebars, LanguageCollection) { + Handlebars.registerHelper('languageLabel', function () { + + var wantedLanguage = this.language; + + var language = LanguageCollection.find(function (lang) { + return lang.get('nameLower') === wantedLanguage; + }); + + var result = '' + language.get('name') + ''; + + return new Handlebars.SafeString(result); + }); + }); diff --git a/src/UI/Settings/Quality/Profile/QualityProfileCollectionTemplate.html b/src/UI/Settings/Profile/ProfileCollectionTemplate.html similarity index 64% rename from src/UI/Settings/Quality/Profile/QualityProfileCollectionTemplate.html rename to src/UI/Settings/Profile/ProfileCollectionTemplate.html index 88182fda8..11e0047ad 100644 --- a/src/UI/Settings/Quality/Profile/QualityProfileCollectionTemplate.html +++ b/src/UI/Settings/Profile/ProfileCollectionTemplate.html @@ -1,10 +1,10 @@ 
- Quality Profiles + Profiles
-
    +
    • -
      +
      diff --git a/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js b/src/UI/Settings/Profile/ProfileCollectionView.js similarity index 69% rename from src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js rename to src/UI/Settings/Profile/ProfileCollectionView.js index b3f5e12af..f4403c144 100644 --- a/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js +++ b/src/UI/Settings/Profile/ProfileCollectionView.js @@ -2,16 +2,16 @@ define(['AppLayout', 'marionette', - 'Settings/Quality/Profile/QualityProfileView', - 'Settings/Quality/Profile/Edit/EditQualityProfileLayout', - 'Settings/Quality/Profile/QualityProfileSchemaCollection', + 'Settings/Profile/ProfileView', + 'Settings/Profile/Edit/EditProfileLayout', + 'Settings/Profile/ProfileSchemaCollection', 'underscore' -], function (AppLayout, Marionette, QualityProfileView, EditProfileView, ProfileCollection, _) { +], function (AppLayout, Marionette, ProfileView, EditProfileView, ProfileCollection, _) { return Marionette.CompositeView.extend({ - itemView : QualityProfileView, - itemViewContainer: '.quality-profiles', - template : 'Settings/Quality/Profile/QualityProfileCollectionTemplate', + itemView : ProfileView, + itemViewContainer: '.profiles', + template : 'Settings/Profile/ProfileCollectionTemplate', ui: { 'addCard': '.x-add-card' diff --git a/src/UI/Settings/Profile/ProfileLayout.js b/src/UI/Settings/Profile/ProfileLayout.js new file mode 100644 index 000000000..16822f4f9 --- /dev/null +++ b/src/UI/Settings/Profile/ProfileLayout.js @@ -0,0 +1,27 @@ +'use strict'; + +define( + [ + 'marionette', + 'Profile/ProfileCollection', + 'Settings/Profile/ProfileCollectionView', + 'Settings/Profile/Language/LanguageCollection' + ], function (Marionette, ProfileCollection, ProfileCollectionView, LanguageCollection) { + return Marionette.Layout.extend({ + template: 'Settings/Profile/ProfileLayoutTemplate', + + regions: { + profile : '#profile' + }, + + initialize: function (options) { + this.settings = options.settings; + ProfileCollection.fetch(); + }, + + onShow: function () { + this.profile.show(new ProfileCollectionView({collection: ProfileCollection})); + } + }); + }); + diff --git a/src/UI/Settings/Profile/ProfileLayoutTemplate.html b/src/UI/Settings/Profile/ProfileLayoutTemplate.html new file mode 100644 index 000000000..1e812f24b --- /dev/null +++ b/src/UI/Settings/Profile/ProfileLayoutTemplate.html @@ -0,0 +1,3 @@ +
      +
      +
      diff --git a/src/UI/Settings/Profile/ProfileSchemaCollection.js b/src/UI/Settings/Profile/ProfileSchemaCollection.js new file mode 100644 index 000000000..79101a506 --- /dev/null +++ b/src/UI/Settings/Profile/ProfileSchemaCollection.js @@ -0,0 +1,13 @@ +'use strict'; + +define( + [ + 'backbone', + 'Profile/ProfileModel' + ], function (Backbone, ProfileModel) { + + return Backbone.Collection.extend({ + model: ProfileModel, + url : window.NzbDrone.ApiRoot + '/profile/schema' + }); + }); diff --git a/src/UI/Settings/Quality/Profile/QualityProfileView.js b/src/UI/Settings/Profile/ProfileView.js similarity index 81% rename from src/UI/Settings/Quality/Profile/QualityProfileView.js rename to src/UI/Settings/Profile/ProfileView.js index 45a5ede8e..45d9005cf 100644 --- a/src/UI/Settings/Quality/Profile/QualityProfileView.js +++ b/src/UI/Settings/Profile/ProfileView.js @@ -4,14 +4,15 @@ define( [ 'AppLayout', 'marionette', - 'Settings/Quality/Profile/Edit/EditQualityProfileLayout', + 'Settings/Profile/Edit/EditProfileLayout', 'Mixins/AsModelBoundView', - 'Settings/Quality/Profile/AllowedLabeler', + 'Settings/Profile/AllowedLabeler', + 'Settings/Profile/LanguageLabel', 'bootstrap' ], function (AppLayout, Marionette, EditProfileView, AsModelBoundView) { var view = Marionette.ItemView.extend({ - template: 'Settings/Quality/Profile/QualityProfileViewTemplate', + template: 'Settings/Profile/ProfileViewTemplate', tagName : 'li', ui: { diff --git a/src/UI/Settings/Profile/ProfileViewTemplate.html b/src/UI/Settings/Profile/ProfileViewTemplate.html new file mode 100644 index 000000000..dece04aa6 --- /dev/null +++ b/src/UI/Settings/Profile/ProfileViewTemplate.html @@ -0,0 +1,16 @@ +
      +
      +

      +
      + +
      + {{languageLabel}} + + {{#if_gt grabDelay compare="0"}} + + {{/if_gt}} +
      +
        + {{allowedLabeler}} +
      +
      \ No newline at end of file diff --git a/src/UI/Settings/Profile/profile.less b/src/UI/Settings/Profile/profile.less new file mode 100644 index 000000000..185ec469d --- /dev/null +++ b/src/UI/Settings/Profile/profile.less @@ -0,0 +1,31 @@ +@import "../../Content/Bootstrap/mixins"; +@import "../../Content/FontAwesome/font-awesome"; +@import "../../Shared/Styles/clickable.less"; + +.profile-item { + .clickable; + + width: 300px; + height: 158px; + padding: 10px 15px; + + &.add-card { + .center { + margin-top: 10px; + } + } + + .allowed-qualities { + + padding-left: 0px; + + li { + list-style-type : none; + margin: 1px; + } + } + + .language { + margin-bottom: 3px; + } +} diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionView.js b/src/UI/Settings/Quality/Definition/QualityDefinitionView.js index d5a3f9d1a..e08bf41c0 100644 --- a/src/UI/Settings/Quality/Definition/QualityDefinitionView.js +++ b/src/UI/Settings/Quality/Definition/QualityDefinitionView.js @@ -25,7 +25,7 @@ define( }, initialize: function (options) { - this.qualityProfileCollection = options.qualityProfiles; + this.profileCollection = options.profiles; this.filesize = fileSize; }, diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js deleted file mode 100644 index 1365de61c..000000000 --- a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; -define( - [ - 'underscore', - 'marionette', - 'Mixins/AsModelBoundView', - 'Mixins/AsValidatedView' - ], function (_, Marionette, AsModelBoundView, AsValidatedView) { - - var view = Marionette.ItemView.extend({ - template: 'Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate', - - ui: { - cutoff : '.x-cutoff' - }, - - getCutoff: function () { - var self = this; - - return _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id: parseInt(self.ui.cutoff.val(), 10)}); - } - }); - - AsValidatedView.call(view); - return AsModelBoundView.call(view); - }); diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html deleted file mode 100644 index ef0da0ef3..000000000 --- a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html +++ /dev/null @@ -1,23 +0,0 @@ -
      - -
      - -
      -
      -
      - - -
      - -
      - -
      - -
      -
      \ No newline at end of file diff --git a/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js b/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js deleted file mode 100644 index 03693ec29..000000000 --- a/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; -define( - [ - 'backbone.collectionview', - 'Settings/Quality/Profile/Edit/EditQualityProfileItemView' - ], function (BackboneSortableCollectionView, EditQualityProfileItemView) { - return BackboneSortableCollectionView.extend({ - - className: 'qualities', - modelView: EditQualityProfileItemView, - - attributes: { - 'validation-name': 'items' - }, - - events: { - 'click li, td' : '_listItem_onMousedown', - 'dblclick li, td' : '_listItem_onDoubleClick', - 'keydown' : '_onKeydown' - } - }); - }); diff --git a/src/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js b/src/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js deleted file mode 100644 index 46cc94027..000000000 --- a/src/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -define( - [ - 'backbone', - 'Quality/QualityProfileModel' - ], function (Backbone, QualityProfileModel) { - - return Backbone.Collection.extend({ - model: QualityProfileModel, - url : window.NzbDrone.ApiRoot + '/qualityprofile/schema' - }); - }); diff --git a/src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html b/src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html deleted file mode 100644 index 6dfc5d3ac..000000000 --- a/src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html +++ /dev/null @@ -1,9 +0,0 @@ -
      -
      -

      -
      - -
        - {{allowedLabeler}} -
      -
      \ No newline at end of file diff --git a/src/UI/Settings/Quality/QualityLayout.js b/src/UI/Settings/Quality/QualityLayout.js index c80486209..67dcc2f9c 100644 --- a/src/UI/Settings/Quality/QualityLayout.js +++ b/src/UI/Settings/Quality/QualityLayout.js @@ -3,28 +3,23 @@ define( [ 'marionette', - 'Quality/QualityProfileCollection', - 'Settings/Quality/Profile/QualityProfileCollectionView', 'Quality/QualityDefinitionCollection', 'Settings/Quality/Definition/QualityDefinitionCollectionView' - ], function (Marionette, QualityProfileCollection, QualityProfileCollectionView, QualityDefinitionCollection, QualityDefinitionCollectionView) { + ], function (Marionette, QualityDefinitionCollection, QualityDefinitionCollectionView) { return Marionette.Layout.extend({ template: 'Settings/Quality/QualityLayoutTemplate', regions: { - qualityProfile : '#quality-profile', qualityDefinition : '#quality-definition' }, initialize: function (options) { this.settings = options.settings; - QualityProfileCollection.fetch(); this.qualityDefinitionCollection = new QualityDefinitionCollection(); this.qualityDefinitionCollection.fetch(); }, onShow: function () { - this.qualityProfile.show(new QualityProfileCollectionView({collection: QualityProfileCollection})); this.qualityDefinition.show(new QualityDefinitionCollectionView({collection: this.qualityDefinitionCollection})); } }); diff --git a/src/UI/Settings/Quality/QualityLayoutTemplate.html b/src/UI/Settings/Quality/QualityLayoutTemplate.html index 1684faf98..8db6f83ee 100644 --- a/src/UI/Settings/Quality/QualityLayoutTemplate.html +++ b/src/UI/Settings/Quality/QualityLayoutTemplate.html @@ -1,9 +1,3 @@ 
      -
      -
      - -
      - -
      diff --git a/src/UI/Settings/Quality/quality.less b/src/UI/Settings/Quality/quality.less index 0376373b4..ad9f69c55 100644 --- a/src/UI/Settings/Quality/quality.less +++ b/src/UI/Settings/Quality/quality.less @@ -2,30 +2,6 @@ @import "../../Content/FontAwesome/font-awesome"; @import "../../Shared/Styles/clickable.less"; -.quality-profile-item { - .clickable; - - width: 300px; - height: 120px; - padding: 10px 15px; - - &.add-card { - .center { - margin-top: 10px; - } - } - - .allowed-qualities { - - padding-left: 0px; - - li { - list-style-type : none; - margin: 1px; - } - } -} - ul.qualities { .user-select(none); diff --git a/src/UI/Settings/SettingsLayout.js b/src/UI/Settings/SettingsLayout.js index eddb56ecc..acd7fbc33 100644 --- a/src/UI/Settings/SettingsLayout.js +++ b/src/UI/Settings/SettingsLayout.js @@ -10,6 +10,7 @@ define( 'Settings/MediaManagement/Naming/NamingModel', 'Settings/MediaManagement/MediaManagementLayout', 'Settings/MediaManagement/MediaManagementSettingsModel', + 'Settings/Profile/ProfileLayout', 'Settings/Quality/QualityLayout', 'Settings/Indexers/IndexerLayout', 'Settings/Indexers/IndexerCollection', @@ -31,6 +32,7 @@ define( NamingModel, MediaManagementLayout, MediaManagementSettingsModel, + ProfileLayout, QualityLayout, IndexerLayout, IndexerCollection, @@ -48,6 +50,7 @@ define( regions: { mediaManagement : '#media-management', + profiles : '#profiles', quality : '#quality', indexers : '#indexers', downloadClient : '#download-client', @@ -59,6 +62,7 @@ define( ui: { mediaManagementTab : '.x-media-management-tab', + profilesTab : '.x-profiles-tab', qualityTab : '.x-quality-tab', indexersTab : '.x-indexers-tab', downloadClientTab : '.x-download-client-tab', @@ -70,6 +74,7 @@ define( events: { 'click .x-media-management-tab' : '_showMediaManagement', + 'click .x-profiles-tab' : '_showProfiles', 'click .x-quality-tab' : '_showQuality', 'click .x-indexers-tab' : '_showIndexers', 'click .x-download-client-tab' : '_showDownloadClient', @@ -109,6 +114,7 @@ define( { self.loading.$el.hide(); self.mediaManagement.show(new MediaManagementLayout({ settings: self.mediaManagementSettings, namingSettings: self.namingSettings })); + self.profiles.show(new ProfileLayout()); self.quality.show(new QualityLayout()); self.indexers.show(new IndexerLayout({ model: self.indexerSettings })); self.downloadClient.show(new DownloadClientLayout({ model: self.downloadClientSettings })); @@ -123,6 +129,9 @@ define( onShow: function () { switch (this.action) { + case 'profiles': + this._showProfiles(); + break; case 'quality': this._showQuality(); break; @@ -158,6 +167,15 @@ define( this._navigate('settings/mediamanagement'); }, + _showProfiles: function (e) { + if (e) { + e.preventDefault(); + } + + this.ui.profilesTab.tab('show'); + this._navigate('settings/profiles'); + }, + _showQuality: function (e) { if (e) { e.preventDefault(); diff --git a/src/UI/Settings/SettingsLayoutTemplate.html b/src/UI/Settings/SettingsLayoutTemplate.html index 84a826ecb..a1deab4da 100644 --- a/src/UI/Settings/SettingsLayoutTemplate.html +++ b/src/UI/Settings/SettingsLayoutTemplate.html @@ -1,5 +1,6 @@