mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
First implementation of custom database table for movies.Some things are not yet working quite well (e.g. search clears when movies are added.). Also movies cannot yet be looked up!
This commit is contained in:
parent
74ca6149e3
commit
5ebfac6cc8
@ -233,6 +233,7 @@
|
|||||||
<Compile Include="Series\SeriesEditorModule.cs" />
|
<Compile Include="Series\SeriesEditorModule.cs" />
|
||||||
<Compile Include="Series\MovieLookupModule.cs" />
|
<Compile Include="Series\MovieLookupModule.cs" />
|
||||||
<Compile Include="Series\SeriesLookupModule.cs" />
|
<Compile Include="Series\SeriesLookupModule.cs" />
|
||||||
|
<Compile Include="Series\MovieModule.cs" />
|
||||||
<Compile Include="Series\SeriesModule.cs" />
|
<Compile Include="Series\SeriesModule.cs" />
|
||||||
<Compile Include="Series\MovieResource.cs" />
|
<Compile Include="Series\MovieResource.cs" />
|
||||||
<Compile Include="Series\SeriesResource.cs" />
|
<Compile Include="Series\SeriesResource.cs" />
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
using NzbDrone.Core.MetadataSource;
|
using NzbDrone.Core.MetadataSource;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Series
|
namespace NzbDrone.Api.Movie
|
||||||
{
|
{
|
||||||
public class MovieLookupModule : NzbDroneRestModule<MovieResource>
|
public class MovieLookupModule : NzbDroneRestModule<MovieResource>
|
||||||
{
|
{
|
||||||
|
225
src/NzbDrone.Api/Series/MovieModule.cs
Normal file
225
src/NzbDrone.Api/Series/MovieModule.cs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.MovieStats;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.SignalR;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Movie
|
||||||
|
{
|
||||||
|
public class MovieModule : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.Movie>,
|
||||||
|
IHandle<EpisodeImportedEvent>,
|
||||||
|
IHandle<EpisodeFileDeletedEvent>,
|
||||||
|
IHandle<MovieUpdatedEvent>,
|
||||||
|
IHandle<MovieEditedEvent>,
|
||||||
|
IHandle<MovieDeletedEvent>,
|
||||||
|
IHandle<MovieRenamedEvent>,
|
||||||
|
IHandle<MediaCoversUpdatedEvent>
|
||||||
|
|
||||||
|
{
|
||||||
|
private readonly IMovieService _moviesService;
|
||||||
|
private readonly IMovieStatisticsService _moviesStatisticsService;
|
||||||
|
private readonly IMapCoversToLocal _coverMapper;
|
||||||
|
|
||||||
|
public MovieModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||||
|
IMovieService moviesService,
|
||||||
|
IMovieStatisticsService moviesStatisticsService,
|
||||||
|
ISceneMappingService sceneMappingService,
|
||||||
|
IMapCoversToLocal coverMapper,
|
||||||
|
RootFolderValidator rootFolderValidator,
|
||||||
|
MoviePathValidator moviesPathValidator,
|
||||||
|
MovieExistsValidator moviesExistsValidator,
|
||||||
|
DroneFactoryValidator droneFactoryValidator,
|
||||||
|
MovieAncestorValidator moviesAncestorValidator,
|
||||||
|
ProfileExistsValidator profileExistsValidator
|
||||||
|
)
|
||||||
|
: base(signalRBroadcaster)
|
||||||
|
{
|
||||||
|
_moviesService = moviesService;
|
||||||
|
_moviesStatisticsService = moviesStatisticsService;
|
||||||
|
|
||||||
|
_coverMapper = coverMapper;
|
||||||
|
|
||||||
|
GetResourceAll = AllMovie;
|
||||||
|
GetResourceById = GetMovie;
|
||||||
|
CreateResource = AddMovie;
|
||||||
|
UpdateResource = UpdateMovie;
|
||||||
|
DeleteResource = DeleteMovie;
|
||||||
|
|
||||||
|
Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.ProfileId));
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(s => s.Path)
|
||||||
|
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||||
|
.IsValidPath()
|
||||||
|
.SetValidator(rootFolderValidator)
|
||||||
|
.SetValidator(moviesPathValidator)
|
||||||
|
.SetValidator(droneFactoryValidator)
|
||||||
|
.SetValidator(moviesAncestorValidator)
|
||||||
|
.When(s => !s.Path.IsNullOrWhiteSpace());
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator);
|
||||||
|
|
||||||
|
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
||||||
|
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
|
||||||
|
PostValidator.RuleFor(s => s.Title).NotEmpty();
|
||||||
|
PostValidator.RuleFor(s => s.ImdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator);
|
||||||
|
|
||||||
|
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MovieResource GetMovie(int id)
|
||||||
|
{
|
||||||
|
var movies = _moviesService.GetMovie(id);
|
||||||
|
return MapToResource(movies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MovieResource MapToResource(Core.Tv.Movie movies)
|
||||||
|
{
|
||||||
|
if (movies == null) return null;
|
||||||
|
|
||||||
|
var resource = movies.ToResource();
|
||||||
|
MapCoversToLocal(resource);
|
||||||
|
FetchAndLinkMovieStatistics(resource);
|
||||||
|
PopulateAlternateTitles(resource);
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MovieResource> AllMovie()
|
||||||
|
{
|
||||||
|
var moviesStats = _moviesStatisticsService.MovieStatistics();
|
||||||
|
var moviesResources = _moviesService.GetAllMovies().ToResource();
|
||||||
|
|
||||||
|
MapCoversToLocal(moviesResources.ToArray());
|
||||||
|
LinkMovieStatistics(moviesResources, moviesStats);
|
||||||
|
PopulateAlternateTitles(moviesResources);
|
||||||
|
|
||||||
|
return moviesResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddMovie(MovieResource moviesResource)
|
||||||
|
{
|
||||||
|
var model = moviesResource.ToModel();
|
||||||
|
|
||||||
|
return _moviesService.AddMovie(model).Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMovie(MovieResource moviesResource)
|
||||||
|
{
|
||||||
|
var model = moviesResource.ToModel(_moviesService.GetMovie(moviesResource.Id));
|
||||||
|
|
||||||
|
_moviesService.UpdateMovie(model);
|
||||||
|
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, moviesResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteMovie(int id)
|
||||||
|
{
|
||||||
|
var deleteFiles = false;
|
||||||
|
var deleteFilesQuery = Request.Query.deleteFiles;
|
||||||
|
|
||||||
|
if (deleteFilesQuery.HasValue)
|
||||||
|
{
|
||||||
|
deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_moviesService.DeleteMovie(id, deleteFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MapCoversToLocal(params MovieResource[] movies)
|
||||||
|
{
|
||||||
|
foreach (var moviesResource in movies)
|
||||||
|
{
|
||||||
|
_coverMapper.ConvertToLocalUrls(moviesResource.Id, moviesResource.Images);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FetchAndLinkMovieStatistics(MovieResource resource)
|
||||||
|
{
|
||||||
|
LinkMovieStatistics(resource, _moviesStatisticsService.MovieStatistics(resource.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LinkMovieStatistics(List<MovieResource> resources, List<MovieStatistics> moviesStatistics)
|
||||||
|
{
|
||||||
|
var dictMovieStats = moviesStatistics.ToDictionary(v => v.MovieId);
|
||||||
|
|
||||||
|
foreach (var movies in resources)
|
||||||
|
{
|
||||||
|
var stats = dictMovieStats.GetValueOrDefault(movies.Id);
|
||||||
|
if (stats == null) continue;
|
||||||
|
|
||||||
|
LinkMovieStatistics(movies, stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LinkMovieStatistics(MovieResource resource, MovieStatistics moviesStatistics)
|
||||||
|
{
|
||||||
|
resource.SizeOnDisk = moviesStatistics.SizeOnDisk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateAlternateTitles(List<MovieResource> resources)
|
||||||
|
{
|
||||||
|
foreach (var resource in resources)
|
||||||
|
{
|
||||||
|
PopulateAlternateTitles(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateAlternateTitles(MovieResource resource)
|
||||||
|
{
|
||||||
|
//var mappings = null;//_sceneMappingService.FindByTvdbId(resource.TvdbId);
|
||||||
|
|
||||||
|
//if (mappings == null) return;
|
||||||
|
|
||||||
|
//resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(EpisodeImportedEvent message)
|
||||||
|
{
|
||||||
|
//BroadcastResourceChange(ModelAction.Updated, message.ImportedEpisode.MovieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(EpisodeFileDeletedEvent message)
|
||||||
|
{
|
||||||
|
if (message.Reason == DeleteMediaFileReason.Upgrade) return;
|
||||||
|
|
||||||
|
//BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.MovieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieUpdatedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieEditedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieDeletedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Deleted, message.Movie.ToResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieRenamedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MediaCoversUpdatedEvent message)
|
||||||
|
{
|
||||||
|
//BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,9 @@
|
|||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Api.Series;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Series
|
namespace NzbDrone.Api.Movie
|
||||||
{
|
{
|
||||||
public class MovieResource : RestResource
|
public class MovieResource : RestResource
|
||||||
{
|
{
|
||||||
|
@ -41,6 +41,34 @@ protected override void MainDbUpgrade()
|
|||||||
.WithColumn("FirstAired").AsDateTime().Nullable()
|
.WithColumn("FirstAired").AsDateTime().Nullable()
|
||||||
.WithColumn("NextAiring").AsDateTime().Nullable();
|
.WithColumn("NextAiring").AsDateTime().Nullable();
|
||||||
|
|
||||||
|
Create.TableForModel("Movies")
|
||||||
|
.WithColumn("ImdbId").AsString().Unique()
|
||||||
|
.WithColumn("Title").AsString()
|
||||||
|
.WithColumn("TitleSlug").AsString().Unique()
|
||||||
|
.WithColumn("SortTitle").AsString().Nullable()
|
||||||
|
.WithColumn("CleanTitle").AsString()
|
||||||
|
.WithColumn("Status").AsInt32()
|
||||||
|
.WithColumn("Overview").AsString().Nullable()
|
||||||
|
.WithColumn("Images").AsString()
|
||||||
|
.WithColumn("Path").AsString()
|
||||||
|
.WithColumn("Monitored").AsBoolean()
|
||||||
|
.WithColumn("QualityProfileId").AsInt32()
|
||||||
|
.WithColumn("SeasonFolder").AsBoolean()
|
||||||
|
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||||
|
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Runtime").AsInt32()
|
||||||
|
.WithColumn("BacklogSetting").AsInt32()
|
||||||
|
.WithColumn("CustomStartDate").AsDateTime().Nullable()
|
||||||
|
.WithColumn("InCinemas").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Year").AsInt32().Nullable()
|
||||||
|
.WithColumn("Added").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Actors").AsString().Nullable()
|
||||||
|
.WithColumn("Ratings").AsString().Nullable()
|
||||||
|
.WithColumn("Genres").AsString().Nullable()
|
||||||
|
.WithColumn("Tags").AsString().Nullable()
|
||||||
|
.WithColumn("Certification").AsString().Nullable();
|
||||||
|
|
||||||
|
|
||||||
Create.TableForModel("Seasons")
|
Create.TableForModel("Seasons")
|
||||||
.WithColumn("SeriesId").AsInt32()
|
.WithColumn("SeriesId").AsInt32()
|
||||||
.WithColumn("SeasonNumber").AsInt32()
|
.WithColumn("SeasonNumber").AsInt32()
|
||||||
|
@ -76,6 +76,11 @@ public static void Map()
|
|||||||
.Relationship()
|
.Relationship()
|
||||||
.HasOne(s => s.Profile, s => s.ProfileId);
|
.HasOne(s => s.Profile, s => s.ProfileId);
|
||||||
|
|
||||||
|
Mapper.Entity<Movie>().RegisterModel("Movies")
|
||||||
|
.Ignore(s => s.RootFolderPath)
|
||||||
|
.Relationship()
|
||||||
|
.HasOne(s => s.Profile, s => s.ProfileId);
|
||||||
|
|
||||||
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles")
|
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles")
|
||||||
.Ignore(f => f.Path)
|
.Ignore(f => f.Path)
|
||||||
.Relationships.AutoMapICollectionOrComplexProperties()
|
.Relationships.AutoMapICollectionOrComplexProperties()
|
||||||
|
15
src/NzbDrone.Core/MediaFiles/Events/MovieRenamedEvent.cs
Normal file
15
src/NzbDrone.Core/MediaFiles/Events/MovieRenamedEvent.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class MovieRenamedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Movie Movie { get; private set; }
|
||||||
|
|
||||||
|
public MovieRenamedEvent(Movie movie)
|
||||||
|
{
|
||||||
|
Movie = movie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/NzbDrone.Core/MovieStats/MovieStatistics.cs
Normal file
42
src/NzbDrone.Core/MovieStats/MovieStatistics.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public class MovieStatistics : ResultSet
|
||||||
|
{
|
||||||
|
public int MovieId { get; set; }
|
||||||
|
public string NextAiringString { get; set; }
|
||||||
|
public string PreviousAiringString { get; set; }
|
||||||
|
public int EpisodeFileCount { get; set; }
|
||||||
|
public int EpisodeCount { get; set; }
|
||||||
|
public int TotalEpisodeCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
public List<SeasonStatistics> SeasonStatistics { get; set; }
|
||||||
|
|
||||||
|
public DateTime? NextAiring
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime nextAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(NextAiringString, out nextAiring)) return null;
|
||||||
|
|
||||||
|
return nextAiring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? PreviousAiring
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime previousAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(PreviousAiringString, out previousAiring)) return null;
|
||||||
|
|
||||||
|
return previousAiring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/NzbDrone.Core/MovieStats/MovieStatisticsRepository.cs
Normal file
86
src/NzbDrone.Core/MovieStats/MovieStatisticsRepository.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public interface IMovieStatisticsRepository
|
||||||
|
{
|
||||||
|
List<SeasonStatistics> MovieStatistics();
|
||||||
|
List<SeasonStatistics> MovieStatistics(int movieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieStatisticsRepository : IMovieStatisticsRepository
|
||||||
|
{
|
||||||
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
|
public MovieStatisticsRepository(IMainDatabase database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SeasonStatistics> MovieStatistics()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.AddParameter("currentDate", DateTime.UtcNow);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine(GetSelectClause());
|
||||||
|
sb.AppendLine(GetEpisodeFilesJoin());
|
||||||
|
sb.AppendLine(GetGroupByClause());
|
||||||
|
var queryText = sb.ToString();
|
||||||
|
|
||||||
|
return new List<SeasonStatistics>();
|
||||||
|
|
||||||
|
return mapper.Query<SeasonStatistics>(queryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SeasonStatistics> MovieStatistics(int movieId)
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.AddParameter("currentDate", DateTime.UtcNow);
|
||||||
|
mapper.AddParameter("movieId", movieId);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine(GetSelectClause());
|
||||||
|
sb.AppendLine(GetEpisodeFilesJoin());
|
||||||
|
sb.AppendLine("WHERE Episodes.MovieId = @movieId");
|
||||||
|
sb.AppendLine(GetGroupByClause());
|
||||||
|
var queryText = sb.ToString();
|
||||||
|
|
||||||
|
return new List<SeasonStatistics>();
|
||||||
|
|
||||||
|
return mapper.Query<SeasonStatistics>(queryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSelectClause()
|
||||||
|
{
|
||||||
|
return @"SELECT Episodes.*, SUM(EpisodeFiles.Size) as SizeOnDisk FROM
|
||||||
|
(SELECT
|
||||||
|
Episodes.MovieId,
|
||||||
|
Episodes.SeasonNumber,
|
||||||
|
SUM(CASE WHEN AirdateUtc <= @currentDate OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS TotalEpisodeCount,
|
||||||
|
SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount,
|
||||||
|
SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount,
|
||||||
|
MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 OR Monitored = 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString,
|
||||||
|
MAX(CASE WHEN AirDateUtc >= @currentDate OR EpisodeFileId = 0 AND Monitored = 0 THEN NULL ELSE AirDateUtc END) AS PreviousAiringString
|
||||||
|
FROM Episodes
|
||||||
|
GROUP BY Episodes.MovieId, Episodes.SeasonNumber) as Episodes";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetGroupByClause()
|
||||||
|
{
|
||||||
|
return "GROUP BY Episodes.MovieId, Episodes.SeasonNumber";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetEpisodeFilesJoin()
|
||||||
|
{
|
||||||
|
return @"LEFT OUTER JOIN EpisodeFiles
|
||||||
|
ON EpisodeFiles.MovieId = Episodes.MovieId
|
||||||
|
AND EpisodeFiles.SeasonNumber = Episodes.SeasonNumber";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/NzbDrone.Core/MovieStats/MovieStatisticsService.cs
Normal file
63
src/NzbDrone.Core/MovieStats/MovieStatisticsService.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public interface IMovieStatisticsService
|
||||||
|
{
|
||||||
|
List<MovieStatistics> MovieStatistics();
|
||||||
|
MovieStatistics MovieStatistics(int movieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieStatisticsService : IMovieStatisticsService
|
||||||
|
{
|
||||||
|
private readonly IMovieStatisticsRepository _movieStatisticsRepository;
|
||||||
|
|
||||||
|
public MovieStatisticsService(IMovieStatisticsRepository movieStatisticsRepository)
|
||||||
|
{
|
||||||
|
_movieStatisticsRepository = movieStatisticsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MovieStatistics> MovieStatistics()
|
||||||
|
{
|
||||||
|
var seasonStatistics = _movieStatisticsRepository.MovieStatistics();
|
||||||
|
|
||||||
|
return seasonStatistics.GroupBy(s => s.MovieId).Select(s => MapMovieStatistics(s.ToList())).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieStatistics MovieStatistics(int movieId)
|
||||||
|
{
|
||||||
|
var stats = _movieStatisticsRepository.MovieStatistics(movieId);
|
||||||
|
|
||||||
|
if (stats == null || stats.Count == 0) return new MovieStatistics();
|
||||||
|
|
||||||
|
return MapMovieStatistics(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MovieStatistics MapMovieStatistics(List<SeasonStatistics> seasonStatistics)
|
||||||
|
{
|
||||||
|
var movieStatistics = new MovieStatistics
|
||||||
|
{
|
||||||
|
SeasonStatistics = seasonStatistics,
|
||||||
|
MovieId = seasonStatistics.First().MovieId,
|
||||||
|
EpisodeFileCount = seasonStatistics.Sum(s => s.EpisodeFileCount),
|
||||||
|
EpisodeCount = seasonStatistics.Sum(s => s.EpisodeCount),
|
||||||
|
TotalEpisodeCount = seasonStatistics.Sum(s => s.TotalEpisodeCount),
|
||||||
|
SizeOnDisk = seasonStatistics.Sum(s => s.SizeOnDisk)
|
||||||
|
};
|
||||||
|
|
||||||
|
var nextAiring = seasonStatistics.Where(s => s.NextAiring != null)
|
||||||
|
.OrderBy(s => s.NextAiring)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
var previousAiring = seasonStatistics.Where(s => s.PreviousAiring != null)
|
||||||
|
.OrderBy(s => s.PreviousAiring)
|
||||||
|
.LastOrDefault();
|
||||||
|
|
||||||
|
movieStatistics.NextAiringString = nextAiring != null ? nextAiring.NextAiringString : null;
|
||||||
|
movieStatistics.PreviousAiringString = previousAiring != null ? previousAiring.PreviousAiringString : null;
|
||||||
|
|
||||||
|
return movieStatistics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/NzbDrone.Core/MovieStats/SeasonStatistics.cs
Normal file
41
src/NzbDrone.Core/MovieStats/SeasonStatistics.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public class SeasonStatistics : ResultSet
|
||||||
|
{
|
||||||
|
public int MovieId { get; set; }
|
||||||
|
public int SeasonNumber { get; set; }
|
||||||
|
public string NextAiringString { get; set; }
|
||||||
|
public string PreviousAiringString { get; set; }
|
||||||
|
public int EpisodeFileCount { get; set; }
|
||||||
|
public int EpisodeCount { get; set; }
|
||||||
|
public int TotalEpisodeCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
|
||||||
|
public DateTime? NextAiring
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime nextAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(NextAiringString, out nextAiring)) return null;
|
||||||
|
|
||||||
|
return nextAiring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? PreviousAiring
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime previousAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(PreviousAiringString, out previousAiring)) return null;
|
||||||
|
|
||||||
|
return previousAiring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -735,6 +735,7 @@
|
|||||||
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\MovieRenamedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
||||||
@ -1024,6 +1025,10 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="RootFolders\UnmappedFolder.cs" />
|
<Compile Include="RootFolders\UnmappedFolder.cs" />
|
||||||
<Compile Include="Security.cs" />
|
<Compile Include="Security.cs" />
|
||||||
|
<Compile Include="MovieStats\SeasonStatistics.cs" />
|
||||||
|
<Compile Include="MovieStats\MovieStatistics.cs" />
|
||||||
|
<Compile Include="MovieStats\MovieStatisticsRepository.cs" />
|
||||||
|
<Compile Include="MovieStats\MovieStatisticsService.cs" />
|
||||||
<Compile Include="SeriesStats\SeasonStatistics.cs" />
|
<Compile Include="SeriesStats\SeasonStatistics.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
||||||
@ -1058,11 +1063,15 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Tv\EpisodeService.cs" />
|
<Compile Include="Tv\EpisodeService.cs" />
|
||||||
<Compile Include="Tv\Events\EpisodeInfoRefreshedEvent.cs" />
|
<Compile Include="Tv\Events\EpisodeInfoRefreshedEvent.cs" />
|
||||||
|
<Compile Include="Tv\Events\MovieAddedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesAddedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesAddedEvent.cs" />
|
||||||
|
<Compile Include="Tv\Events\MovieDeletedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesDeletedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesDeletedEvent.cs" />
|
||||||
|
<Compile Include="Tv\Events\MovieEditedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||||
|
<Compile Include="Tv\Events\MovieUpdateEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
||||||
<Compile Include="Tv\MonitoringOptions.cs" />
|
<Compile Include="Tv\MonitoringOptions.cs" />
|
||||||
<Compile Include="Tv\MoveSeriesService.cs" />
|
<Compile Include="Tv\MoveSeriesService.cs" />
|
||||||
@ -1073,14 +1082,17 @@
|
|||||||
<Compile Include="Tv\Movie.cs" />
|
<Compile Include="Tv\Movie.cs" />
|
||||||
<Compile Include="Tv\Series.cs" />
|
<Compile Include="Tv\Series.cs" />
|
||||||
<Compile Include="Tv\SeriesAddedHandler.cs" />
|
<Compile Include="Tv\SeriesAddedHandler.cs" />
|
||||||
|
<Compile Include="Tv\MovieRepository.cs" />
|
||||||
<Compile Include="Tv\SeriesScannedHandler.cs" />
|
<Compile Include="Tv\SeriesScannedHandler.cs" />
|
||||||
<Compile Include="Tv\SeriesEditedService.cs" />
|
<Compile Include="Tv\SeriesEditedService.cs" />
|
||||||
<Compile Include="Tv\SeriesRepository.cs" />
|
<Compile Include="Tv\SeriesRepository.cs" />
|
||||||
|
<Compile Include="Tv\MovieService.cs" />
|
||||||
<Compile Include="Tv\SeriesService.cs">
|
<Compile Include="Tv\SeriesService.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Tv\MovieStatusType.cs" />
|
<Compile Include="Tv\MovieStatusType.cs" />
|
||||||
<Compile Include="Tv\SeriesStatusType.cs" />
|
<Compile Include="Tv\SeriesStatusType.cs" />
|
||||||
|
<Compile Include="Tv\MovieTitleNormalizer.cs" />
|
||||||
<Compile Include="Tv\SeriesTitleNormalizer.cs" />
|
<Compile Include="Tv\SeriesTitleNormalizer.cs" />
|
||||||
<Compile Include="Tv\SeriesTypes.cs" />
|
<Compile Include="Tv\SeriesTypes.cs" />
|
||||||
<Compile Include="Tv\ShouldRefreshSeries.cs" />
|
<Compile Include="Tv\ShouldRefreshSeries.cs" />
|
||||||
@ -1109,6 +1121,9 @@
|
|||||||
<Compile Include="Validation\Paths\FolderWritableValidator.cs" />
|
<Compile Include="Validation\Paths\FolderWritableValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\PathExistsValidator.cs" />
|
<Compile Include="Validation\Paths\PathExistsValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\PathValidator.cs" />
|
<Compile Include="Validation\Paths\PathValidator.cs" />
|
||||||
|
<Compile Include="Validation\Paths\MoviePathValidation.cs" />
|
||||||
|
<Compile Include="Validation\Paths\MovieAncestorValidator.cs" />
|
||||||
|
<Compile Include="Validation\Paths\MovieExistsValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\StartupFolderValidator.cs" />
|
<Compile Include="Validation\Paths\StartupFolderValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\RootFolderValidator.cs" />
|
<Compile Include="Validation\Paths\RootFolderValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\SeriesAncestorValidator.cs" />
|
<Compile Include="Validation\Paths\SeriesAncestorValidator.cs" />
|
||||||
|
@ -22,6 +22,7 @@ public interface IBuildFileNames
|
|||||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||||
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
||||||
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
||||||
|
string GetMovieFolder(Movie movie);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileNameBuilder : IBuildFileNames
|
public class FileNameBuilder : IBuildFileNames
|
||||||
@ -243,6 +244,11 @@ public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig nami
|
|||||||
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetMovieFolder(Movie movie)
|
||||||
|
{
|
||||||
|
return CleanFolderName(Parser.Parser.CleanSeriesTitle(movie.Title));
|
||||||
|
}
|
||||||
|
|
||||||
public static string CleanTitle(string title)
|
public static string CleanTitle(string title)
|
||||||
{
|
{
|
||||||
title = title.Replace("&", "and");
|
title = title.Replace("&", "and");
|
||||||
|
14
src/NzbDrone.Core/Tv/Events/MovieAddedEvent.cs
Normal file
14
src/NzbDrone.Core/Tv/Events/MovieAddedEvent.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv.Events
|
||||||
|
{
|
||||||
|
public class MovieAddedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Movie Movie { get; private set; }
|
||||||
|
|
||||||
|
public MovieAddedEvent(Movie movie)
|
||||||
|
{
|
||||||
|
Movie = movie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/NzbDrone.Core/Tv/Events/MovieDeletedEvent.cs
Normal file
16
src/NzbDrone.Core/Tv/Events/MovieDeletedEvent.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv.Events
|
||||||
|
{
|
||||||
|
public class MovieDeletedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Movie Movie { get; private set; }
|
||||||
|
public bool DeleteFiles { get; private set; }
|
||||||
|
|
||||||
|
public MovieDeletedEvent(Movie movie, bool deleteFiles)
|
||||||
|
{
|
||||||
|
Movie = movie;
|
||||||
|
DeleteFiles = deleteFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/NzbDrone.Core/Tv/Events/MovieEditedEvent.cs
Normal file
16
src/NzbDrone.Core/Tv/Events/MovieEditedEvent.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv.Events
|
||||||
|
{
|
||||||
|
public class MovieEditedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Movie Movie { get; private set; }
|
||||||
|
public Movie OldMovie { get; private set; }
|
||||||
|
|
||||||
|
public MovieEditedEvent(Movie movie, Movie oldMovie)
|
||||||
|
{
|
||||||
|
Movie = movie;
|
||||||
|
OldMovie = oldMovie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/NzbDrone.Core/Tv/Events/MovieUpdateEvent.cs
Normal file
14
src/NzbDrone.Core/Tv/Events/MovieUpdateEvent.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv.Events
|
||||||
|
{
|
||||||
|
public class MovieUpdatedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Movie Movie { get; private set; }
|
||||||
|
|
||||||
|
public MovieUpdatedEvent(Movie movie)
|
||||||
|
{
|
||||||
|
Movie = movie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/NzbDrone.Core/Tv/MovieRepository.cs
Normal file
50
src/NzbDrone.Core/Tv/MovieRepository.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public interface IMovieRepository : IBasicRepository<Movie>
|
||||||
|
{
|
||||||
|
bool MoviePathExists(string path);
|
||||||
|
Movie FindByTitle(string cleanTitle);
|
||||||
|
Movie FindByTitle(string cleanTitle, int year);
|
||||||
|
Movie FindByImdbId(string imdbid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
||||||
|
{
|
||||||
|
public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoviePathExists(string path)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.Path == path).Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitle(string cleanTitle)
|
||||||
|
{
|
||||||
|
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||||
|
|
||||||
|
return Query.Where(s => s.CleanTitle == cleanTitle)
|
||||||
|
.SingleOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitle(string cleanTitle, int year)
|
||||||
|
{
|
||||||
|
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||||
|
|
||||||
|
return Query.Where(s => s.CleanTitle == cleanTitle)
|
||||||
|
.AndWhere(s => s.Year == year)
|
||||||
|
.SingleOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByImdbId(string imdbid)
|
||||||
|
{
|
||||||
|
return Query.Where(s => s.ImdbId == imdbid).SingleOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
src/NzbDrone.Core/Tv/MovieService.cs
Normal file
194
src/NzbDrone.Core/Tv/MovieService.cs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
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.Parser;
|
||||||
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public interface IMovieService
|
||||||
|
{
|
||||||
|
Movie GetMovie(int movieId);
|
||||||
|
List<Movie> GetMovies(IEnumerable<int> movieIds);
|
||||||
|
Movie AddMovie(Movie newMovie);
|
||||||
|
Movie FindByImdbId(string imdbid);
|
||||||
|
Movie FindByTitle(string title);
|
||||||
|
Movie FindByTitle(string title, int year);
|
||||||
|
Movie FindByTitleInexact(string title);
|
||||||
|
void DeleteMovie(int movieId, bool deleteFiles);
|
||||||
|
List<Movie> GetAllMovies();
|
||||||
|
Movie UpdateMovie(Movie movie);
|
||||||
|
List<Movie> UpdateMovie(List<Movie> movie);
|
||||||
|
bool MoviePathExists(string folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieService : IMovieService
|
||||||
|
{
|
||||||
|
private readonly IMovieRepository _movieRepository;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly IBuildFileNames _fileNameBuilder;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public MovieService(IMovieRepository movieRepository,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
ISceneMappingService sceneMappingService,
|
||||||
|
IEpisodeService episodeService,
|
||||||
|
IBuildFileNames fileNameBuilder,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_movieRepository = movieRepository;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_fileNameBuilder = fileNameBuilder;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie GetMovie(int movieId)
|
||||||
|
{
|
||||||
|
return _movieRepository.Get(movieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Movie> GetMovies(IEnumerable<int> movieIds)
|
||||||
|
{
|
||||||
|
return _movieRepository.Get(movieIds).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie AddMovie(Movie newMovie)
|
||||||
|
{
|
||||||
|
Ensure.That(newMovie, () => newMovie).IsNotNull();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(newMovie.Path))
|
||||||
|
{
|
||||||
|
var folderName = _fileNameBuilder.GetMovieFolder(newMovie);
|
||||||
|
newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path);
|
||||||
|
|
||||||
|
newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle();
|
||||||
|
newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.ImdbId);
|
||||||
|
newMovie.Added = DateTime.UtcNow;
|
||||||
|
|
||||||
|
_movieRepository.Insert(newMovie);
|
||||||
|
_eventAggregator.PublishEvent(new MovieAddedEvent(GetMovie(newMovie.Id)));
|
||||||
|
|
||||||
|
return newMovie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitle(string title)
|
||||||
|
{
|
||||||
|
return _movieRepository.FindByTitle(title.CleanSeriesTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByImdbId(string imdbid)
|
||||||
|
{
|
||||||
|
return _movieRepository.FindByImdbId(imdbid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitleInexact(string title)
|
||||||
|
{
|
||||||
|
// find any movie clean title within the provided release title
|
||||||
|
string cleanTitle = title.CleanSeriesTitle();
|
||||||
|
var list = _movieRepository.All().Where(s => cleanTitle.Contains(s.CleanTitle)).ToList();
|
||||||
|
if (!list.Any())
|
||||||
|
{
|
||||||
|
// no movie matched
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (list.Count == 1)
|
||||||
|
{
|
||||||
|
// return the first movie if there is only one
|
||||||
|
return list.Single();
|
||||||
|
}
|
||||||
|
// build ordered list of movie by position in the search string
|
||||||
|
var query =
|
||||||
|
list.Select(movie => new
|
||||||
|
{
|
||||||
|
position = cleanTitle.IndexOf(movie.CleanTitle),
|
||||||
|
length = movie.CleanTitle.Length,
|
||||||
|
movie = movie
|
||||||
|
})
|
||||||
|
.Where(s => (s.position>=0))
|
||||||
|
.ToList()
|
||||||
|
.OrderBy(s => s.position)
|
||||||
|
.ThenByDescending(s => s.length)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// get the leftmost movie that is the longest
|
||||||
|
// movie are usually the first thing in release title, so we select the leftmost and longest match
|
||||||
|
var match = query.First().movie;
|
||||||
|
|
||||||
|
_logger.Debug("Multiple movie matched {0} from title {1}", match.Title, title);
|
||||||
|
foreach (var entry in list)
|
||||||
|
{
|
||||||
|
_logger.Debug("Multiple movie match candidate: {0} cleantitle: {1}", entry.Title, entry.CleanTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitle(string title, int year)
|
||||||
|
{
|
||||||
|
return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteMovie(int movieId, bool deleteFiles)
|
||||||
|
{
|
||||||
|
var movie = _movieRepository.Get(movieId);
|
||||||
|
_movieRepository.Delete(movieId);
|
||||||
|
_eventAggregator.PublishEvent(new MovieDeletedEvent(movie, deleteFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Movie> GetAllMovies()
|
||||||
|
{
|
||||||
|
return _movieRepository.All().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie UpdateMovie(Movie movie)
|
||||||
|
{
|
||||||
|
var storedMovie = GetMovie(movie.Id);
|
||||||
|
|
||||||
|
var updatedMovie = _movieRepository.Update(movie);
|
||||||
|
_eventAggregator.PublishEvent(new MovieEditedEvent(updatedMovie, storedMovie));
|
||||||
|
|
||||||
|
return updatedMovie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Movie> UpdateMovie(List<Movie> movie)
|
||||||
|
{
|
||||||
|
_logger.Debug("Updating {0} movie", movie.Count);
|
||||||
|
foreach (var s in movie)
|
||||||
|
{
|
||||||
|
_logger.Trace("Updating: {0}", s.Title);
|
||||||
|
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var folderName = new DirectoryInfo(s.Path).Name;
|
||||||
|
s.Path = Path.Combine(s.RootFolderPath, folderName);
|
||||||
|
_logger.Trace("Changing path for {0} to {1}", s.Title, s.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Trace("Not changing path for: {0}", s.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_movieRepository.UpdateMany(movie);
|
||||||
|
_logger.Debug("{0} movie updated", movie.Count);
|
||||||
|
|
||||||
|
return movie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoviePathExists(string folder)
|
||||||
|
{
|
||||||
|
return _movieRepository.MoviePathExists(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
22
src/NzbDrone.Core/Tv/MovieTitleNormalizer.cs
Normal file
22
src/NzbDrone.Core/Tv/MovieTitleNormalizer.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public static class MovieTitleNormalizer
|
||||||
|
{
|
||||||
|
private readonly static Dictionary<string, string> PreComputedTitles = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "tt_109823457098", "a to z" },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string Normalize(string title, string imdbid)
|
||||||
|
{
|
||||||
|
if (PreComputedTitles.ContainsKey(imdbid))
|
||||||
|
{
|
||||||
|
return PreComputedTitles[imdbid];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parser.Parser.NormalizeTitle(title).ToLower();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/NzbDrone.Core/Validation/Paths/MovieAncestorValidator.cs
Normal file
25
src/NzbDrone.Core/Validation/Paths/MovieAncestorValidator.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class MovieAncestorValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IMovieService _seriesService;
|
||||||
|
|
||||||
|
public MovieAncestorValidator(IMovieService seriesService)
|
||||||
|
: base("Path is an ancestor of an existing path")
|
||||||
|
{
|
||||||
|
_seriesService = seriesService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
|
return !_seriesService.GetAllMovies().Any(s => context.PropertyValue.ToString().IsParentPath(s.Path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/NzbDrone.Core/Validation/Paths/MovieExistsValidator.cs
Normal file
26
src/NzbDrone.Core/Validation/Paths/MovieExistsValidator.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class MovieExistsValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IMovieService _seriesService;
|
||||||
|
|
||||||
|
public MovieExistsValidator(IMovieService seriesService)
|
||||||
|
: base("This series has already been added")
|
||||||
|
{
|
||||||
|
_seriesService = seriesService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
|
var imdbid = context.PropertyValue.ToString();
|
||||||
|
|
||||||
|
return (!_seriesService.GetAllMovies().Exists(s => s.ImdbId == imdbid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs
Normal file
27
src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class MoviePathValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IMovieService _seriesService;
|
||||||
|
|
||||||
|
public MoviePathValidator(IMovieService seriesService)
|
||||||
|
: base("Path is already configured for another series")
|
||||||
|
{
|
||||||
|
_seriesService = seriesService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
|
dynamic instance = context.ParentContext.InstanceToValidate;
|
||||||
|
var instanceId = (int)instance.Id;
|
||||||
|
|
||||||
|
return (!_seriesService.GetAllMovies().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != instanceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ var Backbone = require('backbone');
|
|||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
module.exports = Backbone.Model.extend({
|
module.exports = Backbone.Model.extend({
|
||||||
urlRoot : window.NzbDrone.ApiRoot + '/movies',
|
urlRoot : window.NzbDrone.ApiRoot + '/movie',
|
||||||
|
|
||||||
defaults : {
|
defaults : {
|
||||||
episodeFileCount : 0,
|
episodeFileCount : 0,
|
||||||
|
@ -10,9 +10,9 @@ var moment = require('moment');
|
|||||||
require('../Mixins/backbone.signalr.mixin');
|
require('../Mixins/backbone.signalr.mixin');
|
||||||
|
|
||||||
var Collection = PageableCollection.extend({
|
var Collection = PageableCollection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/movies',
|
url : window.NzbDrone.ApiRoot + '/movie',
|
||||||
model : MovieModel,
|
model : MovieModel,
|
||||||
tableName : 'movies',
|
tableName : 'movie',
|
||||||
|
|
||||||
state : {
|
state : {
|
||||||
sortKey : 'sortTitle',
|
sortKey : 'sortTitle',
|
||||||
@ -115,6 +115,6 @@ Collection = AsFilteredCollection.call(Collection);
|
|||||||
Collection = AsSortedCollection.call(Collection);
|
Collection = AsSortedCollection.call(Collection);
|
||||||
Collection = AsPersistedStateCollection.call(Collection);
|
Collection = AsPersistedStateCollection.call(Collection);
|
||||||
|
|
||||||
var data = ApiData.get('series');
|
var data = ApiData.get('movie');
|
||||||
|
|
||||||
module.exports = new Collection(data, { full : true }).bindSignalR();
|
module.exports = new Collection(data, { full : true }).bindSignalR();
|
||||||
|
Loading…
Reference in New Issue
Block a user