diff --git a/frontend/src/Components/Filter/Builder/DownloadStatusFilterBuilderRowValue.js b/frontend/src/Components/Filter/Builder/DownloadStatusFilterBuilderRowValue.js
new file mode 100644
index 0000000000..249b081589
--- /dev/null
+++ b/frontend/src/Components/Filter/Builder/DownloadStatusFilterBuilderRowValue.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import translate from 'Utilities/String/translate';
+import FilterBuilderRowValue from './FilterBuilderRowValue';
+
+const protocols = [
+ {
+ id: 'downloaded',
+ get name() {
+ return translate('DownloadedAndMonitored');
+ }
+ },
+ {
+ id: 'unmonitored',
+ get name() {
+ return translate('DownloadedButNotMonitored');
+ }
+ },
+ {
+ id: 'missingMonitored',
+ get name() {
+ return translate('MissingMonitoredAndConsideredAvailable');
+ }
+ },
+ {
+ id: 'missingUnmonitored',
+ get name() {
+ return translate('MissingNotMonitored');
+ }
+ },
+ {
+ id: 'queue',
+ get name() {
+ return translate('Queued');
+ }
+ },
+ {
+ id: 'continuing',
+ get name() {
+ return translate('Unreleased');
+ }
+ }
+];
+
+function DownloadStatusFilterBuilderRowValue(props) {
+ return (
+
+ );
+}
+
+export default DownloadStatusFilterBuilderRowValue;
diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
index 9fd450d2ef..41ad28497d 100644
--- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
+++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
@@ -6,6 +6,7 @@ import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Prop
import sortByProp from 'Utilities/Array/sortByProp';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
+import DownloadStatusFilterBuilderRowValue from './DownloadStatusFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector';
@@ -87,6 +88,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.MOVIE:
return MovieFilterBuilderRowValue;
+ case filterBuilderValueTypes.DOWNLOAD_STATUS:
+ return DownloadStatusFilterBuilderRowValue;
+
case filterBuilderValueTypes.RELEASE_STATUS:
return ReleaseStatusFilterBuilderRowValue;
diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.js b/frontend/src/Helpers/Props/filterBuilderValueTypes.js
index a27b11dfc3..d1b94f45b7 100644
--- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js
+++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js
@@ -10,6 +10,7 @@ export const QUALITY = 'quality';
export const QUALITY_PROFILE = 'qualityProfile';
export const QUEUE_STATUS = 'queueStatus';
export const MOVIE = 'movie';
+export const DOWNLOAD_STATUS = 'downloadStatus';
export const RELEASE_STATUS = 'releaseStatus';
export const MINIMUM_AVAILABILITY = 'minimumAvailability';
export const TAG = 'tag';
diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.css b/frontend/src/Movie/Index/Table/MovieIndexRow.css
index 6b8091960c..a59f45e78a 100644
--- a/frontend/src/Movie/Index/Table/MovieIndexRow.css
+++ b/frontend/src/Movie/Index/Table/MovieIndexRow.css
@@ -67,6 +67,15 @@
flex-direction: column;
}
+.downloadStatus {
+ composes: cell;
+
+ display: flex;
+ justify-content: center;
+ flex: 0 0 150px;
+ flex-direction: column;
+}
+
.certification {
composes: cell;
diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.css.d.ts b/frontend/src/Movie/Index/Table/MovieIndexRow.css.d.ts
index 441f0219d4..5234c6a057 100644
--- a/frontend/src/Movie/Index/Table/MovieIndexRow.css.d.ts
+++ b/frontend/src/Movie/Index/Table/MovieIndexRow.css.d.ts
@@ -8,6 +8,7 @@ interface CssExports {
'checkInput': string;
'collection': string;
'digitalRelease': string;
+ 'downloadStatus': string;
'externalLinks': string;
'genres': string;
'imdbRating': string;
diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.tsx b/frontend/src/Movie/Index/Table/MovieIndexRow.tsx
index d546a8c511..195c746672 100644
--- a/frontend/src/Movie/Index/Table/MovieIndexRow.tsx
+++ b/frontend/src/Movie/Index/Table/MovieIndexRow.tsx
@@ -70,6 +70,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
releaseDate,
runtime,
minimumAvailability,
+ downloadStatus,
path,
genres = [],
keywords = [],
@@ -314,6 +315,14 @@ function MovieIndexRow(props: MovieIndexRowProps) {
);
}
+ if (name === 'downloadStatus') {
+ return (
+
+ {translate(firstCharToUpper(downloadStatus))}
+
+ );
+ }
+
if (name === 'path') {
return (
diff --git a/frontend/src/Movie/Movie.ts b/frontend/src/Movie/Movie.ts
index b7b4ee6b28..bb568788f2 100644
--- a/frontend/src/Movie/Movie.ts
+++ b/frontend/src/Movie/Movie.ts
@@ -13,6 +13,14 @@ export type MovieStatus =
export type MovieAvailability = 'announced' | 'inCinemas' | 'released';
+export type DownloadStatus =
+ | 'downloaded'
+ | 'unmonitored'
+ | 'missingMonitored'
+ | 'missingUnmonitored'
+ | 'queue'
+ | 'continuing';
+
export type CoverType = 'poster' | 'fanart' | 'headshot';
export interface Image {
@@ -80,6 +88,7 @@ interface Movie extends ModelBase {
rootFolderPath: string;
runtime: number;
minimumAvailability: MovieAvailability;
+ downloadStatus: DownloadStatus;
path: string;
genres: string[];
keywords: string[];
diff --git a/frontend/src/Store/Actions/movieIndexActions.js b/frontend/src/Store/Actions/movieIndexActions.js
index e36bee132a..3142c75bd3 100644
--- a/frontend/src/Store/Actions/movieIndexActions.js
+++ b/frontend/src/Store/Actions/movieIndexActions.js
@@ -440,6 +440,12 @@ export const defaultState = {
type: filterBuilderTypes.DATE,
valueType: filterBuilderValueTypes.DATE
},
+ {
+ name: 'downloadStatus',
+ label: () => translate('DownloadStatus'),
+ type: filterBuilderTypes.EXACT,
+ valueType: filterBuilderValueTypes.DOWNLOAD_STATUS
+ },
{
name: 'physicalRelease',
label: () => translate('PhysicalRelease'),
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index 23a3b71ab8..807955787a 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -576,6 +576,7 @@
"DownloadPropersAndRepacksHelpTextCustomFormat": "Use 'Do not Prefer' to sort by custom format score over Propers/Repacks",
"DownloadPropersAndRepacksHelpTextWarning": "Use custom formats for automatic upgrades to Propers/Repacks",
"DownloadStationStatusExtracting": "Extracting: {progress}%",
+ "DownloadStatus": "Download Status",
"DownloadWarning": "Download warning: {warningMessage}",
"Downloaded": "Downloaded",
"DownloadedAndMonitored": "Downloaded (Monitored)",
diff --git a/src/Radarr.Api.V3/Movies/MovieController.cs b/src/Radarr.Api.V3/Movies/MovieController.cs
index 7bc3ea3c12..36dce0899c 100644
--- a/src/Radarr.Api.V3/Movies/MovieController.cs
+++ b/src/Radarr.Api.V3/Movies/MovieController.cs
@@ -19,6 +19,7 @@
using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.Movies.Translations;
using NzbDrone.Core.MovieStats;
+using NzbDrone.Core.Queue;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
@@ -47,6 +48,7 @@ public class MovieController : RestControllerWithSignalR,
private readonly IRootFolderService _rootFolderService;
private readonly IUpgradableSpecification _qualityUpgradableSpecification;
private readonly IConfigService _configService;
+ private readonly IQueueService _queueService;
public MovieController(IBroadcastSignalRMessage signalRBroadcaster,
IMovieService moviesService,
@@ -58,6 +60,7 @@ public MovieController(IBroadcastSignalRMessage signalRBroadcaster,
IRootFolderService rootFolderService,
IUpgradableSpecification qualityUpgradableSpecification,
IConfigService configService,
+ IQueueService queueService,
RootFolderValidator rootFolderValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator,
MoviePathValidator moviesPathValidator,
@@ -78,6 +81,7 @@ public MovieController(IBroadcastSignalRMessage signalRBroadcaster,
_configService = configService;
_coverMapper = coverMapper;
_commandQueueManager = commandQueueManager;
+ _queueService = queueService;
_rootFolderService = rootFolderService;
SharedValidator.RuleFor(s => s.Path).Cascade(CascadeMode.Stop)
@@ -151,7 +155,7 @@ public List AllMovie(int? tmdbId, bool excludeLocalCovers = false
foreach (var movie in movies)
{
var translation = GetTranslationFromDict(tdict, movie.MovieMetadata, translationLanguage);
- moviesResources.Add(movie.ToResource(availDelay, translation, _qualityUpgradableSpecification));
+ moviesResources.Add(movie.ToResource(availDelay, translation, _qualityUpgradableSpecification, null, _queueService));
}
if (!excludeLocalCovers)
@@ -191,7 +195,7 @@ protected MovieResource MapToResource(Movie movie, Language translationLanguage
var translations = _movieTranslationService.GetAllTranslationsForMovieMetadata(movie.MovieMetadataId);
var translation = GetMovieTranslation(translations, movie.MovieMetadata, translationLanguage);
- var resource = movie.ToResource(availDelay, translation, _qualityUpgradableSpecification);
+ var resource = movie.ToResource(availDelay, translation, _qualityUpgradableSpecification, null, _queueService);
MapCoversToLocal(resource);
FetchAndLinkMovieStatistics(resource);
diff --git a/src/Radarr.Api.V3/Movies/MovieControllerWithSignalR.cs b/src/Radarr.Api.V3/Movies/MovieControllerWithSignalR.cs
index 54c7e55c45..eb0a6f7206 100644
--- a/src/Radarr.Api.V3/Movies/MovieControllerWithSignalR.cs
+++ b/src/Radarr.Api.V3/Movies/MovieControllerWithSignalR.cs
@@ -13,6 +13,7 @@
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Translations;
using NzbDrone.Core.MovieStats;
+using NzbDrone.Core.Queue;
using NzbDrone.SignalR;
using Radarr.Http.REST;
@@ -30,6 +31,7 @@ public abstract class MovieControllerWithSignalR : RestControllerWithSignalR MapToResource(List movies)
var translations = _movieTranslationService.GetAllTranslationsForMovieMetadata(movie.MovieMetadataId);
var translation = GetMovieTranslation(translations, movie.MovieMetadata, language);
- var resource = movie.ToResource(availDelay, translation, _upgradableSpecification, _formatCalculator);
+ var resource = movie.ToResource(availDelay, translation, _upgradableSpecification, _formatCalculator, _queueService);
FetchAndLinkMovieStatistics(resource);
resources.Add(resource);
diff --git a/src/Radarr.Api.V3/Movies/MovieEditorController.cs b/src/Radarr.Api.V3/Movies/MovieEditorController.cs
index 902c224519..109d533fd5 100644
--- a/src/Radarr.Api.V3/Movies/MovieEditorController.cs
+++ b/src/Radarr.Api.V3/Movies/MovieEditorController.cs
@@ -11,6 +11,7 @@
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Translations;
+using NzbDrone.Core.Queue;
using Radarr.Http;
namespace Radarr.Api.V3.Movies
@@ -25,6 +26,7 @@ public class MovieEditorController : Controller
private readonly IManageCommandQueue _commandQueueManager;
private readonly MovieEditorValidator _movieEditorValidator;
private readonly IUpgradableSpecification _upgradableSpecification;
+ private readonly IQueueService _queueService;
public MovieEditorController(IMovieService movieService,
IMovieTranslationService movieTranslationService,
@@ -32,7 +34,8 @@ public MovieEditorController(IMovieService movieService,
IConfigService configService,
IManageCommandQueue commandQueueManager,
MovieEditorValidator movieEditorValidator,
- IUpgradableSpecification upgradableSpecification)
+ IUpgradableSpecification upgradableSpecification,
+ IQueueService queueService)
{
_movieService = movieService;
_movieTranslationService = movieTranslationService;
@@ -41,6 +44,7 @@ public MovieEditorController(IMovieService movieService,
_commandQueueManager = commandQueueManager;
_movieEditorValidator = movieEditorValidator;
_upgradableSpecification = upgradableSpecification;
+ _queueService = queueService;
}
[HttpPut]
@@ -125,7 +129,7 @@ public IActionResult SaveAll([FromBody] MovieEditorResource resource)
foreach (var movie in updatedMovies)
{
var translation = GetTranslationFromDict(tdict, movie.MovieMetadata, configLanguage);
- var movieResource = movie.ToResource(availabilityDelay, translation, _upgradableSpecification);
+ var movieResource = movie.ToResource(availabilityDelay, translation, _upgradableSpecification, null, _queueService);
MapCoversToLocal(movieResource);
diff --git a/src/Radarr.Api.V3/Movies/MovieResource.cs b/src/Radarr.Api.V3/Movies/MovieResource.cs
index 9a9829eadd..cd297ca2a6 100644
--- a/src/Radarr.Api.V3/Movies/MovieResource.cs
+++ b/src/Radarr.Api.V3/Movies/MovieResource.cs
@@ -9,6 +9,7 @@
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Translations;
+using NzbDrone.Core.Queue;
using Radarr.Api.V3.MovieFiles;
using Radarr.Http.REST;
using Swashbuckle.AspNetCore.Annotations;
@@ -85,6 +86,7 @@ public MovieResource()
public float Popularity { get; set; }
public DateTime? LastSearchTime { get; set; }
public MovieStatisticsResource Statistics { get; set; }
+ public string DownloadStatus { get; set; }
// Hiding this so people don't think its usable (only used to set the initial state)
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
@@ -99,7 +101,7 @@ public MovieResource()
public static class MovieResourceMapper
{
- public static MovieResource ToResource(this Movie model, int availDelay, MovieTranslation movieTranslation = null, IUpgradableSpecification upgradableSpecification = null, ICustomFormatCalculationService formatCalculationService = null)
+ public static MovieResource ToResource(this Movie model, int availDelay, MovieTranslation movieTranslation = null, IUpgradableSpecification upgradableSpecification = null, ICustomFormatCalculationService formatCalculationService = null, IQueueService queueService = null)
{
if (model == null)
{
@@ -113,6 +115,20 @@ public static MovieResource ToResource(this Movie model, int availDelay, MovieTr
var collection = model.MovieMetadata.Value.CollectionTmdbId > 0 ? new MovieCollectionResource { Title = model.MovieMetadata.Value.CollectionTitle, TmdbId = model.MovieMetadata.Value.CollectionTmdbId } : null;
+ var hasMovieFile = movieFile != null;
+ var isAvailable = model.IsAvailable(availDelay);
+
+ var isQueued = queueService?.GetQueue().Any(q => q.Movie?.Id == model.Id) == true;
+ var downloadStatus = (isQueued, hasMovieFile, isAvailable, model.Monitored) switch
+ {
+ (true, _, _, _) => "queue",
+ (_, true, _, true) => "downloaded",
+ (_, true, _, false) => "unmonitored",
+ (_, false, true, false) => "missingUnmonitored",
+ (_, false, true, true) => "missingMonitored",
+ _ => "continuing"
+ };
+
return new MovieResource
{
Id = model.Id,
@@ -160,6 +176,7 @@ public static MovieResource ToResource(this Movie model, int availDelay, MovieTr
AlternateTitles = model.MovieMetadata.Value.AlternativeTitles.ToResource(),
Ratings = model.MovieMetadata.Value.Ratings,
MovieFile = movieFile,
+ DownloadStatus = downloadStatus,
YouTubeTrailerId = model.MovieMetadata.Value.YouTubeTrailerId,
Studio = model.MovieMetadata.Value.Studio,
Collection = collection,
@@ -225,9 +242,9 @@ public static Movie ToModel(this MovieResource resource, Movie movie)
return movie;
}
- public static List ToResource(this IEnumerable movies, int availDelay, IUpgradableSpecification upgradableSpecification = null, ICustomFormatCalculationService formatCalculationService = null)
+ public static List ToResource(this IEnumerable movies, int availDelay, IUpgradableSpecification upgradableSpecification = null, ICustomFormatCalculationService formatCalculationService = null, IQueueService queueService = null)
{
- return movies.Select(x => ToResource(x, availDelay, null, upgradableSpecification, formatCalculationService)).ToList();
+ return movies.Select(x => ToResource(x, availDelay, null, upgradableSpecification, formatCalculationService, queueService)).ToList();
}
public static List ToModel(this IEnumerable resources)