1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-05 02:22:31 +01: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:
Leonardo Galli 2017-01-02 18:05:55 +01:00
parent 16e35f68bb
commit 2a3b0304cb
76 changed files with 1509 additions and 74 deletions

View File

@ -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();

View File

@ -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,

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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())
{

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -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);

View File

@ -0,0 +1,11 @@
namespace NzbDrone.Core.IndexerSearch.Definitions
{
public class MovieSearchCriteria : SearchCriteriaBase
{
public override string ToString()
{
return string.Format("[{0}]", Movie.Title);
}
}
}

View File

@ -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; }

View File

@ -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();

View File

@ -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();

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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>();

View File

@ -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);
}
}

View File

@ -10,5 +10,6 @@ public interface IIndexerRequestGenerator
IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria);
IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria);
IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria);
IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria);
}
}

View File

@ -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();

View File

@ -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)
{

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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");
}
}
}
}

View File

@ -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

View 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;
}
}
}

View File

@ -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)

View File

@ -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))

View 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
}
}

View 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;
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}
}
}

View File

@ -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" />

View 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;
}
}
}

View File

@ -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;

View 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;
}
}

View 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;
}
}
}

View 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));
}
}
}

View 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.
}
}
}
}

View 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));
}
}
}
}
}
}
}

View 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;
}
}
}

View File

@ -127,4 +127,4 @@ module.exports = Marionette.Layout.extend({
this.ui.monitored.removeClass('icon-sonarr-monitored');
}
}
});
});

View File

@ -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;
});

View File

@ -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
};
}
});
});

View File

@ -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>

View File

@ -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'
});

View File

@ -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>

View 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
});
}
});

View 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;
}
});

View 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());
}
}
});

View File

@ -0,0 +1 @@
<div class="history-table table-responsive"></div>

View File

@ -0,0 +1,5 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Movies/History/NoHistoryViewTemplate'
});

View File

@ -0,0 +1,3 @@
<p class="text-warning">
No history for this episode.
</p>

View File

@ -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
});
}
});
});

View File

@ -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!'
}

View File

@ -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}}

View File

@ -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();

View File

@ -0,0 +1,5 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Movies/Search/ButtonsViewTemplate'
});

View 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>

View 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'
}));
}
}
});

View File

@ -0,0 +1,2 @@
<div id="episode-release-grid" class="table-responsive"></div>
<button class="btn x-search-back">Back</button>

View 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();
}
});

View File

@ -0,0 +1 @@
<div id="episode-search-region"></div>

View File

@ -0,0 +1,5 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Movies/Search/NoResultsViewTemplate'
});

View File

@ -0,0 +1 @@
<div>No results found</div>

View File

@ -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 });
});
};
};

View File

@ -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;

View File

@ -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 {

View File

@ -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();
}
});
});

View File

@ -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();

View File

@ -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;