mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 03:52:33 +02:00
Fixed a few things with displaying the Movie Details Page. Implemented the first round of Searching for and Downloading movie Releases. ATM this is still a bit hacky and alot of things need to be cleaned up. However, one can now manually search for and download (only in qBittorrent) a movie torrent.
This commit is contained in:
parent
16e35f68bb
commit
2a3b0304cb
@ -82,6 +82,11 @@ private List<ReleaseResource> GetReleases()
|
||||
return GetEpisodeReleases(Request.Query.episodeId);
|
||||
}
|
||||
|
||||
if (Request.Query.movieId != null)
|
||||
{
|
||||
return GetMovieReleases(Request.Query.movieId);
|
||||
}
|
||||
|
||||
return GetRss();
|
||||
}
|
||||
|
||||
@ -102,6 +107,27 @@ private List<ReleaseResource> GetEpisodeReleases(int episodeId)
|
||||
return new List<ReleaseResource>();
|
||||
}
|
||||
|
||||
private List<ReleaseResource> GetMovieReleases(int movieId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var decisions = _nzbSearchService.MovieSearch(movieId, true);
|
||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisionsForMovies(decisions);
|
||||
|
||||
return MapDecisions(prioritizedDecisions);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
_logger.Error(ex, "One or more indexer you selected does not support movie search yet: " + ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Movie search failed: " + ex.Message);
|
||||
}
|
||||
|
||||
return new List<ReleaseResource>();
|
||||
}
|
||||
|
||||
private List<ReleaseResource> GetRss()
|
||||
{
|
||||
var reports = _rssFetcherAndParser.Fetch();
|
||||
|
@ -86,6 +86,11 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||
var parsedEpisodeInfo = model.RemoteEpisode.ParsedEpisodeInfo;
|
||||
var remoteEpisode = model.RemoteEpisode;
|
||||
var torrentInfo = (model.RemoteEpisode.Release as TorrentInfo) ?? new TorrentInfo();
|
||||
var downloadAllowed = model.RemoteEpisode.DownloadAllowed;
|
||||
if (model.IsForMovie)
|
||||
{
|
||||
downloadAllowed = model.RemoteMovie.DownloadAllowed;
|
||||
}
|
||||
|
||||
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
|
||||
return new ReleaseResource
|
||||
@ -119,7 +124,7 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||
CommentUrl = releaseInfo.CommentUrl,
|
||||
DownloadUrl = releaseInfo.DownloadUrl,
|
||||
InfoUrl = releaseInfo.InfoUrl,
|
||||
DownloadAllowed = remoteEpisode.DownloadAllowed,
|
||||
DownloadAllowed = downloadAllowed,
|
||||
//ReleaseWeight
|
||||
|
||||
MagnetUrl = torrentInfo.MagnetUrl,
|
||||
|
@ -178,8 +178,9 @@ public void should_not_add_to_downloaded_list_when_download_fails()
|
||||
public void should_return_an_empty_list_when_none_are_appproved()
|
||||
{
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
|
||||
RemoteEpisode ep = null;
|
||||
decisions.Add(new DownloadDecision(ep, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(ep, new Rejection("Failure!")));
|
||||
|
||||
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
public class DownloadDecision
|
||||
{
|
||||
public RemoteEpisode RemoteEpisode { get; private set; }
|
||||
|
||||
public RemoteMovie RemoteMovie { get; private set; }
|
||||
|
||||
public bool IsForMovie = false;
|
||||
public IEnumerable<Rejection> Rejections { get; private set; }
|
||||
|
||||
public bool Approved => !Rejections.Any();
|
||||
@ -30,6 +34,23 @@ public bool Rejected
|
||||
public DownloadDecision(RemoteEpisode episode, params Rejection[] rejections)
|
||||
{
|
||||
RemoteEpisode = episode;
|
||||
RemoteMovie = new RemoteMovie
|
||||
{
|
||||
Release = episode.Release,
|
||||
ParsedEpisodeInfo = episode.ParsedEpisodeInfo
|
||||
};
|
||||
Rejections = rejections.ToList();
|
||||
}
|
||||
|
||||
public DownloadDecision(RemoteMovie movie, params Rejection[] rejections)
|
||||
{
|
||||
RemoteMovie = movie;
|
||||
RemoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Release = movie.Release,
|
||||
ParsedEpisodeInfo = movie.ParsedEpisodeInfo
|
||||
};
|
||||
IsForMovie = true;
|
||||
Rejections = rejections.ToList();
|
||||
}
|
||||
|
||||
|
@ -37,9 +37,83 @@ public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports)
|
||||
|
||||
public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase)
|
||||
{
|
||||
if (searchCriteriaBase.Movie != null)
|
||||
{
|
||||
return GetMovieDecisions(reports, searchCriteriaBase).ToList();
|
||||
}
|
||||
|
||||
return GetDecisions(reports, searchCriteriaBase).ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
if (reports.Any())
|
||||
{
|
||||
_logger.ProgressInfo("Processing {0} releases", reports.Count);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.ProgressInfo("No results found");
|
||||
}
|
||||
|
||||
var reportNumber = 1;
|
||||
|
||||
foreach (var report in reports)
|
||||
{
|
||||
DownloadDecision decision = null;
|
||||
_logger.ProgressTrace("Processing release {0}/{1}", reportNumber, reports.Count);
|
||||
|
||||
try
|
||||
{
|
||||
var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title);
|
||||
|
||||
if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
RemoteMovie remoteEpisode = _parsingService.Map(parsedEpisodeInfo, "", searchCriteria);
|
||||
remoteEpisode.Release = report;
|
||||
|
||||
if (remoteEpisode.Movie == null)
|
||||
{
|
||||
//remoteEpisode.DownloadAllowed = true; //Fuck you :)
|
||||
//decision = GetDecisionForReport(remoteEpisode, searchCriteria);
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Movie not Found."));
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteEpisode.DownloadAllowed = true;
|
||||
//decision = GetDecisionForReport(remoteEpisode, searchCriteria); TODO: Rewrite this for movies!
|
||||
decision = new DownloadDecision(remoteEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't process release.");
|
||||
|
||||
var remoteEpisode = new RemoteEpisode { Release = report };
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unexpected error processing release"));
|
||||
}
|
||||
|
||||
reportNumber++;
|
||||
|
||||
if (decision != null)
|
||||
{
|
||||
if (decision.Rejections.Any())
|
||||
{
|
||||
_logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Debug("Release accepted");
|
||||
}
|
||||
|
||||
yield return decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
if (reports.Any())
|
||||
@ -82,7 +156,7 @@ private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, Se
|
||||
{
|
||||
//remoteEpisode.DownloadAllowed = true; //Fuck you :)
|
||||
//decision = GetDecisionForReport(remoteEpisode, searchCriteria);
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Movie not Found."));
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Series not Found."));
|
||||
}
|
||||
else if (remoteEpisode.Episodes.Empty())
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
public interface IPrioritizeDownloadDecision
|
||||
{
|
||||
List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions);
|
||||
List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions);
|
||||
}
|
||||
|
||||
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
|
||||
@ -29,5 +30,17 @@ public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisio
|
||||
.Union(decisions.Where(c => c.RemoteEpisode.Series == null))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
|
||||
{
|
||||
return decisions.Where(c => c.RemoteMovie.Movie != null)
|
||||
/*.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
|
||||
{
|
||||
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService));
|
||||
})
|
||||
.SelectMany(c => c)*/
|
||||
.Union(decisions.Where(c => c.RemoteMovie.Movie == null))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string
|
||||
_proxy.SetTorrentLabel(hash.ToLower(), Settings.TvCategory, Settings);
|
||||
}
|
||||
|
||||
var isRecentEpisode = remoteEpisode.IsRecentEpisode();
|
||||
var isRecentEpisode = true;//remoteEpisode.IsRecentEpisode(); TODO: Update to use RemoteMovie!
|
||||
|
||||
if (isRecentEpisode && Settings.RecentTvPriority == (int)QBittorrentPriority.First ||
|
||||
!isRecentEpisode && Settings.OlderTvPriority == (int)QBittorrentPriority.First)
|
||||
|
@ -41,8 +41,8 @@ public DownloadService(IProvideDownloadClient downloadClientProvider,
|
||||
|
||||
public void DownloadReport(RemoteEpisode remoteEpisode)
|
||||
{
|
||||
Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
|
||||
Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems();
|
||||
//Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
|
||||
//Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); TODO update this shit
|
||||
|
||||
var downloadTitle = remoteEpisode.Release.Title;
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteEpisode.Release.DownloadProtocol);
|
||||
|
@ -0,0 +1,11 @@
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public class MovieSearchCriteria : SearchCriteriaBase
|
||||
{
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}]", Movie.Title);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ public abstract class SearchCriteriaBase
|
||||
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public Series Series { get; set; }
|
||||
|
||||
public Movie Movie { get; set; }
|
||||
public List<string> SceneTitles { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
public virtual bool MonitoredEpisodesOnly { get; set; }
|
||||
|
@ -19,6 +19,8 @@ public interface ISearchForNzb
|
||||
{
|
||||
List<DownloadDecision> EpisodeSearch(int episodeId, bool userInvokedSearch);
|
||||
List<DownloadDecision> EpisodeSearch(Episode episode, bool userInvokedSearch);
|
||||
List<DownloadDecision> MovieSearch(int movieId, bool userInvokedSearch);
|
||||
List<DownloadDecision> MovieSearch(Movie movie, bool userInvokedSearch);
|
||||
List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch);
|
||||
}
|
||||
|
||||
@ -29,6 +31,7 @@ public class NzbSearchService : ISearchForNzb
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IMakeDownloadDecision _makeDownloadDecision;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NzbSearchService(IIndexerFactory indexerFactory,
|
||||
@ -36,6 +39,7 @@ public NzbSearchService(IIndexerFactory indexerFactory,
|
||||
ISeriesService seriesService,
|
||||
IEpisodeService episodeService,
|
||||
IMakeDownloadDecision makeDownloadDecision,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
@ -43,6 +47,7 @@ public NzbSearchService(IIndexerFactory indexerFactory,
|
||||
_seriesService = seriesService;
|
||||
_episodeService = episodeService;
|
||||
_makeDownloadDecision = makeDownloadDecision;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -53,6 +58,20 @@ public List<DownloadDecision> EpisodeSearch(int episodeId, bool userInvokedSearc
|
||||
return EpisodeSearch(episode, userInvokedSearch);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> MovieSearch(int movieId, bool userInvokedSearch)
|
||||
{
|
||||
var movie = _movieService.GetMovie(movieId);
|
||||
|
||||
return MovieSearch(movie, userInvokedSearch);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> MovieSearch(Movie movie, bool userInvokedSearch)
|
||||
{
|
||||
var searchSpec = Get<MovieSearchCriteria>(movie, userInvokedSearch);
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> EpisodeSearch(Episode episode, bool userInvokedSearch)
|
||||
{
|
||||
var series = _seriesService.GetSeries(episode.SeriesId);
|
||||
@ -245,6 +264,23 @@ private List<DownloadDecision> SearchAnimeSeason(Series series, List<Episode> ep
|
||||
return spec;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(Movie movie, bool userInvokedSearch) where TSpec : SearchCriteriaBase, new()
|
||||
{
|
||||
var spec = new TSpec();
|
||||
|
||||
spec.Movie = movie;
|
||||
/*spec.SceneTitles = _sceneMapping.GetSceneNames(series.TvdbId,
|
||||
episodes.Select(e => e.SeasonNumber).Distinct().ToList(),
|
||||
episodes.Select(e => e.SceneSeasonNumber ?? e.SeasonNumber).Distinct().ToList());
|
||||
|
||||
spec.Episodes = episodes;
|
||||
|
||||
spec.SceneTitles.Add(series.Title);*/
|
||||
spec.UserInvokedSearch = userInvokedSearch;
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
var indexers = _indexerFactory.SearchEnabled();
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@ -22,6 +23,11 @@ public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearch
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
{
|
||||
@ -189,5 +190,10 @@ private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, BroadcastheNe
|
||||
yield return new IndexerRequest(builder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -84,5 +85,10 @@ private string CleanTitle(string title)
|
||||
{
|
||||
return RemoveCharactersRegex.Replace(title, "");
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
@ -128,5 +129,10 @@ private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
|
||||
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,18 @@ public override IList<ReleaseInfo> Fetch(SpecialEpisodeSearchCriteria searchCrit
|
||||
return FetchReleases(generator.GetSearchRequests(searchCriteria));
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> Fetch(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
if (!SupportsSearch)
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
var generator = GetRequestGenerator();
|
||||
|
||||
return FetchReleases(generator.GetSearchRequests(searchCriteria));
|
||||
}
|
||||
|
||||
protected virtual IList<ReleaseInfo> FetchReleases(IndexerPageableRequestChain pageableRequestChain, bool isRecent = false)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
@ -17,5 +17,6 @@ public interface IIndexer : IProvider
|
||||
IList<ReleaseInfo> Fetch(DailyEpisodeSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(AnimeEpisodeSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(SpecialEpisodeSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(MovieSearchCriteria searchCriteria);
|
||||
}
|
||||
}
|
@ -10,5 +10,6 @@ public interface IIndexerRequestGenerator
|
||||
IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria);
|
||||
IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria);
|
||||
IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria);
|
||||
IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@ -22,6 +23,11 @@ public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearch
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
@ -67,6 +67,7 @@ public virtual IEnumerable<ProviderDefinition> DefaultDefinitions
|
||||
public abstract IList<ReleaseInfo> Fetch(DailyEpisodeSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(AnimeEpisodeSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(SpecialEpisodeSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(MovieSearchCriteria searchCriteria);
|
||||
|
||||
protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases)
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@ -147,5 +148,10 @@ private string PrepareQuery(string query)
|
||||
{
|
||||
return query.Replace('+', ' ');
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@ -273,5 +274,10 @@ private static string NewsnabifyTitle(string title)
|
||||
{
|
||||
return title.Replace("+", "%20");
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@ -102,5 +103,10 @@ private string PrepareQuery(string query)
|
||||
{
|
||||
return query.Replace(' ', '+');
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@ -101,5 +102,10 @@ private IEnumerable<IndexerRequest> GetPagedRequests(string query)
|
||||
|
||||
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@ -88,13 +89,12 @@ private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, s
|
||||
|
||||
if (tvdbId.HasValue)
|
||||
{
|
||||
string imdbId = string.Format("tt{0:D7}", tvdbId);
|
||||
requestBuilder.AddQueryParam("search_imdb", imdbId);
|
||||
requestBuilder.AddQueryParam("search_tvdb", tvdbId.Value);
|
||||
}
|
||||
|
||||
if (query.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
//requestBuilder.AddQueryParam("search_string", string.Format(query, args));
|
||||
requestBuilder.AddQueryParam("search_string", string.Format(query, args));
|
||||
}
|
||||
|
||||
if (!Settings.RankedOnly)
|
||||
@ -102,6 +102,36 @@ private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, s
|
||||
requestBuilder.AddQueryParam("ranked", "0");
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("category", "tv");
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
requestBuilder.AddQueryParam("app_id", "Sonarr");
|
||||
|
||||
yield return new IndexerRequest(requestBuilder.Build());
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetMovieRequest(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
|
||||
.Resource("/pubapi_v2.php")
|
||||
.Accept(HttpAccept.Json);
|
||||
|
||||
if (Settings.CaptchaToken.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.UseSimplifiedUserAgent = true;
|
||||
requestBuilder.SetCookie("cf_clearance", Settings.CaptchaToken);
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("mode", "search");
|
||||
|
||||
requestBuilder.AddQueryParam("search_imdb", searchCriteria.Movie.ImdbId);
|
||||
|
||||
if (!Settings.RankedOnly)
|
||||
{
|
||||
requestBuilder.AddQueryParam("ranked", "0");
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("category", "movies");
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
@ -110,5 +140,18 @@ private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, s
|
||||
|
||||
yield return new IndexerRequest(requestBuilder.Build());
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
|
||||
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetMovieRequest(searchCriteria));
|
||||
|
||||
return pageableRequests;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using NzbDrone.Common.Http;
|
||||
using System;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
@ -22,6 +23,11 @@ public virtual IndexerPageableRequestChain GetRecentRequests()
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@ -23,6 +24,11 @@ public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearch
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@ -22,6 +23,11 @@ public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearch
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
@ -7,7 +7,9 @@
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Torznab
|
||||
@ -110,5 +112,6 @@ protected virtual ValidationFailure TestCapabilities()
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Wombles
|
||||
|
20
src/NzbDrone.Core/MediaFiles/Commands/RescanMovieCommand.cs
Normal file
20
src/NzbDrone.Core/MediaFiles/Commands/RescanMovieCommand.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
{
|
||||
public class RescanMovieCommand : Command
|
||||
{
|
||||
public int? MovieId { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public RescanMovieCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public RescanMovieCommand(int movieId)
|
||||
{
|
||||
MovieId = movieId;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
public interface IDiskScanService
|
||||
{
|
||||
void Scan(Series series);
|
||||
void Scan(Movie movie);
|
||||
string[] GetVideoFiles(string path, bool allDirectories = true);
|
||||
string[] GetNonVideoFiles(string path, bool allDirectories = true);
|
||||
List<string> FilterFiles(Series series, IEnumerable<string> files);
|
||||
@ -30,6 +31,8 @@ public interface IDiskScanService
|
||||
public class DiskScanService :
|
||||
IDiskScanService,
|
||||
IHandle<SeriesUpdatedEvent>,
|
||||
IHandle<MovieUpdatedEvent>,
|
||||
IExecute<RescanMovieCommand>,
|
||||
IExecute<RescanSeriesCommand>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
@ -39,6 +42,7 @@ public class DiskScanService :
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DiskScanService(IDiskProvider diskProvider,
|
||||
@ -48,6 +52,7 @@ public DiskScanService(IDiskProvider diskProvider,
|
||||
ISeriesService seriesService,
|
||||
IMediaFileTableCleanupService mediaFileTableCleanupService,
|
||||
IEventAggregator eventAggregator,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
@ -57,6 +62,7 @@ public DiskScanService(IDiskProvider diskProvider,
|
||||
_seriesService = seriesService;
|
||||
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -121,6 +127,64 @@ public void Scan(Series series)
|
||||
_eventAggregator.PublishEvent(new SeriesScannedEvent(series));
|
||||
}
|
||||
|
||||
public void Scan(Movie movie)
|
||||
{
|
||||
var rootFolder = _diskProvider.GetParentFolder(movie.Path);
|
||||
|
||||
if (!_diskProvider.FolderExists(rootFolder))
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderDoesNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_diskProvider.GetDirectories(rootFolder).Empty())
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderIsEmpty));
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Scanning disk for {0}", movie.Title);
|
||||
|
||||
if (!_diskProvider.FolderExists(movie.Path))
|
||||
{
|
||||
if (_configService.CreateEmptySeriesFolders &&
|
||||
_diskProvider.FolderExists(rootFolder))
|
||||
{
|
||||
_logger.Debug("Creating missing series folder: {0}", movie.Path);
|
||||
_diskProvider.CreateFolder(movie.Path);
|
||||
SetPermissions(movie.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Series folder doesn't exist: {0}", movie.Path);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.MovieFolderDoesNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
var videoFilesStopwatch = Stopwatch.StartNew();
|
||||
var mediaFileList = FilterFiles(movie, GetVideoFiles(movie.Path)).ToList();
|
||||
|
||||
videoFilesStopwatch.Stop();
|
||||
_logger.Trace("Finished getting episode files for: {0} [{1}]", movie, videoFilesStopwatch.Elapsed);
|
||||
|
||||
_logger.Debug("{0} Cleaning up media files in DB", movie);
|
||||
_mediaFileTableCleanupService.Clean(movie, mediaFileList);
|
||||
|
||||
var decisionsStopwatch = Stopwatch.StartNew();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie);
|
||||
decisionsStopwatch.Stop();
|
||||
_logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed);
|
||||
|
||||
_importApprovedEpisodes.Import(decisions, false);
|
||||
|
||||
_logger.Info("Completed scanning disk for {0}", movie.Title);
|
||||
_eventAggregator.PublishEvent(new MovieScannedEvent(movie));
|
||||
}
|
||||
|
||||
public string[] GetVideoFiles(string path, bool allDirectories = true)
|
||||
{
|
||||
_logger.Debug("Scanning '{0}' for video files", path);
|
||||
@ -156,6 +220,13 @@ public List<string> FilterFiles(Series series, IEnumerable<string> files)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<string> FilterFiles(Movie movie, IEnumerable<string> files)
|
||||
{
|
||||
return files.Where(file => !ExcludedSubFoldersRegex.IsMatch(movie.Path.GetRelativePath(file)))
|
||||
.Where(file => !ExcludedFilesRegex.IsMatch(Path.GetFileName(file)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void SetPermissions(string path)
|
||||
{
|
||||
if (!_configService.SetPermissionsLinux)
|
||||
@ -182,6 +253,28 @@ public void Handle(SeriesUpdatedEvent message)
|
||||
Scan(message.Series);
|
||||
}
|
||||
|
||||
public void Handle(MovieUpdatedEvent message)
|
||||
{
|
||||
Scan(message.Movie);
|
||||
}
|
||||
|
||||
public void Execute(RescanMovieCommand message)
|
||||
{
|
||||
if (message.MovieId.HasValue)
|
||||
{
|
||||
var series = _movieService.GetMovie(message.MovieId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var allMovies = _movieService.GetAllMovies();
|
||||
|
||||
foreach (var movie in allMovies)
|
||||
{
|
||||
Scan(movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(RescanSeriesCommand message)
|
||||
{
|
||||
if (message.SeriesId.HasValue)
|
||||
|
@ -18,6 +18,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
public interface IMakeImportDecision
|
||||
{
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource); //TODO: Needs changing to ParsedMovieInfo!!
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||
}
|
||||
|
||||
@ -53,6 +55,11 @@ public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series s
|
||||
return GetImportDecisions(videoFiles, series, null, false);
|
||||
}
|
||||
|
||||
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie)
|
||||
{
|
||||
return GetImportDecisions(videoFiles, movie, null, false);
|
||||
}
|
||||
|
||||
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
|
||||
{
|
||||
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series);
|
||||
@ -70,6 +77,75 @@ public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series s
|
||||
return decisions;
|
||||
}
|
||||
|
||||
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource)
|
||||
{
|
||||
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), movie);
|
||||
|
||||
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
|
||||
|
||||
var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo);
|
||||
var decisions = new List<ImportDecision>();
|
||||
|
||||
foreach (var file in newFiles)
|
||||
{
|
||||
decisions.AddIfNotNull(GetDecision(file, movie, folderInfo, sceneSource, shouldUseFolderName));
|
||||
}
|
||||
|
||||
return decisions;
|
||||
}
|
||||
|
||||
private ImportDecision GetDecision(string file, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
|
||||
{
|
||||
ImportDecision decision = null;
|
||||
|
||||
/*try
|
||||
{
|
||||
var localEpisode = _parsingService.GetLocalEpisode(file, movie, shouldUseFolderName ? folderInfo : null, sceneSource);
|
||||
|
||||
if (localEpisode != null)
|
||||
{
|
||||
localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, movie);
|
||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
_logger.Debug("Size: {0}", localEpisode.Size);
|
||||
|
||||
//TODO: make it so media info doesn't ruin the import process of a new series
|
||||
if (sceneSource)
|
||||
{
|
||||
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.Empty())
|
||||
{
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = GetDecision(localEpisode);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
localEpisode = new LocalEpisode();
|
||||
localEpisode.Path = file;
|
||||
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't import file. " + file);
|
||||
|
||||
var localEpisode = new LocalEpisode { Path = file };
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
|
||||
}*/
|
||||
|
||||
decision = new ImportDecision(null, new Rejection("IMPLEMENTATION MISSING!!!"));
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
|
||||
{
|
||||
ImportDecision decision = null;
|
||||
@ -182,6 +258,40 @@ private bool ShouldUseFolderName(List<string> videoFiles, Series series, ParsedE
|
||||
}) == 1;
|
||||
}
|
||||
|
||||
private bool ShouldUseFolderName(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo)
|
||||
{
|
||||
if (folderInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folderInfo.FullSeason)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return videoFiles.Count(file =>
|
||||
{
|
||||
var size = _diskProvider.GetFileSize(file);
|
||||
var fileQuality = QualityParser.ParseQuality(file);
|
||||
//var sample = null;//_detectSample.IsSample(movie, GetQuality(folderInfo, fileQuality, movie), file, size, folderInfo.IsPossibleSpecialEpisode); //Todo to this
|
||||
|
||||
return true;
|
||||
|
||||
//if (sample)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SceneChecker.IsSceneTitle(Path.GetFileName(file)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}) == 1;
|
||||
}
|
||||
|
||||
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
||||
{
|
||||
if (UseFolderQuality(folderInfo, fileQuality, series))
|
||||
|
24
src/NzbDrone.Core/MediaFiles/Events/MovieScanSkippedEvent.cs
Normal file
24
src/NzbDrone.Core/MediaFiles/Events/MovieScanSkippedEvent.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public class MovieScanSkippedEvent : IEvent
|
||||
{
|
||||
public Movie Movie { get; private set; }
|
||||
public MovieScanSkippedReason Reason { get; set; }
|
||||
|
||||
public MovieScanSkippedEvent(Movie movie, MovieScanSkippedReason reason)
|
||||
{
|
||||
Movie = movie;
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MovieScanSkippedReason
|
||||
{
|
||||
RootFolderDoesNotExist,
|
||||
RootFolderIsEmpty,
|
||||
MovieFolderDoesNotExist
|
||||
}
|
||||
}
|
15
src/NzbDrone.Core/MediaFiles/Events/MovieScannedEvent.cs
Normal file
15
src/NzbDrone.Core/MediaFiles/Events/MovieScannedEvent.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public class MovieScannedEvent : IEvent
|
||||
{
|
||||
public Movie Movie { get; private set; }
|
||||
|
||||
public MovieScannedEvent(Movie movie)
|
||||
{
|
||||
Movie = movie;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,11 @@ public interface IMediaFileService
|
||||
void Update(EpisodeFile episodeFile);
|
||||
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
List<EpisodeFile> GetFilesByMovie(int movieId);
|
||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||
List<string> FilterExistingFiles(List<string> files, Series series);
|
||||
List<string> FilterExistingFiles(List<string> files, Movie movie);
|
||||
EpisodeFile Get(int id);
|
||||
List<EpisodeFile> Get(IEnumerable<int> ids);
|
||||
|
||||
@ -64,6 +66,11 @@ public List<EpisodeFile> GetFilesBySeries(int seriesId)
|
||||
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesByMovie(int movieId)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesBySeries(movieId); //TODO: Update implementation for movie files.
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
|
||||
@ -83,6 +90,15 @@ public List<string> FilterExistingFiles(List<string> files, Series series)
|
||||
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList();
|
||||
}
|
||||
|
||||
public List<string> FilterExistingFiles(List<string> files, Movie movie)
|
||||
{
|
||||
var seriesFiles = GetFilesBySeries(movie.Id).Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList();
|
||||
|
||||
if (!seriesFiles.Any()) return files;
|
||||
|
||||
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList();
|
||||
}
|
||||
|
||||
public EpisodeFile Get(int id)
|
||||
{
|
||||
return _mediaFileRepository.Get(id);
|
||||
|
@ -11,6 +11,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||
public interface IMediaFileTableCleanupService
|
||||
{
|
||||
void Clean(Series series, List<string> filesOnDisk);
|
||||
|
||||
void Clean(Movie movie, List<string> filesOnDisk);
|
||||
}
|
||||
|
||||
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
|
||||
@ -84,5 +86,64 @@ public void Clean(Series series, List<string> filesOnDisk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clean(Movie movie, List<string> filesOnDisk)
|
||||
{
|
||||
|
||||
//TODO: Update implementation for movies.
|
||||
var seriesFiles = _mediaFileService.GetFilesBySeries(movie.Id);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(movie.Id);
|
||||
|
||||
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
|
||||
|
||||
foreach (var seriesFile in seriesFiles)
|
||||
{
|
||||
var episodeFile = seriesFile;
|
||||
var episodeFilePath = Path.Combine(movie.Path, episodeFile.RelativePath);
|
||||
|
||||
try
|
||||
{
|
||||
if (!filesOnDiskKeys.Contains(episodeFilePath))
|
||||
{
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (episodes.None(e => e.EpisodeFileId == episodeFile.Id))
|
||||
{
|
||||
_logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes);
|
||||
continue;
|
||||
}
|
||||
|
||||
// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series);
|
||||
//
|
||||
// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count)
|
||||
// {
|
||||
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path);
|
||||
// _mediaFileService.Delete(episodeFile);
|
||||
// continue;
|
||||
// }
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = string.Format("Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id);
|
||||
_logger.Error(ex, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in episodes)
|
||||
{
|
||||
var episode = e;
|
||||
|
||||
if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId))
|
||||
{
|
||||
episode.EpisodeFileId = 0;
|
||||
_episodeService.UpdateEpisode(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -562,6 +562,7 @@
|
||||
<Compile Include="Http\CloudFlare\CloudFlareHttpInterceptor.cs" />
|
||||
<Compile Include="Http\HttpProxySettingsProvider.cs" />
|
||||
<Compile Include="Http\TorcacheHttpInterceptor.cs" />
|
||||
<Compile Include="IndexerSearch\Definitions\MovieSearchCriteria.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTv.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTvSettings.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTvRequestGenerator.cs" />
|
||||
@ -695,6 +696,7 @@
|
||||
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
|
||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
|
||||
@ -737,6 +739,8 @@
|
||||
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieRenamedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieScannedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieScanSkippedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
||||
<Compile Include="MediaFiles\FileDateType.cs" />
|
||||
@ -868,6 +872,7 @@
|
||||
<Compile Include="Parser\IsoLanguage.cs" />
|
||||
<Compile Include="Parser\IsoLanguages.cs" />
|
||||
<Compile Include="Parser\LanguageParser.cs" />
|
||||
<Compile Include="Parser\Model\RemoteMovie.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
||||
@ -1053,6 +1058,7 @@
|
||||
<Compile Include="Tv\Actor.cs" />
|
||||
<Compile Include="Tv\AddSeriesOptions.cs" />
|
||||
<Compile Include="Tv\Commands\MoveSeriesCommand.cs" />
|
||||
<Compile Include="Tv\Commands\RefreshMovieCommand.cs" />
|
||||
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
||||
<Compile Include="Tv\Episode.cs" />
|
||||
<Compile Include="Tv\EpisodeAddedService.cs" />
|
||||
@ -1070,6 +1076,7 @@
|
||||
<Compile Include="Tv\Events\MovieEditedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieRefreshStartingEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieUpdateEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
||||
@ -1077,12 +1084,15 @@
|
||||
<Compile Include="Tv\MoveSeriesService.cs" />
|
||||
<Compile Include="Tv\Ratings.cs" />
|
||||
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
||||
<Compile Include="Tv\RefreshMovieService.cs" />
|
||||
<Compile Include="Tv\RefreshSeriesService.cs" />
|
||||
<Compile Include="Tv\Season.cs" />
|
||||
<Compile Include="Tv\Movie.cs" />
|
||||
<Compile Include="Tv\Series.cs" />
|
||||
<Compile Include="Tv\MovieAddedHandler.cs" />
|
||||
<Compile Include="Tv\SeriesAddedHandler.cs" />
|
||||
<Compile Include="Tv\MovieRepository.cs" />
|
||||
<Compile Include="Tv\MovieEditedService.cs" />
|
||||
<Compile Include="Tv\SeriesScannedHandler.cs" />
|
||||
<Compile Include="Tv\SeriesEditedService.cs" />
|
||||
<Compile Include="Tv\SeriesRepository.cs" />
|
||||
@ -1095,6 +1105,7 @@
|
||||
<Compile Include="Tv\MovieTitleNormalizer.cs" />
|
||||
<Compile Include="Tv\SeriesTitleNormalizer.cs" />
|
||||
<Compile Include="Tv\SeriesTypes.cs" />
|
||||
<Compile Include="Tv\ShouldRefreshMovie.cs" />
|
||||
<Compile Include="Tv\ShouldRefreshSeries.cs" />
|
||||
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />
|
||||
<Compile Include="Update\InstallUpdateService.cs" />
|
||||
|
20
src/NzbDrone.Core/Parser/Model/RemoteMovie.cs
Normal file
20
src/NzbDrone.Core/Parser/Model/RemoteMovie.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Parser.Model
|
||||
{
|
||||
public class RemoteMovie
|
||||
{
|
||||
public ReleaseInfo Release { get; set; }
|
||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } //TODO: Change to ParsedMovieInfo, for now though ParsedEpisodeInfo will do.
|
||||
public Movie Movie { get; set; }
|
||||
public bool DownloadAllowed { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Release.Title;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ public interface IParsingService
|
||||
Series GetSeries(string title);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
|
||||
RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
|
||||
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
||||
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
}
|
||||
@ -27,16 +28,19 @@ public class ParsingService : IParsingService
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ParsingService(IEpisodeService episodeService,
|
||||
ISeriesService seriesService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
_seriesService = seriesService;
|
||||
_sceneMappingService = sceneMappingService;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -134,6 +138,25 @@ public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tv
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
public RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var remoteEpisode = new RemoteMovie
|
||||
{
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
};
|
||||
|
||||
var movie = GetMovie(parsedEpisodeInfo, imdbId, searchCriteria);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
remoteEpisode.Movie = movie;
|
||||
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
||||
{
|
||||
return new RemoteEpisode
|
||||
@ -248,6 +271,39 @@ private ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, Series series)
|
||||
return null;
|
||||
}
|
||||
|
||||
private Movie GetMovie(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
if (searchCriteria.Movie.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle())
|
||||
{
|
||||
return searchCriteria.Movie;
|
||||
}
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace() && imdbId == searchCriteria.Movie.ImdbId)
|
||||
{
|
||||
//TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import
|
||||
return searchCriteria.Movie;
|
||||
}
|
||||
}
|
||||
|
||||
Movie movie = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
||||
|
||||
if (movie == null && imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
//TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import
|
||||
movie = _movieService.FindByImdbId(imdbId);
|
||||
}
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug("No matching movie {0}", parsedEpisodeInfo.SeriesTitle);
|
||||
return null;
|
||||
}
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
Series series = null;
|
||||
|
22
src/NzbDrone.Core/Tv/Commands/RefreshMovieCommand.cs
Normal file
22
src/NzbDrone.Core/Tv/Commands/RefreshMovieCommand.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Tv.Commands
|
||||
{
|
||||
public class RefreshMovieCommand : Command
|
||||
{
|
||||
public int? MovieId { get; set; }
|
||||
|
||||
public RefreshMovieCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public RefreshMovieCommand(int? movieId)
|
||||
{
|
||||
MovieId = movieId;
|
||||
}
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public override bool UpdateScheduledTask => !MovieId.HasValue;
|
||||
}
|
||||
}
|
14
src/NzbDrone.Core/Tv/Events/MovieRefreshStartingEvent.cs
Normal file
14
src/NzbDrone.Core/Tv/Events/MovieRefreshStartingEvent.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Tv.Events
|
||||
{
|
||||
public class MovieRefreshStartingEvent : IEvent
|
||||
{
|
||||
public bool ManualTrigger { get; set; }
|
||||
|
||||
public MovieRefreshStartingEvent(bool manualTrigger)
|
||||
{
|
||||
ManualTrigger = manualTrigger;
|
||||
}
|
||||
}
|
||||
}
|
22
src/NzbDrone.Core/Tv/MovieAddedHandler.cs
Normal file
22
src/NzbDrone.Core/Tv/MovieAddedHandler.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv.Commands;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
public class MovieAddedHandler : IHandle<MovieAddedEvent>
|
||||
{
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
|
||||
public MovieAddedHandler(IManageCommandQueue commandQueueManager)
|
||||
{
|
||||
_commandQueueManager = commandQueueManager;
|
||||
}
|
||||
|
||||
public void Handle(MovieAddedEvent message)
|
||||
{
|
||||
_commandQueueManager.Push(new RefreshMovieCommand(message.Movie.Id));
|
||||
}
|
||||
}
|
||||
}
|
25
src/NzbDrone.Core/Tv/MovieEditedService.cs
Normal file
25
src/NzbDrone.Core/Tv/MovieEditedService.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv.Commands;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
public class MovieEditedService : IHandle<MovieEditedEvent>
|
||||
{
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
|
||||
public MovieEditedService(IManageCommandQueue commandQueueManager)
|
||||
{
|
||||
_commandQueueManager = commandQueueManager;
|
||||
}
|
||||
|
||||
public void Handle(MovieEditedEvent message)
|
||||
{
|
||||
if (message.Movie.ImdbId != message.OldMovie.ImdbId)
|
||||
{
|
||||
_commandQueueManager.Push(new RefreshMovieCommand(message.Movie.Id)); //Probably not needed, as metadata should stay the same.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
144
src/NzbDrone.Core/Tv/RefreshMovieService.cs
Normal file
144
src/NzbDrone.Core/Tv/RefreshMovieService.cs
Normal file
@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
//using NzbDrone.Core.DataAugmentation.DailyMovie;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Tv.Commands;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
public class RefreshMovieService : IExecute<RefreshMovieCommand>
|
||||
{
|
||||
private readonly IProvideMovieInfo _movieInfo;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IRefreshEpisodeService _refreshEpisodeService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshMovieService(IProvideMovieInfo movieInfo,
|
||||
IMovieService movieService,
|
||||
IRefreshEpisodeService refreshEpisodeService,
|
||||
IEventAggregator eventAggregator,
|
||||
IDiskScanService diskScanService,
|
||||
ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed,
|
||||
Logger logger)
|
||||
{
|
||||
_movieInfo = movieInfo;
|
||||
_movieService = movieService;
|
||||
_refreshEpisodeService = refreshEpisodeService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_diskScanService = diskScanService;
|
||||
_checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private void RefreshMovieInfo(Movie movie)
|
||||
{
|
||||
_logger.ProgressInfo("Updating Info for {0}", movie.Title);
|
||||
|
||||
Movie movieInfo;
|
||||
|
||||
try
|
||||
{
|
||||
movieInfo = _movieInfo.GetMovieInfo(movie.ImdbId);
|
||||
}
|
||||
catch (MovieNotFoundException)
|
||||
{
|
||||
_logger.Error("Movie '{0}' (imdbid {1}) was not found, it may have been removed from TheTVDB.", movie.Title, movie.ImdbId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (movie.ImdbId != movieInfo.ImdbId)
|
||||
{
|
||||
_logger.Warn("Movie '{0}' (tvdbid {1}) was replaced with '{2}' (tvdbid {3}), because the original was a duplicate.", movie.Title, movie.ImdbId, movieInfo.Title, movieInfo.ImdbId);
|
||||
movie.ImdbId = movieInfo.ImdbId;
|
||||
}
|
||||
|
||||
movie.Title = movieInfo.Title;
|
||||
movie.TitleSlug = movieInfo.TitleSlug;
|
||||
movie.ImdbId = movieInfo.ImdbId;
|
||||
movie.Overview = movieInfo.Overview;
|
||||
movie.Status = movieInfo.Status;
|
||||
movie.CleanTitle = movieInfo.CleanTitle;
|
||||
movie.SortTitle = movieInfo.SortTitle;
|
||||
movie.LastInfoSync = DateTime.UtcNow;
|
||||
movie.Runtime = movieInfo.Runtime;
|
||||
movie.Images = movieInfo.Images;
|
||||
movie.Ratings = movieInfo.Ratings;
|
||||
movie.Actors = movieInfo.Actors;
|
||||
movie.Genres = movieInfo.Genres;
|
||||
movie.Certification = movieInfo.Certification;
|
||||
movie.InCinemas = movieInfo.InCinemas;
|
||||
movie.Year = movieInfo.Year;
|
||||
|
||||
try
|
||||
{
|
||||
movie.Path = new DirectoryInfo(movie.Path).FullName;
|
||||
movie.Path = movie.Path.GetActualCasing();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Couldn't update movie path for " + movie.Path);
|
||||
}
|
||||
|
||||
_movieService.UpdateMovie(movie);
|
||||
|
||||
_logger.Debug("Finished movie refresh for {0}", movie.Title);
|
||||
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
|
||||
}
|
||||
|
||||
public void Execute(RefreshMovieCommand message)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new MovieRefreshStartingEvent(message.Trigger == CommandTrigger.Manual));
|
||||
|
||||
if (message.MovieId.HasValue)
|
||||
{
|
||||
var movie = _movieService.GetMovie(message.MovieId.Value);
|
||||
RefreshMovieInfo(movie);
|
||||
}
|
||||
else
|
||||
{
|
||||
var allMovie = _movieService.GetAllMovies().OrderBy(c => c.SortTitle).ToList();
|
||||
|
||||
foreach (var movie in allMovie)
|
||||
{
|
||||
if (message.Trigger == CommandTrigger.Manual || _checkIfMovieShouldBeRefreshed.ShouldRefresh(movie))
|
||||
{
|
||||
try
|
||||
{
|
||||
RefreshMovieInfo(movie);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't refresh info for {0}".Inject(movie));
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("Skipping refresh of movie: {0}", movie.Title);
|
||||
_diskScanService.Scan(movie);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't rescan movie {0}".Inject(movie));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
src/NzbDrone.Core/Tv/ShouldRefreshMovie.cs
Normal file
47
src/NzbDrone.Core/Tv/ShouldRefreshMovie.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
public interface ICheckIfMovieShouldBeRefreshed
|
||||
{
|
||||
bool ShouldRefresh(Movie movie);
|
||||
}
|
||||
|
||||
public class ShouldRefreshMovie : ICheckIfMovieShouldBeRefreshed
|
||||
{
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ShouldRefreshMovie(IEpisodeService episodeService, Logger logger)
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool ShouldRefresh(Movie movie)
|
||||
{
|
||||
if (movie.LastInfoSync < DateTime.UtcNow.AddDays(-30))
|
||||
{
|
||||
_logger.Trace("Movie {0} last updated more than 30 days ago, should refresh.", movie.Title);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (movie.LastInfoSync >= DateTime.UtcNow.AddHours(-6))
|
||||
{
|
||||
_logger.Trace("Movie {0} last updated less than 6 hours ago, should not be refreshed.", movie.Title);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (movie.Status != MovieStatusType.TBA)
|
||||
{
|
||||
_logger.Trace("Movie {0} is announced or released, should refresh.", movie.Title); //We probably have to change this.
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.Trace("Movie {0} ended long ago, should not be refreshed.", movie.Title);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -127,4 +127,4 @@ module.exports = Marionette.Layout.extend({
|
||||
this.ui.monitored.removeClass('icon-sonarr-monitored');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -31,6 +31,16 @@ Handlebars.registerHelper('tvdbUrl', function() {
|
||||
return 'http://imdb.com/title/tt' + this.imdbId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('inCinemas', function() {
|
||||
var monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
var cinemasDate = new Date(this.inCinemas);
|
||||
var year = cinemasDate.getFullYear();
|
||||
var month = monthNames[cinemasDate.getMonth()];
|
||||
return "In Cinemas " + month + " " + year;
|
||||
})
|
||||
|
||||
Handlebars.registerHelper('tvRageUrl', function() {
|
||||
return 'http://www.tvrage.com/shows/id-' + this.tvRageId;
|
||||
});
|
||||
|
@ -4,15 +4,15 @@ module.exports = Marionette.ItemView.extend({
|
||||
template : 'Movies/Details/InfoViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
this.episodeFileCollection = options.episodeFileCollection;
|
||||
//this.episodeFileCollection = options.episodeFileCollection;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.episodeFileCollection, 'sync', this.render);
|
||||
//this.listenTo(this.episodeFileCollection, 'sync', this.render); TODO: Update this;
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return {
|
||||
fileCount : this.episodeFileCollection.length
|
||||
fileCount : 0
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -21,10 +21,10 @@
|
||||
<span class="label label-info"> {{fileCount}} files</span>
|
||||
{{/if_eq}}
|
||||
|
||||
{{#if_eq status compare="continuing"}}
|
||||
<span class="label label-info">Continuing</span>
|
||||
{{#if_eq status compare="released"}}
|
||||
<span class="label label-info">{{inCinemas}}</span>
|
||||
{{else}}
|
||||
<span class="label label-default">Ended</span>
|
||||
<span class="label label-default">Announced</span>
|
||||
{{/if_eq}}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@ -36,14 +36,6 @@
|
||||
{{#if imdbId}}
|
||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if tvRageId}}
|
||||
<a href="{{tvRageUrl}}" class="label label-info">TV Rage</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if tvMazeId}}
|
||||
<a href="{{tvMazeUrl}}" class="label label-info">TV Maze</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,6 +9,8 @@ var InfoView = require('./InfoView');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
|
||||
var HistoryLayout = require('../History/MovieHistoryLayout');
|
||||
var SearchLayout = require('../Search/MovieSearchLayout');
|
||||
require('backstrech');
|
||||
require('../../Mixins/backbone.signalr.mixin');
|
||||
|
||||
@ -18,9 +20,12 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
regions : {
|
||||
seasons : '#seasons',
|
||||
info : '#info'
|
||||
info : '#info',
|
||||
search : '#movie-search',
|
||||
history : '#movie-history'
|
||||
},
|
||||
|
||||
|
||||
ui : {
|
||||
header : '.x-header',
|
||||
monitored : '.x-monitored',
|
||||
@ -29,7 +34,9 @@ module.exports = Marionette.Layout.extend({
|
||||
rename : '.x-rename',
|
||||
search : '.x-search',
|
||||
poster : '.x-movie-poster',
|
||||
manualSearch : '.x-manual-search'
|
||||
manualSearch : '.x-manual-search',
|
||||
history : '.x-movie-history',
|
||||
search : '.x-movie-search'
|
||||
},
|
||||
|
||||
events : {
|
||||
@ -39,7 +46,9 @@ module.exports = Marionette.Layout.extend({
|
||||
'click .x-refresh' : '_refreshMovies',
|
||||
'click .x-rename' : '_renameMovies',
|
||||
'click .x-search' : '_moviesSearch',
|
||||
'click .x-manual-search' : '_manualSearchM'
|
||||
'click .x-manual-search' : '_showSearch',
|
||||
'click .x-movie-history' : '_showHistory',
|
||||
'click .x-movie-search' : '_showSearch'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
@ -60,17 +69,21 @@ module.exports = Marionette.Layout.extend({
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.searchLayout = new SearchLayout({ model : this.model });
|
||||
this.searchLayout.startManualSearch = true;
|
||||
|
||||
this._showBackdrop();
|
||||
this._showSeasons();
|
||||
this._setMonitoredState();
|
||||
this._showInfo();
|
||||
this._showHistory();
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.refresh,
|
||||
command : {
|
||||
name : 'refreshMovies'
|
||||
name : 'refreshMovie'
|
||||
}
|
||||
});
|
||||
CommandController.bindToCommand({
|
||||
@ -110,6 +123,26 @@ module.exports = Marionette.Layout.extend({
|
||||
return undefined;
|
||||
},
|
||||
|
||||
_showHistory : function(e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.ui.history.tab('show');
|
||||
this.history.show(new HistoryLayout({
|
||||
model : this.model
|
||||
}));
|
||||
},
|
||||
|
||||
_showSearch : function(e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.ui.search.tab('show');
|
||||
this.search.show(this.searchLayout);
|
||||
},
|
||||
|
||||
_toggleMonitored : function() {
|
||||
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });
|
||||
|
||||
@ -138,8 +171,8 @@ module.exports = Marionette.Layout.extend({
|
||||
},
|
||||
|
||||
_refreshMovies : function() {
|
||||
CommandController.Execute('refreshMovies', {
|
||||
name : 'refreshMovies',
|
||||
CommandController.Execute('refreshMovie', {
|
||||
name : 'refreshMovie',
|
||||
movieId : this.model.id
|
||||
});
|
||||
},
|
||||
@ -198,8 +231,7 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
_showInfo : function() {
|
||||
this.info.show(new InfoView({
|
||||
model : this.model,
|
||||
episodeFileCollection : this.episodeFileCollection
|
||||
model : this.model
|
||||
}));
|
||||
},
|
||||
|
||||
@ -212,9 +244,9 @@ module.exports = Marionette.Layout.extend({
|
||||
},
|
||||
|
||||
_refresh : function() {
|
||||
this.seasonCollection.add(this.model.get('seasons'), { merge : true });
|
||||
this.episodeCollection.fetch();
|
||||
this.episodeFileCollection.fetch();
|
||||
//this.seasonCollection.add(this.model.get('seasons'), { merge : true });
|
||||
//this.episodeCollection.fetch();
|
||||
//this.episodeFileCollection.fetch();
|
||||
|
||||
this._setMonitoredState();
|
||||
this._showInfo();
|
||||
@ -252,11 +284,11 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
_manualSearchM : function() {
|
||||
console.warn("Manual Search started");
|
||||
console.warn(this.model.get("moviesId"));
|
||||
console.warn(this.model.id);
|
||||
console.warn(this.model)
|
||||
console.warn(this.episodeCollection);
|
||||
vent.trigger(vent.Commands.ShowEpisodeDetails, {
|
||||
episode : this.episodeCollection.models[0],
|
||||
episode : this.model,
|
||||
hideMoviesLink : true,
|
||||
openingTab : 'search'
|
||||
});
|
||||
|
@ -35,4 +35,13 @@
|
||||
<div id="info" class="movie-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="seasons"></div>
|
||||
<div id="seasons">
|
||||
<ul class="nav nav-tabs" id="myTab">
|
||||
<li><a href="#movie-history" class="x-movie-history">History</a></li>
|
||||
<li><a href="#movie-search" class="x-movie-search">Search</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane" id="movie-history"/>
|
||||
<div class="tab-pane" id="movie-search"/>
|
||||
</div>
|
||||
</div>
|
||||
|
35
src/UI/Movies/History/MovieHistoryActionsCell.js
Normal file
35
src/UI/Movies/History/MovieHistoryActionsCell.js
Normal file
@ -0,0 +1,35 @@
|
||||
var $ = require('jquery');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'episode-actions-cell',
|
||||
|
||||
events : {
|
||||
'click .x-failed' : '_markAsFailed'
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
if (this.model.get('eventType') === 'grabbed') {
|
||||
this.$el.html('<i class="icon-sonarr-delete x-failed" title="Mark download as failed"></i>');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_markAsFailed : function() {
|
||||
var url = window.NzbDrone.ApiRoot + '/history/failed';
|
||||
var data = {
|
||||
id : this.model.get('id')
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url : url,
|
||||
type : 'POST',
|
||||
data : data
|
||||
});
|
||||
}
|
||||
});
|
28
src/UI/Movies/History/MovieHistoryDetailsCell.js
Normal file
28
src/UI/Movies/History/MovieHistoryDetailsCell.js
Normal file
@ -0,0 +1,28 @@
|
||||
var $ = require('jquery');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var HistoryDetailsView = require('../../Activity/History/Details/HistoryDetailsView');
|
||||
require('bootstrap');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'episode-history-details-cell',
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
this.$el.html('<i class="icon-sonarr-form-info"></i>');
|
||||
|
||||
var html = new HistoryDetailsView({ model : this.model }).render().$el;
|
||||
|
||||
this.$el.popover({
|
||||
content : html,
|
||||
html : true,
|
||||
trigger : 'hover',
|
||||
title : 'Details',
|
||||
placement : 'left',
|
||||
container : this.$el
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
83
src/UI/Movies/History/MovieHistoryLayout.js
Normal file
83
src/UI/Movies/History/MovieHistoryLayout.js
Normal file
@ -0,0 +1,83 @@
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var HistoryCollection = require('../../Activity/History/HistoryCollection');
|
||||
var EventTypeCell = require('../../Cells/EventTypeCell');
|
||||
var QualityCell = require('../../Cells/QualityCell');
|
||||
var RelativeDateCell = require('../../Cells/RelativeDateCell');
|
||||
var EpisodeHistoryActionsCell = require('./MovieHistoryActionsCell');
|
||||
var EpisodeHistoryDetailsCell = require('./MovieHistoryDetailsCell');
|
||||
var NoHistoryView = require('./NoHistoryView');
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Movies/History/MovieHistoryLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
historyTable : '.history-table'
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : 'eventType',
|
||||
label : '',
|
||||
cell : EventTypeCell,
|
||||
cellValue : 'this'
|
||||
},
|
||||
{
|
||||
name : 'sourceTitle',
|
||||
label : 'Source Title',
|
||||
cell : 'string'
|
||||
},
|
||||
{
|
||||
name : 'quality',
|
||||
label : 'Quality',
|
||||
cell : QualityCell
|
||||
},
|
||||
{
|
||||
name : 'date',
|
||||
label : 'Date',
|
||||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
cell : EpisodeHistoryDetailsCell,
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
cell : EpisodeHistoryActionsCell,
|
||||
sortable : false
|
||||
}
|
||||
],
|
||||
|
||||
initialize : function(options) {
|
||||
this.model = options.model;
|
||||
|
||||
this.collection = new HistoryCollection({
|
||||
episodeId : this.model.id,
|
||||
tableName : 'episodeHistory'
|
||||
});
|
||||
this.collection.fetch();
|
||||
this.listenTo(this.collection, 'sync', this._showTable);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this.historyTable.show(new LoadingView());
|
||||
},
|
||||
|
||||
_showTable : function() {
|
||||
if (this.collection.any()) {
|
||||
this.historyTable.show(new Backgrid.Grid({
|
||||
collection : this.collection,
|
||||
columns : this.columns,
|
||||
className : 'table table-hover table-condensed'
|
||||
}));
|
||||
}
|
||||
|
||||
else {
|
||||
this.historyTable.show(new NoHistoryView());
|
||||
}
|
||||
}
|
||||
});
|
1
src/UI/Movies/History/MovieHistoryLayoutTemplate.hbs
Normal file
1
src/UI/Movies/History/MovieHistoryLayoutTemplate.hbs
Normal file
@ -0,0 +1 @@
|
||||
<div class="history-table table-responsive"></div>
|
5
src/UI/Movies/History/NoHistoryView.js
Normal file
5
src/UI/Movies/History/NoHistoryView.js
Normal file
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Movies/History/NoHistoryViewTemplate'
|
||||
});
|
3
src/UI/Movies/History/NoHistoryViewTemplate.hbs
Normal file
3
src/UI/Movies/History/NoHistoryViewTemplate.hbs
Normal file
@ -0,0 +1,3 @@
|
||||
<p class="text-warning">
|
||||
No history for this episode.
|
||||
</p>
|
@ -16,7 +16,7 @@ module.exports = Marionette.ItemView.extend({
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.refresh,
|
||||
command : {
|
||||
name : 'refreshSeries',
|
||||
name : 'refreshMovie',
|
||||
seriesId : this.model.get('id')
|
||||
}
|
||||
});
|
||||
@ -27,9 +27,9 @@ module.exports = Marionette.ItemView.extend({
|
||||
},
|
||||
|
||||
_refreshSeries : function() {
|
||||
CommandController.Execute('refreshSeries', {
|
||||
name : 'refreshSeries',
|
||||
seriesId : this.model.id
|
||||
CommandController.Execute('refreshMovie', {
|
||||
name : 'refreshMovie',
|
||||
movieId : this.model.id
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -103,7 +103,7 @@ module.exports = Marionette.Layout.extend({
|
||||
{
|
||||
title : 'Update Library',
|
||||
icon : 'icon-sonarr-refresh',
|
||||
command : 'refreshseries',
|
||||
command : 'refreshmovie',
|
||||
successMessage : 'Library was updated!',
|
||||
errorMessage : 'Library update failed!'
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
<div class="center">
|
||||
<div class="series-poster-container x-series-poster-container">
|
||||
<div class="series-controls x-series-controls">
|
||||
<i class="icon-sonarr-refresh x-refresh" title="Refresh Series"/>
|
||||
<i class="icon-sonarr-edit x-edit" title="Edit Series"/>
|
||||
<i class="icon-sonarr-refresh x-refresh" title="Refresh Movie"/>
|
||||
<i class="icon-sonarr-edit x-edit" title="Edit Movie"/>
|
||||
</div>
|
||||
{{#unless_eq status compare="continuing"}}
|
||||
<div class="ended-banner">Ended</div>
|
||||
{{#unless_eq status compare="released"}}
|
||||
<div class="ended-banner">Released</div>
|
||||
{{/unless_eq}}
|
||||
<a href="{{route}}">
|
||||
{{poster}}
|
||||
|
@ -3,6 +3,7 @@ var AppLayout = require('../AppLayout');
|
||||
var MoviesCollection = require('./MoviesCollection');
|
||||
var MoviesIndexLayout = require('./Index/MoviesIndexLayout');
|
||||
var MoviesDetailsLayout = require('./Details/MoviesDetailsLayout');
|
||||
var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout');
|
||||
|
||||
module.exports = NzbDroneController.extend({
|
||||
_originalInit : NzbDroneController.prototype.initialize,
|
||||
@ -22,10 +23,13 @@ module.exports = NzbDroneController.extend({
|
||||
|
||||
seriesDetails : function(query) {
|
||||
var series = MoviesCollection.where({ titleSlug : query });
|
||||
|
||||
if (series.length !== 0) {
|
||||
var targetMovie = series[0];
|
||||
console.log(AppLayout.mainRegion);
|
||||
|
||||
this.setTitle(targetMovie.get('title'));
|
||||
//this.showNotFound();
|
||||
//this.showMainRegion(new SeriesDetailsLayout({model : targetMovie}));
|
||||
this.showMainRegion(new MoviesDetailsLayout({ model : targetMovie }));
|
||||
} else {
|
||||
this.showNotFound();
|
||||
|
5
src/UI/Movies/Search/ButtonsView.js
Normal file
5
src/UI/Movies/Search/ButtonsView.js
Normal file
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Movies/Search/ButtonsViewTemplate'
|
||||
});
|
4
src/UI/Movies/Search/ButtonsViewTemplate.hbs
Normal file
4
src/UI/Movies/Search/ButtonsViewTemplate.hbs
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="search-buttons">
|
||||
<button class="btn btn-lg btn-block x-search-auto"><i class="icon-sonarr-search-automatic"/> Automatic Search</button>
|
||||
<button class="btn btn-lg btn-block btn-primary x-search-manual"><i class="icon-sonarr-search-manual"/> Manual Search</button>
|
||||
</div>
|
86
src/UI/Movies/Search/ManualLayout.js
Normal file
86
src/UI/Movies/Search/ManualLayout.js
Normal file
@ -0,0 +1,86 @@
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var ReleaseTitleCell = require('../../Cells/ReleaseTitleCell');
|
||||
var FileSizeCell = require('../../Cells/FileSizeCell');
|
||||
var QualityCell = require('../../Cells/QualityCell');
|
||||
var ApprovalStatusCell = require('../../Cells/ApprovalStatusCell');
|
||||
var DownloadReportCell = require('../../Release/DownloadReportCell');
|
||||
var AgeCell = require('../../Release/AgeCell');
|
||||
var ProtocolCell = require('../../Release/ProtocolCell');
|
||||
var PeersCell = require('../../Release/PeersCell');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Movies/Search/ManualLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
grid : '#episode-release-grid'
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : 'protocol',
|
||||
label : 'Source',
|
||||
cell : ProtocolCell
|
||||
},
|
||||
{
|
||||
name : 'age',
|
||||
label : 'Age',
|
||||
cell : AgeCell
|
||||
},
|
||||
{
|
||||
name : 'title',
|
||||
label : 'Title',
|
||||
cell : ReleaseTitleCell
|
||||
},
|
||||
{
|
||||
name : 'indexer',
|
||||
label : 'Indexer',
|
||||
cell : Backgrid.StringCell
|
||||
},
|
||||
{
|
||||
name : 'size',
|
||||
label : 'Size',
|
||||
cell : FileSizeCell
|
||||
},
|
||||
{
|
||||
name : 'seeders',
|
||||
label : 'Peers',
|
||||
cell : PeersCell
|
||||
},
|
||||
{
|
||||
name : 'quality',
|
||||
label : 'Quality',
|
||||
cell : QualityCell
|
||||
},
|
||||
{
|
||||
name : 'rejections',
|
||||
label : '<i class="icon-sonarr-header-rejections" />',
|
||||
tooltip : 'Rejections',
|
||||
cell : ApprovalStatusCell,
|
||||
sortable : true,
|
||||
sortType : 'fixed',
|
||||
direction : 'ascending',
|
||||
title : 'Release Rejected'
|
||||
},
|
||||
{
|
||||
name : 'download',
|
||||
label : '<i class="icon-sonarr-download" />',
|
||||
tooltip : 'Auto-Search Prioritization',
|
||||
cell : DownloadReportCell,
|
||||
sortable : true,
|
||||
sortType : 'fixed',
|
||||
direction : 'ascending'
|
||||
}
|
||||
],
|
||||
|
||||
onShow : function() {
|
||||
if (!this.isClosed) {
|
||||
this.grid.show(new Backgrid.Grid({
|
||||
row : Backgrid.Row,
|
||||
columns : this.columns,
|
||||
collection : this.collection,
|
||||
className : 'table table-hover'
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
2
src/UI/Movies/Search/ManualLayoutTemplate.hbs
Normal file
2
src/UI/Movies/Search/ManualLayoutTemplate.hbs
Normal file
@ -0,0 +1,2 @@
|
||||
<div id="episode-release-grid" class="table-responsive"></div>
|
||||
<button class="btn x-search-back">Back</button>
|
82
src/UI/Movies/Search/MovieSearchLayout.js
Normal file
82
src/UI/Movies/Search/MovieSearchLayout.js
Normal file
@ -0,0 +1,82 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var ButtonsView = require('./ButtonsView');
|
||||
var ManualSearchLayout = require('./ManualLayout');
|
||||
var ReleaseCollection = require('../../Release/ReleaseCollection');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
var NoResultsView = require('./NoResultsView');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Movies/Search/MovieSearchLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
main : '#episode-search-region'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-search-auto' : '_searchAuto',
|
||||
'click .x-search-manual' : '_searchManual',
|
||||
'click .x-search-back' : '_showButtons'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.mainView = new ButtonsView();
|
||||
this.releaseCollection = new ReleaseCollection();
|
||||
|
||||
this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
if (this.startManualSearch) {
|
||||
this._searchManual();
|
||||
}
|
||||
|
||||
else {
|
||||
this._showMainView();
|
||||
}
|
||||
},
|
||||
|
||||
_searchAuto : function(e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
CommandController.Execute('episodeSearch', {
|
||||
episodeIds : [this.model.get('id')]
|
||||
});
|
||||
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
},
|
||||
|
||||
_searchManual : function(e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.mainView = new LoadingView();
|
||||
this._showMainView();
|
||||
this.releaseCollection.fetchMovieReleases(this.model.id);
|
||||
},
|
||||
|
||||
_showMainView : function() {
|
||||
this.main.show(this.mainView);
|
||||
},
|
||||
|
||||
_showButtons : function() {
|
||||
this.mainView = new ButtonsView();
|
||||
this._showMainView();
|
||||
},
|
||||
|
||||
_showSearchResults : function() {
|
||||
if (this.releaseCollection.length === 0) {
|
||||
this.mainView = new NoResultsView();
|
||||
}
|
||||
|
||||
else {
|
||||
this.mainView = new ManualSearchLayout({ collection : this.releaseCollection });
|
||||
}
|
||||
|
||||
this._showMainView();
|
||||
}
|
||||
});
|
1
src/UI/Movies/Search/MovieSearchLayoutTemplate.hbs
Normal file
1
src/UI/Movies/Search/MovieSearchLayoutTemplate.hbs
Normal file
@ -0,0 +1 @@
|
||||
<div id="episode-search-region"></div>
|
5
src/UI/Movies/Search/NoResultsView.js
Normal file
5
src/UI/Movies/Search/NoResultsView.js
Normal file
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Movies/Search/NoResultsViewTemplate'
|
||||
});
|
1
src/UI/Movies/Search/NoResultsViewTemplate.hbs
Normal file
1
src/UI/Movies/Search/NoResultsViewTemplate.hbs
Normal file
@ -0,0 +1 @@
|
||||
<div>No results found</div>
|
@ -2,7 +2,7 @@ var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var vent = require('vent');
|
||||
var Backbone = require('backbone');
|
||||
var SeriesCollection = require('../Series/SeriesCollection');
|
||||
var SeriesCollection = require('../Movies/MoviesCollection');
|
||||
require('typeahead');
|
||||
|
||||
vent.on(vent.Hotkeys.NavbarSearch, function() {
|
||||
@ -32,6 +32,6 @@ $.fn.bindSearch = function() {
|
||||
$(this).on('typeahead:selected typeahead:autocompleted', function(e, series) {
|
||||
this.blur();
|
||||
$(this).val('');
|
||||
Backbone.history.navigate('/series/{0}'.format(series.titleSlug), { trigger : true });
|
||||
Backbone.history.navigate('/movies/{0}'.format(series.titleSlug), { trigger : true });
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -48,9 +48,14 @@ var Collection = PagableCollection.extend({
|
||||
|
||||
fetchEpisodeReleases : function(episodeId) {
|
||||
return this.fetch({ data : { episodeId : episodeId } });
|
||||
},
|
||||
|
||||
fetchMovieReleases : function(movieId) {
|
||||
return this.fetch({ data : { movieId : movieId}});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Collection = AsSortedCollection.call(Collection);
|
||||
|
||||
module.exports = Collection;
|
||||
module.exports = Collection;
|
||||
|
@ -8,10 +8,10 @@ module.exports = NzbDroneController.extend({
|
||||
_originalInit : NzbDroneController.prototype.initialize,
|
||||
|
||||
initialize : function() {
|
||||
this.route('', this.series);
|
||||
//this.route('', this.series);
|
||||
this.route('series', this.series);
|
||||
this.route('series/:query', this.seriesDetails);
|
||||
|
||||
|
||||
this._originalInit.apply(this, arguments);
|
||||
},
|
||||
|
||||
@ -21,10 +21,13 @@ module.exports = NzbDroneController.extend({
|
||||
},
|
||||
|
||||
seriesDetails : function(query) {
|
||||
console.warn(AppLayout.mainRegion)
|
||||
|
||||
var series = SeriesCollection.where({ titleSlug : query });
|
||||
|
||||
|
||||
if (series.length !== 0) {
|
||||
var targetSeries = series[0];
|
||||
|
||||
this.setTitle(targetSeries.get('title'));
|
||||
this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries }));
|
||||
} else {
|
||||
|
@ -19,6 +19,7 @@ module.exports = Marionette.AppRouter.extend({
|
||||
vent.on(vent.Commands.EditSeriesCommand, this._editSeries, this);
|
||||
vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this);
|
||||
vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this);
|
||||
vent.on(vent.Commands.ShowMovieDetails, this._showMovie, this);
|
||||
vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this);
|
||||
vent.on(vent.Commands.ShowLogDetails, this._showLogDetails, this);
|
||||
vent.on(vent.Commands.ShowRenamePreview, this._showRenamePreview, this);
|
||||
@ -62,6 +63,15 @@ module.exports = Marionette.AppRouter.extend({
|
||||
AppLayout.modalRegion.show(view);
|
||||
},
|
||||
|
||||
_showMovie : function(options) {
|
||||
var view = new MoviesDetailsLayout({
|
||||
model : options.movie,
|
||||
hideSeriesLink : options.hideSeriesLink,
|
||||
openingTab : options.openingTab
|
||||
});
|
||||
AppLayout.modalRegion.show(view);
|
||||
},
|
||||
|
||||
_showHistory : function(options) {
|
||||
var view = new HistoryDetailsLayout({ model : options.model });
|
||||
AppLayout.modalRegion.show(view);
|
||||
@ -90,4 +100,4 @@ module.exports = Marionette.AppRouter.extend({
|
||||
_closeFileBrowser : function() {
|
||||
AppLayout.modalRegion2.closeModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -5,7 +5,8 @@ var RouteBinder = require('./jQuery/RouteBinder');
|
||||
var SignalRBroadcaster = require('./Shared/SignalRBroadcaster');
|
||||
var NavbarLayout = require('./Navbar/NavbarLayout');
|
||||
var AppLayout = require('./AppLayout');
|
||||
var SeriesController = require('./Movies/MoviesController');
|
||||
var MoviesController = require('./Movies/MoviesController');
|
||||
var SeriesController = require('./Series/SeriesController');
|
||||
var Router = require('./Router');
|
||||
var ModalController = require('./Shared/Modal/ModalController');
|
||||
var ControlPanelController = require('./Shared/ControlPanel/ControlPanelController');
|
||||
@ -20,6 +21,7 @@ require('./Hotkeys/Hotkeys');
|
||||
require('./Shared/piwikCheck');
|
||||
require('./Shared/VersionChangeMonitor');
|
||||
|
||||
new MoviesController();
|
||||
new SeriesController();
|
||||
new ModalController();
|
||||
new ControlPanelController();
|
||||
|
@ -18,6 +18,7 @@ vent.Commands = {
|
||||
OpenModal2Command : 'OpenModal2Command',
|
||||
CloseModal2Command : 'CloseModal2Command',
|
||||
ShowEpisodeDetails : 'ShowEpisodeDetails',
|
||||
ShowMovieDetails : 'ShowMovieDetails',
|
||||
ShowHistoryDetails : 'ShowHistoryDetails',
|
||||
ShowLogDetails : 'ShowLogDetails',
|
||||
SaveSettings : 'saveSettings',
|
||||
@ -36,4 +37,4 @@ vent.Hotkeys = {
|
||||
ShowHotkeys : 'hotkeys:show'
|
||||
};
|
||||
|
||||
module.exports = vent;
|
||||
module.exports = vent;
|
||||
|
Loading…
Reference in New Issue
Block a user