mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
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:
parent
8927e7c2c6
commit
cfcb66fba1
@ -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 @@ public void BuildWhereClause(StringBuilder sql)
|
||||
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)
|
||||
{
|
||||
|
@ -14,8 +14,21 @@ public SqliteRowCountQueryDecorator(SelectQuery innerQuery)
|
||||
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);
|
||||
|
@ -46,7 +46,7 @@ public ReleaseModule(IFetchAndParseRss rssFetcherAndParser,
|
||||
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 @@ private Response DownloadRelease(ReleaseResource release)
|
||||
|
||||
try
|
||||
{
|
||||
_downloadService.DownloadReport(remoteMovie);
|
||||
_downloadService.DownloadReport(remoteMovie, false);
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
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 @@ public class ReleaseResource : RestResource
|
||||
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 @@ public class ReleaseResource : RestResource
|
||||
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 @@ 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;
|
||||
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 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||
//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 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||
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 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||
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 @@ public static ReleaseResource ToResource(this DownloadDecision model)
|
||||
CommentUrl = releaseInfo.CommentUrl,
|
||||
DownloadUrl = releaseInfo.DownloadUrl,
|
||||
InfoUrl = releaseInfo.InfoUrl,
|
||||
DownloadAllowed = downloadAllowed,
|
||||
//DownloadAllowed = downloadAllowed,
|
||||
//ReleaseWeight
|
||||
|
||||
MagnetUrl = torrentInfo.MagnetUrl,
|
||||
|
57
src/NzbDrone.Api/Movies/AlternativeTitleModule.cs
Normal file
57
src/NzbDrone.Api/Movies/AlternativeTitleModule.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
63
src/NzbDrone.Api/Movies/AlternativeYearModule.cs
Normal file
63
src/NzbDrone.Api/Movies/AlternativeYearModule.cs
Normal 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())
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
75
src/NzbDrone.Api/Movies/AlternativeYearResource.cs
Normal file
75
src/NzbDrone.Api/Movies/AlternativeYearResource.cs
Normal 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();
|
||||
}
|
||||
}*/
|
||||
}
|
@ -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" />
|
||||
|
@ -105,7 +105,7 @@ private JsonResponse<QueueResource> Grab()
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
_downloadService.DownloadReport(pendingRelease.RemoteMovie);
|
||||
_downloadService.DownloadReport(pendingRelease.RemoteMovie, false);
|
||||
|
||||
return resource.AsResponse();
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
81
src/NzbDrone.Api/Series/AlternativeTitleResource.cs
Normal file
81
src/NzbDrone.Api/Series/AlternativeTitleResource.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,9 @@ public MovieResource()
|
||||
|
||||
//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 @@ public MovieResource()
|
||||
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 @@ public static MovieResource ToResource(this Core.Tv.Movie model)
|
||||
downloaded = true;
|
||||
movieFile = model.MovieFile.Value.ToResource();
|
||||
}
|
||||
|
||||
//model.AlternativeTitles.LazyLoad();
|
||||
|
||||
return new MovieResource
|
||||
{
|
||||
@ -131,6 +135,8 @@ public static MovieResource ToResource(this Core.Tv.Movie model)
|
||||
Images = model.Images,
|
||||
|
||||
Year = model.Year,
|
||||
SecondaryYear = model.SecondaryYear,
|
||||
SecondaryYearSourceId = model.SecondaryYearSourceId,
|
||||
|
||||
Path = model.Path,
|
||||
ProfileId = model.ProfileId,
|
||||
@ -156,7 +162,7 @@ public static MovieResource ToResource(this Core.Tv.Movie model)
|
||||
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 @@ public static Core.Tv.Movie ToModel(this MovieResource resource)
|
||||
Images = resource.Images,
|
||||
|
||||
Year = resource.Year,
|
||||
SecondaryYear = resource.SecondaryYear,
|
||||
SecondaryYearSourceId = resource.SecondaryYearSourceId,
|
||||
|
||||
Path = resource.Path,
|
||||
ProfileId = resource.ProfileId,
|
||||
@ -209,7 +217,7 @@ public static Core.Tv.Movie ToModel(this MovieResource resource)
|
||||
Tags = resource.Tags,
|
||||
Added = resource.Added,
|
||||
AddOptions = resource.AddOptions,
|
||||
AlternativeTitles = resource.AlternativeTitles,
|
||||
//AlternativeTitles = resource.AlternativeTitles,
|
||||
Ratings = resource.Ratings,
|
||||
YouTubeTrailerId = resource.YouTubeTrailerId,
|
||||
Studio = resource.Studio
|
||||
|
@ -199,7 +199,7 @@ private void PopulateAlternateTitles(SeriesResource resource)
|
||||
|
||||
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)
|
||||
|
@ -20,7 +20,7 @@ public SeriesResource()
|
||||
|
||||
//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
|
||||
|
@ -263,7 +263,7 @@ public void should_not_allow_download_if_series_is_unknown()
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
|
||||
result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
|
||||
//result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -278,7 +278,7 @@ public void should_not_allow_download_if_no_episodes_found()
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
|
||||
result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
|
||||
//result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -76,7 +76,7 @@ public void should_download_report_if_movie_was_not_already_downloaded()
|
||||
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 @@ public void should_only_download_movie_once()
|
||||
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 @@ public void should_not_add_to_downloaded_list_when_download_fails()
|
||||
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 @@ public void should_not_grab_if_pending()
|
||||
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]
|
||||
|
@ -6,7 +6,9 @@
|
||||
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 @@ public void Setup()
|
||||
.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 @@ public void Setup()
|
||||
|
||||
_alternativeTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitle = _movie.AlternativeTitles.First(),
|
||||
MovieTitle = _movie.AlternativeTitles.First().Title,
|
||||
Year = _movie.Year,
|
||||
};
|
||||
|
||||
|
@ -307,6 +307,7 @@ public void should_parse_quality_from_extension(string title)
|
||||
|
||||
[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);
|
||||
|
@ -3,8 +3,10 @@
|
||||
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 @@ public BasicRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||
_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 @@ public void SetFields(TModel model, params Expression<Func<TModel, object>>[] pr
|
||||
|
||||
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 @@ private void PublishModelEvent(TModel model, ModelAction action)
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual QueryBuilder<TModel> AddJoinQueries(QueryBuilder<TModel> baseQuery)
|
||||
{
|
||||
return baseQuery;
|
||||
}
|
||||
|
||||
protected virtual bool PublishModelEvents => false;
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,12 @@ public static RelationshipBuilder<TParent> Relationship<TParent>(this ColumnMapB
|
||||
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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,45 @@
|
||||
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 @@ public static void Map()
|
||||
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");
|
||||
|
||||
|
||||
|
@ -113,11 +113,11 @@ private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> report
|
||||
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 @@ private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> report
|
||||
{
|
||||
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
remoteMovie.DownloadAllowed = true;
|
||||
//remoteMovie.DownloadAllowed = true;
|
||||
if (_configService.AllowHardcodedSubs)
|
||||
{
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
@ -146,7 +146,7 @@ private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> report
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteMovie.DownloadAllowed = true;
|
||||
//remoteMovie.DownloadAllowed = true;
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
}
|
||||
|
||||
|
@ -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 @@ public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisio
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ public void DownloadReport(RemoteEpisode remoteEpisode)
|
||||
_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
|
||||
|
@ -88,7 +88,7 @@ public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions)
|
||||
|
||||
try
|
||||
{
|
||||
_downloadService.DownloadReport(remoteMovie);
|
||||
_downloadService.DownloadReport(remoteMovie, false);
|
||||
grabbed.Add(report);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -59,7 +59,7 @@ public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchC
|
||||
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);
|
||||
|
||||
|
@ -3,7 +3,10 @@
|
||||
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 @@ public interface IRadarrAPIClient
|
||||
{
|
||||
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 @@ private T Execute<T>(HttpRequest request)
|
||||
{
|
||||
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 @@ public List<MovieResult> DiscoverMovies(string action, Func<HttpRequest, HttpReq
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
@ -27,21 +27,183 @@ public class RadarrError
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.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 @@ public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMov
|
||||
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 @@ public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBu
|
||||
_movieService = movieService;
|
||||
_predbService = predbService;
|
||||
_exclusionService = exclusionService;
|
||||
_altTitleService = altTitleService;
|
||||
_radarrAPI = radarrAPI;
|
||||
|
||||
_logger = logger;
|
||||
@ -133,21 +137,28 @@ public Movie GetMovieInfo(int TmdbId, Profile profile = null, bool hasPreDBEntry
|
||||
}
|
||||
|
||||
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 @@ public Movie GetMovieInfo(int TmdbId, Profile profile = null, bool hasPreDBEntry
|
||||
}
|
||||
}
|
||||
|
||||
movie.AlternativeTitles.AddRange(altTitles);
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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" />
|
||||
|
@ -11,7 +11,7 @@ public class RemoteMovie
|
||||
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()
|
||||
{
|
||||
|
@ -9,6 +9,7 @@
|
||||
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 @@ private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId,
|
||||
{
|
||||
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 @@ private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, Search
|
||||
|
||||
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 @@ private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, Search
|
||||
|
||||
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 @@ private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, Search
|
||||
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;
|
||||
|
@ -6,6 +6,8 @@
|
||||
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 @@ public Movie()
|
||||
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 @@ public Movie()
|
||||
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; }
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
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 @@ public PagingSpec<Movie> MoviesWithoutFiles(PagingSpec<Movie> pagingSpec)
|
||||
|
||||
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 @@ public override PagingSpec<Movie> GetPaged(PagingSpec<Movie> pagingSpec)
|
||||
{
|
||||
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 @@ public override PagingSpec<Movie> GetPaged(PagingSpec<Movie> pagingSpec)
|
||||
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 @@ public override PagingSpec<Movie> GetPaged(PagingSpec<Movie> pagingSpec)
|
||||
|
||||
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 @@ private Movie FindByTitle(string cleanTitle, int? year)
|
||||
|
||||
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)
|
||||
|
@ -16,7 +16,7 @@ public static class QueryExtensions
|
||||
{
|
||||
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 @@ public static class EnumerableExtensions
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
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 @@ public class RefreshMovieService : IExecute<RefreshMovieCommand>
|
||||
{
|
||||
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 @@ private void RefreshMovieInfo(Movie movie)
|
||||
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 @@ private void RefreshMovieInfo(Movie movie)
|
||||
_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));
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public ShouldRefreshMovie(IEpisodeService episodeService, Logger logger)
|
||||
|
||||
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);
|
||||
|
@ -47,7 +47,7 @@ private bool BeValidRelease(ReleaseResource releaseResource)
|
||||
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);
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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 });
|
||||
|
||||
|
@ -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>
|
||||
|
22
src/UI/Movies/Titles/LanguageCell.js
Normal file
22
src/UI/Movies/Titles/LanguageCell.js
Normal 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();});
|
||||
}
|
||||
|
||||
|
||||
});
|
5
src/UI/Movies/Titles/NoTitlesView.js
Normal file
5
src/UI/Movies/Titles/NoTitlesView.js
Normal file
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Movies/Titles/NoTitlesViewTemplate'
|
||||
});
|
3
src/UI/Movies/Titles/NoTitlesViewTemplate.hbs
Normal file
3
src/UI/Movies/Titles/NoTitlesViewTemplate.hbs
Normal file
@ -0,0 +1,3 @@
|
||||
<p class="text-warning">
|
||||
No alternative titles for this movie.
|
||||
</p>
|
42
src/UI/Movies/Titles/SourceCell.js
Normal file
42
src/UI/Movies/Titles/SourceCell.js
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
});
|
6
src/UI/Movies/Titles/TitleCell.js
Normal file
6
src/UI/Movies/Titles/TitleCell.js
Normal file
@ -0,0 +1,6 @@
|
||||
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'series-title-cell',
|
||||
template : 'Movies/Titles/TitleTemplate'
|
||||
});
|
3
src/UI/Movies/Titles/TitleModel.js
Normal file
3
src/UI/Movies/Titles/TitleModel.js
Normal file
@ -0,0 +1,3 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({});
|
1
src/UI/Movies/Titles/TitleTemplate.hbs
Normal file
1
src/UI/Movies/Titles/TitleTemplate.hbs
Normal file
@ -0,0 +1 @@
|
||||
{{this}}
|
30
src/UI/Movies/Titles/TitlesCollection.js
Normal file
30
src/UI/Movies/Titles/TitlesCollection.js
Normal 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;
|
117
src/UI/Movies/Titles/TitlesLayout.js
Normal file
117
src/UI/Movies/Titles/TitlesLayout.js
Normal 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();
|
||||
}
|
||||
});
|
3
src/UI/Movies/Titles/TitlesLayoutTemplate.hbs
Normal file
3
src/UI/Movies/Titles/TitlesLayoutTemplate.hbs
Normal file
@ -0,0 +1,3 @@
|
||||
<div id="movie-titles-region">
|
||||
<div id="movie-titles-grid" class="table-responsive"></div>
|
||||
</div>
|
@ -534,3 +534,9 @@
|
||||
list-style-type : none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-text {
|
||||
.year {
|
||||
color : gray;
|
||||
}
|
||||
}
|
6
src/UI/Release/AlternativeTitleModel.js
Normal file
6
src/UI/Release/AlternativeTitleModel.js
Normal file
@ -0,0 +1,6 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
urlRoot : window.NzbDrone.ApiRoot + '/alttitle',
|
||||
});
|
6
src/UI/Release/AlternativeYearModel.js
Normal file
6
src/UI/Release/AlternativeYearModel.js
Normal file
@ -0,0 +1,6 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
urlRoot : window.NzbDrone.ApiRoot + '/altyear',
|
||||
});
|
@ -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;
|
||||
|
81
src/UI/Release/ForceDownloadView.js
Normal file
81
src/UI/Release/ForceDownloadView.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
44
src/UI/Release/ForceDownloadViewTemplate.hbs
Normal file
44
src/UI/Release/ForceDownloadViewTemplate.hbs
Normal file
@ -0,0 +1,44 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" data-dismiss="modal">×</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>
|
@ -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";
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user