mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
33 Commits
v2.0.0.491
...
v0.1-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d835c168d3 | ||
|
|
329786365d | ||
|
|
4f6380a73c | ||
|
|
cde1217356 | ||
|
|
2eedfca78a | ||
|
|
0d65984991 | ||
|
|
2a932fe7e8 | ||
|
|
0e02171938 | ||
|
|
2a3b0304cb | ||
|
|
16e35f68bb | ||
|
|
74b1c846a5 | ||
|
|
1f930c18e4 | ||
|
|
d9e60eff6b | ||
|
|
097982334c | ||
|
|
be6e6b910e | ||
|
|
31b9ec1116 | ||
|
|
ff6c3b70d3 | ||
|
|
0fd0b31a60 | ||
|
|
76e6ebc63c | ||
|
|
2ea35adb98 | ||
|
|
782f63f510 | ||
|
|
c874122fc0 | ||
|
|
2efda4933d | ||
|
|
b7c70d750a | ||
|
|
5ebfac6cc8 | ||
|
|
74ca6149e3 | ||
|
|
40d7590f80 | ||
|
|
2cb27240dc | ||
|
|
837c2683df | ||
|
|
0b278c7db8 | ||
|
|
0b765d10fe | ||
|
|
20dbdfb344 | ||
|
|
426448ed98 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -130,6 +130,7 @@ output/*
|
||||
|
||||
#OS X metadata files
|
||||
._*
|
||||
.DS_Store
|
||||
|
||||
_start
|
||||
_temp_*/**/*
|
||||
|
||||
9
.travis.yml
Normal file
9
.travis.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
language: csharp
|
||||
solution: src/NzbDrone.sln
|
||||
script: # the following commands are just examples, use whatever your build process requires
|
||||
- ./build.sh
|
||||
- chmod +x test.sh
|
||||
# - ./test.sh Linux Unit Takes far too long, maybe even crashes travis :/
|
||||
install:
|
||||
- sudo apt-get install nodejs
|
||||
- sudo apt-get install npm
|
||||
@@ -19,13 +19,16 @@ gulp.task('less', function() {
|
||||
paths.src.root + 'Series/series.less',
|
||||
paths.src.root + 'Activity/activity.less',
|
||||
paths.src.root + 'AddSeries/addSeries.less',
|
||||
paths.src.root + 'AddMovies/addMovies.less',
|
||||
paths.src.root + 'Calendar/calendar.less',
|
||||
paths.src.root + 'Cells/cells.less',
|
||||
paths.src.root + 'ManualImport/manualimport.less',
|
||||
paths.src.root + 'Settings/settings.less',
|
||||
paths.src.root + 'System/Logs/logs.less',
|
||||
paths.src.root + 'System/Update/update.less',
|
||||
paths.src.root + 'System/Info/info.less'
|
||||
paths.src.root + 'System/Info/info.less',
|
||||
paths.src.root + 'Movies/movies.less',
|
||||
|
||||
];
|
||||
|
||||
return gulp.src(src)
|
||||
|
||||
19
readme.md
19
readme.md
@@ -1,7 +1,20 @@
|
||||
# Sonarr #
|
||||
# Radarr [](https://travis-ci.org/galli-leo/Radarr)#
|
||||
|
||||
This fork of Sonarr aims to turn it into something like Couchpotato.
|
||||
|
||||
Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
|
||||
## Currently working:
|
||||
* Adding new movies (Note: Movies are currently added as one series with one season and one episode. This will change in the future)
|
||||
* Manually searching for releases of movies.
|
||||
* Automatically searching for releases.
|
||||
* Rarbg.to indexer (Other indexers are coming, I just need to find the right categories)
|
||||
* Everything that has nothing to do with series from Sonarr should be working as well.
|
||||
|
||||
## Planned Features:
|
||||
* Scanning PreDB to know when a new release is available.
|
||||
* Fixing the other Indexers.
|
||||
* Fixing how movies are stored and displayed.
|
||||
* Importing of Sonarr config.
|
||||
* New TorrentPotato Indexer.
|
||||
|
||||
## Major Features Include: ##
|
||||
|
||||
@@ -17,6 +30,8 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
|
||||
* Full support for specials and multi-episode releases
|
||||
* And a beautiful UI
|
||||
|
||||
## Download
|
||||
The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases.
|
||||
|
||||
## Configuring Development Environment: ##
|
||||
|
||||
|
||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -3,6 +3,7 @@ using Nancy;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
@@ -34,12 +35,18 @@ namespace NzbDrone.Api.History
|
||||
|
||||
resource.Series = model.Series.ToResource();
|
||||
resource.Episode = model.Episode.ToResource();
|
||||
resource.Movie = model.Movie.ToResource();
|
||||
|
||||
if (model.Series != null)
|
||||
{
|
||||
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Series.Profile.Value, model.Quality);
|
||||
}
|
||||
|
||||
if (model.Movie != null)
|
||||
{
|
||||
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Movie.Profile.Value, model.Quality);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -47,6 +54,8 @@ namespace NzbDrone.Api.History
|
||||
{
|
||||
var episodeId = Request.Query.EpisodeId;
|
||||
|
||||
var movieId = Request.Query.MovieId;
|
||||
|
||||
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, Core.History.History>("date", SortDirection.Descending);
|
||||
|
||||
if (pagingResource.FilterKey == "eventType")
|
||||
@@ -61,6 +70,12 @@ namespace NzbDrone.Api.History
|
||||
pagingSpec.FilterExpression = h => h.EpisodeId == i;
|
||||
}
|
||||
|
||||
if (movieId.HasValue)
|
||||
{
|
||||
int i = (int)movieId;
|
||||
pagingSpec.FilterExpression = h => h.MovieId == i;
|
||||
}
|
||||
|
||||
return ApplyToPage(_historyService.Paged, pagingSpec, MapToResource);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
@@ -12,6 +13,7 @@ namespace NzbDrone.Api.History
|
||||
public class HistoryResource : RestResource
|
||||
{
|
||||
public int EpisodeId { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
@@ -22,7 +24,7 @@ namespace NzbDrone.Api.History
|
||||
public HistoryEventType EventType { get; set; }
|
||||
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
|
||||
public MovieResource Movie { get; set; }
|
||||
public EpisodeResource Episode { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
}
|
||||
@@ -39,6 +41,7 @@ namespace NzbDrone.Api.History
|
||||
|
||||
EpisodeId = model.EpisodeId,
|
||||
SeriesId = model.SeriesId,
|
||||
MovieId = model.MovieId,
|
||||
SourceTitle = model.SourceTitle,
|
||||
Quality = model.Quality,
|
||||
//QualityCutoffNotMet
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace NzbDrone.Api.Indexers
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly ICached<RemoteEpisode> _remoteEpisodeCache;
|
||||
private readonly ICached<RemoteMovie> _remoteMovieCache;
|
||||
|
||||
public ReleaseModule(IFetchAndParseRss rssFetcherAndParser,
|
||||
ISearchForNzb nzbSearchService,
|
||||
@@ -49,6 +50,7 @@ namespace NzbDrone.Api.Indexers
|
||||
PostValidator.RuleFor(s => s.Guid).NotEmpty();
|
||||
|
||||
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
|
||||
_remoteMovieCache = cacheManager.GetCache<RemoteMovie>(GetType(), "remoteMovies");
|
||||
}
|
||||
|
||||
private Response DownloadRelease(ReleaseResource release)
|
||||
@@ -59,7 +61,26 @@ namespace NzbDrone.Api.Indexers
|
||||
{
|
||||
_logger.Debug("Couldn't find requested release in cache, cache timeout probably expired.");
|
||||
|
||||
return new NotFoundResponse();
|
||||
var remoteMovie = _remoteMovieCache.Find(release.Guid);
|
||||
|
||||
if (remoteMovie == null)
|
||||
{
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_downloadService.DownloadReport(remoteMovie);
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
{
|
||||
_logger.Error(ex, ex.Message);
|
||||
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
|
||||
}
|
||||
|
||||
return release.AsResponse();
|
||||
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
@@ -82,6 +103,11 @@ namespace NzbDrone.Api.Indexers
|
||||
return GetEpisodeReleases(Request.Query.episodeId);
|
||||
}
|
||||
|
||||
if (Request.Query.movieId != null)
|
||||
{
|
||||
return GetMovieReleases(Request.Query.movieId);
|
||||
}
|
||||
|
||||
return GetRss();
|
||||
}
|
||||
|
||||
@@ -102,6 +128,27 @@ namespace NzbDrone.Api.Indexers
|
||||
return new List<ReleaseResource>();
|
||||
}
|
||||
|
||||
private List<ReleaseResource> GetMovieReleases(int movieId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var decisions = _nzbSearchService.MovieSearch(movieId, true);
|
||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisionsForMovies(decisions);
|
||||
|
||||
return MapDecisions(prioritizedDecisions);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
_logger.Error(ex, "One or more indexer you selected does not support movie search yet: " + ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Movie search failed: " + ex.Message);
|
||||
}
|
||||
|
||||
return new List<ReleaseResource>();
|
||||
}
|
||||
|
||||
private List<ReleaseResource> GetRss()
|
||||
{
|
||||
var reports = _rssFetcherAndParser.Fetch();
|
||||
@@ -113,7 +160,15 @@ namespace NzbDrone.Api.Indexers
|
||||
|
||||
protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
|
||||
{
|
||||
_remoteEpisodeCache.Set(decision.RemoteEpisode.Release.Guid, decision.RemoteEpisode, TimeSpan.FromMinutes(30));
|
||||
if (decision.IsForMovie)
|
||||
{
|
||||
_remoteMovieCache.Set(decision.RemoteMovie.Release.Guid, decision.RemoteMovie, TimeSpan.FromMinutes(30));
|
||||
}
|
||||
else
|
||||
{
|
||||
_remoteEpisodeCache.Set(decision.RemoteEpisode.Release.Guid, decision.RemoteEpisode, TimeSpan.FromMinutes(30));
|
||||
}
|
||||
|
||||
return base.MapDecision(decision, initialWeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ namespace NzbDrone.Api.Indexers
|
||||
var parsedEpisodeInfo = model.RemoteEpisode.ParsedEpisodeInfo;
|
||||
var remoteEpisode = model.RemoteEpisode;
|
||||
var torrentInfo = (model.RemoteEpisode.Release as TorrentInfo) ?? new TorrentInfo();
|
||||
var downloadAllowed = model.RemoteEpisode.DownloadAllowed;
|
||||
if (model.IsForMovie)
|
||||
{
|
||||
downloadAllowed = model.RemoteMovie.DownloadAllowed;
|
||||
}
|
||||
|
||||
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
|
||||
return new ReleaseResource
|
||||
@@ -119,7 +124,7 @@ namespace NzbDrone.Api.Indexers
|
||||
CommentUrl = releaseInfo.CommentUrl,
|
||||
DownloadUrl = releaseInfo.DownloadUrl,
|
||||
InfoUrl = releaseInfo.InfoUrl,
|
||||
DownloadAllowed = remoteEpisode.DownloadAllowed,
|
||||
DownloadAllowed = downloadAllowed,
|
||||
//ReleaseWeight
|
||||
|
||||
MagnetUrl = torrentInfo.MagnetUrl,
|
||||
|
||||
@@ -231,8 +231,11 @@
|
||||
<Compile Include="Series\SeasonResource.cs" />
|
||||
<Compile Include="SeasonPass\SeasonPassModule.cs" />
|
||||
<Compile Include="Series\SeriesEditorModule.cs" />
|
||||
<Compile Include="Series\MovieLookupModule.cs" />
|
||||
<Compile Include="Series\SeriesLookupModule.cs" />
|
||||
<Compile Include="Series\MovieModule.cs" />
|
||||
<Compile Include="Series\SeriesModule.cs" />
|
||||
<Compile Include="Series\MovieResource.cs" />
|
||||
<Compile Include="Series\SeriesResource.cs" />
|
||||
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
||||
<Compile Include="System\Backup\BackupModule.cs" />
|
||||
|
||||
@@ -4,6 +4,7 @@ using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using System.Linq;
|
||||
@@ -14,6 +15,7 @@ namespace NzbDrone.Api.Queue
|
||||
{
|
||||
public SeriesResource Series { get; set; }
|
||||
public EpisodeResource Episode { get; set; }
|
||||
public MovieResource Movie { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public decimal Size { get; set; }
|
||||
public string Title { get; set; }
|
||||
@@ -49,7 +51,8 @@ namespace NzbDrone.Api.Queue
|
||||
TrackedDownloadStatus = model.TrackedDownloadStatus,
|
||||
StatusMessages = model.StatusMessages,
|
||||
DownloadId = model.DownloadId,
|
||||
Protocol = model.Protocol
|
||||
Protocol = model.Protocol,
|
||||
Movie = model.Movie.ToResource()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
44
src/NzbDrone.Api/Series/MovieLookupModule.cs
Normal file
44
src/NzbDrone.Api/Series/MovieLookupModule.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using Nancy;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Api.Movie
|
||||
{
|
||||
public class MovieLookupModule : NzbDroneRestModule<MovieResource>
|
||||
{
|
||||
private readonly ISearchForNewMovie _searchProxy;
|
||||
|
||||
public MovieLookupModule(ISearchForNewMovie searchProxy)
|
||||
: base("/movies/lookup")
|
||||
{
|
||||
_searchProxy = searchProxy;
|
||||
Get["/"] = x => Search();
|
||||
}
|
||||
|
||||
|
||||
private Response Search()
|
||||
{
|
||||
var imdbResults = _searchProxy.SearchForNewMovie((string)Request.Query.term);
|
||||
return MapToResource(imdbResults).AsResponse();
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
|
||||
{
|
||||
foreach (var currentSeries in movies)
|
||||
{
|
||||
var resource = currentSeries.ToResource();
|
||||
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
||||
if (poster != null)
|
||||
{
|
||||
resource.RemotePoster = poster.Url;
|
||||
}
|
||||
|
||||
yield return resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/NzbDrone.Api/Series/MovieResource.cs
Normal file
183
src/NzbDrone.Api/Series/MovieResource.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Api.Series;
|
||||
|
||||
namespace NzbDrone.Api.Movie
|
||||
{
|
||||
public class MovieResource : RestResource
|
||||
{
|
||||
public MovieResource()
|
||||
{
|
||||
Monitored = true;
|
||||
}
|
||||
|
||||
//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
|
||||
|
||||
//View Only
|
||||
public string Title { get; set; }
|
||||
public List<AlternateTitleResource> AlternateTitles { get; set; }
|
||||
public string SortTitle { get; set; }
|
||||
public long? SizeOnDisk { get; set; }
|
||||
public MovieStatusType Status { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public DateTime? InCinemas { get; set; }
|
||||
public List<MediaCover> Images { get; set; }
|
||||
|
||||
public string RemotePoster { get; set; }
|
||||
public int Year { get; set; }
|
||||
|
||||
//View & Edit
|
||||
public string Path { get; set; }
|
||||
public int ProfileId { get; set; }
|
||||
|
||||
//Editing Only
|
||||
public bool Monitored { get; set; }
|
||||
public int Runtime { get; set; }
|
||||
public DateTime? LastInfoSync { get; set; }
|
||||
public string CleanTitle { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public string TitleSlug { get; set; }
|
||||
public string RootFolderPath { get; set; }
|
||||
public string Certification { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public AddMovieOptions AddOptions { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
|
||||
//TODO: Add series statistics as a property of the series (instead of individual properties)
|
||||
|
||||
//Used to support legacy consumers
|
||||
public int QualityProfileId
|
||||
{
|
||||
get
|
||||
{
|
||||
return ProfileId;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 0 && ProfileId == 0)
|
||||
{
|
||||
ProfileId = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MovieResourceMapper
|
||||
{
|
||||
public static MovieResource ToResource(this Core.Tv.Movie model)
|
||||
{
|
||||
if (model == null) return null;
|
||||
|
||||
return new MovieResource
|
||||
{
|
||||
Id = model.Id,
|
||||
|
||||
Title = model.Title,
|
||||
//AlternateTitles
|
||||
SortTitle = model.SortTitle,
|
||||
InCinemas = model.InCinemas,
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
//SizeOnDisk
|
||||
Status = model.Status,
|
||||
Overview = model.Overview,
|
||||
//NextAiring
|
||||
//PreviousAiring
|
||||
Images = model.Images,
|
||||
|
||||
Year = model.Year,
|
||||
|
||||
Path = model.Path,
|
||||
ProfileId = model.ProfileId,
|
||||
|
||||
Monitored = model.Monitored,
|
||||
|
||||
Runtime = model.Runtime,
|
||||
LastInfoSync = model.LastInfoSync,
|
||||
CleanTitle = model.CleanTitle,
|
||||
ImdbId = model.ImdbId,
|
||||
TitleSlug = model.TitleSlug,
|
||||
RootFolderPath = model.RootFolderPath,
|
||||
Certification = model.Certification,
|
||||
Genres = model.Genres,
|
||||
Tags = model.Tags,
|
||||
Added = model.Added,
|
||||
AddOptions = model.AddOptions,
|
||||
Ratings = model.Ratings
|
||||
};
|
||||
}
|
||||
|
||||
public static Core.Tv.Movie ToModel(this MovieResource resource)
|
||||
{
|
||||
if (resource == null) return null;
|
||||
|
||||
return new Core.Tv.Movie
|
||||
{
|
||||
Id = resource.Id,
|
||||
|
||||
Title = resource.Title,
|
||||
//AlternateTitles
|
||||
SortTitle = resource.SortTitle,
|
||||
InCinemas = resource.InCinemas,
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
//SizeOnDisk
|
||||
Overview = resource.Overview,
|
||||
//NextAiring
|
||||
//PreviousAiring
|
||||
Images = resource.Images,
|
||||
|
||||
Year = resource.Year,
|
||||
|
||||
Path = resource.Path,
|
||||
ProfileId = resource.ProfileId,
|
||||
|
||||
Monitored = resource.Monitored,
|
||||
|
||||
Runtime = resource.Runtime,
|
||||
LastInfoSync = resource.LastInfoSync,
|
||||
CleanTitle = resource.CleanTitle,
|
||||
ImdbId = resource.ImdbId,
|
||||
TitleSlug = resource.TitleSlug,
|
||||
RootFolderPath = resource.RootFolderPath,
|
||||
Certification = resource.Certification,
|
||||
Genres = resource.Genres,
|
||||
Tags = resource.Tags,
|
||||
Added = resource.Added,
|
||||
AddOptions = resource.AddOptions,
|
||||
Ratings = resource.Ratings
|
||||
};
|
||||
}
|
||||
|
||||
public static Core.Tv.Movie ToModel(this MovieResource resource, Core.Tv.Movie movie)
|
||||
{
|
||||
movie.ImdbId = resource.ImdbId;
|
||||
|
||||
movie.Path = resource.Path;
|
||||
movie.ProfileId = resource.ProfileId;
|
||||
|
||||
movie.Monitored = resource.Monitored;
|
||||
|
||||
movie.RootFolderPath = resource.RootFolderPath;
|
||||
movie.Tags = resource.Tags;
|
||||
movie.AddOptions = resource.AddOptions;
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
public static List<MovieResource> ToResource(this IEnumerable<Core.Tv.Movie> movies)
|
||||
{
|
||||
return movies.Select(ToResource).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
}
|
||||
else
|
||||
{
|
||||
AppDataFolder = Path.Combine(Environment.GetFolderPath(DATA_SPECIAL_FOLDER, Environment.SpecialFolderOption.None), "NzbDrone");
|
||||
AppDataFolder = Path.Combine(Environment.GetFolderPath(DATA_SPECIAL_FOLDER, Environment.SpecialFolderOption.None), "Radarr");
|
||||
}
|
||||
|
||||
StartUpFolder = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
|
||||
@@ -178,8 +178,9 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
public void should_return_an_empty_list_when_none_are_appproved()
|
||||
{
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
|
||||
RemoteEpisode ep = null;
|
||||
decisions.Add(new DownloadDecision(ep, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(ep, new Rejection("Failure!")));
|
||||
|
||||
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,32 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.WithColumn("FirstAired").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("ProfileId").AsInt32()
|
||||
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
||||
.WithColumn("Runtime").AsInt32()
|
||||
.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()
|
||||
.WithColumn("AddOptions").AsString().Nullable();
|
||||
|
||||
|
||||
Create.TableForModel("Seasons")
|
||||
.WithColumn("SeriesId").AsInt32()
|
||||
.WithColumn("SeasonNumber").AsInt32()
|
||||
@@ -79,7 +105,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.WithColumn("Quality").AsString()
|
||||
.WithColumn("Indexer").AsString()
|
||||
.WithColumn("NzbInfoUrl").AsString().Nullable()
|
||||
.WithColumn("ReleaseGroup").AsString().Nullable();
|
||||
.WithColumn("ReleaseGroup").AsString().Nullable()
|
||||
.WithColumn("MovieId").AsInt32().WithDefaultValue(0);
|
||||
|
||||
Create.TableForModel("Notifications")
|
||||
.WithColumn("Name").AsString()
|
||||
|
||||
@@ -21,11 +21,12 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
//Add HeldReleases
|
||||
Create.TableForModel("PendingReleases")
|
||||
.WithColumn("SeriesId").AsInt32()
|
||||
.WithColumn("SeriesId").AsInt32().WithDefaultValue(0)
|
||||
.WithColumn("Title").AsString()
|
||||
.WithColumn("Added").AsDateTime()
|
||||
.WithColumn("ParsedEpisodeInfo").AsString()
|
||||
.WithColumn("Release").AsString();
|
||||
.WithColumn("Release").AsString()
|
||||
.WithColumn("MovieId").AsInt32().WithDefaultValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,11 @@ namespace NzbDrone.Core.Datastore
|
||||
.Relationship()
|
||||
.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")
|
||||
.Ignore(f => f.Path)
|
||||
.Relationships.AutoMapICollectionOrComplexProperties()
|
||||
|
||||
@@ -7,6 +7,10 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
public class DownloadDecision
|
||||
{
|
||||
public RemoteEpisode RemoteEpisode { get; private set; }
|
||||
|
||||
public RemoteMovie RemoteMovie { get; private set; }
|
||||
|
||||
public bool IsForMovie = false;
|
||||
public IEnumerable<Rejection> Rejections { get; private set; }
|
||||
|
||||
public bool Approved => !Rejections.Any();
|
||||
@@ -30,6 +34,23 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
public DownloadDecision(RemoteEpisode episode, params Rejection[] rejections)
|
||||
{
|
||||
RemoteEpisode = episode;
|
||||
RemoteMovie = new RemoteMovie
|
||||
{
|
||||
Release = episode.Release,
|
||||
ParsedEpisodeInfo = episode.ParsedEpisodeInfo
|
||||
};
|
||||
Rejections = rejections.ToList();
|
||||
}
|
||||
|
||||
public DownloadDecision(RemoteMovie movie, params Rejection[] rejections)
|
||||
{
|
||||
RemoteMovie = movie;
|
||||
RemoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Release = movie.Release,
|
||||
ParsedEpisodeInfo = movie.ParsedEpisodeInfo
|
||||
};
|
||||
IsForMovie = true;
|
||||
Rejections = rejections.ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,9 +37,83 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase)
|
||||
{
|
||||
if (searchCriteriaBase.Movie != null)
|
||||
{
|
||||
return GetMovieDecisions(reports, searchCriteriaBase).ToList();
|
||||
}
|
||||
|
||||
return GetDecisions(reports, searchCriteriaBase).ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
if (reports.Any())
|
||||
{
|
||||
_logger.ProgressInfo("Processing {0} releases", reports.Count);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.ProgressInfo("No results found");
|
||||
}
|
||||
|
||||
var reportNumber = 1;
|
||||
|
||||
foreach (var report in reports)
|
||||
{
|
||||
DownloadDecision decision = null;
|
||||
_logger.ProgressTrace("Processing release {0}/{1}", reportNumber, reports.Count);
|
||||
|
||||
try
|
||||
{
|
||||
var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title);
|
||||
|
||||
if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
RemoteMovie remoteEpisode = _parsingService.Map(parsedEpisodeInfo, "", searchCriteria);
|
||||
remoteEpisode.Release = report;
|
||||
|
||||
if (remoteEpisode.Movie == null)
|
||||
{
|
||||
//remoteEpisode.DownloadAllowed = true; //Fuck you :)
|
||||
//decision = GetDecisionForReport(remoteEpisode, searchCriteria);
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Movie not Found."));
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteEpisode.DownloadAllowed = true;
|
||||
//decision = GetDecisionForReport(remoteEpisode, searchCriteria); TODO: Rewrite this for movies!
|
||||
decision = new DownloadDecision(remoteEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't process release.");
|
||||
|
||||
var remoteEpisode = new RemoteEpisode { Release = report };
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unexpected error processing release"));
|
||||
}
|
||||
|
||||
reportNumber++;
|
||||
|
||||
if (decision != null)
|
||||
{
|
||||
if (decision.Rejections.Any())
|
||||
{
|
||||
_logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Debug("Release accepted");
|
||||
}
|
||||
|
||||
yield return decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
if (reports.Any())
|
||||
@@ -80,7 +154,9 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
if (remoteEpisode.Series == null)
|
||||
{
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series"));
|
||||
//remoteEpisode.DownloadAllowed = true; //Fuck you :)
|
||||
//decision = GetDecisionForReport(remoteEpisode, searchCriteria);
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Series not Found."));
|
||||
}
|
||||
else if (remoteEpisode.Episodes.Empty())
|
||||
{
|
||||
@@ -143,8 +219,9 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
e.Data.Add("report", remoteEpisode.Release.ToJson());
|
||||
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
|
||||
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title);
|
||||
return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));
|
||||
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name);
|
||||
//return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS!
|
||||
//return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
public interface IPrioritizeDownloadDecision
|
||||
{
|
||||
List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions);
|
||||
List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions);
|
||||
}
|
||||
|
||||
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
|
||||
@@ -29,5 +30,17 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
.Union(decisions.Where(c => c.RemoteEpisode.Series == null))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
|
||||
{
|
||||
return decisions.Where(c => c.RemoteMovie.Movie != null)
|
||||
/*.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
|
||||
{
|
||||
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService));
|
||||
})
|
||||
.SelectMany(c => c)*/
|
||||
.Union(decisions.Where(c => c.RemoteMovie.Movie == null))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (subject.Episodes.Any(e => !e.AirDateUtc.HasValue || e.AirDateUtc.Value.After(DateTime.UtcNow)))
|
||||
{
|
||||
_logger.Debug("Full season release {0} rejected. All episodes haven't aired yet.", subject.Release.Title);
|
||||
return Decision.Reject("Full season release rejected. All episodes haven't aired yet.");
|
||||
//return Decision.Reject("Full season release rejected. All episodes haven't aired yet.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
|
||||
{
|
||||
_logger.Debug("Season number does not match searched season number, skipping.");
|
||||
return Decision.Reject("Wrong season");
|
||||
//return Decision.Reject("Wrong season");
|
||||
//Unnecessary for Movies
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
@@ -29,19 +29,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
|
||||
{
|
||||
_logger.Debug("Season number does not match searched season number, skipping.");
|
||||
return Decision.Reject("Wrong season");
|
||||
//return Decision.Reject("Wrong season");
|
||||
}
|
||||
|
||||
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Any())
|
||||
{
|
||||
_logger.Debug("Full season result during single episode search, skipping.");
|
||||
return Decision.Reject("Full season pack");
|
||||
//return Decision.Reject("Full season pack");
|
||||
}
|
||||
|
||||
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Contains(singleEpisodeSpec.EpisodeNumber))
|
||||
{
|
||||
_logger.Debug("Episode number does not match searched episode number, skipping.");
|
||||
return Decision.Reject("Wrong episode");
|
||||
//return Decision.Reject("Wrong episode");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
@@ -58,6 +58,32 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
return GetDownloadClientId(strmFile);
|
||||
}
|
||||
|
||||
public override string Download(RemoteMovie remoteEpisode)
|
||||
{
|
||||
var url = remoteEpisode.Release.DownloadUrl;
|
||||
var title = remoteEpisode.Release.Title;
|
||||
|
||||
if (remoteEpisode.ParsedEpisodeInfo.FullSeason)
|
||||
{
|
||||
throw new NotSupportedException("Full season releases are not supported with Pneumatic.");
|
||||
}
|
||||
|
||||
title = FileNameBuilder.CleanFileName(title);
|
||||
|
||||
//Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC)
|
||||
var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb");
|
||||
|
||||
_logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile);
|
||||
_httpClient.DownloadFile(url, nzbFile);
|
||||
|
||||
_logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile);
|
||||
|
||||
var strmFile = WriteStrmFile(title, nzbFile);
|
||||
|
||||
|
||||
return GetDownloadClientId(strmFile);
|
||||
}
|
||||
|
||||
public bool IsConfigured => !string.IsNullOrWhiteSpace(Settings.NzbFolder);
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
|
||||
@@ -71,6 +71,46 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||
|
||||
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash.ToLower(), Settings.TvCategory, Settings);
|
||||
}
|
||||
|
||||
/*var isRecentEpisode = remoteEpisode.IsRecentEpisode();
|
||||
|
||||
if (isRecentEpisode && Settings.RecentTvPriority == (int)QBittorrentPriority.First ||
|
||||
!isRecentEpisode && Settings.OlderTvPriority == (int)QBittorrentPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings);
|
||||
}*/ //TODO: Maybe reimplement for movies
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, Byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
|
||||
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash.ToLower(), Settings.TvCategory, Settings);
|
||||
}
|
||||
|
||||
/*var isRecentEpisode = remoteEpisode.IsRecentEpisode();
|
||||
|
||||
if (isRecentEpisode && Settings.RecentTvPriority == (int)QBittorrentPriority.First ||
|
||||
!isRecentEpisode && Settings.OlderTvPriority == (int)QBittorrentPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings);
|
||||
}*/
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override string Name => "qBittorrent";
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace NzbDrone.Core.Download
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
private readonly ISeriesService _seriesService;
|
||||
|
||||
@@ -37,6 +38,7 @@ namespace NzbDrone.Core.Download
|
||||
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
||||
IParsingService parsingService,
|
||||
ISeriesService seriesService,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_configService = configService;
|
||||
@@ -44,6 +46,7 @@ namespace NzbDrone.Core.Download
|
||||
_historyService = historyService;
|
||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||
_parsingService = parsingService;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
_seriesService = seriesService;
|
||||
}
|
||||
@@ -88,19 +91,31 @@ namespace NzbDrone.Core.Download
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
if (historyItem != null)
|
||||
{
|
||||
series = _seriesService.GetSeries(historyItem.SeriesId);
|
||||
//series = _seriesService.GetSeries(historyItem.SeriesId);
|
||||
}
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
|
||||
return;
|
||||
var movie = _parsingService.GetMovie(trackedDownload.DownloadItem.Title);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
movie = _movieService.GetMovie(historyItem.MovieId);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
trackedDownload.Warn("Movie title mismatch, automatic import is not possible.");
|
||||
}
|
||||
}
|
||||
//trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
|
||||
//return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace NzbDrone.Core.Download
|
||||
get;
|
||||
}
|
||||
|
||||
|
||||
public abstract string Download(RemoteEpisode remoteEpisode);
|
||||
public abstract IEnumerable<DownloadClientItem> GetItems();
|
||||
public abstract void RemoveItem(string downloadId, bool deleteData);
|
||||
@@ -147,5 +148,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract string Download(RemoteMovie remoteMovie);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Download
|
||||
public interface IDownloadService
|
||||
{
|
||||
void DownloadReport(RemoteEpisode remoteEpisode);
|
||||
void DownloadReport(RemoteMovie remoteMovie);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +42,8 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public void DownloadReport(RemoteEpisode remoteEpisode)
|
||||
{
|
||||
Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
|
||||
Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems();
|
||||
//Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
|
||||
//Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); TODO update this shit
|
||||
|
||||
var downloadTitle = remoteEpisode.Release.Title;
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteEpisode.Release.DownloadProtocol);
|
||||
@@ -91,5 +92,62 @@ namespace NzbDrone.Core.Download
|
||||
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
|
||||
_eventAggregator.PublishEvent(episodeGrabbedEvent);
|
||||
}
|
||||
|
||||
public void DownloadReport(RemoteMovie remoteMovie)
|
||||
{
|
||||
//Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
|
||||
//Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); TODO update this shit
|
||||
|
||||
var downloadTitle = remoteMovie.Release.Title;
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteMovie.Release.DownloadProtocol);
|
||||
|
||||
if (downloadClient == null)
|
||||
{
|
||||
_logger.Warn("{0} Download client isn't configured yet.", remoteMovie.Release.DownloadProtocol);
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit grabs to 2 per second.
|
||||
if (remoteMovie.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteMovie.Release.DownloadUrl.StartsWith("magnet:"))
|
||||
{
|
||||
var url = new HttpUri(remoteMovie.Release.DownloadUrl);
|
||||
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
string downloadClientId = "";
|
||||
try
|
||||
{
|
||||
downloadClientId = downloadClient.Download(remoteMovie);
|
||||
_indexerStatusService.RecordSuccess(remoteMovie.Release.IndexerId);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
_logger.Error(ex, "The download client you are using is currently not configured to download movies. Please choose another one.");
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
{
|
||||
var http429 = ex.InnerException as TooManyRequestsException;
|
||||
if (http429 != null)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(remoteMovie.Release.IndexerId, http429.RetryAfter);
|
||||
}
|
||||
else
|
||||
{
|
||||
_indexerStatusService.RecordFailure(remoteMovie.Release.IndexerId);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
var episodeGrabbedEvent = new MovieGrabbedEvent(remoteMovie);
|
||||
episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(downloadClientId))
|
||||
{
|
||||
episodeGrabbedEvent.DownloadId = downloadClientId;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
|
||||
_eventAggregator.PublishEvent(episodeGrabbedEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Download
|
||||
DownloadProtocol Protocol { get; }
|
||||
|
||||
string Download(RemoteEpisode remoteEpisode);
|
||||
string Download(RemoteMovie remoteMovie);
|
||||
IEnumerable<DownloadClientItem> GetItems();
|
||||
void RemoveItem(string downloadId, bool deleteData);
|
||||
DownloadClientStatus GetStatus();
|
||||
|
||||
17
src/NzbDrone.Core/Download/MovieGrabbedEvent.cs
Normal file
17
src/NzbDrone.Core/Download/MovieGrabbedEvent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class MovieGrabbedEvent : IEvent
|
||||
{
|
||||
public RemoteMovie Movie { get; private set; }
|
||||
public string DownloadClient { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
||||
public MovieGrabbedEvent(RemoteMovie movie)
|
||||
{
|
||||
Movie = movie;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
public class PendingRelease : ModelBase
|
||||
{
|
||||
public int SeriesId { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
||||
@@ -14,5 +15,6 @@ namespace NzbDrone.Core.Download.Pending
|
||||
|
||||
//Not persisted
|
||||
public RemoteEpisode RemoteEpisode { get; set; }
|
||||
public RemoteMovie RemoteMovie { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
public class PendingReleaseService : IPendingReleaseService,
|
||||
IHandle<SeriesDeletedEvent>,
|
||||
IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<MovieGrabbedEvent>,
|
||||
IHandle<RssSyncCompleteEvent>
|
||||
{
|
||||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
@@ -341,6 +342,11 @@ namespace NzbDrone.Core.Download.Pending
|
||||
RemoveGrabbed(message.Episode);
|
||||
}
|
||||
|
||||
public void Handle(MovieGrabbedEvent message)
|
||||
{
|
||||
//RemoveGrabbed(message.Movie);
|
||||
}
|
||||
|
||||
public void Handle(RssSyncCompleteEvent message)
|
||||
{
|
||||
RemoveRejected(message.ProcessedDecisions.Rejected);
|
||||
|
||||
@@ -32,53 +32,112 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions)
|
||||
{
|
||||
var qualifiedReports = GetQualifiedReports(decisions);
|
||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
|
||||
//var qualifiedReports = GetQualifiedReports(decisions);
|
||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
|
||||
var grabbed = new List<DownloadDecision>();
|
||||
var pending = new List<DownloadDecision>();
|
||||
|
||||
foreach (var report in prioritizedDecisions)
|
||||
{
|
||||
var remoteEpisode = report.RemoteEpisode;
|
||||
|
||||
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList();
|
||||
|
||||
//Skip if already grabbed
|
||||
if (grabbed.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any())
|
||||
if (report.IsForMovie)
|
||||
{
|
||||
continue;
|
||||
var remoteMovie = report.RemoteMovie;
|
||||
|
||||
if (report.TemporarilyRejected)
|
||||
{
|
||||
_pendingReleaseService.Add(report);
|
||||
pending.Add(report);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (remoteMovie == null || remoteMovie.Movie == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<int> movieIds = new List<int> { remoteMovie.Movie.Id };
|
||||
|
||||
|
||||
//Skip if already grabbed
|
||||
if (grabbed.Select(r => r.RemoteMovie.Movie)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(movieIds)
|
||||
.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pending.Select(r => r.RemoteMovie.Movie)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(movieIds)
|
||||
.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_downloadService.DownloadReport(remoteMovie);
|
||||
grabbed.Add(report);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//TODO: support for store & forward
|
||||
//We'll need to differentiate between a download client error and an indexer error
|
||||
_logger.Warn(e, "Couldn't add report to download queue. " + remoteMovie);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var remoteEpisode = report.RemoteEpisode;
|
||||
|
||||
if (report.TemporarilyRejected)
|
||||
{
|
||||
_pendingReleaseService.Add(report);
|
||||
pending.Add(report);
|
||||
continue;
|
||||
}
|
||||
if (remoteEpisode == null || remoteEpisode.Episodes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pending.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList();
|
||||
|
||||
try
|
||||
{
|
||||
_downloadService.DownloadReport(remoteEpisode);
|
||||
grabbed.Add(report);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//TODO: support for store & forward
|
||||
//We'll need to differentiate between a download client error and an indexer error
|
||||
_logger.Warn(e, "Couldn't add report to download queue. " + remoteEpisode);
|
||||
//Skip if already grabbed
|
||||
if (grabbed.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (report.TemporarilyRejected)
|
||||
{
|
||||
_pendingReleaseService.Add(report);
|
||||
pending.Add(report);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pending.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_downloadService.DownloadReport(remoteEpisode);
|
||||
grabbed.Add(report);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//TODO: support for store & forward
|
||||
//We'll need to differentiate between a download client error and an indexer error
|
||||
_logger.Warn(e, "Couldn't add report to download queue. " + remoteEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,35 +40,43 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
protected abstract string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink);
|
||||
protected abstract string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent);
|
||||
|
||||
public override string Download(RemoteEpisode remoteEpisode)
|
||||
protected virtual string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
var torrentInfo = remoteEpisode.Release as TorrentInfo;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
protected virtual string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string Download(RemoteMovie remoteMovie)
|
||||
{
|
||||
var torrentInfo = remoteMovie.Release as TorrentInfo;
|
||||
|
||||
string magnetUrl = null;
|
||||
string torrentUrl = null;
|
||||
|
||||
if (remoteEpisode.Release.DownloadUrl.IsNotNullOrWhiteSpace() && remoteEpisode.Release.DownloadUrl.StartsWith("magnet:"))
|
||||
if (remoteMovie.Release.DownloadUrl.IsNotNullOrWhiteSpace() && remoteMovie.Release.DownloadUrl.StartsWith("magnet:"))
|
||||
{
|
||||
magnetUrl = remoteEpisode.Release.DownloadUrl;
|
||||
magnetUrl = remoteMovie.Release.DownloadUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
torrentUrl = remoteEpisode.Release.DownloadUrl;
|
||||
torrentUrl = remoteMovie.Release.DownloadUrl;
|
||||
}
|
||||
|
||||
if (torrentInfo != null && !torrentInfo.MagnetUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
magnetUrl = torrentInfo.MagnetUrl;
|
||||
}
|
||||
|
||||
|
||||
if (PreferTorrentFile)
|
||||
{
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromWebUrl(remoteEpisode, torrentUrl);
|
||||
return DownloadFromWebUrl(remoteMovie, torrentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -85,11 +93,11 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(remoteEpisode, magnetUrl);
|
||||
return DownloadFromMagnetUrl(remoteMovie, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
throw new ReleaseDownloadException(remoteMovie.Release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,13 +107,13 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(remoteEpisode, magnetUrl);
|
||||
return DownloadFromMagnetUrl(remoteMovie, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
if (torrentUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
throw new ReleaseDownloadException(remoteMovie.Release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
}
|
||||
|
||||
_logger.Debug("Magnet not supported by download client, trying torrent. ({0})", ex.Message);
|
||||
@@ -114,13 +122,193 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return DownloadFromWebUrl(remoteEpisode, torrentUrl);
|
||||
return DownloadFromWebUrl(remoteMovie, torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string Download(RemoteEpisode remoteMovie)
|
||||
{
|
||||
var torrentInfo = remoteMovie.Release as TorrentInfo;
|
||||
|
||||
string magnetUrl = null;
|
||||
string torrentUrl = null;
|
||||
|
||||
if (remoteMovie.Release.DownloadUrl.IsNotNullOrWhiteSpace() && remoteMovie.Release.DownloadUrl.StartsWith("magnet:"))
|
||||
{
|
||||
magnetUrl = remoteMovie.Release.DownloadUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
torrentUrl = remoteMovie.Release.DownloadUrl;
|
||||
}
|
||||
|
||||
if (torrentInfo != null && !torrentInfo.MagnetUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
magnetUrl = torrentInfo.MagnetUrl;
|
||||
}
|
||||
|
||||
if (PreferTorrentFile)
|
||||
{
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromWebUrl(remoteMovie, torrentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!magnetUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.Debug("Torrent download failed, trying magnet. ({0})", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
if (magnetUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(remoteMovie, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
throw new ReleaseDownloadException(remoteMovie.Release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (magnetUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(remoteMovie, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
if (torrentUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ReleaseDownloadException(remoteMovie.Release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
}
|
||||
|
||||
_logger.Debug("Magnet not supported by download client, trying torrent. ({0})", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return DownloadFromWebUrl(remoteMovie, torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DownloadFromWebUrl(RemoteMovie remoteEpisode, string torrentUrl)
|
||||
{
|
||||
byte[] torrentFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new HttpRequest(torrentUrl);
|
||||
request.Headers.Accept = "application/x-bittorrent";
|
||||
request.AllowAutoRedirect = false;
|
||||
|
||||
var response = _httpClient.Get(request);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.SeeOther || response.StatusCode == HttpStatusCode.Found)
|
||||
{
|
||||
var locationHeader = response.Headers.GetSingleValue("Location");
|
||||
|
||||
_logger.Trace("Torrent request is being redirected to: {0}", locationHeader);
|
||||
|
||||
if (locationHeader != null)
|
||||
{
|
||||
if (locationHeader.StartsWith("magnet:"))
|
||||
{
|
||||
return DownloadFromMagnetUrl(remoteEpisode, locationHeader);
|
||||
}
|
||||
|
||||
return DownloadFromWebUrl(remoteEpisode, locationHeader);
|
||||
}
|
||||
|
||||
throw new WebException("Remote website tried to redirect without providing a location.");
|
||||
}
|
||||
|
||||
torrentFile = response.ResponseData;
|
||||
|
||||
_logger.Debug("Downloading torrent for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if ((int)ex.Response.StatusCode == 429)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", torrentUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
|
||||
|
||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
var filename = string.Format("{0}.torrent", FileNameBuilder.CleanFileName(remoteEpisode.Release.Title));
|
||||
var hash = _torrentFileInfoReader.GetHashFromTorrentFile(torrentFile);
|
||||
var actualHash = AddFromTorrentFile(remoteEpisode, hash, filename, torrentFile);
|
||||
|
||||
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
||||
{
|
||||
_logger.Debug(
|
||||
"{0} did not return the expected InfoHash for '{1}', Sonarr could potentially lose track of the download in progress.",
|
||||
Definition.Implementation, remoteEpisode.Release.DownloadUrl);
|
||||
}
|
||||
|
||||
return actualHash;
|
||||
}
|
||||
|
||||
private string DownloadFromMagnetUrl(RemoteMovie remoteEpisode, string magnetUrl)
|
||||
{
|
||||
string hash = null;
|
||||
string actualHash = null;
|
||||
|
||||
try
|
||||
{
|
||||
hash = new MagnetLink(magnetUrl).InfoHash.ToHex();
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to parse magnetlink for episode '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hash != null)
|
||||
{
|
||||
actualHash = AddFromMagnetLink(remoteEpisode, hash, magnetUrl);
|
||||
}
|
||||
|
||||
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
||||
{
|
||||
_logger.Debug(
|
||||
"{0} did not return the expected InfoHash for '{1}', Sonarr could potentially lose track of the download in progress.",
|
||||
Definition.Implementation, remoteEpisode.Release.DownloadUrl);
|
||||
}
|
||||
|
||||
return actualHash;
|
||||
}
|
||||
|
||||
private string DownloadFromWebUrl(RemoteEpisode remoteEpisode, string torrentUrl)
|
||||
{
|
||||
byte[] torrentFile = null;
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
public TrackedDownloadStage State { get; set; }
|
||||
public TrackedDownloadStatus Status { get; private set; }
|
||||
public RemoteEpisode RemoteEpisode { get; set; }
|
||||
public RemoteMovie RemoteMovie { get; set; }
|
||||
public TrackedDownloadStatusMessage[] StatusMessages { get; private set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
if (parsedEpisodeInfo != null)
|
||||
{
|
||||
trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
|
||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedEpisodeInfo, "", null);
|
||||
}
|
||||
|
||||
if (historyItems.Any())
|
||||
@@ -69,10 +70,10 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
var firstHistoryItem = historyItems.OrderByDescending(h => h.Date).First();
|
||||
trackedDownload.State = GetStateFromHistory(firstHistoryItem.EventType);
|
||||
|
||||
if (parsedEpisodeInfo == null ||
|
||||
if ((parsedEpisodeInfo == null ||
|
||||
trackedDownload.RemoteEpisode == null ||
|
||||
trackedDownload.RemoteEpisode.Series == null ||
|
||||
trackedDownload.RemoteEpisode.Episodes.Empty())
|
||||
trackedDownload.RemoteEpisode.Episodes.Empty()) && trackedDownload.RemoteMovie == null)
|
||||
{
|
||||
// Try parsing the original source title and if that fails, try parsing it as a special
|
||||
// TODO: Pass the TVDB ID and TVRage IDs in as well so we have a better chance for finding the item
|
||||
@@ -85,7 +86,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
}
|
||||
}
|
||||
|
||||
if (trackedDownload.RemoteEpisode == null)
|
||||
if (trackedDownload.RemoteEpisode == null && trackedDownload.RemoteMovie == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
@@ -31,6 +32,11 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
protected abstract string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent);
|
||||
|
||||
protected virtual string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string Download(RemoteEpisode remoteEpisode)
|
||||
{
|
||||
var url = remoteEpisode.Release.DownloadUrl;
|
||||
@@ -67,5 +73,42 @@ namespace NzbDrone.Core.Download
|
||||
_logger.Info("Adding report [{0}] to the queue.", remoteEpisode.Release.Title);
|
||||
return AddFromNzbFile(remoteEpisode, filename, nzbData);
|
||||
}
|
||||
|
||||
public override string Download(RemoteMovie remoteEpisode)
|
||||
{
|
||||
var url = remoteEpisode.Release.DownloadUrl;
|
||||
var filename = FileNameBuilder.CleanFileName(remoteEpisode.Release.Title) + ".nzb";
|
||||
|
||||
byte[] nzbData;
|
||||
|
||||
try
|
||||
{
|
||||
nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData;
|
||||
|
||||
_logger.Debug("Downloaded nzb for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if ((int)ex.Response.StatusCode == 429)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url);
|
||||
|
||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
|
||||
}
|
||||
|
||||
_logger.Info("Adding report [{0}] to the queue.", remoteEpisode.Release.Title);
|
||||
return AddFromNzbFile(remoteEpisode, filename, nzbData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/NzbDrone.Core/Exceptions/MovieNotFoundExceptions.cs
Normal file
27
src/NzbDrone.Core/Exceptions/MovieNotFoundExceptions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class MovieNotFoundException : NzbDroneException
|
||||
{
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
public MovieNotFoundException(string imdbid)
|
||||
: base(string.Format("Movie with imdbid {0} was not found, it may have been removed from IMDb.", imdbid))
|
||||
{
|
||||
ImdbId = imdbid;
|
||||
}
|
||||
|
||||
public MovieNotFoundException(string imdbid, string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
ImdbId = imdbid;
|
||||
}
|
||||
|
||||
public MovieNotFoundException(string imdbid, string message)
|
||||
: base(message)
|
||||
{
|
||||
ImdbId = imdbid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,11 @@ namespace NzbDrone.Core.History
|
||||
|
||||
public int EpisodeId { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public Movie Movie { get; set; }
|
||||
public Episode Episode { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public HistoryEventType EventType { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Core.Datastore;
|
||||
@@ -16,6 +17,7 @@ namespace NzbDrone.Core.History
|
||||
List<History> FindByDownloadId(string downloadId);
|
||||
List<History> FindDownloadHistory(int idSeriesId, QualityModel quality);
|
||||
void DeleteForSeries(int seriesId);
|
||||
History MostRecentForMovie(int movieId);
|
||||
}
|
||||
|
||||
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
||||
@@ -71,10 +73,20 @@ namespace NzbDrone.Core.History
|
||||
|
||||
protected override SortBuilder<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
|
||||
{
|
||||
var baseQuery = query.Join<History, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
|
||||
.Join<History, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id);
|
||||
var baseQuery = query/*.Join<History, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
|
||||
.Join<History, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id)*/
|
||||
.Join<History, Movie>(JoinType.Inner, h => h.Movie, (h, e) => h.MovieId == e.Id);
|
||||
|
||||
|
||||
|
||||
return base.GetPagedQuery(baseQuery, pagingSpec);
|
||||
}
|
||||
|
||||
public History MostRecentForMovie(int movieId)
|
||||
{
|
||||
return Query.Where(h => h.MovieId == movieId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ namespace NzbDrone.Core.History
|
||||
{
|
||||
QualityModel GetBestQualityInHistory(Profile profile, int episodeId);
|
||||
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
|
||||
History MostRecentForMovie(int movieId);
|
||||
History MostRecentForEpisode(int episodeId);
|
||||
History MostRecentForDownloadId(string downloadId);
|
||||
History Get(int historyId);
|
||||
@@ -29,6 +30,7 @@ namespace NzbDrone.Core.History
|
||||
|
||||
public class HistoryService : IHistoryService,
|
||||
IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<MovieGrabbedEvent>,
|
||||
IHandle<EpisodeImportedEvent>,
|
||||
IHandle<DownloadFailedEvent>,
|
||||
IHandle<EpisodeFileDeletedEvent>,
|
||||
@@ -53,6 +55,11 @@ namespace NzbDrone.Core.History
|
||||
return _historyRepository.MostRecentForEpisode(episodeId);
|
||||
}
|
||||
|
||||
public History MostRecentForMovie(int movieId)
|
||||
{
|
||||
return _historyRepository.MostRecentForMovie(movieId);
|
||||
}
|
||||
|
||||
public History MostRecentForDownloadId(string downloadId)
|
||||
{
|
||||
return _historyRepository.MostRecentForDownloadId(downloadId);
|
||||
@@ -138,7 +145,8 @@ namespace NzbDrone.Core.History
|
||||
SourceTitle = message.Episode.Release.Title,
|
||||
SeriesId = episode.SeriesId,
|
||||
EpisodeId = episode.Id,
|
||||
DownloadId = message.DownloadId
|
||||
DownloadId = message.DownloadId,
|
||||
MovieId = 0
|
||||
};
|
||||
|
||||
history.Data.Add("Indexer", message.Episode.Release.Indexer);
|
||||
@@ -172,6 +180,50 @@ namespace NzbDrone.Core.History
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(MovieGrabbedEvent message)
|
||||
{
|
||||
var history = new History
|
||||
{
|
||||
EventType = HistoryEventType.Grabbed,
|
||||
Date = DateTime.UtcNow,
|
||||
Quality = message.Movie.ParsedEpisodeInfo.Quality,
|
||||
SourceTitle = message.Movie.Release.Title,
|
||||
SeriesId = 0,
|
||||
EpisodeId = 0,
|
||||
DownloadId = message.DownloadId,
|
||||
MovieId = message.Movie.Movie.Id
|
||||
};
|
||||
|
||||
history.Data.Add("Indexer", message.Movie.Release.Indexer);
|
||||
history.Data.Add("NzbInfoUrl", message.Movie.Release.InfoUrl);
|
||||
history.Data.Add("ReleaseGroup", message.Movie.ParsedEpisodeInfo.ReleaseGroup);
|
||||
history.Data.Add("Age", message.Movie.Release.Age.ToString());
|
||||
history.Data.Add("AgeHours", message.Movie.Release.AgeHours.ToString());
|
||||
history.Data.Add("AgeMinutes", message.Movie.Release.AgeMinutes.ToString());
|
||||
history.Data.Add("PublishedDate", message.Movie.Release.PublishDate.ToString("s") + "Z");
|
||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||
history.Data.Add("Size", message.Movie.Release.Size.ToString());
|
||||
history.Data.Add("DownloadUrl", message.Movie.Release.DownloadUrl);
|
||||
history.Data.Add("Guid", message.Movie.Release.Guid);
|
||||
history.Data.Add("TvdbId", message.Movie.Release.TvdbId.ToString());
|
||||
history.Data.Add("TvRageId", message.Movie.Release.TvRageId.ToString());
|
||||
history.Data.Add("Protocol", ((int)message.Movie.Release.DownloadProtocol).ToString());
|
||||
|
||||
if (!message.Movie.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace())
|
||||
{
|
||||
history.Data.Add("ReleaseHash", message.Movie.ParsedEpisodeInfo.ReleaseHash);
|
||||
}
|
||||
|
||||
var torrentRelease = message.Movie.Release as TorrentInfo;
|
||||
|
||||
if (torrentRelease != null)
|
||||
{
|
||||
history.Data.Add("TorrentInfoHash", torrentRelease.InfoHash);
|
||||
}
|
||||
|
||||
_historyRepository.Insert(history);
|
||||
}
|
||||
|
||||
public void Handle(EpisodeImportedEvent message)
|
||||
{
|
||||
if (!message.NewDownload)
|
||||
@@ -189,15 +241,18 @@ namespace NzbDrone.Core.History
|
||||
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||
{
|
||||
var history = new History
|
||||
{
|
||||
EventType = HistoryEventType.DownloadFolderImported,
|
||||
Date = DateTime.UtcNow,
|
||||
Quality = message.EpisodeInfo.Quality,
|
||||
SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path),
|
||||
SeriesId = message.ImportedEpisode.SeriesId,
|
||||
EpisodeId = episode.Id,
|
||||
DownloadId = downloadId
|
||||
};
|
||||
{
|
||||
EventType = HistoryEventType.DownloadFolderImported,
|
||||
Date = DateTime.UtcNow,
|
||||
Quality = message.EpisodeInfo.Quality,
|
||||
SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path),
|
||||
SeriesId = message.ImportedEpisode.SeriesId,
|
||||
EpisodeId = episode.Id,
|
||||
DownloadId = downloadId,
|
||||
MovieId = 0,
|
||||
|
||||
|
||||
};
|
||||
|
||||
//Won't have a value since we publish this event before saving to DB.
|
||||
//history.Data.Add("FileId", message.ImportedEpisode.Id.ToString());
|
||||
@@ -249,6 +304,7 @@ namespace NzbDrone.Core.History
|
||||
SourceTitle = message.EpisodeFile.Path,
|
||||
SeriesId = message.EpisodeFile.SeriesId,
|
||||
EpisodeId = episode.Id,
|
||||
MovieId = 0
|
||||
};
|
||||
|
||||
history.Data.Add("Reason", message.Reason.ToString());
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public class MovieSearchCriteria : SearchCriteriaBase
|
||||
{
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}]", Movie.Title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public Series Series { get; set; }
|
||||
|
||||
public Movie Movie { get; set; }
|
||||
public List<string> SceneTitles { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
public virtual bool MonitoredEpisodesOnly { get; set; }
|
||||
|
||||
11
src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs
Normal file
11
src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class MoviesSearchCommand : Command
|
||||
{
|
||||
public int MovieId { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
}
|
||||
}
|
||||
46
src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
Normal file
46
src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class MovieSearchService : IExecute<MoviesSearchCommand>
|
||||
{
|
||||
private readonly IMovieService _seriesService;
|
||||
private readonly ISearchForNzb _nzbSearchService;
|
||||
private readonly IProcessDownloadDecisions _processDownloadDecisions;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MovieSearchService(IMovieService seriesService,
|
||||
ISearchForNzb nzbSearchService,
|
||||
IProcessDownloadDecisions processDownloadDecisions,
|
||||
Logger logger)
|
||||
{
|
||||
_seriesService = seriesService;
|
||||
_nzbSearchService = nzbSearchService;
|
||||
_processDownloadDecisions = processDownloadDecisions;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Execute(MoviesSearchCommand message)
|
||||
{
|
||||
var series = _seriesService.GetMovie(message.MovieId);
|
||||
|
||||
var downloadedCount = 0;
|
||||
|
||||
if (!series.Monitored)
|
||||
{
|
||||
_logger.Debug("Movie {0} is not monitored, skipping search", series.Title);
|
||||
}
|
||||
|
||||
var decisions = _nzbSearchService.MovieSearch(message.MovieId, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual);
|
||||
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
|
||||
|
||||
|
||||
_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
List<DownloadDecision> EpisodeSearch(int episodeId, bool userInvokedSearch);
|
||||
List<DownloadDecision> EpisodeSearch(Episode episode, bool userInvokedSearch);
|
||||
List<DownloadDecision> MovieSearch(int movieId, bool userInvokedSearch);
|
||||
List<DownloadDecision> MovieSearch(Movie movie, bool userInvokedSearch);
|
||||
List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch);
|
||||
}
|
||||
|
||||
@@ -29,6 +31,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IMakeDownloadDecision _makeDownloadDecision;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NzbSearchService(IIndexerFactory indexerFactory,
|
||||
@@ -36,6 +39,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
ISeriesService seriesService,
|
||||
IEpisodeService episodeService,
|
||||
IMakeDownloadDecision makeDownloadDecision,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
@@ -43,6 +47,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
_seriesService = seriesService;
|
||||
_episodeService = episodeService;
|
||||
_makeDownloadDecision = makeDownloadDecision;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -53,6 +58,20 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
return EpisodeSearch(episode, userInvokedSearch);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> MovieSearch(int movieId, bool userInvokedSearch)
|
||||
{
|
||||
var movie = _movieService.GetMovie(movieId);
|
||||
|
||||
return MovieSearch(movie, userInvokedSearch);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> MovieSearch(Movie movie, bool userInvokedSearch)
|
||||
{
|
||||
var searchSpec = Get<MovieSearchCriteria>(movie, userInvokedSearch);
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> EpisodeSearch(Episode episode, bool userInvokedSearch)
|
||||
{
|
||||
var series = _seriesService.GetSeries(episode.SeriesId);
|
||||
@@ -245,6 +264,23 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
return spec;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(Movie movie, bool userInvokedSearch) where TSpec : SearchCriteriaBase, new()
|
||||
{
|
||||
var spec = new TSpec();
|
||||
|
||||
spec.Movie = movie;
|
||||
/*spec.SceneTitles = _sceneMapping.GetSceneNames(series.TvdbId,
|
||||
episodes.Select(e => e.SeasonNumber).Distinct().ToList(),
|
||||
episodes.Select(e => e.SceneSeasonNumber ?? e.SeasonNumber).Distinct().ToList());
|
||||
|
||||
spec.Episodes = episodes;
|
||||
|
||||
spec.SceneTitles.Add(series.Title);*/
|
||||
spec.UserInvokedSearch = userInvokedSearch;
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
var indexers = _indexerFactory.SearchEnabled();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -22,6 +23,11 @@ namespace NzbDrone.Core.Indexers.BitMeTv
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
{
|
||||
@@ -189,5 +190,10 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
yield return new IndexerRequest(builder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -84,5 +85,10 @@ namespace NzbDrone.Core.Indexers.Fanzub
|
||||
{
|
||||
return RemoveCharactersRegex.Replace(title, "");
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
@@ -128,5 +129,10 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,18 @@ namespace NzbDrone.Core.Indexers
|
||||
return FetchReleases(generator.GetSearchRequests(searchCriteria));
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> Fetch(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
if (!SupportsSearch)
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
var generator = GetRequestGenerator();
|
||||
|
||||
return FetchReleases(generator.GetSearchRequests(searchCriteria));
|
||||
}
|
||||
|
||||
protected virtual IList<ReleaseInfo> FetchReleases(IndexerPageableRequestChain pageableRequestChain, bool isRecent = false)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
@@ -17,5 +17,6 @@ namespace NzbDrone.Core.Indexers
|
||||
IList<ReleaseInfo> Fetch(DailyEpisodeSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(AnimeEpisodeSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(SpecialEpisodeSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(MovieSearchCriteria searchCriteria);
|
||||
}
|
||||
}
|
||||
@@ -10,5 +10,6 @@ namespace NzbDrone.Core.Indexers
|
||||
IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria);
|
||||
IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria);
|
||||
IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria);
|
||||
IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -22,6 +23,11 @@ namespace NzbDrone.Core.Indexers.IPTorrents
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace NzbDrone.Core.Indexers
|
||||
public abstract IList<ReleaseInfo> Fetch(DailyEpisodeSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(AnimeEpisodeSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(SpecialEpisodeSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(MovieSearchCriteria searchCriteria);
|
||||
|
||||
protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -147,5 +148,10 @@ namespace NzbDrone.Core.Indexers.KickassTorrents
|
||||
{
|
||||
return query.Replace('+', ' ');
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -273,5 +274,10 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
return title.Replace("+", "%20");
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -102,5 +103,10 @@ namespace NzbDrone.Core.Indexers.Nyaa
|
||||
{
|
||||
return query.Replace(' ', '+');
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -101,5 +102,10 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
||||
|
||||
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -101,7 +102,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
requestBuilder.AddQueryParam("ranked", "0");
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("category", "18;41");
|
||||
requestBuilder.AddQueryParam("category", "tv");
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
@@ -109,5 +110,48 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
|
||||
yield return new IndexerRequest(requestBuilder.Build());
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetMovieRequest(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
|
||||
.Resource("/pubapi_v2.php")
|
||||
.Accept(HttpAccept.Json);
|
||||
|
||||
if (Settings.CaptchaToken.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.UseSimplifiedUserAgent = true;
|
||||
requestBuilder.SetCookie("cf_clearance", Settings.CaptchaToken);
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("mode", "search");
|
||||
|
||||
requestBuilder.AddQueryParam("search_imdb", searchCriteria.Movie.ImdbId);
|
||||
|
||||
if (!Settings.RankedOnly)
|
||||
{
|
||||
requestBuilder.AddQueryParam("ranked", "0");
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("category", "movies");
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
requestBuilder.AddQueryParam("app_id", "Sonarr");
|
||||
|
||||
yield return new IndexerRequest(requestBuilder.Build());
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
|
||||
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetMovieRequest(searchCriteria));
|
||||
|
||||
return pageableRequests;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Common.Http;
|
||||
using System;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
@@ -22,6 +23,11 @@ namespace NzbDrone.Core.Indexers
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -23,6 +24,11 @@ namespace NzbDrone.Core.Indexers.TorrentRss
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -22,6 +23,11 @@ namespace NzbDrone.Core.Indexers.Torrentleech
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
|
||||
@@ -7,7 +7,9 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Torznab
|
||||
@@ -110,5 +112,6 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Wombles
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
public class MediaCoverService :
|
||||
IHandleAsync<SeriesUpdatedEvent>,
|
||||
IHandleAsync<MovieUpdatedEvent>,
|
||||
IHandleAsync<MovieAddedEvent>,
|
||||
IHandleAsync<SeriesDeletedEvent>,
|
||||
IMapCoversToLocal
|
||||
{
|
||||
@@ -83,6 +85,8 @@ namespace NzbDrone.Core.MediaCover
|
||||
return Path.Combine(_coverRootFolder, seriesId.ToString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void EnsureCovers(Series series)
|
||||
{
|
||||
foreach (var cover in series.Images)
|
||||
@@ -110,6 +114,33 @@ namespace NzbDrone.Core.MediaCover
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCovers(Movie movie)
|
||||
{
|
||||
foreach (var cover in movie.Images)
|
||||
{
|
||||
var fileName = GetCoverPath(movie.Id, cover.CoverType);
|
||||
var alreadyExists = false;
|
||||
try
|
||||
{
|
||||
alreadyExists = _coverExistsSpecification.AlreadyExists(cover.Url, fileName);
|
||||
if (!alreadyExists)
|
||||
{
|
||||
DownloadCover(movie, cover);
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn(string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't download media cover for " + movie);
|
||||
}
|
||||
|
||||
EnsureResizedCovers(movie, cover, !alreadyExists);
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadCover(Series series, MediaCover cover)
|
||||
{
|
||||
var fileName = GetCoverPath(series.Id, cover.CoverType);
|
||||
@@ -118,6 +149,14 @@ namespace NzbDrone.Core.MediaCover
|
||||
_httpClient.DownloadFile(cover.Url, fileName);
|
||||
}
|
||||
|
||||
private void DownloadCover(Movie series, MediaCover cover)
|
||||
{
|
||||
var fileName = GetCoverPath(series.Id, cover.CoverType);
|
||||
|
||||
_logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url);
|
||||
_httpClient.DownloadFile(cover.Url, fileName);
|
||||
}
|
||||
|
||||
private void EnsureResizedCovers(Series series, MediaCover cover, bool forceResize)
|
||||
{
|
||||
int[] heights;
|
||||
@@ -163,12 +202,69 @@ namespace NzbDrone.Core.MediaCover
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureResizedCovers(Movie series, MediaCover cover, bool forceResize)
|
||||
{
|
||||
int[] heights;
|
||||
|
||||
switch (cover.CoverType)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
|
||||
case MediaCoverTypes.Poster:
|
||||
case MediaCoverTypes.Headshot:
|
||||
heights = new[] { 500, 250 };
|
||||
break;
|
||||
|
||||
case MediaCoverTypes.Banner:
|
||||
heights = new[] { 70, 35 };
|
||||
break;
|
||||
|
||||
case MediaCoverTypes.Fanart:
|
||||
case MediaCoverTypes.Screenshot:
|
||||
heights = new[] { 360, 180 };
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var height in heights)
|
||||
{
|
||||
var mainFileName = GetCoverPath(series.Id, cover.CoverType);
|
||||
var resizeFileName = GetCoverPath(series.Id, cover.CoverType, height);
|
||||
|
||||
if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0)
|
||||
{
|
||||
_logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, series);
|
||||
|
||||
try
|
||||
{
|
||||
_resizer.Resize(mainFileName, resizeFileName, height);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Debug("Couldn't resize media cover {0}-{1} for {2}, using full size image instead.", cover.CoverType, height, series);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleAsync(SeriesUpdatedEvent message)
|
||||
{
|
||||
EnsureCovers(message.Series);
|
||||
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Series));
|
||||
}
|
||||
|
||||
public void HandleAsync(MovieUpdatedEvent message)
|
||||
{
|
||||
EnsureCovers(message.Movie);
|
||||
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Movie));
|
||||
}
|
||||
|
||||
public void HandleAsync(MovieAddedEvent message)
|
||||
{
|
||||
EnsureCovers(message.Movie);
|
||||
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Movie));
|
||||
}
|
||||
|
||||
public void HandleAsync(SeriesDeletedEvent message)
|
||||
{
|
||||
var path = GetSeriesCoverPath(message.Series.Id);
|
||||
|
||||
@@ -7,9 +7,16 @@ namespace NzbDrone.Core.MediaCover
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
|
||||
public Movie Movie { get; set; }
|
||||
|
||||
public MediaCoversUpdatedEvent(Series series)
|
||||
{
|
||||
Series = series;
|
||||
}
|
||||
|
||||
public MediaCoversUpdatedEvent(Movie movie)
|
||||
{
|
||||
Movie = movie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
src/NzbDrone.Core/MediaFiles/Commands/RescanMovieCommand.cs
Normal file
20
src/NzbDrone.Core/MediaFiles/Commands/RescanMovieCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
{
|
||||
public class RescanMovieCommand : Command
|
||||
{
|
||||
public int? MovieId { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public RescanMovieCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public RescanMovieCommand(int movieId)
|
||||
{
|
||||
MovieId = movieId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
public interface IDiskScanService
|
||||
{
|
||||
void Scan(Series series);
|
||||
void Scan(Movie movie);
|
||||
string[] GetVideoFiles(string path, bool allDirectories = true);
|
||||
string[] GetNonVideoFiles(string path, bool allDirectories = true);
|
||||
List<string> FilterFiles(Series series, IEnumerable<string> files);
|
||||
@@ -30,6 +31,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||
public class DiskScanService :
|
||||
IDiskScanService,
|
||||
IHandle<SeriesUpdatedEvent>,
|
||||
IHandle<MovieUpdatedEvent>,
|
||||
IExecute<RescanMovieCommand>,
|
||||
IExecute<RescanSeriesCommand>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
@@ -39,6 +42,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DiskScanService(IDiskProvider diskProvider,
|
||||
@@ -48,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
ISeriesService seriesService,
|
||||
IMediaFileTableCleanupService mediaFileTableCleanupService,
|
||||
IEventAggregator eventAggregator,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
@@ -57,6 +62,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
_seriesService = seriesService;
|
||||
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -121,6 +127,64 @@ namespace NzbDrone.Core.MediaFiles
|
||||
_eventAggregator.PublishEvent(new SeriesScannedEvent(series));
|
||||
}
|
||||
|
||||
public void Scan(Movie movie)
|
||||
{
|
||||
var rootFolder = _diskProvider.GetParentFolder(movie.Path);
|
||||
|
||||
if (!_diskProvider.FolderExists(rootFolder))
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderDoesNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_diskProvider.GetDirectories(rootFolder).Empty())
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderIsEmpty));
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Scanning disk for {0}", movie.Title);
|
||||
|
||||
if (!_diskProvider.FolderExists(movie.Path))
|
||||
{
|
||||
if (_configService.CreateEmptySeriesFolders &&
|
||||
_diskProvider.FolderExists(rootFolder))
|
||||
{
|
||||
_logger.Debug("Creating missing series folder: {0}", movie.Path);
|
||||
_diskProvider.CreateFolder(movie.Path);
|
||||
SetPermissions(movie.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Series folder doesn't exist: {0}", movie.Path);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.MovieFolderDoesNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
var videoFilesStopwatch = Stopwatch.StartNew();
|
||||
var mediaFileList = FilterFiles(movie, GetVideoFiles(movie.Path)).ToList();
|
||||
|
||||
videoFilesStopwatch.Stop();
|
||||
_logger.Trace("Finished getting episode files for: {0} [{1}]", movie, videoFilesStopwatch.Elapsed);
|
||||
|
||||
_logger.Debug("{0} Cleaning up media files in DB", movie);
|
||||
_mediaFileTableCleanupService.Clean(movie, mediaFileList);
|
||||
|
||||
var decisionsStopwatch = Stopwatch.StartNew();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie);
|
||||
decisionsStopwatch.Stop();
|
||||
_logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed);
|
||||
|
||||
_importApprovedEpisodes.Import(decisions, false);
|
||||
|
||||
_logger.Info("Completed scanning disk for {0}", movie.Title);
|
||||
_eventAggregator.PublishEvent(new MovieScannedEvent(movie));
|
||||
}
|
||||
|
||||
public string[] GetVideoFiles(string path, bool allDirectories = true)
|
||||
{
|
||||
_logger.Debug("Scanning '{0}' for video files", path);
|
||||
@@ -156,6 +220,13 @@ namespace NzbDrone.Core.MediaFiles
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<string> FilterFiles(Movie movie, IEnumerable<string> files)
|
||||
{
|
||||
return files.Where(file => !ExcludedSubFoldersRegex.IsMatch(movie.Path.GetRelativePath(file)))
|
||||
.Where(file => !ExcludedFilesRegex.IsMatch(Path.GetFileName(file)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void SetPermissions(string path)
|
||||
{
|
||||
if (!_configService.SetPermissionsLinux)
|
||||
@@ -182,6 +253,28 @@ namespace NzbDrone.Core.MediaFiles
|
||||
Scan(message.Series);
|
||||
}
|
||||
|
||||
public void Handle(MovieUpdatedEvent message)
|
||||
{
|
||||
Scan(message.Movie);
|
||||
}
|
||||
|
||||
public void Execute(RescanMovieCommand message)
|
||||
{
|
||||
if (message.MovieId.HasValue)
|
||||
{
|
||||
var series = _movieService.GetMovie(message.MovieId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var allMovies = _movieService.GetAllMovies();
|
||||
|
||||
foreach (var movie in allMovies)
|
||||
{
|
||||
Scan(movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(RescanSeriesCommand message)
|
||||
{
|
||||
if (message.SeriesId.HasValue)
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
public interface IMakeImportDecision
|
||||
{
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource); //TODO: Needs changing to ParsedMovieInfo!!
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||
}
|
||||
|
||||
@@ -53,6 +55,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
return GetImportDecisions(videoFiles, series, null, false);
|
||||
}
|
||||
|
||||
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie)
|
||||
{
|
||||
return GetImportDecisions(videoFiles, movie, null, false);
|
||||
}
|
||||
|
||||
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
|
||||
{
|
||||
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series);
|
||||
@@ -70,6 +77,75 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
return decisions;
|
||||
}
|
||||
|
||||
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource)
|
||||
{
|
||||
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), movie);
|
||||
|
||||
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
|
||||
|
||||
var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo);
|
||||
var decisions = new List<ImportDecision>();
|
||||
|
||||
foreach (var file in newFiles)
|
||||
{
|
||||
decisions.AddIfNotNull(GetDecision(file, movie, folderInfo, sceneSource, shouldUseFolderName));
|
||||
}
|
||||
|
||||
return decisions;
|
||||
}
|
||||
|
||||
private ImportDecision GetDecision(string file, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
|
||||
{
|
||||
ImportDecision decision = null;
|
||||
|
||||
/*try
|
||||
{
|
||||
var localEpisode = _parsingService.GetLocalEpisode(file, movie, shouldUseFolderName ? folderInfo : null, sceneSource);
|
||||
|
||||
if (localEpisode != null)
|
||||
{
|
||||
localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, movie);
|
||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
_logger.Debug("Size: {0}", localEpisode.Size);
|
||||
|
||||
//TODO: make it so media info doesn't ruin the import process of a new series
|
||||
if (sceneSource)
|
||||
{
|
||||
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.Empty())
|
||||
{
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = GetDecision(localEpisode);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
localEpisode = new LocalEpisode();
|
||||
localEpisode.Path = file;
|
||||
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't import file. " + file);
|
||||
|
||||
var localEpisode = new LocalEpisode { Path = file };
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
|
||||
}*/
|
||||
|
||||
decision = new ImportDecision(null, new Rejection("IMPLEMENTATION MISSING!!!"));
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
|
||||
{
|
||||
ImportDecision decision = null;
|
||||
@@ -182,6 +258,40 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
}) == 1;
|
||||
}
|
||||
|
||||
private bool ShouldUseFolderName(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo)
|
||||
{
|
||||
if (folderInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folderInfo.FullSeason)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return videoFiles.Count(file =>
|
||||
{
|
||||
var size = _diskProvider.GetFileSize(file);
|
||||
var fileQuality = QualityParser.ParseQuality(file);
|
||||
//var sample = null;//_detectSample.IsSample(movie, GetQuality(folderInfo, fileQuality, movie), file, size, folderInfo.IsPossibleSpecialEpisode); //Todo to this
|
||||
|
||||
return true;
|
||||
|
||||
//if (sample)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SceneChecker.IsSceneTitle(Path.GetFileName(file)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}) == 1;
|
||||
}
|
||||
|
||||
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
||||
{
|
||||
if (UseFolderQuality(folderInfo, fileQuality, series))
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||
{
|
||||
if (localEpisode.ParsedEpisodeInfo.FullSeason)
|
||||
{
|
||||
_logger.Debug("Single episode file detected as containing all episodes in the season");
|
||||
return Decision.Reject("Single episode file contains all episodes in seasons");
|
||||
//_logger.Debug("Single episode file detected as containing all episodes in the season"); //Not needed for Movies mwhahahahah
|
||||
//return Decision.Reject("Single episode file contains all episodes in seasons");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/NzbDrone.Core/MediaFiles/Events/MovieScanSkippedEvent.cs
Normal file
24
src/NzbDrone.Core/MediaFiles/Events/MovieScanSkippedEvent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public class MovieScanSkippedEvent : IEvent
|
||||
{
|
||||
public Movie Movie { get; private set; }
|
||||
public MovieScanSkippedReason Reason { get; set; }
|
||||
|
||||
public MovieScanSkippedEvent(Movie movie, MovieScanSkippedReason reason)
|
||||
{
|
||||
Movie = movie;
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MovieScanSkippedReason
|
||||
{
|
||||
RootFolderDoesNotExist,
|
||||
RootFolderIsEmpty,
|
||||
MovieFolderDoesNotExist
|
||||
}
|
||||
}
|
||||
15
src/NzbDrone.Core/MediaFiles/Events/MovieScannedEvent.cs
Normal file
15
src/NzbDrone.Core/MediaFiles/Events/MovieScannedEvent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public class MovieScannedEvent : IEvent
|
||||
{
|
||||
public Movie Movie { get; private set; }
|
||||
|
||||
public MovieScannedEvent(Movie movie)
|
||||
{
|
||||
Movie = movie;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@ namespace NzbDrone.Core.MediaFiles
|
||||
void Update(EpisodeFile episodeFile);
|
||||
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
List<EpisodeFile> GetFilesByMovie(int movieId);
|
||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||
List<string> FilterExistingFiles(List<string> files, Series series);
|
||||
List<string> FilterExistingFiles(List<string> files, Movie movie);
|
||||
EpisodeFile Get(int id);
|
||||
List<EpisodeFile> Get(IEnumerable<int> ids);
|
||||
|
||||
@@ -64,6 +66,11 @@ namespace NzbDrone.Core.MediaFiles
|
||||
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesByMovie(int movieId)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesBySeries(movieId); //TODO: Update implementation for movie files.
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
|
||||
@@ -83,6 +90,15 @@ namespace NzbDrone.Core.MediaFiles
|
||||
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList();
|
||||
}
|
||||
|
||||
public List<string> FilterExistingFiles(List<string> files, Movie movie)
|
||||
{
|
||||
var seriesFiles = GetFilesBySeries(movie.Id).Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList();
|
||||
|
||||
if (!seriesFiles.Any()) return files;
|
||||
|
||||
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList();
|
||||
}
|
||||
|
||||
public EpisodeFile Get(int id)
|
||||
{
|
||||
return _mediaFileRepository.Get(id);
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||
public interface IMediaFileTableCleanupService
|
||||
{
|
||||
void Clean(Series series, List<string> filesOnDisk);
|
||||
|
||||
void Clean(Movie movie, List<string> filesOnDisk);
|
||||
}
|
||||
|
||||
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
|
||||
@@ -84,5 +86,64 @@ namespace NzbDrone.Core.MediaFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clean(Movie movie, List<string> filesOnDisk)
|
||||
{
|
||||
|
||||
//TODO: Update implementation for movies.
|
||||
var seriesFiles = _mediaFileService.GetFilesBySeries(movie.Id);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(movie.Id);
|
||||
|
||||
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
|
||||
|
||||
foreach (var seriesFile in seriesFiles)
|
||||
{
|
||||
var episodeFile = seriesFile;
|
||||
var episodeFilePath = Path.Combine(movie.Path, episodeFile.RelativePath);
|
||||
|
||||
try
|
||||
{
|
||||
if (!filesOnDiskKeys.Contains(episodeFilePath))
|
||||
{
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (episodes.None(e => e.EpisodeFileId == episodeFile.Id))
|
||||
{
|
||||
_logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes);
|
||||
continue;
|
||||
}
|
||||
|
||||
// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series);
|
||||
//
|
||||
// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count)
|
||||
// {
|
||||
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path);
|
||||
// _mediaFileService.Delete(episodeFile);
|
||||
// continue;
|
||||
// }
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = string.Format("Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id);
|
||||
_logger.Error(ex, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in episodes)
|
||||
{
|
||||
var episode = e;
|
||||
|
||||
if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId))
|
||||
{
|
||||
episode.EpisodeFileId = 0;
|
||||
_episodeService.UpdateEpisode(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs
Normal file
11
src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MetadataSource
|
||||
{
|
||||
public interface IProvideMovieInfo
|
||||
{
|
||||
Movie GetMovieInfo(string ImdbId);
|
||||
}
|
||||
}
|
||||
10
src/NzbDrone.Core/MetadataSource/ISearchForNewMovie.cs
Normal file
10
src/NzbDrone.Core/MetadataSource/ISearchForNewMovie.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MetadataSource
|
||||
{
|
||||
public interface ISearchForNewMovie
|
||||
{
|
||||
List<Movie> SearchForNewMovie(string title);
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,11 @@ using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
|
||||
using NzbDrone.Core.Tv;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
{
|
||||
public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries
|
||||
public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMovieInfo, ISearchForNewMovie
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
@@ -57,6 +58,136 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
return new Tuple<Series, List<Episode>>(series, episodes.ToList());
|
||||
}
|
||||
|
||||
public Movie GetMovieInfo(string ImdbId)
|
||||
{
|
||||
var imdbRequest = new HttpRequest("http://www.omdbapi.com/?i=" + ImdbId + "&plot=full&r=json");
|
||||
|
||||
var httpResponse = _httpClient.Get(imdbRequest);
|
||||
|
||||
if (httpResponse.HasHttpError)
|
||||
{
|
||||
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new MovieNotFoundException(ImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpException(imdbRequest, httpResponse);
|
||||
}
|
||||
}
|
||||
|
||||
var response = httpResponse.Content;
|
||||
|
||||
dynamic json = JsonConvert.DeserializeObject(response);
|
||||
|
||||
var movie = new Movie();
|
||||
|
||||
movie.Title = json.Title;
|
||||
movie.TitleSlug = movie.Title.ToLower().Replace(" ", "-");
|
||||
movie.Overview = json.Plot;
|
||||
movie.CleanTitle = Parser.Parser.CleanSeriesTitle(movie.Title);
|
||||
string airDateStr = json.Released;
|
||||
DateTime airDate = DateTime.Parse(airDateStr);
|
||||
movie.InCinemas = airDate;
|
||||
movie.Year = airDate.Year;
|
||||
movie.ImdbId = ImdbId;
|
||||
string imdbRating = json.imdbVotes;
|
||||
if (imdbRating == "N/A")
|
||||
{
|
||||
movie.Status = MovieStatusType.Announced;
|
||||
}
|
||||
else
|
||||
{
|
||||
movie.Status = MovieStatusType.Released;
|
||||
}
|
||||
string url = json.Poster;
|
||||
var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, url);
|
||||
movie.Images.Add(imdbPoster);
|
||||
string runtime = json.Runtime;
|
||||
int runtimeNum = 0;
|
||||
int.TryParse(runtime.Replace("min", "").Trim(), out runtimeNum);
|
||||
movie.Runtime = runtimeNum;
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
public List<Movie> SearchForNewMovie(string title)
|
||||
{
|
||||
var lowerTitle = title.ToLower();
|
||||
|
||||
if (lowerTitle.StartsWith("imdb:") || lowerTitle.StartsWith("imdbid:"))
|
||||
{
|
||||
var slug = lowerTitle.Split(':')[1].Trim();
|
||||
|
||||
string imdbid = slug;
|
||||
|
||||
if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace))
|
||||
{
|
||||
return new List<Movie>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new List<Movie> { GetMovieInfo(imdbid) };
|
||||
}
|
||||
catch (SeriesNotFoundException)
|
||||
{
|
||||
return new List<Movie>();
|
||||
}
|
||||
}
|
||||
|
||||
var searchTerm = lowerTitle.Replace("+", "_").Replace(" ", "_");
|
||||
|
||||
var firstChar = searchTerm.First();
|
||||
|
||||
var imdbRequest = new HttpRequest("https://v2.sg.media-imdb.com/suggests/" + firstChar + "/" + searchTerm + ".json");
|
||||
|
||||
var response = _httpClient.Get(imdbRequest);
|
||||
|
||||
var imdbCallback = "imdb$" + searchTerm + "(";
|
||||
|
||||
var responseCleaned = response.Content.Replace(imdbCallback, "").TrimEnd(")");
|
||||
|
||||
dynamic json = JsonConvert.DeserializeObject(responseCleaned);
|
||||
|
||||
var imdbMovies = new List<Movie>();
|
||||
|
||||
foreach (dynamic entry in json.d)
|
||||
{
|
||||
var imdbMovie = new Movie();
|
||||
imdbMovie.ImdbId = entry.id;
|
||||
try
|
||||
{
|
||||
imdbMovie.SortTitle = entry.l;
|
||||
imdbMovie.Title = entry.l;
|
||||
string titleSlug = entry.l;
|
||||
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
|
||||
imdbMovie.Year = entry.y;
|
||||
imdbMovie.Images = new List<MediaCover.MediaCover>();
|
||||
try
|
||||
{
|
||||
string url = entry.i[0];
|
||||
var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, url);
|
||||
imdbMovie.Images.Add(imdbPoster);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Debug(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
imdbMovies.Add(imdbMovie);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return imdbMovies;
|
||||
}
|
||||
|
||||
public List<Series> SearchForNewSeries(string title)
|
||||
{
|
||||
try
|
||||
@@ -84,11 +215,15 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var httpRequest = _requestBuilder.Create()
|
||||
.SetSegment("route", "search")
|
||||
.AddQueryParam("term", title.ToLower().Trim())
|
||||
.Build();
|
||||
|
||||
|
||||
|
||||
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest);
|
||||
|
||||
return httpResponse.Resource.SelectList(MapSeries);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,6 +457,7 @@
|
||||
<Compile Include="Download\Clients\Vuze\Vuze.cs" />
|
||||
<Compile Include="Download\CompletedDownloadService.cs" />
|
||||
<Compile Include="Download\DownloadEventHub.cs" />
|
||||
<Compile Include="Download\MovieGrabbedEvent.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\DownloadMonitoringService.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\TrackedDownload.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\TrackedDownloadService.cs" />
|
||||
@@ -488,6 +489,7 @@
|
||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||
<Compile Include="Exceptions\MovieNotFoundExceptions.cs" />
|
||||
<Compile Include="Exceptions\SeriesNotFoundException.cs" />
|
||||
<Compile Include="Exceptions\ReleaseDownloadException.cs" />
|
||||
<Compile Include="Exceptions\StatusCodeToExceptions.cs" />
|
||||
@@ -561,6 +563,9 @@
|
||||
<Compile Include="Http\CloudFlare\CloudFlareHttpInterceptor.cs" />
|
||||
<Compile Include="Http\HttpProxySettingsProvider.cs" />
|
||||
<Compile Include="Http\TorcacheHttpInterceptor.cs" />
|
||||
<Compile Include="IndexerSearch\Definitions\MovieSearchCriteria.cs" />
|
||||
<Compile Include="IndexerSearch\MoviesSearchCommand.cs" />
|
||||
<Compile Include="IndexerSearch\MoviesSearchService.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTv.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTvSettings.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTvRequestGenerator.cs" />
|
||||
@@ -694,6 +699,7 @@
|
||||
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
|
||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
|
||||
@@ -734,7 +740,10 @@
|
||||
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieRenamedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieScannedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieScanSkippedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
||||
<Compile Include="MediaFiles\FileDateType.cs" />
|
||||
@@ -778,6 +787,8 @@
|
||||
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
||||
<Compile Include="Messaging\Events\IHandle.cs" />
|
||||
<Compile Include="Messaging\IProcessMessage.cs" />
|
||||
<Compile Include="MetadataSource\IProvideMovieInfo.cs" />
|
||||
<Compile Include="MetadataSource\ISearchForNewMovie.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\Resource\ImageResource.cs" />
|
||||
@@ -864,6 +875,7 @@
|
||||
<Compile Include="Parser\IsoLanguage.cs" />
|
||||
<Compile Include="Parser\IsoLanguages.cs" />
|
||||
<Compile Include="Parser\LanguageParser.cs" />
|
||||
<Compile Include="Parser\Model\RemoteMovie.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
||||
@@ -1021,6 +1033,10 @@
|
||||
</Compile>
|
||||
<Compile Include="RootFolders\UnmappedFolder.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\SeriesStatistics.cs" />
|
||||
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
||||
@@ -1045,6 +1061,7 @@
|
||||
<Compile Include="Tv\Actor.cs" />
|
||||
<Compile Include="Tv\AddSeriesOptions.cs" />
|
||||
<Compile Include="Tv\Commands\MoveSeriesCommand.cs" />
|
||||
<Compile Include="Tv\Commands\RefreshMovieCommand.cs" />
|
||||
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
||||
<Compile Include="Tv\Episode.cs" />
|
||||
<Compile Include="Tv\EpisodeAddedService.cs" />
|
||||
@@ -1055,29 +1072,44 @@
|
||||
</Compile>
|
||||
<Compile Include="Tv\EpisodeService.cs" />
|
||||
<Compile Include="Tv\Events\EpisodeInfoRefreshedEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieAddedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesAddedEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieDeletedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesDeletedEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieEditedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieRefreshStartingEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||
<Compile Include="Tv\Events\MovieUpdateEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
||||
<Compile Include="Tv\MonitoringOptions.cs" />
|
||||
<Compile Include="Tv\MoveSeriesService.cs" />
|
||||
<Compile Include="Tv\Ratings.cs" />
|
||||
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
||||
<Compile Include="Tv\RefreshMovieService.cs" />
|
||||
<Compile Include="Tv\RefreshSeriesService.cs" />
|
||||
<Compile Include="Tv\Season.cs" />
|
||||
<Compile Include="Tv\Movie.cs" />
|
||||
<Compile Include="Tv\Series.cs" />
|
||||
<Compile Include="Tv\MovieAddedHandler.cs" />
|
||||
<Compile Include="Tv\SeriesAddedHandler.cs" />
|
||||
<Compile Include="Tv\MovieRepository.cs" />
|
||||
<Compile Include="Tv\MovieEditedService.cs" />
|
||||
<Compile Include="Tv\MovieScannedHandler.cs" />
|
||||
<Compile Include="Tv\SeriesScannedHandler.cs" />
|
||||
<Compile Include="Tv\SeriesEditedService.cs" />
|
||||
<Compile Include="Tv\SeriesRepository.cs" />
|
||||
<Compile Include="Tv\MovieService.cs" />
|
||||
<Compile Include="Tv\SeriesService.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Tv\MovieStatusType.cs" />
|
||||
<Compile Include="Tv\SeriesStatusType.cs" />
|
||||
<Compile Include="Tv\MovieTitleNormalizer.cs" />
|
||||
<Compile Include="Tv\SeriesTitleNormalizer.cs" />
|
||||
<Compile Include="Tv\SeriesTypes.cs" />
|
||||
<Compile Include="Tv\ShouldRefreshMovie.cs" />
|
||||
<Compile Include="Tv\ShouldRefreshSeries.cs" />
|
||||
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />
|
||||
<Compile Include="Update\InstallUpdateService.cs" />
|
||||
@@ -1104,6 +1136,9 @@
|
||||
<Compile Include="Validation\Paths\FolderWritableValidator.cs" />
|
||||
<Compile Include="Validation\Paths\PathExistsValidator.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\RootFolderValidator.cs" />
|
||||
<Compile Include="Validation\Paths\SeriesAncestorValidator.cs" />
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Core.Organizer
|
||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
||||
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
||||
string GetMovieFolder(Movie movie);
|
||||
}
|
||||
|
||||
public class FileNameBuilder : IBuildFileNames
|
||||
@@ -243,6 +244,11 @@ namespace NzbDrone.Core.Organizer
|
||||
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)
|
||||
{
|
||||
title = title.Replace("&", "and");
|
||||
|
||||
20
src/NzbDrone.Core/Parser/Model/RemoteMovie.cs
Normal file
20
src/NzbDrone.Core/Parser/Model/RemoteMovie.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Parser.Model
|
||||
{
|
||||
public class RemoteMovie
|
||||
{
|
||||
public ReleaseInfo Release { get; set; }
|
||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } //TODO: Change to ParsedMovieInfo, for now though ParsedEpisodeInfo will do.
|
||||
public Movie Movie { get; set; }
|
||||
public bool DownloadAllowed { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Release.Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@ namespace NzbDrone.Core.Parser
|
||||
new Regex(@"^(?:\W*S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,3}(?!\d+)))+){2,}",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Matches Movie name with AirYear
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
|
||||
new Regex(@"^(?:S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
@@ -296,6 +300,8 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
public static ParsedEpisodeInfo ParseTitle(string title)
|
||||
{
|
||||
|
||||
ParsedEpisodeInfo realResult = null;
|
||||
try
|
||||
{
|
||||
if (!ValidateBeforeParsing(title)) return null;
|
||||
@@ -342,6 +348,8 @@ namespace NzbDrone.Core.Parser
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
foreach (var regex in ReportTitleRegex)
|
||||
{
|
||||
var match = regex.Matches(simpleTitle);
|
||||
@@ -383,6 +391,8 @@ namespace NzbDrone.Core.Parser
|
||||
Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash);
|
||||
}
|
||||
|
||||
realResult = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -401,7 +411,7 @@ namespace NzbDrone.Core.Parser
|
||||
}
|
||||
|
||||
Logger.Debug("Unable to parse {0}", title);
|
||||
return null;
|
||||
return realResult;
|
||||
}
|
||||
|
||||
public static string ParseSeriesName(string title)
|
||||
@@ -525,6 +535,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
int airYear;
|
||||
int.TryParse(matchCollection[0].Groups["airyear"].Value, out airYear);
|
||||
//int.TryParse(matchCollection[0].Groups["year"].Value, out airYear);
|
||||
|
||||
ParsedEpisodeInfo result;
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@ namespace NzbDrone.Core.Parser
|
||||
LocalEpisode GetLocalEpisode(string filename, Series series);
|
||||
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||
Series GetSeries(string title);
|
||||
Movie GetMovie(string title);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
|
||||
RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
|
||||
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
||||
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
}
|
||||
@@ -27,16 +29,19 @@ namespace NzbDrone.Core.Parser
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ParsingService(IEpisodeService episodeService,
|
||||
ISeriesService seriesService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IMovieService movieService,
|
||||
Logger logger)
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
_seriesService = seriesService;
|
||||
_sceneMappingService = sceneMappingService;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -114,6 +119,26 @@ namespace NzbDrone.Core.Parser
|
||||
return series;
|
||||
}
|
||||
|
||||
public Movie GetMovie(string title)
|
||||
{
|
||||
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
||||
|
||||
if (parsedEpisodeInfo == null)
|
||||
{
|
||||
return _movieService.FindByTitle(title);
|
||||
}
|
||||
|
||||
var series = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
series = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
|
||||
parsedEpisodeInfo.SeriesTitleInfo.Year);
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var remoteEpisode = new RemoteEpisode
|
||||
@@ -134,6 +159,25 @@ namespace NzbDrone.Core.Parser
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
public RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var remoteEpisode = new RemoteMovie
|
||||
{
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
};
|
||||
|
||||
var movie = GetMovie(parsedEpisodeInfo, imdbId, searchCriteria);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
remoteEpisode.Movie = movie;
|
||||
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
||||
{
|
||||
return new RemoteEpisode
|
||||
@@ -248,14 +292,49 @@ namespace NzbDrone.Core.Parser
|
||||
return null;
|
||||
}
|
||||
|
||||
private Movie GetMovie(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
if (searchCriteria.Movie.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle())
|
||||
{
|
||||
return searchCriteria.Movie;
|
||||
}
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace() && imdbId == searchCriteria.Movie.ImdbId)
|
||||
{
|
||||
//TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import
|
||||
return searchCriteria.Movie;
|
||||
}
|
||||
}
|
||||
|
||||
Movie movie = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
||||
|
||||
if (movie == null && imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
//TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import
|
||||
movie = _movieService.FindByImdbId(imdbId);
|
||||
}
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug("No matching movie {0}", parsedEpisodeInfo.SeriesTitle);
|
||||
return null;
|
||||
}
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
Series series = null;
|
||||
|
||||
/*var localEpisode = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
||||
|
||||
var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle);
|
||||
if (sceneMappingTvdbId.HasValue)
|
||||
if (localEpisode != null)
|
||||
{
|
||||
if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMappingTvdbId.Value)
|
||||
if (searchCriteria != null && searchCriteria.Series.TvdbId == localEpisode.TvdbId)
|
||||
{
|
||||
return searchCriteria.Series;
|
||||
}
|
||||
@@ -269,7 +348,7 @@ namespace NzbDrone.Core.Parser
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
}*/ //This is only to find scene mapping should not be necessary for movies.
|
||||
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Qualities
|
||||
new QualityDefinition(Quality.WEBDL720p) { Weight = 8, MinSize = 0, MaxSize = 100 },
|
||||
new QualityDefinition(Quality.Bluray720p) { Weight = 9, MinSize = 0, MaxSize = 100 },
|
||||
new QualityDefinition(Quality.WEBDL1080p) { Weight = 10, MinSize = 0, MaxSize = 100 },
|
||||
new QualityDefinition(Quality.Bluray1080p) { Weight = 11, MinSize = 0, MaxSize = 100 },
|
||||
new QualityDefinition(Quality.Bluray1080p) { Weight = 11, MinSize = 0, MaxSize = null },
|
||||
new QualityDefinition(Quality.HDTV2160p) { Weight = 12, MinSize = 0, MaxSize = null },
|
||||
new QualityDefinition(Quality.WEBDL2160p) { Weight = 13, MinSize = 0, MaxSize = null },
|
||||
new QualityDefinition(Quality.Bluray2160p) { Weight = 14, MinSize = 0, MaxSize = null },
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Queue
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
public Episode Episode { get; set; }
|
||||
public Movie Movie { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public decimal Size { get; set; }
|
||||
public string Title { get; set; }
|
||||
@@ -24,6 +25,7 @@ namespace NzbDrone.Core.Queue
|
||||
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public RemoteEpisode RemoteEpisode { get; set; }
|
||||
public RemoteMovie RemoteMovie { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,42 @@ namespace NzbDrone.Core.Queue
|
||||
yield return MapEpisode(trackedDownload, episode);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (trackedDownload.RemoteMovie.Movie != null)
|
||||
{
|
||||
// FIXME: Present queue items with unknown series/episodes
|
||||
yield return MapMovie(trackedDownload, trackedDownload.RemoteMovie.Movie);
|
||||
}
|
||||
}
|
||||
|
||||
private Queue MapMovie(TrackedDownload trackedDownload, Movie movie)
|
||||
{
|
||||
var queue = new Queue
|
||||
{
|
||||
Id = HashConverter.GetHashInt31(string.Format("trackedDownload-{0}", trackedDownload.DownloadItem.DownloadId)),
|
||||
Series = null,
|
||||
Episode = null,
|
||||
Quality = trackedDownload.RemoteMovie.ParsedEpisodeInfo.Quality,
|
||||
Title = trackedDownload.DownloadItem.Title,
|
||||
Size = trackedDownload.DownloadItem.TotalSize,
|
||||
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
||||
Timeleft = trackedDownload.DownloadItem.RemainingTime,
|
||||
Status = trackedDownload.DownloadItem.Status.ToString(),
|
||||
TrackedDownloadStatus = trackedDownload.Status.ToString(),
|
||||
StatusMessages = trackedDownload.StatusMessages.ToList(),
|
||||
RemoteEpisode = trackedDownload.RemoteEpisode,
|
||||
RemoteMovie = trackedDownload.RemoteMovie,
|
||||
DownloadId = trackedDownload.DownloadItem.DownloadId,
|
||||
Protocol = trackedDownload.Protocol,
|
||||
Movie = movie
|
||||
};
|
||||
|
||||
if (queue.Timeleft.HasValue)
|
||||
{
|
||||
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
private Queue MapEpisode(TrackedDownload trackedDownload, Episode episode)
|
||||
{
|
||||
var queue = new Queue
|
||||
|
||||
22
src/NzbDrone.Core/Tv/Commands/RefreshMovieCommand.cs
Normal file
22
src/NzbDrone.Core/Tv/Commands/RefreshMovieCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Tv.Commands
|
||||
{
|
||||
public class RefreshMovieCommand : Command
|
||||
{
|
||||
public int? MovieId { get; set; }
|
||||
|
||||
public RefreshMovieCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public RefreshMovieCommand(int? movieId)
|
||||
{
|
||||
MovieId = movieId;
|
||||
}
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public override bool UpdateScheduledTask => !MovieId.HasValue;
|
||||
}
|
||||
}
|
||||
14
src/NzbDrone.Core/Tv/Events/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/MovieRefreshStartingEvent.cs
Normal file
14
src/NzbDrone.Core/Tv/Events/MovieRefreshStartingEvent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Tv.Events
|
||||
{
|
||||
public class MovieRefreshStartingEvent : IEvent
|
||||
{
|
||||
public bool ManualTrigger { get; set; }
|
||||
|
||||
public MovieRefreshStartingEvent(bool manualTrigger)
|
||||
{
|
||||
ManualTrigger = manualTrigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user