New: HealthCheck and Status for Movies Deleted from TMDb

This commit is contained in:
Qstick 2020-02-25 23:18:49 -05:00
parent ea9494019e
commit 0c44ee5f88
18 changed files with 505 additions and 202 deletions

View File

@ -190,6 +190,7 @@ export const SCORE = fasUserPlus;
export const SEARCH = fasSearch;
export const MOVIE_CONTINUING = fasPlay;
export const SERIES_ENDED = fasStop;
export const MOVIE_DELETED = fasExclamationTriangle;
export const SETTINGS = fasCogs;
export const SHUTDOWN = fasPowerOff;
export const SORT = fasSort;

View File

@ -13,7 +13,7 @@ import MovieTitleLink from 'Movie/MovieTitleLink';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
import MovieStatusCell from './MovieStatusCell';
import MovieStatusConnector from 'Movie/MovieStatusConnector';
import MovieFileStatusConnector from 'Movie/MovieFileStatusConnector';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import styles from './MovieIndexRow.css';
@ -278,7 +278,7 @@ class MovieIndexRow extends Component {
key={name}
className={styles[name]}
>
<MovieStatusConnector
<MovieFileStatusConnector
movieId={id}
/>
</VirtualTableRowCell>

View File

@ -3,6 +3,7 @@ import React from 'react';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
import { getMovieStatusDetails } from 'Movie/MovieStatus';
import styles from './MovieStatusCell.css';
function MovieStatusCell(props) {
@ -14,6 +15,8 @@ function MovieStatusCell(props) {
...otherProps
} = props;
const statusDetails = getMovieStatusDetails(status);
return (
<Component
className={className}
@ -25,32 +28,12 @@ function MovieStatusCell(props) {
title={monitored ? 'Movie is monitored' : 'Movie is unmonitored'}
/>
{
status === 'announced' ?
<Icon
className={styles.statusIcon}
name={icons.ANNOUNCED}
title={'Movie is announced'}
/> : null
}
<Icon
className={styles.statusIcon}
name={statusDetails.icon}
title={`${statusDetails.title}: ${statusDetails.message}`}
/>
{
status === 'inCinemas' ?
<Icon
className={styles.statusIcon}
name={icons.IN_CINEMAS}
title={'Movie is in Cinemas'}
/> : null
}
{
status === 'released' ?
<Icon
className={styles.statusIcon}
name={icons.MOVIE_FILE}
title={'Movie is released'}
/> : null
}
</Component>
);
}

View File

@ -0,0 +1,124 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons, kinds, sizes } from 'Helpers/Props';
import Icon from 'Components/Icon';
import ProgressBar from 'Components/ProgressBar';
import QueueDetails from 'Activity/Queue/QueueDetails';
import MovieQuality from 'Movie/MovieQuality';
import Label from 'Components/Label';
import styles from './MovieFileStatus.css';
function MovieFileStatus(props) {
const {
inCinemas,
isAvailable,
monitored,
grabbed,
queueItem,
movieFile
} = props;
const hasMovieFile = !!movieFile;
const isQueued = !!queueItem;
const hasReleased = isAvailable && inCinemas;
if (isQueued) {
const {
sizeleft,
size
} = queueItem;
const progress = (100 - sizeleft / size * 100);
return (
<div className={styles.center}>
<QueueDetails
{...queueItem}
progressBar={
<ProgressBar
title={`Movie is downloading - ${progress.toFixed(1)}% ${queueItem.title}`}
progress={progress}
kind={kinds.PURPLE}
size={sizes.MEDIUM}
/>
}
/>
</div>
);
}
if (grabbed) {
return (
<div className={styles.center}>
<Icon
name={icons.DOWNLOADING}
title="Movie is downloading"
/>
</div>
);
}
if (hasMovieFile) {
const quality = movieFile.quality;
return (
<div className={styles.center}>
<MovieQuality
title={quality.quality.name}
size={movieFile.size}
quality={quality}
isMonitored={monitored}
isCutoffNotMet={movieFile.qualityCutoffNotMet}
/>
</div>
);
}
if (!monitored) {
return (
<div className={styles.center}>
<Label
title="Announced"
kind={kinds.WARNING}
>
Not Monitored
</Label>
</div>
);
}
if (hasReleased) {
return (
<div className={styles.center}>
<Label
title="Movie Available, but Missing"
kind={kinds.DANGER}
>
Missing
</Label>
</div>
);
}
return (
<div className={styles.center}>
<Label
title="Announced"
kind={kinds.INFO}
>
Not Available
</Label>
</div>
);
}
MovieFileStatus.propTypes = {
inCinemas: PropTypes.string,
isAvailable: PropTypes.bool,
monitored: PropTypes.bool.isRequired,
grabbed: PropTypes.bool,
queueItem: PropTypes.object,
movieFile: PropTypes.object
};
export default MovieFileStatus;

View File

@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import createQueueItemSelector from 'Store/Selectors/createQueueItemSelector';
import MovieStatus from './MovieStatus';
import MovieFileStatus from './MovieFileStatus';
function createMapStateToProps() {
return createSelector(
@ -30,22 +30,22 @@ function createMapStateToProps() {
const mapDispatchToProps = {
};
class MovieStatusConnector extends Component {
class MovieFileStatusConnector extends Component {
//
// Render
render() {
return (
<MovieStatus
<MovieFileStatus
{...this.props}
/>
);
}
}
MovieStatusConnector.propTypes = {
MovieFileStatusConnector.propTypes = {
movieId: PropTypes.number.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MovieStatusConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(MovieFileStatusConnector);

View File

@ -1,124 +1,32 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons, kinds, sizes } from 'Helpers/Props';
import Icon from 'Components/Icon';
import ProgressBar from 'Components/ProgressBar';
import QueueDetails from 'Activity/Queue/QueueDetails';
import MovieQuality from 'Movie/MovieQuality';
import Label from 'Components/Label';
import styles from './MovieStatus.css';
import { icons } from 'Helpers/Props';
function MovieStatus(props) {
const {
inCinemas,
isAvailable,
monitored,
grabbed,
queueItem,
movieFile
} = props;
export function getMovieStatusDetails(status) {
const hasMovieFile = !!movieFile;
const isQueued = !!queueItem;
const hasReleased = isAvailable && inCinemas;
let statusDetails = {
icon: icons.ANNOUNCED,
title: 'Announced',
message: 'Movie is announced'
};
if (isQueued) {
const {
sizeleft,
size
} = queueItem;
const progress = (100 - sizeleft / size * 100);
return (
<div className={styles.center}>
<QueueDetails
{...queueItem}
progressBar={
<ProgressBar
title={`Movie is downloading - ${progress.toFixed(1)}% ${queueItem.title}`}
progress={progress}
kind={kinds.PURPLE}
size={sizes.MEDIUM}
/>
}
/>
</div>
);
if (status === 'deleted') {
statusDetails = {
icon: icons.MOVIE_DELETED,
title: 'Deleted',
message: 'Movie was deleted from TMDb'
};
} else if (status === 'inCinemas') {
statusDetails = {
icon: icons.IN_CINEMAS,
title: 'In Cinemas',
message: 'Movie is in Cinemas'
};
} else if (status === 'released') {
statusDetails = {
icon: icons.MOVIE_FILE,
title: 'Released',
message: 'Movie is released'
};
}
if (grabbed) {
return (
<div className={styles.center}>
<Icon
name={icons.DOWNLOADING}
title="Movie is downloading"
/>
</div>
);
}
if (hasMovieFile) {
const quality = movieFile.quality;
return (
<div className={styles.center}>
<MovieQuality
title={quality.quality.name}
size={movieFile.size}
quality={quality}
isMonitored={monitored}
isCutoffNotMet={movieFile.qualityCutoffNotMet}
/>
</div>
);
}
if (!monitored) {
return (
<div className={styles.center}>
<Label
title="Announced"
kind={kinds.WARNING}
>
Not Monitored
</Label>
</div>
);
}
if (hasReleased) {
return (
<div className={styles.center}>
<Label
title="Movie Available, but Missing"
kind={kinds.DANGER}
>
Missing
</Label>
</div>
);
}
return (
<div className={styles.center}>
<Label
title="Announced"
kind={kinds.INFO}
>
Not Available
</Label>
</div>
);
return statusDetails;
}
MovieStatus.propTypes = {
inCinemas: PropTypes.string,
isAvailable: PropTypes.bool,
monitored: PropTypes.bool.isRequired,
grabbed: PropTypes.bool,
queueItem: PropTypes.object,
movieFile: PropTypes.object
};
export default MovieStatus;

View File

@ -0,0 +1,75 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class RemovedSeriesCheckFixture : CoreTest<RemovedSeriesCheck>
{
private void GivenMovie(int amount, int deleted)
{
List<Movie> movie;
if (amount == 0)
{
movie = new List<Movie>();
}
else if (deleted == 0)
{
movie = Builder<Movie>.CreateListOfSize(amount)
.All()
.With(v => v.Status = MovieStatusType.Released)
.BuildList();
}
else
{
movie = Builder<Movie>.CreateListOfSize(amount)
.All()
.With(v => v.Status = MovieStatusType.Released)
.Random(deleted)
.With(v => v.Status = MovieStatusType.Deleted)
.BuildList();
}
Mocker.GetMock<IMovieService>()
.Setup(v => v.GetAllMovies())
.Returns(movie);
}
[Test]
public void should_return_error_if_movie_no_longer_on_tmdb()
{
GivenMovie(4, 1);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_error_if_multiple_movie_no_longer_on_tmdb()
{
GivenMovie(4, 2);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_ok_if_all_movie_still_on_tmdb()
{
GivenMovie(4, 0);
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_ok_if_no_movie_exist()
{
GivenMovie(0, 0);
Subject.Check().ShouldBeOk();
}
}
}

View File

@ -0,0 +1,81 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck
{
public class HealthCheckServiceFixture : CoreTest<HealthCheckService>
{
private FakeHealthCheck _healthCheck;
[SetUp]
public void SetUp()
{
_healthCheck = new FakeHealthCheck();
Mocker.SetConstant<IEnumerable<IProvideHealthCheck>>(new[] { _healthCheck });
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
}
[Test]
public void should_not_execute_conditional()
{
Subject.HandleAsync(new FakeEvent());
_healthCheck.Executed.Should().BeFalse();
}
[Test]
public void should_execute_conditional()
{
Subject.HandleAsync(new FakeEvent() { ShouldExecute = true });
_healthCheck.Executed.Should().BeTrue();
}
[Test]
public void should_execute_unconditional()
{
Subject.HandleAsync(new FakeEvent2());
_healthCheck.Executed.Should().BeTrue();
}
}
public class FakeEvent : IEvent
{
public bool ShouldExecute { get; set; }
}
public class FakeEvent2 : IEvent
{
public bool ShouldExecute { get; set; }
}
[CheckOn(typeof(FakeEvent))]
[CheckOn(typeof(FakeEvent2))]
public class FakeHealthCheck : IProvideHealthCheck, ICheckOnCondition<FakeEvent>
{
public bool CheckOnStartup => false;
public bool CheckOnSchedule => false;
public bool Executed { get; set; }
public bool Checked { get; set; }
public Core.HealthCheck.HealthCheck Check()
{
Executed = true;
return new Core.HealthCheck.HealthCheck(GetType());
}
public bool ShouldCheckOnEvent(FakeEvent message)
{
return message.ShouldExecute;
}
}
}

View File

@ -1,3 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
@ -6,6 +9,7 @@ using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Credits;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@ -13,7 +17,6 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MovieTests
{
[TestFixture]
[Ignore("Weird moq errors")]
public class RefreshMovieServiceFixture : CoreTest<RefreshMovieService>
{
private Movie _movie;
@ -22,36 +25,37 @@ namespace NzbDrone.Core.Test.MovieTests
public void Setup()
{
_movie = Builder<Movie>.CreateNew()
.Build();
.With(s => s.Status = MovieStatusType.Released)
.Build();
Mocker.GetMock<IMovieService>()
.Setup(s => s.GetMovie(_movie.Id))
.Returns(_movie);
Mocker.GetMock<IProvideMovieInfo>()
.Setup(s => s.GetMovieInfo(It.IsAny<int>(), It.IsAny<Profile>(), false))
.Callback<int>(p => { throw new MovieNotFoundException(p.ToString()); });
.Setup(s => s.GetMovieInfo(It.IsAny<int>(), It.IsAny<Profile>(), It.IsAny<bool>()))
.Callback<int, Profile, bool>((i, p, b) => { throw new MovieNotFoundException(i); });
}
private void GivenNewMovieInfo(Movie movie)
{
Mocker.GetMock<IProvideMovieInfo>()
.Setup(s => s.GetMovieInfo(_movie.ImdbId))
.Returns(movie);
.Setup(s => s.GetMovieInfo(_movie.TmdbId, It.IsAny<Profile>(), It.IsAny<bool>()))
.Returns(new Tuple<Movie, List<Credit>>(movie, new List<Credit>()));
}
[Test]
public void should_update_tvrage_id_if_changed()
public void should_update_imdb_id_if_changed()
{
var newSeriesInfo = _movie.JsonClone();
newSeriesInfo.ImdbId = _movie.ImdbId + 1;
var newMovieInfo = _movie.JsonClone();
newMovieInfo.ImdbId = _movie.ImdbId + 1;
GivenNewMovieInfo(newSeriesInfo);
GivenNewMovieInfo(newMovieInfo);
Subject.Execute(new RefreshMovieCommand(_movie.Id));
Mocker.GetMock<IMovieService>()
.Verify(v => v.UpdateMovie(It.Is<Movie>(s => s.ImdbId == newSeriesInfo.ImdbId)));
.Verify(v => v.UpdateMovie(It.Is<List<Movie>>(s => s.First().ImdbId == newMovieInfo.ImdbId), true));
}
[Test]
@ -60,7 +64,7 @@ namespace NzbDrone.Core.Test.MovieTests
Subject.Execute(new RefreshMovieCommand(_movie.Id));
Mocker.GetMock<IMovieService>()
.Verify(v => v.UpdateMovie(It.IsAny<Movie>()), Times.Never());
.Verify(v => v.UpdateMovie(It.Is<Movie>(s => s.Status == MovieStatusType.Deleted)), Times.Once());
ExceptionVerification.ExpectedErrors(1);
}
@ -68,17 +72,41 @@ namespace NzbDrone.Core.Test.MovieTests
[Test]
public void should_update_if_tmdb_id_changed()
{
var newSeriesInfo = _movie.JsonClone();
newSeriesInfo.TmdbId = _movie.TmdbId + 1;
var newMovieInfo = _movie.JsonClone();
newMovieInfo.TmdbId = _movie.TmdbId + 1;
GivenNewMovieInfo(newSeriesInfo);
GivenNewMovieInfo(newMovieInfo);
Subject.Execute(new RefreshMovieCommand(_movie.Id));
Mocker.GetMock<IMovieService>()
.Verify(v => v.UpdateMovie(It.Is<Movie>(s => s.TmdbId == newSeriesInfo.TmdbId)));
.Verify(v => v.UpdateMovie(It.Is<List<Movie>>(s => s.First().TmdbId == newMovieInfo.TmdbId), true));
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_mark_as_deleted_if_tmdb_id_not_found()
{
Subject.Execute(new RefreshMovieCommand(_movie.Id));
Mocker.GetMock<IMovieService>()
.Verify(v => v.UpdateMovie(It.Is<Movie>(s => s.Status == MovieStatusType.Deleted)), Times.Once());
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void should_not_remark_as_deleted_if_tmdb_id_not_found()
{
_movie.Status = MovieStatusType.Deleted;
Subject.Execute(new RefreshMovieCommand(_movie.Id));
Mocker.GetMock<IMovieService>()
.Verify(v => v.UpdateMovie(It.IsAny<Movie>()), Times.Never());
ExceptionVerification.ExpectedErrors(1);
}
}
}

View File

@ -1,27 +1,33 @@
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Exceptions
{
public class MovieNotFoundException : NzbDroneException
{
public string ImdbId { get; set; }
public int TmdbMovieId { get; set; }
public MovieNotFoundException(string imdbid)
: base(string.Format("Movie with imdbid {0} was not found, it may have been removed from IMDb.", imdbid))
public MovieNotFoundException(int tmdbMovieId)
: base(string.Format("Movie with tmdbId {0} was not found, it may have been removed from TMDb.", tmdbMovieId))
{
ImdbId = imdbid;
TmdbMovieId = tmdbMovieId;
}
public MovieNotFoundException(string imdbid, string message, params object[] args)
public MovieNotFoundException(string imdbId)
: base(string.Format("Movie with IMDBId {0} was not found, it may have been removed from TMDb.", imdbId))
{
TmdbMovieId = 0;
}
public MovieNotFoundException(int tmdbMovieId, string message, params object[] args)
: base(message, args)
{
ImdbId = imdbid;
TmdbMovieId = tmdbMovieId;
}
public MovieNotFoundException(string imdbid, string message)
public MovieNotFoundException(int tmdbMovieId, string message)
: base(message)
{
ImdbId = imdbid;
TmdbMovieId = tmdbMovieId;
}
}
}

View File

@ -0,0 +1,48 @@
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(MovieUpdatedEvent))]
[CheckOn(typeof(MovieDeletedEvent), CheckOnCondition.FailedOnly)]
public class RemovedSeriesCheck : HealthCheckBase, ICheckOnCondition<MovieUpdatedEvent>, ICheckOnCondition<MovieDeletedEvent>
{
private readonly IMovieService _movieService;
public RemovedSeriesCheck(IMovieService movieService)
{
_movieService = movieService;
}
public override HealthCheck Check()
{
var deletedMovie = _movieService.GetAllMovies().Where(v => v.Status == MovieStatusType.Deleted).ToList();
if (deletedMovie.Empty())
{
return new HealthCheck(GetType());
}
var movieText = deletedMovie.Select(s => $"{s.Title} (tmdbid {s.TmdbId})").Join(", ");
if (deletedMovie.Count == 1)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Movie {movieText} was removed from TMDb");
}
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Movie {movieText} were removed from TMDb");
}
public bool ShouldCheckOnEvent(MovieDeletedEvent message)
{
return message.Movie.Status == MovieStatusType.Deleted;
}
public bool ShouldCheckOnEvent(MovieUpdatedEvent message)
{
return message.Movie.Status == MovieStatusType.Deleted;
}
}
}

View File

@ -1,14 +1,45 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.HealthCheck
{
public class EventDrivenHealthCheck
public interface IEventDrivenHealthCheck
{
IProvideHealthCheck HealthCheck { get; }
bool ShouldExecute(IEvent message, bool previouslyFailed);
}
public class EventDrivenHealthCheck<TEvent> : IEventDrivenHealthCheck
{
public IProvideHealthCheck HealthCheck { get; set; }
public CheckOnCondition Condition { get; set; }
public ICheckOnCondition<TEvent> EventFilter { get; set; }
public EventDrivenHealthCheck(IProvideHealthCheck healthCheck, CheckOnCondition condition)
{
HealthCheck = healthCheck;
Condition = condition;
EventFilter = healthCheck as ICheckOnCondition<TEvent>;
}
public bool ShouldExecute(IEvent message, bool previouslyFailed)
{
if (Condition == CheckOnCondition.SuccessfulOnly && previouslyFailed)
{
return false;
}
if (Condition == CheckOnCondition.FailedOnly && !previouslyFailed)
{
return false;
}
if (EventFilter != null && !EventFilter.ShouldCheckOnEvent((TEvent)message))
{
return false;
}
return true;
}
}
}

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Messaging;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Lifecycle;
@ -25,7 +24,7 @@ namespace NzbDrone.Core.HealthCheck
private readonly IProvideHealthCheck[] _healthChecks;
private readonly IProvideHealthCheck[] _startupHealthChecks;
private readonly IProvideHealthCheck[] _scheduledHealthChecks;
private readonly Dictionary<Type, EventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly IEventAggregator _eventAggregator;
private readonly ICacheManager _cacheManager;
private readonly Logger _logger;
@ -54,10 +53,16 @@ namespace NzbDrone.Core.HealthCheck
return _healthCheckResults.Values.ToList();
}
private Dictionary<Type, EventDrivenHealthCheck[]> GetEventDrivenHealthChecks()
private Dictionary<Type, IEventDrivenHealthCheck[]> GetEventDrivenHealthChecks()
{
return _healthChecks
.SelectMany(h => h.GetType().GetAttributes<CheckOnAttribute>().Select(a => Tuple.Create(a.EventType, new EventDrivenHealthCheck(h, a.Condition))))
.SelectMany(h => h.GetType().GetAttributes<CheckOnAttribute>().Select(a =>
{
var eventDrivenType = typeof(EventDrivenHealthCheck<>).MakeGenericType(a.EventType);
var eventDriven = (IEventDrivenHealthCheck)Activator.CreateInstance(eventDrivenType, h, a.Condition);
return Tuple.Create(a.EventType, eventDriven);
}))
.GroupBy(t => t.Item1, t => t.Item2)
.ToDictionary(g => g.Key, g => g.ToArray());
}
@ -111,7 +116,7 @@ namespace NzbDrone.Core.HealthCheck
return;
}
EventDrivenHealthCheck[] checks;
IEventDrivenHealthCheck[] checks;
if (!_eventDrivenHealthChecks.TryGetValue(message.GetType(), out checks))
{
return;
@ -122,23 +127,10 @@ namespace NzbDrone.Core.HealthCheck
foreach (var eventDrivenHealthCheck in checks)
{
if (eventDrivenHealthCheck.Condition == CheckOnCondition.Always)
{
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
continue;
}
var healthCheckType = eventDrivenHealthCheck.HealthCheck.GetType();
var previouslyFailed = healthCheckResults.Any(r => r.Source == healthCheckType);
if (eventDrivenHealthCheck.Condition == CheckOnCondition.FailedOnly &&
healthCheckResults.Any(r => r.Source == healthCheckType))
{
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
continue;
}
if (eventDrivenHealthCheck.Condition == CheckOnCondition.SuccessfulOnly &&
healthCheckResults.None(r => r.Source == healthCheckType))
if (eventDrivenHealthCheck.ShouldExecute(message, previouslyFailed))
{
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
}

View File

@ -0,0 +1,7 @@
namespace NzbDrone.Core.HealthCheck
{
public interface ICheckOnCondition<TEvent>
{
bool ShouldCheckOnEvent(TEvent message);
}
}

View File

@ -100,7 +100,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
if (response.StatusCode == HttpStatusCode.NotFound)
{
throw new MovieNotFoundException("Movie not found.");
throw new MovieNotFoundException(tmdbId);
}
if (response.StatusCode != HttpStatusCode.OK)

View File

@ -1,7 +1,8 @@
namespace NzbDrone.Core.Movies
namespace NzbDrone.Core.Movies
{
public enum MovieStatusType
{
Deleted = -1,
TBA = 0, //Nothing yet announced, only rumors, but still IMDb page (this might not be used)
Announced = 1, //Movie is announced but Cinema date is in the future or unknown
InCinemas = 2, //Been in Cinemas for less than 3 months (since TMDB lacks complete information)

View File

@ -63,13 +63,31 @@ namespace NzbDrone.Core.Movies
{
_logger.ProgressInfo("Updating Info for {0}", movie.Title);
var tuple = _movieInfo.GetMovieInfo(movie.TmdbId, movie.Profile, movie.HasPreDBEntry);
Movie movieInfo;
List<Credit> credits;
var movieInfo = tuple.Item1;
try
{
var tuple = _movieInfo.GetMovieInfo(movie.TmdbId, movie.Profile, movie.HasPreDBEntry);
movieInfo = tuple.Item1;
credits = tuple.Item2;
}
catch (MovieNotFoundException)
{
if (movie.Status != MovieStatusType.Deleted)
{
movie.Status = MovieStatusType.Deleted;
_movieService.UpdateMovie(movie);
_logger.Debug("Movie marked as deleted on tmdb for {0}", movie.Title);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
}
throw;
}
if (movie.TmdbId != movieInfo.TmdbId)
{
_logger.Warn("Movie '{0}' (tvdbid {1}) was replaced with '{2}' (tvdbid {3}), because the original was a duplicate.", movie.Title, movie.TmdbId, movieInfo.Title, movieInfo.TmdbId);
_logger.Warn("Movie '{0}' (TmdbId {1}) was replaced with '{2}' (TmdbId {3}), because the original was a duplicate.", movie.Title, movie.TmdbId, movieInfo.Title, movieInfo.TmdbId);
movie.TmdbId = movieInfo.TmdbId;
}
@ -139,7 +157,7 @@ namespace NzbDrone.Core.Movies
}
_movieService.UpdateMovie(new List<Movie> { movie }, true);
_creditService.UpdateCredits(tuple.Item2, movie);
_creditService.UpdateCredits(credits, movie);
try
{