diff --git a/frontend/src/Components/Filter/Builder/DateFilterBuilderRowValue.js b/frontend/src/Components/Filter/Builder/DateFilterBuilderRowValue.js index 9309a80eee..19718d4756 100644 --- a/frontend/src/Components/Filter/Builder/DateFilterBuilderRowValue.js +++ b/frontend/src/Components/Filter/Builder/DateFilterBuilderRowValue.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import NumberInput from 'Components/Form/NumberInput'; import SelectInput from 'Components/Form/SelectInput'; import TextInput from 'Components/Form/TextInput'; -import { IN_LAST, IN_NEXT, NOT_IN_LAST, NOT_IN_NEXT } from 'Helpers/Props/filterTypes'; +import { IN_LAST, IN_NEXT, IN_PAST, NOT_IN_LAST, NOT_IN_NEXT } from 'Helpers/Props/filterTypes'; import isString from 'Utilities/String/isString'; import translate from 'Utilities/String/translate'; import { NAME } from './FilterBuilderRowValue'; @@ -57,6 +57,12 @@ function isInFilter(filterType) { ); } +function isPastFutureFilter(filterType) { + return ( + filterType === IN_PAST + ); +} + class DateFilterBuilderRowValue extends Component { // @@ -187,6 +193,10 @@ class DateFilterBuilderRowValue extends Component { ); } + if (isPastFutureFilter(filterType)) { + return null; + } + return ( translate('FilterNotInNext') - } + }, + { key: filterTypes.IN_PAST, value: 'before today' } ], [EQUAL]: [ diff --git a/frontend/src/Helpers/Props/filterTypes.js b/frontend/src/Helpers/Props/filterTypes.js index 239a4e7e94..7fa6c7b8ae 100644 --- a/frontend/src/Helpers/Props/filterTypes.js +++ b/frontend/src/Helpers/Props/filterTypes.js @@ -8,6 +8,7 @@ export const IN_NEXT = 'inNext'; export const NOT_IN_NEXT = 'notInNext'; export const LESS_THAN = 'lessThan'; export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual'; +export const IN_PAST = 'inPast'; export const NOT_CONTAINS = 'notContains'; export const NOT_EQUAL = 'notEqual'; export const STARTS_WITH = 'startsWith'; @@ -22,6 +23,7 @@ export const all = [ GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, + IN_PAST, NOT_CONTAINS, NOT_EQUAL, IN_LAST, diff --git a/frontend/src/Movie/Index/Overview/MovieIndexOverview.tsx b/frontend/src/Movie/Index/Overview/MovieIndexOverview.tsx index 2fe8713bf4..66fa1417c2 100644 --- a/frontend/src/Movie/Index/Overview/MovieIndexOverview.tsx +++ b/frontend/src/Movie/Index/Overview/MovieIndexOverview.tsx @@ -74,6 +74,7 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) { tags, hasFile, isAvailable, + dateConsideredAvailable, tmdbId, imdbId, studio, @@ -168,6 +169,7 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) { monitored={monitored} hasFile={hasFile} isAvailable={isAvailable} + dateConsideredAvailable={dateConsideredAvailable} status={status} width={posterWidth} detailedProgressBar={overviewOptions.detailedProgressBar} diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPoster.tsx b/frontend/src/Movie/Index/Posters/MovieIndexPoster.tsx index 9894478406..5dccfe6d2b 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPoster.tsx +++ b/frontend/src/Movie/Index/Posters/MovieIndexPoster.tsx @@ -78,6 +78,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) { studio, added, year, + dateConsideredAvailable, inCinemas, physicalRelease, digitalRelease, @@ -222,6 +223,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) { monitored={monitored} hasFile={hasFile} isAvailable={isAvailable} + dateConsideredAvailable={dateConsideredAvailable} status={status} width={posterWidth} detailedProgressBar={detailedProgressBar} diff --git a/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.tsx b/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.tsx index aef82e2b8d..e41a867ebf 100644 --- a/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.tsx +++ b/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.tsx @@ -18,6 +18,7 @@ interface MovieIndexProgressBarProps { status: MovieStatus; hasFile: boolean; isAvailable: boolean; + dateConsideredAvailable: string; width: number; detailedProgressBar: boolean; bottomRadius?: boolean; @@ -31,6 +32,7 @@ function MovieIndexProgressBar({ status, hasFile, isAvailable, + dateConsideredAvailable, width, detailedProgressBar, bottomRadius, @@ -74,6 +76,7 @@ function MovieIndexProgressBar({ showText={detailedProgressBar} width={width} text={queueStatusText ? queueStatusText : movieStatus} + title={movieStatus === 'NotAvailable' ? dateConsideredAvailable : ''} /> ); } diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.tsx b/frontend/src/Movie/Index/Table/MovieIndexRow.tsx index d546a8c511..71db6b6cf4 100644 --- a/frontend/src/Movie/Index/Table/MovieIndexRow.tsx +++ b/frontend/src/Movie/Index/Table/MovieIndexRow.tsx @@ -80,6 +80,7 @@ function MovieIndexRow(props: MovieIndexRowProps) { tmdbId, imdbId, isAvailable, + dateConsideredAvailable, hasFile, movieFile, youTubeTrailerId, @@ -363,6 +364,7 @@ function MovieIndexRow(props: MovieIndexRowProps) { monitored={monitored} hasFile={hasFile} isAvailable={isAvailable} + dateConsideredAvailable={dateConsideredAvailable} status={status} width={125} detailedProgressBar={true} diff --git a/frontend/src/Movie/Movie.ts b/frontend/src/Movie/Movie.ts index b7b4ee6b28..cb7f36a1ec 100644 --- a/frontend/src/Movie/Movie.ts +++ b/frontend/src/Movie/Movie.ts @@ -95,6 +95,7 @@ interface Movie extends ModelBase { grabbed?: boolean; lastSearchTime?: string; isAvailable: boolean; + dateConsideredAvailable: string; isSaving?: boolean; addOptions: MovieAddOptions; } diff --git a/frontend/src/Store/Actions/discoverMovieActions.js b/frontend/src/Store/Actions/discoverMovieActions.js index 9371b0c559..f6e63bfd4a 100644 --- a/frontend/src/Store/Actions/discoverMovieActions.js +++ b/frontend/src/Store/Actions/discoverMovieActions.js @@ -473,6 +473,12 @@ export const defaultState = { type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.BOOL }, + { + name: 'dateConsideredAvailable', + label: translate('DateConsideredAvailable'), + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, { name: 'minimumAvailability', label: () => translate('MinimumAvailability'), @@ -514,6 +520,12 @@ export const defaultState = { label: () => translate('Popularity'), type: filterBuilderTypes.NUMBER }, + { + name: 'minimumAvailabilityDate', + label: translate('MinimumAvailabilityDate'), + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, { name: 'certification', label: () => translate('Certification'), diff --git a/frontend/src/Store/Actions/movieActions.js b/frontend/src/Store/Actions/movieActions.js index cd56135894..8cc215d9e9 100644 --- a/frontend/src/Store/Actions/movieActions.js +++ b/frontend/src/Store/Actions/movieActions.js @@ -152,6 +152,14 @@ export const filterPredicates = { return dateFilterPredicate(item.physicalRelease, filterValue, type); }, + dateConsideredAvailable: function(item, filterValue, type) { + return dateFilterPredicate(item.dateConsideredAvailable, filterValue, type); + }, + + minimumAvailabilityDate: function(item, filterValue, type) { + return dateFilterPredicate(item.minimumAvailabilityDate, filterValue, type); + }, + digitalRelease: function(item, filterValue, type) { return dateFilterPredicate(item.digitalRelease, filterValue, type); }, diff --git a/frontend/src/Store/Actions/movieIndexActions.js b/frontend/src/Store/Actions/movieIndexActions.js index e36bee132a..d3b79fa6b8 100644 --- a/frontend/src/Store/Actions/movieIndexActions.js +++ b/frontend/src/Store/Actions/movieIndexActions.js @@ -316,12 +316,24 @@ export const defaultState = { type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.BOOL }, + { + name: 'dateConsideredAvailable', + label: translate('DateConsideredAvailable'), + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, { name: 'minimumAvailability', label: () => translate('MinimumAvailability'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.MINIMUM_AVAILABILITY }, + { + name: 'minimumAvailabilityDate', + label: translate('MinimumAvailabilityDate'), + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, { name: 'title', label: () => translate('Title'), diff --git a/frontend/src/Utilities/Date/dateFilterPredicate.js b/frontend/src/Utilities/Date/dateFilterPredicate.js index 59407e3ba9..4a55292a71 100644 --- a/frontend/src/Utilities/Date/dateFilterPredicate.js +++ b/frontend/src/Utilities/Date/dateFilterPredicate.js @@ -37,6 +37,9 @@ export default function(itemValue, filterValue, type) { isAfter(itemValue, { [filterValue.time]: filterValue.value }) ); + case filterTypes.IN_PAST: + return moment(itemValue).isBefore(new Date()); + default: return false; } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 23a3b71ab8..d1a646093d 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -305,6 +305,7 @@ "Database": "Database", "DatabaseMigration": "Database Migration", "Date": "Date", + "DateConsideredAvailable": "Date Considered Available", "Dates": "Dates", "Day": "Day", "DayOfWeekAt": "{day} at {time}", @@ -1076,6 +1077,7 @@ "MinimumAge": "Minimum Age", "MinimumAgeHelpText": "Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider.", "MinimumAvailability": "Minimum Availability", + "MinimumAvailabilityDate": "Minimum Availability Date", "MinimumCustomFormatScore": "Minimum Custom Format Score", "MinimumCustomFormatScoreHelpText": "Minimum custom format score allowed to download", "MinimumCustomFormatScoreIncrement": "Minimum Custom Format Score Increment", diff --git a/src/NzbDrone.Core/Movies/Movie.cs b/src/NzbDrone.Core/Movies/Movie.cs index 1ef429fa2c..a34b0b29bc 100644 --- a/src/NzbDrone.Core/Movies/Movie.cs +++ b/src/NzbDrone.Core/Movies/Movie.cs @@ -76,70 +76,40 @@ public string FolderName() public bool IsAvailable(int delay = 0) { - // the below line is what was used before delay was implemented, could still be used for cases when delay==0 - // return (Status >= MinimumAvailability || (MinimumAvailability == MovieStatusType.PreDB && Status >= MovieStatusType.Released)); - - // This more complex sequence handles the delay - DateTime minimumAvailabilityDate; - - if (MinimumAvailability is MovieStatusType.TBA or MovieStatusType.Announced) - { - minimumAvailabilityDate = DateTime.MinValue; - } - else if (MinimumAvailability == MovieStatusType.InCinemas && MovieMetadata.Value.InCinemas.HasValue) - { - minimumAvailabilityDate = MovieMetadata.Value.InCinemas.Value; - } - else - { - if (MovieMetadata.Value.PhysicalRelease.HasValue && MovieMetadata.Value.DigitalRelease.HasValue) - { - minimumAvailabilityDate = new DateTime(Math.Min(MovieMetadata.Value.PhysicalRelease.Value.Ticks, MovieMetadata.Value.DigitalRelease.Value.Ticks)); - } - else if (MovieMetadata.Value.PhysicalRelease.HasValue) - { - minimumAvailabilityDate = MovieMetadata.Value.PhysicalRelease.Value; - } - else if (MovieMetadata.Value.DigitalRelease.HasValue) - { - minimumAvailabilityDate = MovieMetadata.Value.DigitalRelease.Value; - } - else - { - minimumAvailabilityDate = MovieMetadata.Value.InCinemas?.AddDays(90) ?? DateTime.MaxValue; - } - } - - if (minimumAvailabilityDate == DateTime.MinValue || minimumAvailabilityDate == DateTime.MaxValue) - { - return DateTime.UtcNow >= minimumAvailabilityDate; - } - - return DateTime.UtcNow >= minimumAvailabilityDate.AddDays(delay); + return DateTime.UtcNow >= GetReleaseDate(delay, true); } - public DateTime? GetReleaseDate() + public DateTime? GetReleaseDate(int delay = 0, bool isAvailabilityCheck = false) { if (MinimumAvailability is MovieStatusType.TBA or MovieStatusType.Announced) { - return new[] { MovieMetadata.Value.InCinemas, MovieMetadata.Value.DigitalRelease, MovieMetadata.Value.PhysicalRelease } - .Where(x => x.HasValue) - .Min(); + if (isAvailabilityCheck) + { + return DateTime.MinValue; + } + else if (MovieMetadata.Value.InCinemas.HasValue || MovieMetadata.Value.DigitalRelease.HasValue || MovieMetadata.Value.PhysicalRelease.HasValue) + { + return new[] { MovieMetadata.Value.InCinemas, MovieMetadata.Value.DigitalRelease, MovieMetadata.Value.PhysicalRelease } + .Where(x => x.HasValue) + .Min()?.AddDays(delay); + } } - - if (MinimumAvailability == MovieStatusType.InCinemas && MovieMetadata.Value.InCinemas.HasValue) + else if (MinimumAvailability == MovieStatusType.InCinemas && MovieMetadata.Value.InCinemas.HasValue) { - return MovieMetadata.Value.InCinemas.Value; + return MovieMetadata.Value.InCinemas.Value.AddDays(delay); } - - if (MovieMetadata.Value.DigitalRelease.HasValue || MovieMetadata.Value.PhysicalRelease.HasValue) + else if (MovieMetadata.Value.DigitalRelease.HasValue || MovieMetadata.Value.PhysicalRelease.HasValue) { return new[] { MovieMetadata.Value.DigitalRelease, MovieMetadata.Value.PhysicalRelease } .Where(x => x.HasValue) - .Min(); + .Min()?.AddDays(delay); + } + else if (!MovieMetadata.Value.InCinemas.HasValue && isAvailabilityCheck) + { + return DateTime.MaxValue; } - return MovieMetadata.Value.InCinemas?.AddDays(90); + return MovieMetadata.Value.InCinemas?.AddDays(90 + delay); } public override string ToString() diff --git a/src/Radarr.Api.V3/Movies/MovieResource.cs b/src/Radarr.Api.V3/Movies/MovieResource.cs index 9a9829eadd..1ea969d69c 100644 --- a/src/Radarr.Api.V3/Movies/MovieResource.cs +++ b/src/Radarr.Api.V3/Movies/MovieResource.cs @@ -63,7 +63,9 @@ public MovieResource() // Editing Only public bool Monitored { get; set; } public MovieStatusType MinimumAvailability { get; set; } + public DateTime? MinimumAvailabilityDate { get; set; } public bool IsAvailable { get; set; } + public DateTime? DateConsideredAvailable { get; set; } public string FolderName { get; set; } public int Runtime { get; set; } @@ -143,6 +145,8 @@ public static MovieResource ToResource(this Movie model, int availDelay, MovieTr MinimumAvailability = model.MinimumAvailability, IsAvailable = model.IsAvailable(availDelay), + DateConsideredAvailable = model.GetReleaseDate(availDelay), + MinimumAvailabilityDate = model.GetReleaseDate(), FolderName = model.FolderName(), Runtime = model.MovieMetadata.Value.Runtime,