1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-05 13:21:25 -05:00

Compare commits

..

33 Commits
0.05 ... v0.1-b

Author SHA1 Message Date
Leonardo Galli
e9f9f66b2f Allow Sonarr and Radarr to run together.
Also changes default port of Radarr to 7878.
However, now multiple instances of Radarr can also be run. This should
be fixed in the future.
2017-01-03 16:06:06 +01:00
Leonardo Galli
6ca88f98af Fix package.sh permissions for travis 2017-01-03 16:04:41 +01:00
Leonardo Galli
631cf776f6 Travis now automatically pushes a build to a server. 2017-01-03 15:54:15 +01:00
Leonardo Galli
6ea9b4b94a Added Script for easier packaging. 2017-01-03 14:18:13 +01:00
Leonardo Galli
d835c168d3 Searching for movies directly when adding them is now working. 2017-01-03 13:26:09 +01:00
Leonardo Galli
329786365d Fixed adding multiple movies. 2017-01-03 12:52:09 +01:00
Leonardo Galli
4f6380a73c Automatically downloading the best movie release works now. 2017-01-03 11:59:03 +01:00
Leonardo Galli
cde1217356 Movies now show up in the Queue. 2017-01-02 22:01:11 +01:00
Leonardo Galli
2eedfca78a Movie history now fully implemented. 2017-01-02 21:02:54 +01:00
Leonardo Galli
0d65984991 Fix History for Movie Details page. 2017-01-02 20:41:44 +01:00
Leonardo Galli
2a932fe7e8 First implementation of History for Movies. However, nothing is returned from the Database query misteriously. 2017-01-02 20:15:13 +01:00
Leonardo Galli
0e02171938 Fixes downloading a movie. However, now all downloaders except QBittorrent are non functional until they get fixed. See #14 2017-01-02 19:20:32 +01:00
Leonardo Galli
2a3b0304cb Fixed a few things with displaying the Movie Details Page. Implemented the first round of Searching for and Downloading movie Releases. ATM this is still a bit hacky and alot of things need to be cleaned up. However, one can now manually search for and download (only in qBittorrent) a movie torrent. 2017-01-02 18:05:55 +01:00
Leonardo Galli
16e35f68bb Merge branch 'develop' of https://github.com/galli-leo/Radarr into develop 2017-01-02 13:21:11 +01:00
Leonardo Galli
74b1c846a5 Fixed css for movies. 2017-01-02 13:20:50 +01:00
Leonardo Galli
1f930c18e4 Update readme.md 2017-01-02 11:45:11 +01:00
Leonardo Galli
d9e60eff6b Update .travis.yml
Removed tests since they take far too long
2017-01-02 11:25:28 +01:00
Leonardo Galli
097982334c Update .travis.yml
Fixed syntax for test.sh
2017-01-02 10:40:09 +01:00
Leonardo Galli
be6e6b910e Update .travis.yml
symlinking node was already done in apt-get and therefore resulted in an error.
2017-01-02 10:32:23 +01:00
Leonardo Galli
31b9ec1116 Create .travis.yml
Should enable automatically testing.
2017-01-02 10:27:59 +01:00
Leonardo Galli
ff6c3b70d3 Merge pull request #12 from fedoranimus/develop
Fix template references and 'movie' strings
2017-01-02 10:12:49 +01:00
Tim Turner
0fd0b31a60 Fix template references and 'movie' strings 2016-12-31 14:42:32 -05:00
Leonardo Galli
76e6ebc63c Merge branch 'develop' of https://github.com/galli-leo/Radarr into develop 2016-12-30 11:58:43 +01:00
Leonardo Galli
2ea35adb98 Fixed issue with Homepage movies not loading correctly. 2016-12-30 11:58:39 +01:00
Leonardo Galli
782f63f510 Update readme.md 2016-12-30 11:34:11 +01:00
Leonardo Galli
c874122fc0 Use different folder to store sqlite database. Fixes #10. 2016-12-30 11:25:25 +01:00
Leonardo Galli
2efda4933d Changed the name in the UI to Radarr. 2016-12-29 17:44:51 +01:00
Leonardo Galli
b7c70d750a Movies should now show on the main page. However, a lot has to be done to the detail controller before it is really going to work. 2016-12-29 17:38:54 +01:00
Leonardo Galli
5ebfac6cc8 First implementation of custom database table for movies.Some things are not yet working quite well (e.g. search clears when movies are added.). Also movies cannot yet be looked up! 2016-12-29 16:04:01 +01:00
Leonardo Galli
74ca6149e3 Merge branch 'develop' of https://github.com/galli-leo/Radarr into develop 2016-12-29 14:07:43 +01:00
Leonardo Galli
40d7590f80 First implementation of completely rewriting the way Radarr handles movies. Searching for new movies is now mostly feature complete. 2016-12-29 14:06:51 +01:00
Leonardo Galli
2cb27240dc Update readme.md 2016-12-28 22:37:06 +01:00
Leonardo Galli
837c2683df Update readme.md 2016-12-28 18:43:31 +01:00
218 changed files with 7397 additions and 253 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

4
.gitignore vendored
View File

@@ -127,9 +127,13 @@ bin
obj
output/*
#Packages
Radarr_*/
Radarr_*.zip
#OS X metadata files
._*
.DS_Store
_start
_temp_*/**/*

12
.travis.yml Normal file
View File

@@ -0,0 +1,12 @@
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
after_success:
- chmod +x package.sh
- ./package.sh

View File

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

45
package.sh Normal file
View File

@@ -0,0 +1,45 @@
if [ $# -eq 0 ]; then
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
echo "Need to supply version argument" && exit;
fi
fi
if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
#VERSION="`date +%H:%M:%S`"
VERSION="15-11-15"
YEAR="`date +%Y`"
MONTH="`date +%m`"
DAY="`date +%d`"
else
VERSION=$1
fi
outputFolder='./_output'
outputFolderMono='./_output_mono'
outputFolderOsx='./_output_osx'
outputFolderOsxApp='./_output_osx_app'
cp -r $outputFolder Radarr_Windows_$VERSION
cp -r $outputFolderMono Radarr_Mono_$VERSION
cp -r $outputFolderOsxApp Radarr_OSX_$VERSION
zip -r Radarr_Windows_$VERSION.zip Radarr_Windows_$VERSION >& /dev/null
zip -r Radarr_Mono_$VERSION.zip Radarr_Mono_$VERSION >& /dev/null
zip -r Radarr_OSX_$VERSION.zip Radarr_OSX_$VERSION >& /dev/null
ftp -n ftp.leonardogalli.ch << END_SCRIPT
quote USER $FTP_USER
quote PASS $FTP_PASS
mkdir builds
cd builds
mkdir $YEAR
cd $YEAR
mkdir $MONTH
cd $MONTH
mkdir $DAY
cd $DAY
binary
put Radarr_Windows_$VERSION.zip
put Radarr_Mono_$VERSION.zip
put Radarr_OSX_$VERSION.zip
quit
END_SCRIPT

View File

@@ -1,7 +1,20 @@
# Sonarr #
# Radarr [![Build Status](https://travis-ci.org/galli-leo/Radarr.svg?branch=develop)](https://travis-ci.org/galli-leo/Radarr)#
This fork of Sonarr aims to turn it into something like Couchpotato.
At the moment almost nothing is implemented.
## 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 @@ At the moment almost nothing is implemented.
* 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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Automation.Test
_runner.KillAll();
_runner.Start();
driver.Url = "http://localhost:8989";
driver.Url = "http://localhost:7878";
var page = new PageBase(driver);
page.WaitForNoSpinner();

View File

@@ -49,7 +49,7 @@ namespace NzbDrone.Common.Test
public void GetValue_Success()
{
const string key = "Port";
const string value = "8989";
const string value = "7878";
var result = Subject.GetValue(key, value);
@@ -60,7 +60,7 @@ namespace NzbDrone.Common.Test
public void GetInt_Success()
{
const string key = "Port";
const int value = 8989;
const int value = 7878;
var result = Subject.GetValueInt(key, value);
@@ -95,7 +95,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetPort_Success()
{
const int value = 8989;
const int value = 7878;
var result = Subject.Port;

View File

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

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Console
{
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(exception.Message + ". This can happen if another instance of Sonarr is already running another application is using the same port (default: 8989) or the user has insufficient permissions");
Logger.Fatal(exception.Message + ". This can happen if another instance of Sonarr is already running another application is using the same port (default: 7878) or the user has insufficient permissions");
System.Console.WriteLine("Press enter to exit...");
System.Console.ReadLine();
Environment.Exit(1);

View File

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

View File

@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Configuration
}
}
public int Port => GetValueInt("Port", 8989);
public int Port => GetValueInt("Port", 7878);
public int SslPort => GetValueInt("SslPort", 9898);

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,10 @@ namespace NzbDrone.Core.DecisionEngine
public class DownloadDecision
{
public RemoteEpisode RemoteEpisode { get; private set; }
public RemoteMovie RemoteMovie { get; private set; }
public bool IsForMovie = false;
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved => !Rejections.Any();
@@ -30,6 +34,23 @@ 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();
}

View File

@@ -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())
@@ -82,7 +156,7 @@ namespace NzbDrone.Core.DecisionEngine
{
//remoteEpisode.DownloadAllowed = true; //Fuck you :)
//decision = GetDecisionForReport(remoteEpisode, searchCriteria);
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Movie not Found."));
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Series not Found."));
}
else if (remoteEpisode.Episodes.Empty())
{

View File

@@ -7,6 +7,7 @@ namespace NzbDrone.Core.DecisionEngine
public interface IPrioritizeDownloadDecision
{
List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions);
List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions);
}
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
@@ -29,5 +30,17 @@ 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();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,8 @@ 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; }

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

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

View File

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

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -22,6 +23,11 @@ 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();

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
using System;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
@@ -189,5 +190,10 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
yield return new IndexerRequest(builder.Build());
}
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -84,5 +85,10 @@ namespace NzbDrone.Core.Indexers.Fanzub
{
return RemoveCharactersRegex.Replace(title, "");
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
@@ -128,5 +129,10 @@ namespace NzbDrone.Core.Indexers.HDBits
yield return new IndexerRequest(request);
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

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

View File

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

View File

@@ -10,5 +10,6 @@ namespace NzbDrone.Core.Indexers
IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria);
IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria);
IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria);
IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -22,6 +23,11 @@ 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();

View File

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

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -147,5 +148,10 @@ namespace NzbDrone.Core.Indexers.KickassTorrents
{
return query.Replace('+', ' ');
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -273,5 +274,10 @@ namespace NzbDrone.Core.Indexers.Newznab
{
return title.Replace("+", "%20");
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -102,5 +103,10 @@ namespace NzbDrone.Core.Indexers.Nyaa
{
return query.Replace(' ', '+');
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Text;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -101,5 +102,10 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -88,13 +89,12 @@ namespace NzbDrone.Core.Indexers.Rarbg
if (tvdbId.HasValue)
{
string imdbId = string.Format("tt{0:D7}", tvdbId);
requestBuilder.AddQueryParam("search_imdb", imdbId);
requestBuilder.AddQueryParam("search_tvdb", tvdbId.Value);
}
if (query.IsNotNullOrWhiteSpace())
{
//requestBuilder.AddQueryParam("search_string", string.Format(query, args));
requestBuilder.AddQueryParam("search_string", string.Format(query, args));
}
if (!Settings.RankedOnly)
@@ -102,6 +102,36 @@ namespace NzbDrone.Core.Indexers.Rarbg
requestBuilder.AddQueryParam("ranked", "0");
}
requestBuilder.AddQueryParam("category", "tv");
requestBuilder.AddQueryParam("limit", "100");
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
requestBuilder.AddQueryParam("format", "json_extended");
requestBuilder.AddQueryParam("app_id", "Sonarr");
yield return new IndexerRequest(requestBuilder.Build());
}
private IEnumerable<IndexerRequest> GetMovieRequest(MovieSearchCriteria searchCriteria)
{
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
.Resource("/pubapi_v2.php")
.Accept(HttpAccept.Json);
if (Settings.CaptchaToken.IsNotNullOrWhiteSpace())
{
requestBuilder.UseSimplifiedUserAgent = true;
requestBuilder.SetCookie("cf_clearance", Settings.CaptchaToken);
}
requestBuilder.AddQueryParam("mode", "search");
requestBuilder.AddQueryParam("search_imdb", searchCriteria.Movie.ImdbId);
if (!Settings.RankedOnly)
{
requestBuilder.AddQueryParam("ranked", "0");
}
requestBuilder.AddQueryParam("category", "movies");
requestBuilder.AddQueryParam("limit", "100");
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
@@ -110,5 +140,18 @@ namespace NzbDrone.Core.Indexers.Rarbg
yield return new IndexerRequest(requestBuilder.Build());
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetMovieRequest(searchCriteria));
return pageableRequests;
}
}
}

View File

@@ -1,4 +1,5 @@
using NzbDrone.Common.Http;
using System;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers
@@ -22,6 +23,11 @@ namespace NzbDrone.Core.Indexers
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
throw new NotImplementedException();
}
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -23,6 +24,11 @@ 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();

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -22,6 +23,11 @@ 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();

View File

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

View File

@@ -1,7 +1,11 @@
using NLog;
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Wombles

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class RescanMovieCommand : Command
{
public int? MovieId { get; set; }
public override bool SendUpdatesToClient => true;
public RescanMovieCommand()
{
}
public RescanMovieCommand(int movieId)
{
MovieId = movieId;
}
}
}

View File

@@ -22,6 +22,7 @@ namespace NzbDrone.Core.MediaFiles
public interface IDiskScanService
{
void Scan(Series series);
void Scan(Movie movie);
string[] GetVideoFiles(string path, bool allDirectories = true);
string[] GetNonVideoFiles(string path, bool allDirectories = true);
List<string> FilterFiles(Series series, IEnumerable<string> files);
@@ -30,6 +31,8 @@ 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)

View File

@@ -18,6 +18,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public interface IMakeImportDecision
{
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource); //TODO: Needs changing to ParsedMovieInfo!!
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
}
@@ -53,6 +55,11 @@ 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))

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

View File

@@ -0,0 +1,24 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.Events
{
public class MovieScanSkippedEvent : IEvent
{
public Movie Movie { get; private set; }
public MovieScanSkippedReason Reason { get; set; }
public MovieScanSkippedEvent(Movie movie, MovieScanSkippedReason reason)
{
Movie = movie;
Reason = reason;
}
}
public enum MovieScanSkippedReason
{
RootFolderDoesNotExist,
RootFolderIsEmpty,
MovieFolderDoesNotExist
}
}

View File

@@ -0,0 +1,15 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.Events
{
public class MovieScannedEvent : IEvent
{
public Movie Movie { get; private set; }
public MovieScannedEvent(Movie movie)
{
Movie = movie;
}
}
}

View File

@@ -16,9 +16,11 @@ 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);

View File

@@ -11,6 +11,8 @@ namespace NzbDrone.Core.MediaFiles
public interface IMediaFileTableCleanupService
{
void Clean(Series series, List<string> filesOnDisk);
void Clean(Movie movie, List<string> filesOnDisk);
}
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
@@ -84,5 +86,64 @@ 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);
}
}
}
}
}

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

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

View File

@@ -14,7 +14,7 @@ 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;
@@ -38,11 +38,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
string imdbId = string.Format("tt{0:D7}", tvdbSeriesId);
var imdbRequest = new HttpRequest("http://www.omdbapi.com/?i="+ imdbId + "&plot=full&r=json");
var httpResponse = _httpClient.Get(imdbRequest);
var httpResponse = _httpClient.Get<ShowResource>(httpRequest);
if (httpResponse.HasHttpError)
{
@@ -56,49 +52,140 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
var episodes = httpResponse.Resource.Episodes.Select(MapEpisode);
var series = MapSeries(httpResponse.Resource);
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 series = new Series();
var movie = new Movie();
series.Title = json.Title;
series.TitleSlug = series.Title.ToLower().Replace(" ", "-");
series.Overview = json.Plot;
series.CleanTitle = Parser.Parser.CleanSeriesTitle(series.Title);
series.TvdbId = tvdbSeriesId;
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);
series.FirstAired = airDate;
series.Year = airDate.Year;
series.ImdbId = imdbId;
series.Images = new List<MediaCover.MediaCover>();
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);
series.Images.Add(imdbPoster);
movie.Images.Add(imdbPoster);
string runtime = json.Runtime;
int runtimeNum = 0;
int.TryParse(runtime.Replace("min", "").Trim(), out runtimeNum);
series.Runtime = runtimeNum;
movie.Runtime = runtimeNum;
var season = new Season();
season.SeasonNumber = 1;
season.Monitored = true;
series.Seasons.Add(season);
return movie;
}
var episode = new Episode();
public List<Movie> SearchForNewMovie(string title)
{
var lowerTitle = title.ToLower();
episode.AirDate = airDate.ToBestDateString();
episode.Title = json.Title;
episode.SeasonNumber = 1;
episode.EpisodeNumber = 1;
episode.Overview = series.Overview;
episode.AirDate = airDate.ToShortDateString();
if (lowerTitle.StartsWith("imdb:") || lowerTitle.StartsWith("imdbid:"))
{
var slug = lowerTitle.Split(':')[1].Trim();
var episodes = new List<Episode> { episode };
string imdbid = slug;
return new Tuple<Series, List<Episode>>(series, episodes.ToList());
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)
@@ -128,70 +215,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
var httpRequest = _requestBuilder.Create()
.SetSegment("route", "search")
.AddQueryParam("term", title.ToLower().Trim())
.Build();
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<Series>();
foreach (dynamic entry in json.d)
{
var imdbMovie = new Series();
imdbMovie.ImdbId = entry.id;
string noTT = imdbMovie.ImdbId.Replace("tt", "");
try
{
imdbMovie.TvdbId = (int)Double.Parse(noTT);
}
catch
{
imdbMovie.TvdbId = 0;
}
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;
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest);

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

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

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

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser.Model
{
public class RemoteMovie
{
public ReleaseInfo Release { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } //TODO: Change to ParsedMovieInfo, for now though ParsedEpisodeInfo will do.
public Movie Movie { get; set; }
public bool DownloadAllowed { get; set; }
public override string ToString()
{
return Release.Title;
}
}
}

View File

@@ -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;
}
@@ -100,7 +105,7 @@ namespace NzbDrone.Core.Parser
if (parsedEpisodeInfo == null)
{
return _seriesService.FindByTitle(title); //Here we have a problem since it is not possible for movies to find a scene mapping, so these releases are always rejected :(
return _seriesService.FindByTitle(title);
}
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
@@ -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,6 +292,39 @@ 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;

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Tv.Commands
{
public class RefreshMovieCommand : Command
{
public int? MovieId { get; set; }
public RefreshMovieCommand()
{
}
public RefreshMovieCommand(int? movieId)
{
MovieId = movieId;
}
public override bool SendUpdatesToClient => true;
public override bool UpdateScheduledTask => !MovieId.HasValue;
}
}

View File

@@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Tv.Events
{
public class MovieAddedEvent : IEvent
{
public Movie Movie { get; private set; }
public MovieAddedEvent(Movie movie)
{
Movie = movie;
}
}
}

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

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

View File

@@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Tv.Events
{
public class MovieRefreshStartingEvent : IEvent
{
public bool ManualTrigger { get; set; }
public MovieRefreshStartingEvent(bool manualTrigger)
{
ManualTrigger = manualTrigger;
}
}
}

View File

@@ -0,0 +1,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;
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Marr.Data;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Core.Tv
{
public class Movie : ModelBase
{
public Movie()
{
Images = new List<MediaCover.MediaCover>();
Genres = new List<string>();
Actors = new List<Actor>();
Tags = new HashSet<int>();
}
public string ImdbId { get; set; }
public string Title { get; set; }
public string CleanTitle { get; set; }
public string SortTitle { get; set; }
public MovieStatusType Status { get; set; }
public string Overview { get; set; }
public bool Monitored { get; set; }
public int ProfileId { get; set; }
public DateTime? LastInfoSync { get; set; }
public int Runtime { get; set; }
public List<MediaCover.MediaCover> Images { get; set; }
public string TitleSlug { get; set; }
public string Path { get; set; }
public int Year { get; set; }
public Ratings Ratings { get; set; }
public List<string> Genres { get; set; }
public List<Actor> Actors { get; set; }
public string Certification { get; set; }
public string RootFolderPath { get; set; }
public DateTime Added { get; set; }
public DateTime? InCinemas { get; set; }
public LazyLoaded<Profile> Profile { get; set; }
public HashSet<int> Tags { get; set; }
public AddMovieOptions AddOptions { get; set; }
public override string ToString()
{
return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe());
}
}
public class AddMovieOptions : MonitoringOptions
{
public bool SearchForMovie { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More