Changed: Alternative Titles were reworked greatly. This should speed up RSS Sync massively, especially for large libraries (up to 4x).

This commit is contained in:
Leonardo Galli 2017-08-09 22:14:01 +02:00 committed by GitHub
parent 8927e7c2c6
commit cfcb66fba1
63 changed files with 1480 additions and 100 deletions

View File

@ -1,4 +1,5 @@
using System.Text;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using Marr.Data.QGen.Dialects;
@ -129,7 +130,16 @@ namespace Marr.Data.QGen
public void BuildOrderClause(StringBuilder sql)
{
sql.Append(OrderBy.ToString());
}
}
public void BuildGroupBy(StringBuilder sql)
{
var baseTable = this.Tables.First();
var primaryKeyColumn = baseTable.Columns.Single(c => c.ColumnInfo.IsPrimaryKey);
string token = this.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", primaryKeyColumn.ColumnInfo.Name));
sql.AppendFormat(" GROUP BY {0}", token);
}
private string TranslateJoin(JoinType join)
{

View File

@ -14,8 +14,21 @@ namespace Marr.Data.QGen
public string Generate()
{
StringBuilder sql = new StringBuilder();
BuildSelectCountClause(sql);
if (_innerQuery.IsJoin)
{
sql.Append(" FROM (");
_innerQuery.BuildSelectClause(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildGroupBy(sql);
sql.Append(") ");
return sql.ToString();
}
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);

View File

@ -46,7 +46,7 @@ namespace NzbDrone.Api.Indexers
GetResourceAll = GetReleases;
Post["/"] = x => DownloadRelease(this.Bind<ReleaseResource>());
PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
//PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
PostValidator.RuleFor(s => s.Guid).NotEmpty();
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
@ -70,7 +70,7 @@ namespace NzbDrone.Api.Indexers
try
{
_downloadService.DownloadReport(remoteMovie);
_downloadService.DownloadReport(remoteMovie, false);
}
catch (ReleaseDownloadException ex)
{

View File

@ -8,6 +8,7 @@ using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.DecisionEngine;
using System.Linq;
using NzbDrone.Core.Datastore.Migration;
namespace NzbDrone.Api.Indexers
{
@ -29,8 +30,8 @@ namespace NzbDrone.Api.Indexers
public bool FullSeason { get; set; }
public int SeasonNumber { get; set; }
public Language Language { get; set; }
public string AirDate { get; set; }
public string SeriesTitle { get; set; }
public int Year { get; set; }
public string MovieTitle { get; set; }
public int[] EpisodeNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; }
public bool Approved { get; set; }
@ -43,8 +44,9 @@ namespace NzbDrone.Api.Indexers
public string CommentUrl { get; set; }
public string DownloadUrl { get; set; }
public string InfoUrl { get; set; }
public bool DownloadAllowed { get; set; }
public MappingResultType MappingResult { get; set; }
public int ReleaseWeight { get; set; }
public int SuspectedMovieId { get; set; }
public IEnumerable<string> IndexerFlags { get; set; }
@ -88,11 +90,12 @@ namespace NzbDrone.Api.Indexers
var parsedEpisodeInfo = model.RemoteEpisode.ParsedEpisodeInfo;
var remoteEpisode = model.RemoteEpisode;
var torrentInfo = (model.RemoteEpisode.Release as TorrentInfo) ?? new TorrentInfo();
var downloadAllowed = model.RemoteEpisode.DownloadAllowed;
var mappingResult = MappingResultType.Success;
if (model.IsForMovie)
{
downloadAllowed = model.RemoteMovie.DownloadAllowed;
mappingResult = model.RemoteMovie.MappingResult;
var parsedMovieInfo = model.RemoteMovie.ParsedMovieInfo;
var movieId = model.RemoteMovie.Movie?.Id ?? 0;
return new ReleaseResource
{
@ -111,8 +114,8 @@ namespace NzbDrone.Api.Indexers
//FullSeason = parsedMovieInfo.FullSeason,
//SeasonNumber = parsedMovieInfo.SeasonNumber,
Language = parsedMovieInfo.Language,
AirDate = "",
SeriesTitle = parsedMovieInfo.MovieTitle,
Year = parsedMovieInfo.Year,
MovieTitle = parsedMovieInfo.MovieTitle,
EpisodeNumbers = new int[0],
AbsoluteEpisodeNumbers = new int[0],
Approved = model.Approved,
@ -125,8 +128,10 @@ namespace NzbDrone.Api.Indexers
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = downloadAllowed,
MappingResult = mappingResult,
//ReleaseWeight
SuspectedMovieId = movieId,
MagnetUrl = torrentInfo.MagnetUrl,
InfoHash = torrentInfo.InfoHash,
@ -161,8 +166,8 @@ namespace NzbDrone.Api.Indexers
FullSeason = parsedEpisodeInfo.FullSeason,
SeasonNumber = parsedEpisodeInfo.SeasonNumber,
Language = parsedEpisodeInfo.Language,
AirDate = parsedEpisodeInfo.AirDate,
SeriesTitle = parsedEpisodeInfo.SeriesTitle,
//AirDate = parsedEpisodeInfo.AirDate,
//SeriesTitle = parsedEpisodeInfo.SeriesTitle,
EpisodeNumbers = parsedEpisodeInfo.EpisodeNumbers,
AbsoluteEpisodeNumbers = parsedEpisodeInfo.AbsoluteEpisodeNumbers,
Approved = model.Approved,
@ -175,7 +180,7 @@ namespace NzbDrone.Api.Indexers
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = downloadAllowed,
//DownloadAllowed = downloadAllowed,
//ReleaseWeight
MagnetUrl = torrentInfo.MagnetUrl,

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Nancy;
using NzbDrone.Api;
using NzbDrone.Api.Movie;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Api.Movie
{
public class AlternativeTitleModule : NzbDroneRestModule<AlternativeTitleResource>
{
private readonly IAlternativeTitleService _altTitleService;
private readonly IMovieService _movieService;
private readonly IRadarrAPIClient _radarrApi;
private readonly IEventAggregator _eventAggregator;
public AlternativeTitleModule(IAlternativeTitleService altTitleService, IMovieService movieService, IRadarrAPIClient radarrApi, IEventAggregator eventAggregator)
: base("/alttitle")
{
_altTitleService = altTitleService;
_movieService = movieService;
_radarrApi = radarrApi;
CreateResource = AddTitle;
GetResourceById = GetTitle;
_eventAggregator = eventAggregator;
}
private int AddTitle(AlternativeTitleResource altTitle)
{
var title = altTitle.ToModel();
var movie = _movieService.GetMovie(altTitle.MovieId);
var newTitle = _radarrApi.AddNewAlternativeTitle(title, movie.TmdbId);
var addedTitle = _altTitleService.AddAltTitle(newTitle, movie);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
return addedTitle.Id;
}
private AlternativeTitleResource GetTitle(int id)
{
return _altTitleService.GetById(id).ToResource();
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Nancy;
using NzbDrone.Api;
using NzbDrone.Api.Movie;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Api.Movie
{
public class AlternativeYearModule : NzbDroneRestModule<AlternativeYearResource>
{
private readonly IMovieService _movieService;
private readonly IRadarrAPIClient _radarrApi;
private readonly ICached<int> _yearCache;
private readonly IEventAggregator _eventAggregator;
public AlternativeYearModule(IMovieService movieService, IRadarrAPIClient radarrApi, ICacheManager cacheManager, IEventAggregator eventAggregator)
: base("/altyear")
{
_movieService = movieService;
_radarrApi = radarrApi;
CreateResource = AddYear;
GetResourceById = GetYear;
_yearCache = cacheManager.GetCache<int>(GetType(), "altYears");
_eventAggregator = eventAggregator;
}
private int AddYear(AlternativeYearResource altYear)
{
var id = new Random().Next();
_yearCache.Set(id.ToString(), altYear.Year, TimeSpan.FromMinutes(1));
var movie = _movieService.GetMovie(altYear.MovieId);
var newYear = _radarrApi.AddNewAlternativeYear(altYear.Year, movie.TmdbId);
movie.SecondaryYear = newYear.Year;
movie.SecondaryYearSourceId = newYear.SourceId;
_movieService.UpdateMovie(movie);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
return id;
}
private AlternativeYearResource GetYear(int id)
{
return new AlternativeYearResource
{
Year = _yearCache.Find(id.ToString())
};
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Movie
{
public class AlternativeYearResource : RestResource
{
public AlternativeYearResource()
{
}
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire Profile instead of ID and Name separately
public int MovieId { get; set; }
public int Year { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
}
/*public static class AlternativeYearResourceMapper
{
/*public static AlternativeYearResource ToResource(this AlternativeTitle model)
{
if (model == null) return null;
AlternativeTitleResource resource = null;
return new AlternativeTitleResource
{
Id = model.Id,
SourceType = model.SourceType,
MovieId = model.MovieId,
Title = model.Title,
SourceId = model.SourceId,
Votes = model.Votes,
VoteCount = model.VoteCount,
Language = model.Language
};
}
public static AlternativeTitle ToModel(this AlternativeTitleResource resource)
{
if (resource == null) return null;
return new AlternativeTitle
{
Id = resource.Id,
SourceType = resource.SourceType,
MovieId = resource.MovieId,
Title = resource.Title,
SourceId = resource.SourceId,
Votes = resource.Votes,
VoteCount = resource.VoteCount,
Language = resource.Language
};
}
public static List<AlternativeTitleResource> ToResource(this IEnumerable<AlternativeTitle> movies)
{
return movies.Select(ToResource).ToList();
}
}*/
}

View File

@ -119,6 +119,9 @@
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" />
<Compile Include="Indexers\ReleasePushModule.cs" />
<Compile Include="Movies\AlternativeTitleModule.cs" />
<Compile Include="Movies\AlternativeYearResource.cs" />
<Compile Include="Movies\AlternativeYearModule.cs" />
<Compile Include="Movies\MovieModuleWithSignalR.cs" />
<Compile Include="Movies\MovieBulkImportModule.cs" />
<Compile Include="Movies\MovieFileModule.cs" />
@ -240,7 +243,7 @@
<Compile Include="RootFolders\RootFolderModule.cs" />
<Compile Include="RootFolders\RootFolderResource.cs" />
<Compile Include="SeasonPass\SeasonPassResource.cs" />
<Compile Include="Series\AlternateTitleResource.cs" />
<Compile Include="Series\AlternativeTitleResource.cs" />
<Compile Include="Series\MovieFileResource.cs" />
<Compile Include="Series\FetchMovieListModule.cs" />
<Compile Include="Series\SeasonResource.cs" />

View File

@ -105,7 +105,7 @@ namespace NzbDrone.Api.Queue
throw new NotFoundException();
}
_downloadService.DownloadReport(pendingRelease.RemoteMovie);
_downloadService.DownloadReport(pendingRelease.RemoteMovie, false);
return resource.AsResponse();
}

View File

@ -1,9 +0,0 @@
namespace NzbDrone.Api.Series
{
public class AlternateTitleResource
{
public string Title { get; set; }
public int? SeasonNumber { get; set; }
public int? SceneSeasonNumber { get; set; }
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Movie
{
public class AlternativeTitleResource : RestResource
{
public AlternativeTitleResource()
{
}
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire Profile instead of ID and Name separately
public SourceType SourceType { get; set; }
public int MovieId { get; set; }
public string Title { get; set; }
public string CleanTitle { get; set; }
public int SourceId { get; set; }
public int Votes { get; set; }
public int VoteCount { get; set; }
public Language Language { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
}
public static class AlternativeTitleResourceMapper
{
public static AlternativeTitleResource ToResource(this AlternativeTitle model)
{
if (model == null) return null;
AlternativeTitleResource resource = null;
return new AlternativeTitleResource
{
Id = model.Id,
SourceType = model.SourceType,
MovieId = model.MovieId,
Title = model.Title,
SourceId = model.SourceId,
Votes = model.Votes,
VoteCount = model.VoteCount,
Language = model.Language
};
}
public static AlternativeTitle ToModel(this AlternativeTitleResource resource)
{
if (resource == null) return null;
return new AlternativeTitle
{
Id = resource.Id,
SourceType = resource.SourceType,
MovieId = resource.MovieId,
Title = resource.Title,
SourceId = resource.SourceId,
Votes = resource.Votes,
VoteCount = resource.VoteCount,
Language = resource.Language
};
}
public static List<AlternativeTitleResource> ToResource(this IEnumerable<AlternativeTitle> movies)
{
return movies.Select(ToResource).ToList();
}
}
}

View File

@ -21,7 +21,9 @@ namespace NzbDrone.Api.Movie
//View Only
public string Title { get; set; }
public List<AlternateTitleResource> AlternateTitles { get; set; }
public List<AlternativeTitleResource> AlternativeTitles { get; set; }
public int? SecondaryYear { get; set; }
public int SecondaryYearSourceId { get; set; }
public string SortTitle { get; set; }
public long? SizeOnDisk { get; set; }
public MovieStatusType Status { get; set; }
@ -62,7 +64,7 @@ namespace NzbDrone.Api.Movie
public DateTime Added { get; set; }
public AddMovieOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
public List<string> AlternativeTitles { get; set; }
//public List<string> AlternativeTitles { get; set; }
public MovieFileResource MovieFile { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
@ -107,6 +109,8 @@ namespace NzbDrone.Api.Movie
downloaded = true;
movieFile = model.MovieFile.Value.ToResource();
}
//model.AlternativeTitles.LazyLoad();
return new MovieResource
{
@ -131,6 +135,8 @@ namespace NzbDrone.Api.Movie
Images = model.Images,
Year = model.Year,
SecondaryYear = model.SecondaryYear,
SecondaryYearSourceId = model.SecondaryYearSourceId,
Path = model.Path,
ProfileId = model.ProfileId,
@ -156,7 +162,7 @@ namespace NzbDrone.Api.Movie
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,
AlternativeTitles = model.AlternativeTitles,
AlternativeTitles = model.AlternativeTitles.ToResource(),
Ratings = model.Ratings,
MovieFile = movieFile,
YouTubeTrailerId = model.YouTubeTrailerId,
@ -189,6 +195,8 @@ namespace NzbDrone.Api.Movie
Images = resource.Images,
Year = resource.Year,
SecondaryYear = resource.SecondaryYear,
SecondaryYearSourceId = resource.SecondaryYearSourceId,
Path = resource.Path,
ProfileId = resource.ProfileId,
@ -209,7 +217,7 @@ namespace NzbDrone.Api.Movie
Tags = resource.Tags,
Added = resource.Added,
AddOptions = resource.AddOptions,
AlternativeTitles = resource.AlternativeTitles,
//AlternativeTitles = resource.AlternativeTitles,
Ratings = resource.Ratings,
YouTubeTrailerId = resource.YouTubeTrailerId,
Studio = resource.Studio

View File

@ -199,7 +199,7 @@ namespace NzbDrone.Api.Series
if (mappings == null) return;
resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
//resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
}
public void Handle(EpisodeImportedEvent message)

View File

@ -20,7 +20,7 @@ namespace NzbDrone.Api.Series
//View Only
public string Title { get; set; }
public List<AlternateTitleResource> AlternateTitles { get; set; }
//public List<AlternativeTitleResource> AlternateTitles { get; set; }
public string SortTitle { get; set; }
public int SeasonCount

View File

@ -263,7 +263,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
result.Should().HaveCount(1);
result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
//result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
}
[Test]
@ -278,7 +278,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
result.Should().HaveCount(1);
result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
//result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
}
[Test]

View File

@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), false), Times.Once());
}
[Test]
@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), false), Times.Once());
}
[Test]
@ -157,7 +157,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie));
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>())).Throws(new Exception());
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>(), false)).Throws(new Exception());
Subject.ProcessDecisions(decisions).Grabbed.Should().BeEmpty();
ExceptionVerification.ExpectedWarns(1);
}
@ -183,7 +183,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Never());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), false), Times.Never());
}
[Test]

View File

@ -6,7 +6,9 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@ -43,7 +45,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.With(m => m.Title = "Fack Ju Göthe 2")
.With(m => m.CleanTitle = "fackjugoethe2")
.With(m => m.Year = 2015)
.With(m => m.AlternativeTitles = new List<string> { "Fack Ju Göthe 2: Same same" })
.With(m => m.AlternativeTitles = new LazyList<AlternativeTitle>( new List<AlternativeTitle> {new AlternativeTitle("Fack Ju Göthe 2: Same same")}))
.Build();
_episodes = Builder<Episode>.CreateListOfSize(1)
@ -80,7 +82,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
_alternativeTitleInfo = new ParsedMovieInfo
{
MovieTitle = _movie.AlternativeTitles.First(),
MovieTitle = _movie.AlternativeTitles.First().Title,
Year = _movie.Year,
};

View File

@ -307,6 +307,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "korsub")]
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "korsubs")]
[TestCase("Wonder Woman 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
public void should_parse_hardcoded_subs(string postTitle, string sub)
{
QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub);

View File

@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Hosting;
using Marr.Data;
using Marr.Data.QGen;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Datastore.Extensions;
@ -48,7 +50,7 @@ namespace NzbDrone.Core.Datastore
_eventAggregator = eventAggregator;
}
protected QueryBuilder<TModel> Query => DataMapper.Query<TModel>();
protected QueryBuilder<TModel> Query => AddJoinQueries(DataMapper.Query<TModel>());
protected void Delete(Expression<Func<TModel, bool>> filter)
{
@ -246,18 +248,23 @@ namespace NzbDrone.Core.Datastore
public virtual PagingSpec<TModel> GetPaged(PagingSpec<TModel> pagingSpec)
{
pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
pagingSpec.Records = GetPagedQuery(Query, pagingSpec).Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize).ToList();
pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
var queryStr = GetPagedQuery(Query, pagingSpec).BuildQuery();
var beforeQuery = Query.BuildQuery();
pagingSpec.SortKey = beforeQuery;
pagingSpec.SortKey = queryStr;
return pagingSpec;
}
protected virtual SortBuilder<TModel> GetPagedQuery(QueryBuilder<TModel> query, PagingSpec<TModel> pagingSpec)
{
return query.Where(pagingSpec.FilterExpression)
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection());
}
protected void ModelCreated(TModel model)
@ -283,6 +290,11 @@ namespace NzbDrone.Core.Datastore
}
}
protected virtual QueryBuilder<TModel> AddJoinQueries(QueryBuilder<TModel> baseQuery)
{
return baseQuery;
}
protected virtual bool PublishModelEvents => false;
}
}

View File

@ -28,12 +28,12 @@ namespace NzbDrone.Core.Datastore.Extensions
return mapBuilder.Relationships.AutoMapComplexTypeProperties<ILazyLoaded>();
}
public static RelationshipBuilder<TParent> HasMany<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyList<TChild>>> portalExpression, Func<TParent, int> childIdSelector)
public static RelationshipBuilder<TParent> HasMany<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyList<TChild>>> portalExpression, Func<TChild, int> parentIdSelector)
where TParent : ModelBase
where TChild : ModelBase
{
return relationshipBuilder.For(portalExpression.GetMemberName())
.LazyLoad((db, parent) => db.Query<TChild>().Where(c => c.Id == childIdSelector(parent)).ToList());
.LazyLoad((db, parent) => db.Query<TChild>().Where(c => parentIdSelector(c) == parent.Id).ToList());
}
private static string GetMemberName<T, TMember>(this Expression<Func<T, TMember>> member)

View File

@ -0,0 +1,72 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Text;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
using Marr.Data.QGen;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(140)]
public class add_alternative_titles_table : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("alternative_titles").Exists())
{
Create.TableForModel("AlternativeTitles")
.WithColumn("MovieId").AsInt64().NotNullable()
.WithColumn("Title").AsString().NotNullable()
.WithColumn("CleanTitle").AsString().NotNullable()
.WithColumn("SourceType").AsInt64().WithDefault(0)
.WithColumn("SourceId").AsInt64().WithDefault(0)
.WithColumn("Votes").AsInt64().WithDefault(0)
.WithColumn("VoteCount").AsInt64().WithDefault(0)
.WithColumn("Language").AsInt64().WithDefault(0);
Delete.Column("AlternativeTitles").FromTable("Movies");
}
Alter.Table("Movies").AddColumn("SecondaryYear").AsInt32().Nullable();
Alter.Table("Movies").AddColumn("SecondaryYearSourceId").AsInt64().Nullable().WithDefault(0);
Execute.WithConnection(AddExisting);
}
private void AddExisting(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand getSeriesCmd = conn.CreateCommand())
{
getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"SELECT Key, Value FROM Config WHERE Key = 'importexclusions'";
TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
{
while (seriesReader.Read())
{
var Key = seriesReader.GetString(0);
var Value = seriesReader.GetString(1);
var importExclusions = Value.Split(',').Select(x => {
return string.Format("(\"{0}\", \"{1}\")", Regex.Replace(x, @"^.*\-(.*)$", "$1"),
textInfo.ToTitleCase(string.Join(" ", x.Split('-').DropLast(1))));
}).ToList();
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "INSERT INTO ImportExclusions (tmdbid, MovieTitle) VALUES " + string.Join(", ", importExclusions);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

View File

@ -36,6 +36,45 @@ using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
using System;
using System.Collections.Generic;
using Marr.Data;
using Marr.Data.Mapping;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tags;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Extras.Metadata;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Datastore
{
@ -101,12 +140,19 @@ namespace NzbDrone.Core.Datastore
query: (db, parent) => db.Query<Movie>().Where(c => c.MovieFileId == parent.Id).ToList())
.HasOne(file => file.Movie, file => file.MovieId);
Mapper.Entity<Movie>().RegisterModel("Movies")
Mapper.Entity<Movie>().RegisterModel("Movies")
.Ignore(s => s.RootFolderPath)
.Relationship()
.HasOne(s => s.Profile, s => s.ProfileId)
.HasOne(m => m.MovieFile, m => m.MovieFileId);
Mapper.Entity<AlternativeTitle>().RegisterModel("AlternativeTitles")
.For(t => t.Id)
.SetAltName("AltTitle_Id")
.Relationship()
.HasOne(t => t.Movie, t => t.MovieId);
Mapper.Entity<ImportExclusion>().RegisterModel("ImportExclusions");

View File

@ -113,11 +113,11 @@ namespace NzbDrone.Core.DecisionEngine
var remoteMovie = result.RemoteMovie;
remoteMovie.Release = report;
remoteMovie.MappingResult = result.MappingResultType;
if (result.MappingResultType != MappingResultType.Success && result.MappingResultType != MappingResultType.SuccessLenientMapping)
{
var rejection = result.ToRejection();
remoteMovie.Movie = null; // HACK: For now!
decision = new DownloadDecision(remoteMovie, rejection);
}
@ -125,7 +125,7 @@ namespace NzbDrone.Core.DecisionEngine
{
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{
remoteMovie.DownloadAllowed = true;
//remoteMovie.DownloadAllowed = true;
if (_configService.AllowHardcodedSubs)
{
decision = GetDecisionForReport(remoteMovie, searchCriteria);
@ -146,7 +146,7 @@ namespace NzbDrone.Core.DecisionEngine
}
else
{
remoteMovie.DownloadAllowed = true;
//remoteMovie.DownloadAllowed = true;
decision = GetDecisionForReport(remoteMovie, searchCriteria);
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.DecisionEngine
{
@ -36,13 +37,13 @@ namespace NzbDrone.Core.DecisionEngine
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
{
return decisions.Where(c => c.RemoteMovie.Movie != null)
return decisions.Where(c => c.RemoteMovie.MappingResult == MappingResultType.Success || c.RemoteMovie.MappingResult == MappingResultType.SuccessLenientMapping)
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
{
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
})
.SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.Movie == null))
.Union(decisions.Where(c => c.RemoteMovie.MappingResult != MappingResultType.Success || c.RemoteMovie.MappingResult != MappingResultType.SuccessLenientMapping))
.ToList();
}
}

View File

@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download
public interface IDownloadService
{
void DownloadReport(RemoteEpisode remoteEpisode);
void DownloadReport(RemoteMovie remoteMovie);
void DownloadReport(RemoteMovie remoteMovie, bool forceDownload);
}
@ -92,7 +92,7 @@ namespace NzbDrone.Core.Download
_eventAggregator.PublishEvent(episodeGrabbedEvent);
}
public void DownloadReport(RemoteMovie remoteMovie)
public void DownloadReport(RemoteMovie remoteMovie, bool foceDownload = false)
{
//Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
//Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); TODO update this shit

View File

@ -88,7 +88,7 @@ namespace NzbDrone.Core.Download
try
{
_downloadService.DownloadReport(remoteMovie);
_downloadService.DownloadReport(remoteMovie, false);
grabbed.Add(report);
}
catch (Exception e)

View File

@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Newznab
else
{
var searchTitle = System.Web.HttpUtility.UrlPathEncode(Parser.Parser.ReplaceGermanUmlauts(Parser.Parser.NormalizeTitle(searchCriteria.Movie.Title)));
var altTitles = searchCriteria.Movie.AlternativeTitles.DistinctBy(t => Parser.Parser.CleanSeriesTitle(t)).Take(5).ToList();
var altTitles = searchCriteria.Movie.AlternativeTitles.Take(5).Select(t => t.Title).ToList();
var realMaxPages = (int)MaxPages / (altTitles.Count() + 1);

View File

@ -3,7 +3,10 @@ using NzbDrone.Core.Configuration;
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.MetadataSource.RadarrAPI
{
@ -11,6 +14,10 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
{
IHttpRequestBuilderFactory RadarrAPI { get; }
List<MovieResult> DiscoverMovies(string action, Func<HttpRequest, HttpRequest> enhanceRequest);
List<AlternativeTitle> AlternativeTitlesForMovie(int TmdbId);
Tuple<List<AlternativeTitle>, AlternativeYear> AlternativeTitlesAndYearForMovie(int tmdbId);
AlternativeTitle AddNewAlternativeTitle(AlternativeTitle title, int TmdbId);
AlternativeYear AddNewAlternativeYear(int year, int tmdbId);
string APIURL { get; }
}
@ -65,7 +72,7 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
{
var error = JsonConvert.DeserializeObject<RadarrError>(response.Content);
if (error != null && error.Errors.Count != 0)
if (error != null && error.Errors != null && error.Errors.Count != 0)
{
throw new RadarrAPIException(error);
}
@ -96,6 +103,83 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
return Execute<List<MovieResult>>(request);
}
public List<AlternativeTitle> AlternativeTitlesForMovie(int TmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "find").AddQueryParam("tmdbid", TmdbId).Build();
var mappings = Execute<Mapping>(request);
var titles = new List<NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle>();
foreach (var altTitle in mappings.Mappings.Titles)
{
titles.Add(new NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle(altTitle.Info.AkaTitle, SourceType.Mappings, altTitle.Id));
}
return titles;
}
public Tuple<List<AlternativeTitle>, AlternativeYear> AlternativeTitlesAndYearForMovie(int tmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "find").AddQueryParam("tmdbid", tmdbId).Build();
var mappings = Execute<Mapping>(request);
var titles = new List<NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle>();
foreach (var altTitle in mappings.Mappings.Titles)
{
titles.Add(new NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle(altTitle.Info.AkaTitle, SourceType.Mappings, altTitle.Id));
}
var year = mappings.Mappings.Years.Where(y => y.Votes >= 3).OrderBy(y => y.Votes).FirstOrDefault();
AlternativeYear newYear = null;
if (year != null)
{
newYear = new AlternativeYear
{
Year = year.Info.AkaYear,
SourceId = year.Id
};
}
return new Tuple<List<AlternativeTitle>, AlternativeYear>(titles, newYear);
}
public AlternativeTitle AddNewAlternativeTitle(AlternativeTitle title, int TmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "add")
.AddQueryParam("tmdbid", TmdbId).AddQueryParam("type", "title")
.AddQueryParam("language", IsoLanguages.Get(title.Language).TwoLetterCode)
.AddQueryParam("aka_title", title.Title).Build();
var newMapping = Execute<AddTitleMapping>(request);
var newTitle = new AlternativeTitle(newMapping.Info.AkaTitle, SourceType.Mappings, newMapping.Id, title.Language);
newTitle.VoteCount = newMapping.VoteCount;
newTitle.Votes = newMapping.Votes;
return newTitle;
}
public AlternativeYear AddNewAlternativeYear(int year, int tmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "add")
.AddQueryParam("tmdbid", tmdbId).AddQueryParam("type", "year")
.AddQueryParam("aka_year", year).Build();
var newYear = Execute<AddYearMapping>(request);
return new AlternativeYear
{
Year = newYear.Info.AkaYear,
SourceId = newYear.Id
};
}
public IHttpRequestBuilderFactory RadarrAPI { get; private set; }
}
}

View File

@ -27,21 +27,183 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
public class RadarrAPIException : Exception
{
RadarrError APIErrors;
public RadarrError APIErrors;
public RadarrAPIException(RadarrError apiError) : base(HumanReadable(apiError))
{
APIErrors = apiError;
}
private static string HumanReadable(RadarrError APIErrors)
private static string HumanReadable(RadarrError apiErrors)
{
var firstError = APIErrors.Errors.First();
var details = string.Join("\n", APIErrors.Errors.Select(error =>
var firstError = apiErrors.Errors.First();
var details = string.Join("\n", apiErrors.Errors.Select(error =>
{
return $"{error.Title} ({error.Status}, RayId: {error.RayId}), Details: {error.Detail}";
}));
return $"Error while calling api: {firstError.Title}\nFull error(s): {details}";
}
}
public class TitleInfo
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("aka_title")]
public string AkaTitle { get; set; }
[JsonProperty("aka_clean_title")]
public string AkaCleanTitle { get; set; }
}
public class YearInfo
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("aka_year")]
public int AkaYear { get; set; }
}
public class Title
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tmdbid")]
public int Tmdbid { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("info")]
public TitleInfo Info { get; set; }
}
public class Year
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tmdbid")]
public int Tmdbid { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("info")]
public YearInfo Info { get; set; }
}
public class Mappings
{
[JsonProperty("titles")]
public IList<Title> Titles { get; set; }
[JsonProperty("years")]
public IList<Year> Years { get; set; }
}
public class Mapping
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("imdb_id")]
public string ImdbId { get; set; }
[JsonProperty("mappings")]
public Mappings Mappings { get; set; }
}
public class AddTitleMapping
{
[JsonProperty("tmdbid")]
public string Tmdbid { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("info")]
public TitleInfo Info { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
}
public class AddYearMapping
{
[JsonProperty("tmdbid")]
public string Tmdbid { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("info")]
public YearInfo Info { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
}
}

View File

@ -2,7 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using System.ServiceModel;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@ -19,6 +20,7 @@ using NzbDrone.Common.Serializer;
using NzbDrone.Core.NetImport.ImportExclusions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
namespace NzbDrone.Core.MetadataSource.SkyHook
{
@ -33,12 +35,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
private readonly IMovieService _movieService;
private readonly IPreDBService _predbService;
private readonly IImportExclusionsService _exclusionService;
private readonly IAlternativeTitleService _altTitleService;
private readonly IRadarrAPIClient _radarrAPI;
private readonly IHttpRequestBuilderFactory _apiBuilder;
public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, IMovieService movieService,
IPreDBService predbService, IImportExclusionsService exclusionService, IRadarrAPIClient radarrAPI, Logger logger)
IPreDBService predbService, IImportExclusionsService exclusionService, IAlternativeTitleService altTitleService, IRadarrAPIClient radarrAPI, Logger logger)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder.SkyHookTvdb;
@ -47,6 +50,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_movieService = movieService;
_predbService = predbService;
_exclusionService = exclusionService;
_altTitleService = altTitleService;
_radarrAPI = radarrAPI;
_logger = logger;
@ -133,21 +137,28 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
var movie = new Movie();
var altTitles = new List<AlternativeTitle>();
if (langCode != "us")
if (langCode != "en")
{
movie.AlternativeTitles.Add(resource.original_title);
var iso = IsoLanguages.Find(resource.original_language);
if (iso != null)
{
altTitles.Add(new AlternativeTitle(resource.original_title, SourceType.TMDB, TmdbId, iso.Language));
}
//movie.AlternativeTitles.Add(resource.original_title);
}
foreach (var alternativeTitle in resource.alternative_titles.titles)
{
if (alternativeTitle.iso_3166_1.ToLower() == langCode)
{
movie.AlternativeTitles.Add(alternativeTitle.title);
altTitles.Add(new AlternativeTitle(alternativeTitle.title, SourceType.TMDB, TmdbId, IsoLanguages.Find(alternativeTitle.iso_3166_1.ToLower()).Language));
}
else if (alternativeTitle.iso_3166_1.ToLower() == "us")
{
movie.AlternativeTitles.Add(alternativeTitle.title);
altTitles.Add(new AlternativeTitle(alternativeTitle.title, SourceType.TMDB, TmdbId, Language.English));
}
}
@ -321,6 +332,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
movie.AlternativeTitles.AddRange(altTitles);
return movie;
}

View File

@ -0,0 +1,77 @@
using System;
using Marr.Data;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Movies.AlternativeTitles
{
public class AlternativeTitle : ModelBase
{
public SourceType SourceType { get; set; }
public int MovieId { get; set; }
public string Title { get; set; }
public string CleanTitle { get; set; }
public int SourceId { get; set; }
public int Votes { get; set; }
public int VoteCount { get; set; }
public Language Language { get; set; }
public LazyLoaded<Movie> Movie { get; set; }
public AlternativeTitle()
{
}
public AlternativeTitle(string title, SourceType sourceType = SourceType.TMDB, int sourceId = 0, Language language = Language.English)
{
Title = title;
CleanTitle = title.CleanSeriesTitle();
SourceType = sourceType;
SourceId = sourceId;
Language = language;
}
public bool IsTrusted(int minVotes = 3)
{
switch (SourceType)
{
case SourceType.TMDB:
return Votes >= minVotes;
default:
return true;
}
}
public override bool Equals(object obj)
{
var item = obj as AlternativeTitle;
if (item == null)
{
return false;
}
return item.CleanTitle == this.CleanTitle;
}
public override String ToString()
{
return Title;
}
}
public enum SourceType
{
TMDB = 0,
Mappings = 1,
User = 2,
Indexer = 3
}
public class AlternativeYear
{
public int Year { get; set; }
public int SourceId { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Movies.AlternativeTitles
{
public interface IAlternativeTitleRepository : IBasicRepository<AlternativeTitle>
{
}
public class AlternativeTitleRepository : BasicRepository<AlternativeTitle>, IAlternativeTitleRepository
{
protected IMainDatabase _database;
public AlternativeTitleRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
_database = database;
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Movies.AlternativeTitles
{
public interface IAlternativeTitleService
{
List<AlternativeTitle> GetAllTitlesForMovie(Movie movie);
AlternativeTitle AddAltTitle(AlternativeTitle title, Movie movie);
List<AlternativeTitle> AddAltTitles(List<AlternativeTitle> titles, Movie movie);
AlternativeTitle GetById(int id);
}
public class AlternativeTitleService : IAlternativeTitleService
{
private readonly IAlternativeTitleRepository _titleRepo;
private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public AlternativeTitleService(IAlternativeTitleRepository titleRepo,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_titleRepo = titleRepo;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
}
public List<AlternativeTitle> GetAllTitlesForMovie(Movie movie)
{
return _titleRepo.All().ToList();
}
public AlternativeTitle AddAltTitle(AlternativeTitle title, Movie movie)
{
title.MovieId = movie.Id;
return _titleRepo.Insert(title);
}
public List<AlternativeTitle> AddAltTitles(List<AlternativeTitle> titles, Movie movie)
{
titles.ForEach(t => t.MovieId = movie.Id);
_titleRepo.InsertMany(titles);
return titles;
}
public AlternativeTitle GetById(int id)
{
return _titleRepo.Get(id);
}
}
}

View File

@ -125,6 +125,7 @@
<Compile Include="Authentication\UserRepository.cs" />
<Compile Include="Authentication\UserService.cs" />
<Compile Include="Datastore\Migration\123_create_netimport_table.cs" />
<Compile Include="Datastore\Migration\140_add_alternative_titles_table.cs" />
<Compile Include="MediaFiles\Events\MovieFileUpdatedEvent.cs" />
<Compile Include="Datastore\Migration\134_add_remux_qualities_for_the_wankers.cs" />
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
@ -134,6 +135,9 @@
<Compile Include="Datastore\Migration\133_add_minimumavailability.cs" />
<Compile Include="IndexerSearch\CutoffUnmetMoviesSearchCommand.cs" />
<Compile Include="Indexers\HDBits\HDBitsInfo.cs" />
<Compile Include="Movies\AlternativeTitles\AlternativeTitle.cs" />
<Compile Include="Movies\AlternativeTitles\AlternativeTitleRepository.cs" />
<Compile Include="Movies\AlternativeTitles\AlternativeTitleService.cs" />
<Compile Include="NetImport\NetImportListLevels.cs" />
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
<Compile Include="NetImport\TMDb\TMDbSettings.cs" />

View File

@ -11,7 +11,7 @@ namespace NzbDrone.Core.Parser.Model
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } //TODO: Change to ParsedMovieInfo, for now though ParsedEpisodeInfo will do.
public ParsedMovieInfo ParsedMovieInfo { get; set; }
public Movie Movie { get; set; }
public bool DownloadAllowed { get; set; }
public MappingResultType MappingResult { get; set; }
public override string ToString()
{

View File

@ -9,6 +9,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Tv;
@ -381,7 +382,7 @@ namespace NzbDrone.Core.Parser
{
var movie = _movieService.FindByImdbId(imdbId);
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
if (movie != null && parsedMovieInfo.Year > 1800 && (parsedMovieInfo.Year != movie.Year && movie.SecondaryYear != parsedMovieInfo.Year))
{
result = new MappingResult { Movie = movie, MappingResultType = MappingResultType.WrongYear};
return false;
@ -458,9 +459,9 @@ namespace NzbDrone.Core.Parser
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
foreach (AlternativeTitle altTitle in searchCriteria.Movie.AlternativeTitles)
{
possibleTitles.Add(altTitle.CleanSeriesTitle());
possibleTitles.Add(altTitle.CleanTitle);
}
string cleanTitle = parsedMovieInfo.MovieTitle.CleanSeriesTitle();
@ -494,7 +495,7 @@ namespace NzbDrone.Core.Parser
if (possibleMovie != null)
{
if (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year)
if (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year || possibleMovie.SecondaryYear == parsedMovieInfo.Year)
{
result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.Success };
return true;
@ -509,7 +510,7 @@ namespace NzbDrone.Core.Parser
cleanTitle.Contains(searchCriteria.Movie.CleanTitle))
{
possibleMovie = searchCriteria.Movie;
if (parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year == possibleMovie.Year)
if (parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year == possibleMovie.Year || possibleMovie.SecondaryYear == parsedMovieInfo.Year)
{
result = new MappingResult {Movie = possibleMovie, MappingResultType = MappingResultType.SuccessLenientMapping};
return true;

View File

@ -6,6 +6,8 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.MediaFiles;
using System.IO;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.AlternativeTitles;
namespace NzbDrone.Core.Tv
{
@ -17,7 +19,7 @@ namespace NzbDrone.Core.Tv
Genres = new List<string>();
Actors = new List<Actor>();
Tags = new HashSet<int>();
AlternativeTitles = new List<string>();
AlternativeTitles = new List<AlternativeTitle>();
}
public int TmdbId { get; set; }
public string ImdbId { get; set; }
@ -52,7 +54,10 @@ namespace NzbDrone.Core.Tv
public LazyLoaded<MovieFile> MovieFile { get; set; }
public bool HasPreDBEntry { get; set; }
public int MovieFileId { get; set; }
public List<string> AlternativeTitles { get; set; }
//Get Loaded via a Join Query
public List<AlternativeTitle> AlternativeTitles { get; set; }
public int? SecondaryYear { get; set; }
public int SecondaryYearSourceId { get; set; }
public string YouTubeTrailerId{ get; set; }
public string Studio { get; set; }

View File

@ -6,6 +6,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Datastore.Extensions;
using Marr.Data.QGen;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Qualities;
using CoreParser = NzbDrone.Core.Parser.Parser;
@ -103,7 +104,7 @@ namespace NzbDrone.Core.Tv
public override PagingSpec<Movie> GetPaged(PagingSpec<Movie> pagingSpec)
{
if (pagingSpec.SortKey == "downloadedQuality")
/*if (pagingSpec.SortKey == "downloadedQuality")
{
var mapper = _database.GetDataMapper();
var offset = pagingSpec.PagingOffset();
@ -113,7 +114,7 @@ namespace NzbDrone.Core.Tv
{
direction = "DESC";
}
var q = mapper.Query<Movie>($"SELECT * from \"Movies\" , \"MovieFiles\", \"QualityDefinitions\" WHERE Movies.MovieFileId=MovieFiles.Id AND instr(MovieFiles.Quality, ('quality\": ' || QualityDefinitions.Quality || \",\")) > 0 ORDER BY QualityDefinitions.Title {direction} LIMIT {offset},{limit};");
var q = Query.Select($"SELECT * from \"Movies\" , \"MovieFiles\", \"QualityDefinitions\" WHERE Movies.MovieFileId=MovieFiles.Id AND instr(MovieFiles.Quality, ('quality\": ' || QualityDefinitions.Quality || \",\")) > 0 ORDER BY QualityDefinitions.Title {direction} LIMIT {offset},{limit};");
var q2 = mapper.Query<Movie>("SELECT * from \"Movies\" , \"MovieFiles\", \"QualityDefinitions\" WHERE Movies.MovieFileId=MovieFiles.Id AND instr(MovieFiles.Quality, ('quality\": ' || QualityDefinitions.Quality || \",\")) > 0 ORDER BY QualityDefinitions.Title ASC;");
//var ok = q.BuildQuery();
@ -122,9 +123,11 @@ namespace NzbDrone.Core.Tv
pagingSpec.TotalRecords = q2.Count();
}
else
else*/
{
pagingSpec = base.GetPaged(pagingSpec);
//pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
//pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
}
if (pagingSpec.Records.Count == 0 && pagingSpec.Page != 1)
@ -136,6 +139,22 @@ namespace NzbDrone.Core.Tv
return pagingSpec;
}
/*protected override SortBuilder<Movie> GetPagedQuery(QueryBuilder<Movie> query, PagingSpec<Movie> pagingSpec)
{
return DataMapper.Query<Movie>().Join<Movie, AlternativeTitle>(JoinType.Left, m => m.AlternativeTitles,
(m, t) => m.Id == t.MovieId).Where(pagingSpec.FilterExpression)
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
}*/
/*protected override SortBuilder<Movie> GetPagedQuery(QueryBuilder<Movie> query, PagingSpec<Movie> pagingSpec)
{
var newQuery = base.GetPagedQuery(query.Join<Movie, AlternativeTitle>(JoinType.Left, m => m.JoinAlternativeTitles, (movie, title) => title.MovieId == movie.Id), pagingSpec);
System.Console.WriteLine(newQuery.ToString());
return newQuery;
}*/
public SortBuilder<Movie> GetMoviesWithoutFilesQuery(PagingSpec<Movie> pagingSpec)
{
@ -247,22 +266,39 @@ namespace NzbDrone.Core.Tv
if (result == null)
{
IEnumerable<Movie> movies = All();
/*IEnumerable<Movie> movies = All();
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
Func<IEnumerable<string>, string, bool> altTitleComparer =
Func<IEnumerable<AlternativeTitle>, string, bool> altTitleComparer =
(alternativeTitles, atitle) =>
alternativeTitles.Any(altTitle => titleCleaner(altTitle) == atitle);
alternativeTitles.Any(altTitle => altTitle.CleanTitle == atitle);*/
result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
/*result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);*/
//result = Query.Join<Movie, AlternativeTitle>(JoinType.Inner, m => m._newAltTitles,
//(m, t) => m.Id == t.MovieId && (t.CleanTitle == cleanTitle)).FirstWithYear(year);
result = Query.Where<AlternativeTitle>(t =>
t.CleanTitle == cleanTitle || t.CleanTitle == cleanTitleWithArabicNumbers
|| t.CleanTitle == cleanTitleWithRomanNumbers).FirstWithYear(year);
}
}
return result;
/*return year.HasValue
? results?.FirstOrDefault(movie => movie.Year == year.Value)
: results?.FirstOrDefault();*/
: results?.FirstOrDefault();*/
}
protected override QueryBuilder<Movie> AddJoinQueries(QueryBuilder<Movie> baseQuery)
{
baseQuery = base.AddJoinQueries(baseQuery);
baseQuery = baseQuery.Join<Movie, AlternativeTitle>(JoinType.Left, m => m.AlternativeTitles,
(m, t) => m.Id == t.MovieId);
return baseQuery;
}
public Movie FindByTmdbId(int tmdbid)

View File

@ -16,7 +16,7 @@ namespace NzbDrone.Core
{
public static Movie FirstWithYear(this SortBuilder<Movie> query, int? year)
{
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year) : query.FirstOrDefault();
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year || movie.SecondaryYear == year) : query.FirstOrDefault();
}
}
@ -24,7 +24,7 @@ namespace NzbDrone.Core
{
public static Movie FirstWithYear(this IEnumerable<Movie> query, int? year)
{
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year) : query.FirstOrDefault();
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year || movie.SecondaryYear == year) : query.FirstOrDefault();
}
}
}

View File

@ -14,6 +14,8 @@ using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
namespace NzbDrone.Core.Tv
{
@ -21,27 +23,34 @@ namespace NzbDrone.Core.Tv
{
private readonly IProvideMovieInfo _movieInfo;
private readonly IMovieService _movieService;
private readonly IAlternativeTitleService _titleService;
private readonly IRefreshEpisodeService _refreshEpisodeService;
private readonly IEventAggregator _eventAggregator;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IDiskScanService _diskScanService;
private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed;
private readonly IRadarrAPIClient _apiClient;
private readonly Logger _logger;
public RefreshMovieService(IProvideMovieInfo movieInfo,
IMovieService movieService,
IAlternativeTitleService titleService,
IRefreshEpisodeService refreshEpisodeService,
IEventAggregator eventAggregator,
IDiskScanService diskScanService,
IRadarrAPIClient apiClient,
ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed,
IManageCommandQueue commandQueue,
Logger logger)
{
_movieInfo = movieInfo;
_movieService = movieService;
_titleService = titleService;
_refreshEpisodeService = refreshEpisodeService;
_eventAggregator = eventAggregator;
_commandQueueManager = commandQueue;
_apiClient = apiClient;
_commandQueueManager = commandQueue;
_diskScanService = diskScanService;
_checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed;
_logger = logger;
@ -85,7 +94,7 @@ namespace NzbDrone.Core.Tv
movie.Certification = movieInfo.Certification;
movie.InCinemas = movieInfo.InCinemas;
movie.Website = movieInfo.Website;
movie.AlternativeTitles = movieInfo.AlternativeTitles;
//movie.AlternativeTitles = movieInfo.AlternativeTitles;
movie.Year = movieInfo.Year;
movie.PhysicalRelease = movieInfo.PhysicalRelease;
movie.YouTubeTrailerId = movieInfo.YouTubeTrailerId;
@ -102,8 +111,47 @@ namespace NzbDrone.Core.Tv
_logger.Warn(e, "Couldn't update movie path for " + movie.Path);
}
movieInfo.AlternativeTitles = movieInfo.AlternativeTitles.Where(t => t.CleanTitle != movie.CleanTitle)
.DistinctBy(t => t.CleanTitle)
.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles, t => t.CleanTitle, EqualityComparer<string>.Default).ToList();
try
{
var mappings = _apiClient.AlternativeTitlesAndYearForMovie(movieInfo.TmdbId);
var mappingsTitles = mappings.Item1;
movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(movieInfo.AlternativeTitles, movie));
mappingsTitles = mappingsTitles.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles,
t => t.CleanTitle, EqualityComparer<string>.Default).ToList();
movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(mappingsTitles, movie));
if (mappings.Item2 != null)
{
movie.SecondaryYear = mappings.Item2.Year;
movie.SecondaryYearSourceId = mappings.Item2.SourceId;
}
}
catch (RadarrAPIException ex)
{
//Not that wild, could just be a 404.
}
_movieService.UpdateMovie(movie);
try
{
var newTitles = movieInfo.AlternativeTitles.Except(movie.AlternativeTitles);
//_titleService.AddAltTitles(newTitles.ToList(), movie);
}
catch (Exception e)
{
_logger.Debug(e, "Failed adding alternative titles.");
throw;
}
_logger.Debug("Finished movie refresh for {0}", movie.Title);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
}

View File

@ -22,6 +22,7 @@ namespace NzbDrone.Core.Tv
public bool ShouldRefresh(Movie movie)
{
//return false;
if (movie.LastInfoSync < DateTime.UtcNow.AddDays(-30))
{
_logger.Trace("Movie {0} last updated more than 30 days ago, should refresh.", movie.Title);

View File

@ -47,7 +47,7 @@ namespace NzbDrone.Integration.Test.ApiTests
releaseResource.Age.Should().BeGreaterOrEqualTo(-1);
releaseResource.Title.Should().NotBeNullOrWhiteSpace();
releaseResource.DownloadUrl.Should().NotBeNullOrWhiteSpace();
releaseResource.SeriesTitle.Should().NotBeNullOrWhiteSpace();
releaseResource.MovieTitle.Should().NotBeNullOrWhiteSpace();
//TODO: uncomment these after moving to restsharp for rss
//releaseResource.NzbInfoUrl.Should().NotBeNullOrWhiteSpace();
//releaseResource.Size.Should().BeGreaterThan(0);

View File

@ -268,6 +268,11 @@
.fa-icon-color(@brand-warning);
}
.icon-radarr-download-warning {
.fa-icon-content(@fa-var-download);
.fa-icon-color(@brand-warning);
}
.icon-sonarr-shutdown {
.fa-icon-content(@fa-var-power-off);
.fa-icon-color(@brand-danger);

View File

@ -64,6 +64,11 @@ Handlebars.registerHelper('alternativeTitlesString', function() {
if (titles.length === 0) {
return "";
}
titles = _.map(titles, function(item){
return item.title;
});
if (titles.length === 1) {
return titles[0];
}

View File

@ -12,6 +12,7 @@ var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEdito
var HistoryLayout = require('../History/MovieHistoryLayout');
var SearchLayout = require('../Search/MovieSearchLayout');
var FilesLayout = require("../Files/FilesLayout");
var TitlesLayout = require("../Titles/TitlesLayout");
require('backstrech');
require('../../Mixins/backbone.signalr.mixin');
@ -24,7 +25,8 @@ module.exports = Marionette.Layout.extend({
info : '#info',
search : '#movie-search',
history : '#movie-history',
files : "#movie-files"
files : "#movie-files",
titles: "#movie-titles",
},
@ -39,7 +41,8 @@ module.exports = Marionette.Layout.extend({
manualSearch : '.x-manual-search',
history : '.x-movie-history',
search : '.x-movie-search',
files : ".x-movie-files"
files : ".x-movie-files",
titles: ".x-movie-titles",
},
events : {
@ -53,6 +56,7 @@ module.exports = Marionette.Layout.extend({
'click .x-movie-history' : '_showHistory',
'click .x-movie-search' : '_showSearch',
"click .x-movie-files" : "_showFiles",
"click .x-movie-titles" : "_showTitles",
},
initialize : function() {
@ -83,6 +87,7 @@ module.exports = Marionette.Layout.extend({
this.searchLayout.startManualSearch = true;
this.filesLayout = new FilesLayout({ model : this.model });
this.titlesLayout = new TitlesLayout({ model : this.model });
this._showBackdrop();
this._showSeasons();
@ -170,6 +175,15 @@ module.exports = Marionette.Layout.extend({
this.files.show(this.filesLayout);
},
_showTitles : function(e) {
if (e) {
e.preventDefault();
}
this.ui.titles.tab("show");
this.titles.show(this.titlesLayout);
},
_toggleMonitored : function() {
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });

View File

@ -6,7 +6,8 @@
<div>
<h1 class="header-text">
<i class="x-monitored" title="Toggle monitored state for movie"/>
{{title}}
{{title}} <span class="year">({{year}}{{#if secondaryYear}} / <a href="https://mappings.radarr.video/mapping/{{secondaryYearSourceId}}" target="_blank"><span title="Secondary year pulled from Radarr Mappings.
Click to head on over there and tell us whether this is correct or not.">{{secondaryYear}}</span></a>{{/if}})</span>
<div class="movie-actions pull-right">
<div class="x-episode-file-editor">
<i class="icon-sonarr-episode-file" title="Modify movie files"/>
@ -43,11 +44,13 @@
<li><a href="#movie-history" class="x-movie-history">History</a></li>
<li><a href="#movie-search" class="x-movie-search">Search</a></li>
<li><a href="#movie-files" class="x-movie-files">Files</a></li>
<li><a href="#movie-titles" class="x-movie-titles">Titles</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="movie-history"/>
<div class="tab-pane" id="movie-search"/>
<div class="tab-pane" id="movie-files"/>
<div class="tab-pane" id="movie-titles"/>
</div>
</div>
</div>

View File

@ -0,0 +1,22 @@
var NzbDroneCell = require('../../Cells/NzbDroneCell');
module.exports = NzbDroneCell.extend({
className : 'language-cell',
render : function() {
this.$el.empty();
var language = this.model.get("language");
this.$el.html(this.toTitleCase(language));
return this;
},
toTitleCase : function(str)
{
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
});

View File

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

View File

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

View File

@ -0,0 +1,42 @@
var NzbDroneCell = require('../../Cells/NzbDroneCell');
module.exports = NzbDroneCell.extend({
className : 'title-source-cell',
render : function() {
this.$el.empty();
var link = undefined;
var sourceTitle = this.model.get("sourceType");
var sourceId = this.model.get("sourceId");
switch (sourceTitle) {
case "tmdb":
sourceTitle = "TMDB";
link = "https://themoviedb.org/movie/" + sourceId;
break;
case "mappings":
sourceTitle = "Radarr Mappings";
link = "https://mappings.radarr.video/mapping/" + sourceId;
break;
case "user":
sourceTitle = "Force Download";
break;
case "indexer":
sourceTitle = "Indexer";
break;
}
var a = "{0}";
if (link) {
a = "<a href='"+link+"' target='_blank'>{0}</a>"
}
this.$el.html(a.format(sourceTitle));
return this;
}
});

View File

@ -0,0 +1,6 @@
var TemplatedCell = require('../../Cells/TemplatedCell');
module.exports = TemplatedCell.extend({
className : 'series-title-cell',
template : 'Movies/Titles/TitleTemplate'
});

View File

@ -0,0 +1,3 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({});

View File

@ -0,0 +1 @@
{{this}}

View File

@ -0,0 +1,30 @@
var PagableCollection = require('backbone.pageable');
var TitleModel = require('./TitleModel');
var AsSortedCollection = require('../../Mixins/AsSortedCollection');
var Collection = PagableCollection.extend({
url : window.NzbDrone.ApiRoot + "/aka",
model : TitleModel,
state : {
pageSize : 2000,
sortKey : 'title',
order : -1
},
mode : 'client',
sortMappings : {
"source" : {
sortKey : "sourceType"
},
"language" : {
sortKey : "language"
}
},
});
Collection = AsSortedCollection.call(Collection);
module.exports = Collection;

View File

@ -0,0 +1,117 @@
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
//var ButtonsView = require('./ButtonsView');
//var ManualSearchLayout = require('./ManualLayout');
var TitlesCollection = require('./TitlesCollection');
var CommandController = require('../../Commands/CommandController');
var LoadingView = require('../../Shared/LoadingView');
var NoResultsView = require('./NoTitlesView');
var TitleModel = require("./TitleModel");
var TitleCell = require("./TitleCell");
var SourceCell = require("./SourceCell");
var LanguageCell = require("./LanguageCell");
module.exports = Marionette.Layout.extend({
template : 'Movies/Titles/TitlesLayoutTemplate',
regions : {
main : '#movie-titles-region',
grid : "#movie-titles-grid"
},
events : {
'click .x-search-auto' : '_searchAuto',
'click .x-search-manual' : '_searchManual',
'click .x-search-back' : '_showButtons'
},
columns : [
{
name : 'title',
label : 'Title',
cell : Backgrid.StringCell
},
{
name : "this",
label : "Source",
cell : SourceCell,
sortKey : "sourceType",
},
{
name : "this",
label : "Language",
cell : LanguageCell
}
],
initialize : function(movie) {
this.titlesCollection = new TitlesCollection();
var titles = movie.model.get("alternativeTitles");
this.movie = movie;
this.titlesCollection.add(titles);
//this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
this.listenTo(this.model, 'change', function(model, options) {
if (options && options.changeSource === 'signalr') {
this._refresh(model);
}
});
//vent.on(vent.Commands.MovieFileEdited, this._showGrid, this);
},
_refresh : function(model) {
this.titlesCollection = new TitlesCollection();
var file = model.get("alternativeTitles");
this.titlesCollection.add(file);
this.onShow();
},
_refreshClose : function(options) {
this.titlesCollection = new TitlesCollection();
var file = this.movie.model.get("alternativeTitles");
this.titlesCollection.add(file);
this._showGrid();
},
onShow : function() {
this.grid.show(new Backgrid.Grid({
row : Backgrid.Row,
columns : this.columns,
collection : this.titlesCollection,
className : 'table table-hover'
}));
},
_showGrid : function() {
this.regionManager.get('grid').show(new Backgrid.Grid({
row : Backgrid.Row,
columns : this.columns,
collection : this.titlesCollection,
className : 'table table-hover'
}));
},
_showMainView : function() {
this.main.show(this.mainView);
},
_showButtons : function() {
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,3 @@
<div id="movie-titles-region">
<div id="movie-titles-grid" class="table-responsive"></div>
</div>

View File

@ -534,3 +534,9 @@
list-style-type : none;
}
}
.header-text {
.year {
color : gray;
}
}

View File

@ -0,0 +1,6 @@
var Backbone = require('backbone');
var _ = require('underscore');
module.exports = Backbone.Model.extend({
urlRoot : window.NzbDrone.ApiRoot + '/alttitle',
});

View File

@ -0,0 +1,6 @@
var Backbone = require('backbone');
var _ = require('underscore');
module.exports = Backbone.Model.extend({
urlRoot : window.NzbDrone.ApiRoot + '/altyear',
});

View File

@ -1,4 +1,6 @@
var Backgrid = require('backgrid');
var AppLayout = require('../AppLayout');
var ForceDownloadView = require('./ForceDownloadView');
module.exports = Backgrid.Cell.extend({
className : 'download-report-cell',
@ -8,7 +10,12 @@ module.exports = Backgrid.Cell.extend({
},
_onClick : function() {
if (!this.model.get('downloadAllowed')) {
if (!this.model.downloadOk()) {
var view = new ForceDownloadView({
release : this.model
});
AppLayout.modalRegion.show(view);
return;
}
@ -38,10 +45,11 @@ module.exports = Backgrid.Cell.extend({
if (this.model.get('queued')) {
this.$el.html('<i class="icon-sonarr-downloading" title="Added to downloaded queue" />');
} else if (this.model.get('downloadAllowed')) {
} else if (this.model.downloadOk()) {
this.$el.html('<i class="icon-sonarr-download" title="Add to download queue" />');
} else {
this.className = 'no-download-report-cell';
} else if (this.model.forceDownloadOk()){
this.$el.html('<i class="icon-radarr-download-warning" title="Force add to download queue."/>');
this.className = 'force-download-report-cell';
}
return this;

View File

@ -0,0 +1,81 @@
var _ = require('underscore');
var $ = require('jquery');
var vent = require('vent');
var AppLayout = require('../AppLayout');
var Marionette = require('marionette');
var Config = require('../Config');
var LanguageCollection = require('../Settings/Profile/Language/LanguageCollection');
var AltTitleModel = require("./AlternativeTitleModel");
var AltYearModel = require("./AlternativeYearModel");
var Messenger = require('../Shared/Messenger');
require('../Form/FormBuilder');
require('bootstrap');
module.exports = Marionette.ItemView.extend({
template : 'Release/ForceDownloadViewTemplate',
events : {
'click .x-download' : '_forceDownload',
},
ui : {
titleMapping : "#title-mapping",
yearMapping : "#year-mapping",
language : "#language-selection",
indicator : ".x-indicator",
},
initialize : function(options) {
this.release = options.release;
this.templateHelpers = {};
this._configureTemplateHelpers();
},
onShow : function() {
if (this.release.get("mappingResult") == "wrongYear") {
this.ui.titleMapping.hide();
} else {
this.ui.yearMapping.hide();
}
},
_configureTemplateHelpers : function() {
this.templateHelpers.release = this.release.toJSON();
this.templateHelpers.languages = LanguageCollection.toJSON()
},
_forceDownload : function() {
this.ui.indicator.show();
var self = this;
if (this.release.get("mappingResult") == "wrongYear") {
var altYear = new AltYearModel({
movieId : this.release.get("suspectedMovieId"),
year : this.release.get("year")
});
this.savePromise = altYear.save();
} else {
var altTitle = new AltTitleModel({
movieId : this.release.get("suspectedMovieId"),
title : this.release.get("movieTitle"),
language : this.ui.language.val(),
});
this.savePromise = altTitle.save();
}
this.savePromise.always(function(){
self.ui.indicator.hide();
});
this.savePromise.success(function(){
self.release.save(null, {
success : function() {
self.release.set('queued', true);
vent.trigger(vent.Commands.CloseModalCommand);
}
});
});
},
});

View File

@ -0,0 +1,44 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" data-dismiss="modal">&times;</button>
<h3>Force Download</h3>
</div>
<div class="modal-body indexer-modal">
<div id="title-mapping">
<p>The title "{{release.movieTitle}}" could not be found amongst the alternative titles of the movie. This could lead to problems when Radarr wants to import your movie.
If you click force download below, the title will be added to the alternative titles using the language selected below.</p>
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Language</label>
<div class="col-sm-5">
<select id="language-selection" class="form-control" name="language">
{{#each languages}}
{{#unless_eq nameLower compare="unknown"}}
<option value="{{nameLower}}" {{#if_eq nameLower compare="english"}} selected {{/if_eq}}>{{name}}</option>
{{/unless_eq}}
{{/each}}
</select>
</div>
<div class="col-sm-1 help-inline">
<i class="icon-sonarr-form-info" title="Language of the alternative title."/>
</div>
</div>
</div>
</div>
<div id="year-mapping">
<p>The year {{release.year}} does not match the expected release year. This could lead to problems when Radarr wants to import your movie.
If you click force download below, the year will be added as a secondary year for this movie.</p>
</div>
</div>
<div class="modal-footer">
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
<button class="btn" data-dismiss="modal">Cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-download">Force Download</button>
</div>
</div>
</div>

View File

@ -1,3 +1,11 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({});
module.exports = Backbone.Model.extend({
downloadOk : function() {
return this.get("mappingResult") == "success" || this.get("mappingResult") == "successLenientMapping";
},
forceDownloadOk : function() {
return this.get("mappingResult") == "wrongYear" || this.get("mappingResult") == "wrongTitle";
}
});