mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89e5001bad | |||
| 056fb154a8 | |||
| 3edc2b80cf | |||
| a1745cd02e | |||
| 1c0f9b64ca | |||
| 7d6a518f30 | |||
| 9f2fcebc24 | |||
| 91295f50b0 | |||
| 68bf97f52c | |||
| 363048e68e | |||
| 9c20c0b889 | |||
| 50891e5dd7 | |||
| f393a95501 | |||
| a68dd6d2f7 | |||
| dadf6708ab | |||
| 7a86c78896 | |||
| 81688399c0 | |||
| 1e28a2e5d4 | |||
| c5bb259555 | |||
| 0d5d75d6ea | |||
| 5bae9bbbcc | |||
| a3e681078f | |||
| 758228e159 | |||
| 1b900a006f | |||
| 9b5c5169ef | |||
| e78a55ac6e | |||
| e82cf70399 | |||
| e7d65ee4ae | |||
| 1db3669afa | |||
| 93e55b7575 | |||
| f850c65b56 | |||
| 297348fffe | |||
| 07ff6558d1 | |||
| 85843efcb0 | |||
| 3d4b1c3be5 | |||
| a3f389af5e | |||
| 1aeb3c6fd6 | |||
| 6ab6c016c0 | |||
| a1961603d7 | |||
| 50ac95dec5 | |||
| a16e46cf38 | |||
| ea33b75764 | |||
| 31e657d052 | |||
| fe0dfef83c | |||
| bc1a47ff5a | |||
| 4e8089dd42 | |||
| 6dc9f90a8b | |||
| 1aae3ae2b5 | |||
| 75436bcce4 | |||
| 61df3ef40e | |||
| f45aab27d1 | |||
| f477c46406 | |||
| 2af07d7e0d | |||
| df691488a9 | |||
| 800e7ae508 | |||
| fcf156293e | |||
| 94f44a0eb7 | |||
| 1e2c28f67a | |||
| 62b45f7ea7 | |||
| f577590ad6 | |||
| 0941247f63 | |||
| 3170060f37 | |||
| 35b384439f |
@@ -1,9 +1,12 @@
|
|||||||
|
**Description:**
|
||||||
|
|
||||||
|
Provide a description of the feature request or bug here, the more details the better.
|
||||||
|
Please also try to include the following if you are reporting a bug
|
||||||
|
|
||||||
|
**Radarr Version:**
|
||||||
|
|
||||||
|
**Logs:**
|
||||||
|
|
||||||
|
|
||||||
Please use the search bar and make sure you are not submitting an already submitted issue.
|
Please use the search bar and make sure you are not submitting an already submitted issue.
|
||||||
|
Visit our [Discord server](https://discord.gg/NWYch8M) for support or longer discussions.
|
||||||
Provide a description of the feature request or bug, the more details the better.
|
|
||||||
When possible include a log!
|
|
||||||
|
|
||||||
Please use our [Discord server](https://discord.gg/NWYch8M) for support or longer discussions.
|
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
YES | NO
|
YES | NO
|
||||||
|
|
||||||
#### Description
|
#### Description
|
||||||
A few sentences describing the overall goals of the pull request's commits.
|
|
||||||
|
|
||||||
#### Todos
|
#### Todos
|
||||||
- [ ] Tests
|
- [ ] Tests
|
||||||
- [ ] Documentation
|
|
||||||
|
|
||||||
|
|
||||||
#### Issues Fixed or Closed by this PR
|
#### Issues Fixed or Closed by this PR
|
||||||
|
|
||||||
*
|
* #
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ addons:
|
|||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- nodejs
|
- nodejs
|
||||||
- npm
|
# - npm apparently not needed anymore.
|
||||||
script:
|
script:
|
||||||
- ./build.sh
|
- ./build.sh
|
||||||
- chmod +x test.sh
|
- chmod +x test.sh
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Nancy;
|
||||||
using NzbDrone.Api.Episodes;
|
using NzbDrone.Api.Episodes;
|
||||||
using NzbDrone.Api.Movie;
|
using NzbDrone.Api.Movie;
|
||||||
|
using NzbDrone.Api.Series;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Api.EpisodeFiles
|
|||||||
|
|
||||||
private void DeleteEpisodeFile(int id)
|
private void DeleteEpisodeFile(int id)
|
||||||
{
|
{
|
||||||
var episodeFile = _mediaFileService.Get(id);
|
var episodeFile = _mediaFileService.Get(id);
|
||||||
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
||||||
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
|
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||||
|
|
||||||
|
|||||||
@@ -66,13 +66,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
|||||||
private Response LogError(NancyContext context, Exception exception)
|
private Response LogError(NancyContext context, Exception exception)
|
||||||
{
|
{
|
||||||
var response = _errorPipeline.HandleException(context, exception);
|
var response = _errorPipeline.HandleException(context, exception);
|
||||||
|
|
||||||
context.Response = response;
|
context.Response = response;
|
||||||
|
|
||||||
LogEnd(context);
|
LogEnd(context);
|
||||||
|
|
||||||
context.Response = null;
|
context.Response = null;
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,12 +76,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
|||||||
{
|
{
|
||||||
if (request.Url.Query.IsNotNullOrWhiteSpace())
|
if (request.Url.Query.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
return string.Concat(request.Url.Path, "?", request.Url.Query);
|
return string.Concat(request.Url.Path, request.Url.Query);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return request.Url.Path;
|
|
||||||
}
|
}
|
||||||
|
return request.Url.Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Nancy;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MetadataSource;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
using Marr.Data;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.RootFolders;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Movie
|
||||||
|
{
|
||||||
|
|
||||||
|
public class UnmappedComparer : IComparer<UnmappedFolder>
|
||||||
|
{
|
||||||
|
public int Compare(UnmappedFolder a, UnmappedFolder b)
|
||||||
|
{
|
||||||
|
return a.Name.CompareTo(b.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieBulkImportModule : NzbDroneRestModule<MovieResource>
|
||||||
|
{
|
||||||
|
private readonly ISearchForNewMovie _searchProxy;
|
||||||
|
private readonly IRootFolderService _rootFolderService;
|
||||||
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
|
private readonly IDiskScanService _diskScanService;
|
||||||
|
private readonly ICached<Core.Tv.Movie> _mappedMovies;
|
||||||
|
private readonly IMovieService _movieService;
|
||||||
|
|
||||||
|
public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService rootFolderService, IMakeImportDecision importDecisionMaker,
|
||||||
|
IDiskScanService diskScanService, ICacheManager cacheManager, IMovieService movieService)
|
||||||
|
: base("/movies/bulkimport")
|
||||||
|
{
|
||||||
|
_searchProxy = searchProxy;
|
||||||
|
_rootFolderService = rootFolderService;
|
||||||
|
_importDecisionMaker = importDecisionMaker;
|
||||||
|
_diskScanService = diskScanService;
|
||||||
|
_mappedMovies = cacheManager.GetCache<Core.Tv.Movie>(GetType(), "mappedMoviesCache");
|
||||||
|
_movieService = movieService;
|
||||||
|
Get["/"] = x => Search();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Response Search()
|
||||||
|
{
|
||||||
|
if (Request.Query.Id == 0)
|
||||||
|
{
|
||||||
|
//Todo error handling
|
||||||
|
}
|
||||||
|
|
||||||
|
RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id);
|
||||||
|
|
||||||
|
int page = Request.Query.page;
|
||||||
|
int per_page = Request.Query.per_page;
|
||||||
|
|
||||||
|
int min = (page - 1) * per_page;
|
||||||
|
|
||||||
|
int max = page * per_page;
|
||||||
|
|
||||||
|
var unmapped = rootFolder.UnmappedFolders.OrderBy(f => f.Name).ToList();
|
||||||
|
|
||||||
|
int total_count = unmapped.Count;
|
||||||
|
|
||||||
|
if (Request.Query.total_entries.HasValue)
|
||||||
|
{
|
||||||
|
total_count = Request.Query.total_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
max = total_count >= max ? max : total_count;
|
||||||
|
|
||||||
|
var paged = unmapped.GetRange(min, max-min);
|
||||||
|
|
||||||
|
var mapped = paged.Select(f =>
|
||||||
|
{
|
||||||
|
Core.Tv.Movie m = null;
|
||||||
|
|
||||||
|
var mappedMovie = _mappedMovies.Find(f.Name);
|
||||||
|
|
||||||
|
if (mappedMovie != null)
|
||||||
|
{
|
||||||
|
return mappedMovie;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedTitle = Parser.ParseMoviePath(f.Name);
|
||||||
|
if (parsedTitle == null)
|
||||||
|
{
|
||||||
|
m = new Core.Tv.Movie
|
||||||
|
{
|
||||||
|
Title = f.Name.Replace(".", " ").Replace("-", " "),
|
||||||
|
Path = f.Path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m = new Core.Tv.Movie
|
||||||
|
{
|
||||||
|
Title = parsedTitle.MovieTitle,
|
||||||
|
Year = parsedTitle.Year,
|
||||||
|
ImdbId = parsedTitle.ImdbId,
|
||||||
|
Path = f.Path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = _diskScanService.GetVideoFiles(f.Path);
|
||||||
|
|
||||||
|
var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m);
|
||||||
|
|
||||||
|
var decision = decisions.Where(d => d.Approved && !d.Rejections.Any()).FirstOrDefault();
|
||||||
|
|
||||||
|
if (decision != null)
|
||||||
|
{
|
||||||
|
var local = decision.LocalMovie;
|
||||||
|
|
||||||
|
m.MovieFile = new LazyLoaded<MovieFile>(new MovieFile
|
||||||
|
{
|
||||||
|
Path = local.Path,
|
||||||
|
Edition = local.ParsedMovieInfo.Edition,
|
||||||
|
Quality = local.Quality,
|
||||||
|
MediaInfo = local.MediaInfo,
|
||||||
|
ReleaseGroup = local.ParsedMovieInfo.ReleaseGroup,
|
||||||
|
RelativePath = f.Path.GetRelativePath(local.Path)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedMovie = _searchProxy.MapMovieToTmdbMovie(m);
|
||||||
|
|
||||||
|
if (mappedMovie != null)
|
||||||
|
{
|
||||||
|
mappedMovie.Monitored = true;
|
||||||
|
|
||||||
|
_mappedMovies.Set(f.Name, mappedMovie, TimeSpan.FromDays(2));
|
||||||
|
|
||||||
|
return mappedMovie;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return new PagingResource<MovieResource>
|
||||||
|
{
|
||||||
|
Page = page,
|
||||||
|
PageSize = per_page,
|
||||||
|
SortDirection = SortDirection.Ascending,
|
||||||
|
SortKey = Request.Query.sort_by,
|
||||||
|
TotalRecords = total_count - mapped.Where(m => m == null).Count(),
|
||||||
|
Records = MapToResource(mapped.Where(m => m != null)).ToList()
|
||||||
|
}.AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
|
||||||
|
{
|
||||||
|
foreach (var currentMovie in movies)
|
||||||
|
{
|
||||||
|
var resource = currentMovie.ToResource();
|
||||||
|
var poster = currentMovie.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
||||||
|
if (poster != null)
|
||||||
|
{
|
||||||
|
resource.RemotePoster = poster.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using NzbDrone.Api.Movie;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.SignalR;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Movies
|
||||||
|
{
|
||||||
|
public abstract class MovieModuleWithSignalR : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.Movie>,
|
||||||
|
IHandle<MovieGrabbedEvent>,
|
||||||
|
IHandle<MovieDownloadedEvent>
|
||||||
|
{
|
||||||
|
protected readonly IMovieService _episodeService;
|
||||||
|
protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||||
|
|
||||||
|
protected MovieModuleWithSignalR(IMovieService episodeService,
|
||||||
|
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
|
IBroadcastSignalRMessage signalRBroadcaster)
|
||||||
|
: base(signalRBroadcaster)
|
||||||
|
{
|
||||||
|
_episodeService = episodeService;
|
||||||
|
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||||
|
|
||||||
|
GetResourceById = GetMovie;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MovieModuleWithSignalR(IMovieService episodeService,
|
||||||
|
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
|
IBroadcastSignalRMessage signalRBroadcaster,
|
||||||
|
string resource)
|
||||||
|
: base(signalRBroadcaster, resource)
|
||||||
|
{
|
||||||
|
_episodeService = episodeService;
|
||||||
|
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||||
|
|
||||||
|
GetResourceById = GetMovie;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MovieResource GetMovie(int id)
|
||||||
|
{
|
||||||
|
var episode = _episodeService.GetMovie(id);
|
||||||
|
var resource = MapToResource(episode, true);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MovieResource MapToResource(Core.Tv.Movie episode, bool includeSeries)
|
||||||
|
{
|
||||||
|
var resource = episode.ToResource();
|
||||||
|
|
||||||
|
if (includeSeries)
|
||||||
|
{
|
||||||
|
var series = episode ?? _episodeService.GetMovie(episode.Id);
|
||||||
|
resource = series.ToResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieGrabbedEvent message)
|
||||||
|
{
|
||||||
|
var resource = message.Movie.Movie.ToResource();
|
||||||
|
|
||||||
|
//add a grabbed field in MovieResource?
|
||||||
|
//resource.Grabbed = true;
|
||||||
|
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieDownloadedEvent message)
|
||||||
|
{
|
||||||
|
var resource = message.Movie.Movie.ToResource();
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,8 +118,10 @@
|
|||||||
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
|
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
|
||||||
<Compile Include="Indexers\ReleaseModuleBase.cs" />
|
<Compile Include="Indexers\ReleaseModuleBase.cs" />
|
||||||
<Compile Include="Indexers\ReleasePushModule.cs" />
|
<Compile Include="Indexers\ReleasePushModule.cs" />
|
||||||
|
<Compile Include="Movies\MovieModuleWithSignalR.cs" />
|
||||||
|
<Compile Include="Movies\MovieBulkImportModule.cs" />
|
||||||
<Compile Include="Movies\MovieFileModule.cs" />
|
<Compile Include="Movies\MovieFileModule.cs" />
|
||||||
<Compile Include="Movies\MovieModule.cs" />
|
<Compile Include="Series\MovieModule.cs" />
|
||||||
<Compile Include="Movies\RenameMovieModule.cs" />
|
<Compile Include="Movies\RenameMovieModule.cs" />
|
||||||
<Compile Include="Movies\RenameMovieResource.cs" />
|
<Compile Include="Movies\RenameMovieResource.cs" />
|
||||||
<Compile Include="Movies\MovieEditorModule.cs" />
|
<Compile Include="Movies\MovieEditorModule.cs" />
|
||||||
@@ -245,7 +247,6 @@
|
|||||||
<Compile Include="Series\SeriesEditorModule.cs" />
|
<Compile Include="Series\SeriesEditorModule.cs" />
|
||||||
<Compile Include="Series\MovieLookupModule.cs" />
|
<Compile Include="Series\MovieLookupModule.cs" />
|
||||||
<Compile Include="Series\SeriesLookupModule.cs" />
|
<Compile Include="Series\SeriesLookupModule.cs" />
|
||||||
<Compile Include="Series\MovieModule.cs" />
|
|
||||||
<Compile Include="Series\SeriesModule.cs" />
|
<Compile Include="Series\SeriesModule.cs" />
|
||||||
<Compile Include="Series\MovieResource.cs" />
|
<Compile Include="Series\MovieResource.cs" />
|
||||||
<Compile Include="Series\SeriesResource.cs" />
|
<Compile Include="Series\SeriesResource.cs" />
|
||||||
@@ -267,6 +268,7 @@
|
|||||||
<Compile Include="Wanted\CutoffModule.cs" />
|
<Compile Include="Wanted\CutoffModule.cs" />
|
||||||
<Compile Include="Wanted\LegacyMissingModule.cs" />
|
<Compile Include="Wanted\LegacyMissingModule.cs" />
|
||||||
<Compile Include="Wanted\MissingModule.cs" />
|
<Compile Include="Wanted\MissingModule.cs" />
|
||||||
|
<Compile Include="Wanted\MovieCutoffModule.cs" />
|
||||||
<Compile Include="Wanted\MovieMissingModule.cs" />
|
<Compile Include="Wanted\MovieMissingModule.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -295,11 +297,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
||||||
@@ -4,18 +4,35 @@ using NzbDrone.Api.Extensions;
|
|||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MetadataSource;
|
using NzbDrone.Core.MetadataSource;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Api.REST;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Movie
|
namespace NzbDrone.Api.Movie
|
||||||
{
|
{
|
||||||
public class MovieLookupModule : NzbDroneRestModule<MovieResource>
|
public class MovieLookupModule : NzbDroneRestModule<MovieResource>
|
||||||
{
|
{
|
||||||
private readonly ISearchForNewMovie _searchProxy;
|
private readonly ISearchForNewMovie _searchProxy;
|
||||||
|
private readonly IProvideMovieInfo _movieInfo;
|
||||||
|
|
||||||
public MovieLookupModule(ISearchForNewMovie searchProxy)
|
public MovieLookupModule(ISearchForNewMovie searchProxy, IProvideMovieInfo movieInfo)
|
||||||
: base("/movies/lookup")
|
: base("/movies/lookup")
|
||||||
{
|
{
|
||||||
|
_movieInfo = movieInfo;
|
||||||
_searchProxy = searchProxy;
|
_searchProxy = searchProxy;
|
||||||
Get["/"] = x => Search();
|
Get["/"] = x => Search();
|
||||||
|
Get["/tmdb"] = x => SearchByTmdbId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response SearchByTmdbId()
|
||||||
|
{
|
||||||
|
int tmdbId = -1;
|
||||||
|
if(Int32.TryParse(Request.Query.tmdbId, out tmdbId))
|
||||||
|
{
|
||||||
|
var result = _movieInfo.GetMovieInfo(tmdbId, null);
|
||||||
|
return result.ToResource().AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException("Tmdb Id was not valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +42,6 @@ namespace NzbDrone.Api.Movie
|
|||||||
return MapToResource(imdbResults).AsResponse();
|
return MapToResource(imdbResults).AsResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
|
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
|
||||||
{
|
{
|
||||||
foreach (var currentSeries in movies)
|
foreach (var currentSeries in movies)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Api.Wanted
|
|||||||
ISeriesService seriesService,
|
ISeriesService seriesService,
|
||||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
IBroadcastSignalRMessage signalRBroadcaster)
|
IBroadcastSignalRMessage signalRBroadcaster)
|
||||||
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff")
|
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff-old")
|
||||||
{
|
{
|
||||||
_episodeCutoffService = episodeCutoffService;
|
_episodeCutoffService = episodeCutoffService;
|
||||||
GetResourcePaged = GetCutoffUnmetEpisodes;
|
GetResourcePaged = GetCutoffUnmetEpisodes;
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using NzbDrone.Api.Movie;
|
||||||
|
using NzbDrone.Api.Movies;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.SignalR;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Wanted
|
||||||
|
{
|
||||||
|
public class MovieCutoffModule : MovieModuleWithSignalR
|
||||||
|
{
|
||||||
|
private readonly IMovieCutoffService _movieCutoffService;
|
||||||
|
|
||||||
|
public MovieCutoffModule(IMovieCutoffService movieCutoffService,
|
||||||
|
IMovieService movieService,
|
||||||
|
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
|
IBroadcastSignalRMessage signalRBroadcaster)
|
||||||
|
: base(movieService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff")
|
||||||
|
{
|
||||||
|
_movieCutoffService = movieCutoffService;
|
||||||
|
GetResourcePaged = GetCutoffUnmetMovies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PagingResource<MovieResource> GetCutoffUnmetMovies(PagingResource<MovieResource> pagingResource)
|
||||||
|
{
|
||||||
|
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("title", SortDirection.Ascending);
|
||||||
|
|
||||||
|
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
|
||||||
|
{
|
||||||
|
pagingSpec.FilterExpression = v => v.Monitored == false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pagingSpec.FilterExpression = v => v.Monitored == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = ApplyToPage(_movieCutoffService.MoviesWhereCutoffUnmet, pagingSpec, v => MapToResource(v, true));
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,16 +12,14 @@ using NzbDrone.Core.Datastore.Events;
|
|||||||
|
|
||||||
namespace NzbDrone.Api.Wanted
|
namespace NzbDrone.Api.Wanted
|
||||||
{
|
{
|
||||||
class MovieMissingModule : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.Movie>,
|
class MovieMissingModule : MovieModuleWithSignalR
|
||||||
IHandle<MovieGrabbedEvent>,
|
|
||||||
IHandle<MovieDownloadedEvent>
|
|
||||||
{
|
{
|
||||||
protected readonly IMovieService _movieService;
|
protected readonly IMovieService _movieService;
|
||||||
|
|
||||||
public MovieMissingModule(IMovieService movieService,
|
public MovieMissingModule(IMovieService movieService,
|
||||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
IBroadcastSignalRMessage signalRBroadcaster)
|
IBroadcastSignalRMessage signalRBroadcaster)
|
||||||
: base(signalRBroadcaster, "wanted/missing")
|
: base(movieService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing")
|
||||||
{
|
{
|
||||||
|
|
||||||
_movieService = movieService;
|
_movieService = movieService;
|
||||||
@@ -30,7 +28,7 @@ namespace NzbDrone.Api.Wanted
|
|||||||
|
|
||||||
private PagingResource<MovieResource> GetMissingMovies(PagingResource<MovieResource> pagingResource)
|
private PagingResource<MovieResource> GetMissingMovies(PagingResource<MovieResource> pagingResource)
|
||||||
{
|
{
|
||||||
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("physicalRelease", SortDirection.Descending);
|
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("title", SortDirection.Descending);
|
||||||
|
|
||||||
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
|
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
|
||||||
{
|
{
|
||||||
@@ -41,37 +39,9 @@ namespace NzbDrone.Api.Wanted
|
|||||||
pagingSpec.FilterExpression = v => v.Monitored == true;
|
pagingSpec.FilterExpression = v => v.Monitored == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, false));
|
var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, true));
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MovieResource GetMovie(int id)
|
|
||||||
{
|
|
||||||
var movie = _movieService.GetMovie(id);
|
|
||||||
var resource = MapToResource(movie, true);
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MovieResource MapToResource(Core.Tv.Movie movie, bool includeMovieFile)
|
|
||||||
{
|
|
||||||
var resource = movie.ToResource();
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Handle(MovieGrabbedEvent message)
|
|
||||||
{
|
|
||||||
var resource = message.Movie.Movie.ToResource();
|
|
||||||
|
|
||||||
//add a grabbed field in MovieResource?
|
|
||||||
//resource.Grabbed = true;
|
|
||||||
|
|
||||||
BroadcastResourceChange(ModelAction.Updated, resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Handle(MovieDownloadedEvent message)
|
|
||||||
{
|
|
||||||
BroadcastResourceChange(ModelAction.Updated, message.Movie.Movie.Id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Moq;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.BulkImport
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class AddMultiMoviesFixture : CoreTest<MovieService>
|
||||||
|
{
|
||||||
|
private List<Movie> fakeMovies;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
fakeMovies = Builder<Movie>.CreateListOfSize(3).BuildList();
|
||||||
|
fakeMovies.ForEach(m =>
|
||||||
|
{
|
||||||
|
m.Path = null;
|
||||||
|
m.RootFolderPath = @"C:\Test\TV";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void movies_added_event_should_have_proper_path()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IBuildFileNames>()
|
||||||
|
.Setup(s => s.GetMovieFolder(It.IsAny<Movie>(), null))
|
||||||
|
.Returns((Movie m, NamingConfig n) => m.Title);
|
||||||
|
|
||||||
|
var movies = Subject.AddMovies(fakeMovies);
|
||||||
|
|
||||||
|
foreach (Movie movie in movies)
|
||||||
|
{
|
||||||
|
movie.Path.Should().NotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Subject.GetAllMovies().Should().HaveCount(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void movies_added_should_ignore_already_added()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IBuildFileNames>()
|
||||||
|
.Setup(s => s.GetMovieFolder(It.IsAny<Movie>(), null))
|
||||||
|
.Returns((Movie m, NamingConfig n) => m.Title);
|
||||||
|
|
||||||
|
Mocker.GetMock<IMovieRepository>().Setup(s => s.All()).Returns(new List<Movie> { fakeMovies[0] });
|
||||||
|
|
||||||
|
var movies = Subject.AddMovies(fakeMovies);
|
||||||
|
|
||||||
|
Mocker.GetMock<IMovieRepository>().Verify(v => v.InsertMany(It.Is<List<Movie>>(l => l.Count == 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void movies_added_should_ignore_duplicates()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IBuildFileNames>()
|
||||||
|
.Setup(s => s.GetMovieFolder(It.IsAny<Movie>(), null))
|
||||||
|
.Returns((Movie m, NamingConfig n) => m.Title);
|
||||||
|
|
||||||
|
fakeMovies[2].TmdbId = fakeMovies[0].TmdbId;
|
||||||
|
|
||||||
|
var movies = Subject.AddMovies(fakeMovies);
|
||||||
|
|
||||||
|
Mocker.GetMock<IMovieRepository>().Verify(v => v.InsertMany(It.Is<List<Movie>>(l => l.Count == 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
{
|
{
|
||||||
private Profile _profile;
|
private Profile _profile;
|
||||||
private DelayProfile _delayProfile;
|
private DelayProfile _delayProfile;
|
||||||
private RemoteEpisode _remoteEpisode;
|
private RemoteMovie _remoteEpisode;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
@@ -38,12 +38,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
.With(d => d.PreferredProtocol = DownloadProtocol.Usenet)
|
.With(d => d.PreferredProtocol = DownloadProtocol.Usenet)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var series = Builder<Series>.CreateNew()
|
var series = Builder<Movie>.CreateNew()
|
||||||
.With(s => s.Profile = _profile)
|
.With(s => s.Profile = _profile)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
|
_remoteEpisode = Builder<RemoteMovie>.CreateNew()
|
||||||
.With(r => r.Series = series)
|
.With(r => r.Movie = series)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_profile.Items = new List<ProfileQualityItem>();
|
_profile.Items = new List<ProfileQualityItem>();
|
||||||
@@ -53,30 +53,30 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
|
|
||||||
_profile.Cutoff = Quality.WEBDL720p;
|
_profile.Cutoff = Quality.WEBDL720p;
|
||||||
|
|
||||||
_remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
|
_remoteEpisode.ParsedMovieInfo = new ParsedMovieInfo();
|
||||||
_remoteEpisode.Release = new ReleaseInfo();
|
_remoteEpisode.Release = new ReleaseInfo();
|
||||||
_remoteEpisode.Release.DownloadProtocol = DownloadProtocol.Usenet;
|
_remoteEpisode.Release.DownloadProtocol = DownloadProtocol.Usenet;
|
||||||
|
|
||||||
_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1).Build().ToList();
|
//_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1).Build().ToList();
|
||||||
_remoteEpisode.Episodes.First().EpisodeFileId = 0;
|
//_remoteEpisode.Episodes.First().EpisodeFileId = 0;
|
||||||
|
|
||||||
Mocker.GetMock<IDelayProfileService>()
|
//Mocker.GetMock<IDelayProfileService>()
|
||||||
.Setup(s => s.BestForTags(It.IsAny<HashSet<int>>()))
|
// .Setup(s => s.BestForTags(It.IsAny<HashSet<int>>()))
|
||||||
.Returns(_delayProfile);
|
// .Returns(_delayProfile);
|
||||||
|
|
||||||
Mocker.GetMock<IPendingReleaseService>()
|
//Mocker.GetMock<IPendingReleaseService>()
|
||||||
.Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<int>()))
|
// .Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<int>()))
|
||||||
.Returns(new List<RemoteEpisode>());
|
// .Returns(new List<RemoteEpisode>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenExistingFile(QualityModel quality)
|
private void GivenExistingFile(QualityModel quality)
|
||||||
{
|
{
|
||||||
_remoteEpisode.Episodes.First().EpisodeFileId = 1;
|
//_remoteEpisode.Episodes.First().EpisodeFileId = 1;
|
||||||
|
|
||||||
_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
|
//_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
|
||||||
{
|
// {
|
||||||
Quality = quality
|
// Quality = quality
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenUpgradeForExistingFile()
|
private void GivenUpgradeForExistingFile()
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download.Clients;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SerialNumberProviderFixture : CoreTest<SerialNumberProvider>
|
||||||
|
{
|
||||||
|
protected DownloadStationSettings _settings;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
protected void Setup()
|
||||||
|
{
|
||||||
|
_settings = new DownloadStationSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenValidResponse()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDSMInfoProxy>()
|
||||||
|
.Setup(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Returns("serial");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenInvalidResponse()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDSMInfoProxy>()
|
||||||
|
.Setup(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Throws(new DownloadClientException("Serial response invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_hashedserialnumber()
|
||||||
|
{
|
||||||
|
GivenValidResponse();
|
||||||
|
|
||||||
|
var serial = Subject.GetSerialNumber(_settings);
|
||||||
|
|
||||||
|
// This hash should remain the same for 'serial', so don't update the test if you change HashConverter, fix the code instead.
|
||||||
|
serial.Should().Be("50DE66B735D30738618568294742FCF1DFA52A47");
|
||||||
|
|
||||||
|
Mocker.GetMock<IDSMInfoProxy>()
|
||||||
|
.Verify(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_cache_serialnumber()
|
||||||
|
{
|
||||||
|
GivenValidResponse();
|
||||||
|
|
||||||
|
var serial1 = Subject.GetSerialNumber(_settings);
|
||||||
|
var serial2 = Subject.GetSerialNumber(_settings);
|
||||||
|
|
||||||
|
serial2.Should().Be(serial1);
|
||||||
|
|
||||||
|
Mocker.GetMock<IDSMInfoProxy>()
|
||||||
|
.Verify(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_throw_if_serial_number_unavailable()
|
||||||
|
{
|
||||||
|
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetSerialNumber(_settings));
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Download.Clients;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SharedFolderResolverFixture : CoreTest<SharedFolderResolver>
|
||||||
|
{
|
||||||
|
protected string _serialNumber = "SERIALNUMBER";
|
||||||
|
protected OsPath _sharedFolder;
|
||||||
|
protected OsPath _physicalPath;
|
||||||
|
protected DownloadStationSettings _settings;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
protected void Setup()
|
||||||
|
{
|
||||||
|
_sharedFolder = new OsPath("/myFolder");
|
||||||
|
_physicalPath = new OsPath("/mnt/sda1/folder");
|
||||||
|
_settings = new DownloadStationSettings();
|
||||||
|
|
||||||
|
Mocker.GetMock<IFileStationProxy>()
|
||||||
|
.Setup(f => f.GetSharedFolderMapping(It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Throws(new DownloadClientException("There is no shared folder"));
|
||||||
|
|
||||||
|
Mocker.GetMock<IFileStationProxy>()
|
||||||
|
.Setup(f => f.GetSharedFolderMapping(_sharedFolder.FullPath, It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Returns(new SharedFolderMapping(_sharedFolder.FullPath, _physicalPath.FullPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_throw_when_cannot_resolve_shared_folder()
|
||||||
|
{
|
||||||
|
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.RemapToFullPath(new OsPath("/unknownFolder"), _settings, _serialNumber));
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_valid_sharedfolder()
|
||||||
|
{
|
||||||
|
var mapping = Subject.RemapToFullPath(_sharedFolder, _settings, "abc");
|
||||||
|
|
||||||
|
mapping.Should().Be(_physicalPath);
|
||||||
|
|
||||||
|
Mocker.GetMock<IFileStationProxy>()
|
||||||
|
.Verify(f => f.GetSharedFolderMapping(It.IsAny<string>(), It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_cache_mapping()
|
||||||
|
{
|
||||||
|
Subject.RemapToFullPath(_sharedFolder, _settings, "abc");
|
||||||
|
Subject.RemapToFullPath(_sharedFolder, _settings, "abc");
|
||||||
|
|
||||||
|
Mocker.GetMock<IFileStationProxy>()
|
||||||
|
.Verify(f => f.GetSharedFolderMapping(It.IsAny<string>(), It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_remap_subfolder()
|
||||||
|
{
|
||||||
|
var mapping = Subject.RemapToFullPath(_sharedFolder + "sub", _settings, "abc");
|
||||||
|
|
||||||
|
mapping.Should().Be(_physicalPath + "sub");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+600
@@ -0,0 +1,600 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||||
|
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TorrentDownloadStationFixture : DownloadClientFixtureBase<TorrentDownloadStation>
|
||||||
|
{
|
||||||
|
protected DownloadStationSettings _settings;
|
||||||
|
|
||||||
|
protected DownloadStationTorrent _queued;
|
||||||
|
protected DownloadStationTorrent _downloading;
|
||||||
|
protected DownloadStationTorrent _failed;
|
||||||
|
protected DownloadStationTorrent _completed;
|
||||||
|
protected DownloadStationTorrent _seeding;
|
||||||
|
protected DownloadStationTorrent _magnet;
|
||||||
|
protected DownloadStationTorrent _singleFile;
|
||||||
|
protected DownloadStationTorrent _multipleFiles;
|
||||||
|
protected DownloadStationTorrent _singleFileCompleted;
|
||||||
|
protected DownloadStationTorrent _multipleFilesCompleted;
|
||||||
|
|
||||||
|
protected string _serialNumber = "SERIALNUMBER";
|
||||||
|
protected string _category = "sonarr";
|
||||||
|
protected string _tvDirectory = @"video/Series";
|
||||||
|
protected string _defaultDestination = "somepath";
|
||||||
|
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
|
||||||
|
|
||||||
|
protected Dictionary<string, object> _downloadStationConfigItems;
|
||||||
|
|
||||||
|
protected string DownloadURL => "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download";
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_settings = new DownloadStationSettings()
|
||||||
|
{
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
Port = 5000,
|
||||||
|
Username = "admin",
|
||||||
|
Password = "pass"
|
||||||
|
};
|
||||||
|
|
||||||
|
Subject.Definition = new DownloadClientDefinition();
|
||||||
|
Subject.Definition.Settings = _settings;
|
||||||
|
|
||||||
|
_queued = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id1",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Waiting,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "0"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_completed = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id2",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Finished,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "1000"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_seeding = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id2",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Seeding,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "1000"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_downloading = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id3",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Downloading,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "100"},
|
||||||
|
{ "speed_download", "50" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_failed = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id4",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Error,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "10"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_singleFile = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id5",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Seeding,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "a.mkv",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "1000"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_multipleFiles = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id6",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Seeding,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "1000"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_singleFileCompleted = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id6",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Finished,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "a.mkv",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "1000"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_multipleFilesCompleted = new DownloadStationTorrent()
|
||||||
|
{
|
||||||
|
Id = "id6",
|
||||||
|
Size = 1000,
|
||||||
|
Status = DownloadStationTaskStatus.Finished,
|
||||||
|
Type = DownloadStationTaskType.BT,
|
||||||
|
Username = "admin",
|
||||||
|
Title = "title",
|
||||||
|
Additional = new DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
Detail = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "destination","shared/folder" },
|
||||||
|
{ "uri", DownloadURL }
|
||||||
|
},
|
||||||
|
Transfer = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "size_downloaded", "1000"},
|
||||||
|
{ "speed_download", "0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ITorrentFileInfoReader>()
|
||||||
|
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
|
||||||
|
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpClient>()
|
||||||
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
|
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
|
||||||
|
|
||||||
|
_downloadStationConfigItems = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "default_destination", _defaultDestination },
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Returns(_downloadStationConfigItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GivenSharedFolder()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISharedFolderResolver>()
|
||||||
|
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
|
||||||
|
.Returns<OsPath, DownloadStationSettings, string>((path, setttings, serial) => _physicalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GivenSerialNumber()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISerialNumberProvider>()
|
||||||
|
.Setup(s => s.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Returns(_serialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GivenTvCategory()
|
||||||
|
{
|
||||||
|
_settings.TvCategory = _category;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GivenTvDirectory()
|
||||||
|
{
|
||||||
|
_settings.TvDirectory = _tvDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void GivenTorrents(List<DownloadStationTorrent> torrents)
|
||||||
|
{
|
||||||
|
if (torrents == null)
|
||||||
|
{
|
||||||
|
torrents = new List<DownloadStationTorrent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Setup(s => s.GetTorrents(It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Returns(torrents);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void PrepareClientToReturnQueuedItem()
|
||||||
|
{
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>
|
||||||
|
{
|
||||||
|
_queued
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GivenSuccessfulDownload()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHttpClient>()
|
||||||
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
|
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Callback(PrepareClientToReturnQueuedItem);
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Setup(s => s.AddTorrentFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
|
||||||
|
.Callback(PrepareClientToReturnQueuedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override RemoteEpisode CreateRemoteEpisode()
|
||||||
|
{
|
||||||
|
var episode = base.CreateRemoteEpisode();
|
||||||
|
|
||||||
|
episode.Release.DownloadUrl = DownloadURL;
|
||||||
|
|
||||||
|
return episode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int GivenAllKindOfTasks()
|
||||||
|
{
|
||||||
|
var tasks = new List<DownloadStationTorrent>() { _queued, _completed, _failed, _downloading, _seeding };
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Setup(d => d.GetTorrents(_settings))
|
||||||
|
.Returns(tasks);
|
||||||
|
|
||||||
|
return tasks.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Download_with_TvDirectory_should_force_directory()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenTvDirectory();
|
||||||
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
|
var remoteEpisode = CreateRemoteEpisode();
|
||||||
|
|
||||||
|
var id = Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Download_with_category_should_force_directory()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenTvCategory();
|
||||||
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
|
var remoteEpisode = CreateRemoteEpisode();
|
||||||
|
|
||||||
|
var id = Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
|
var remoteEpisode = CreateRemoteEpisode();
|
||||||
|
|
||||||
|
var id = Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_ignore_downloads_in_wrong_folder()
|
||||||
|
{
|
||||||
|
_settings.TvDirectory = @"/shared/folder/sub";
|
||||||
|
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent> { _completed });
|
||||||
|
|
||||||
|
Subject.GetItems().Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_throw_if_shared_folder_resolve_fails()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISharedFolderResolver>()
|
||||||
|
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
|
||||||
|
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||||
|
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenAllKindOfTasks();
|
||||||
|
|
||||||
|
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
|
||||||
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_throw_if_serial_number_unavailable()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISerialNumberProvider>()
|
||||||
|
.Setup(s => s.GetSerialNumber(_settings))
|
||||||
|
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||||
|
|
||||||
|
GivenSharedFolder();
|
||||||
|
GivenAllKindOfTasks();
|
||||||
|
|
||||||
|
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
|
||||||
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Download_should_throw_and_not_add_torrent_if_cannot_get_serial_number()
|
||||||
|
{
|
||||||
|
var remoteEpisode = CreateRemoteEpisode();
|
||||||
|
|
||||||
|
Mocker.GetMock<ISerialNumberProvider>()
|
||||||
|
.Setup(s => s.GetSerialNumber(_settings))
|
||||||
|
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||||
|
|
||||||
|
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadStationProxy>()
|
||||||
|
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_non_finished_torrent()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>() { _singleFile });
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().OutputPath.Should().Be(_physicalPath + _singleFile.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_non_finished_torrent()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>() { _multipleFiles });
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().OutputPath.Should().Be(_physicalPath + _multipleFiles.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_finished_torrent()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>() { _singleFileCompleted });
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().OutputPath.Should().Be(_physicalPath + _singleFileCompleted.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_finished_torrent()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>() { _multipleFilesCompleted });
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().OutputPath.Should().Be($"{_physicalPath}/{_multipleFiles.Title}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_not_map_outputpath_for_queued_or_downloading_torrents()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>
|
||||||
|
{
|
||||||
|
_queued, _downloading
|
||||||
|
});
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(2);
|
||||||
|
items.Should().OnlyContain(v => v.OutputPath.IsEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItems_should_map_outputpath_for_completed_or_failed_torrents()
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>
|
||||||
|
{
|
||||||
|
_completed, _failed, _seeding
|
||||||
|
});
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(3);
|
||||||
|
items.Should().OnlyContain(v => !v.OutputPath.IsEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Seeding, DownloadItemStatus.Completed, true)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)]
|
||||||
|
public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected)
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
_queued.Status = apiStatus;
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>() { _queued });
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().IsReadOnly.Should().Be(readOnlyExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Error, DownloadItemStatus.Failed)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Extracting, DownloadItemStatus.Downloading)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Finishing, DownloadItemStatus.Downloading)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.HashChecking, DownloadItemStatus.Downloading)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Paused, DownloadItemStatus.Paused)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Seeding, DownloadItemStatus.Completed)]
|
||||||
|
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued)]
|
||||||
|
public void GetItems_should_return_item_as_downloadItemStatus(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
|
||||||
|
{
|
||||||
|
GivenSerialNumber();
|
||||||
|
GivenSharedFolder();
|
||||||
|
|
||||||
|
_queued.Status = apiStatus;
|
||||||
|
|
||||||
|
GivenTorrents(new List<DownloadStationTorrent>() { _queued });
|
||||||
|
|
||||||
|
var items = Subject.GetItems();
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
|
||||||
|
items.First().Status.Should().Be(expectedItemStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-21
@@ -20,22 +20,19 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
public class RemoveGrabbedFixture : CoreTest<PendingReleaseService>
|
public class RemoveGrabbedFixture : CoreTest<PendingReleaseService>
|
||||||
{
|
{
|
||||||
private DownloadDecision _temporarilyRejected;
|
private DownloadDecision _temporarilyRejected;
|
||||||
private Series _series;
|
private Movie _series;
|
||||||
private Episode _episode;
|
private Episode _episode;
|
||||||
private Profile _profile;
|
private Profile _profile;
|
||||||
private ReleaseInfo _release;
|
private ReleaseInfo _release;
|
||||||
private ParsedEpisodeInfo _parsedEpisodeInfo;
|
private ParsedMovieInfo _parsedEpisodeInfo;
|
||||||
private RemoteEpisode _remoteEpisode;
|
private RemoteMovie _remoteEpisode;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_series = Builder<Series>.CreateNew()
|
_series = Builder<Movie>.CreateNew()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_episode = Builder<Episode>.CreateNew()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_profile = new Profile
|
_profile = new Profile
|
||||||
{
|
{
|
||||||
Name = "Test",
|
Name = "Test",
|
||||||
@@ -52,13 +49,13 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
|
|
||||||
_release = Builder<ReleaseInfo>.CreateNew().Build();
|
_release = Builder<ReleaseInfo>.CreateNew().Build();
|
||||||
|
|
||||||
_parsedEpisodeInfo = Builder<ParsedEpisodeInfo>.CreateNew().Build();
|
_parsedEpisodeInfo = Builder<ParsedMovieInfo>.CreateNew().Build();
|
||||||
_parsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
|
_parsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
|
||||||
|
|
||||||
_remoteEpisode = new RemoteEpisode();
|
_remoteEpisode = new RemoteMovie();
|
||||||
_remoteEpisode.Episodes = new List<Episode>{ _episode };
|
//_remoteEpisode.Episodes = new List<Episode>{ _episode };
|
||||||
_remoteEpisode.Series = _series;
|
_remoteEpisode.Movie = _series;
|
||||||
_remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo;
|
_remoteEpisode.ParsedMovieInfo = _parsedEpisodeInfo;
|
||||||
_remoteEpisode.Release = _release;
|
_remoteEpisode.Release = _release;
|
||||||
|
|
||||||
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
|
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||||
@@ -67,13 +64,13 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
.Setup(s => s.All())
|
.Setup(s => s.All())
|
||||||
.Returns(new List<PendingRelease>());
|
.Returns(new List<PendingRelease>());
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<IMovieService>()
|
||||||
.Setup(s => s.GetSeries(It.IsAny<int>()))
|
.Setup(s => s.GetMovie(It.IsAny<int>()))
|
||||||
.Returns(_series);
|
.Returns(_series);
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
//Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
// .Setup(s => s.GetMovie(It.IsAny<ParsedMovieInfo>(), _series.Title))
|
||||||
.Returns(new List<Episode> {_episode});
|
// .Returns(_episode);
|
||||||
|
|
||||||
Mocker.GetMock<IPrioritizeDownloadDecision>()
|
Mocker.GetMock<IPrioritizeDownloadDecision>()
|
||||||
.Setup(s => s.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>()))
|
.Setup(s => s.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>()))
|
||||||
@@ -89,7 +86,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
.All()
|
.All()
|
||||||
.With(h => h.SeriesId = _series.Id)
|
.With(h => h.SeriesId = _series.Id)
|
||||||
.With(h => h.Release = _release.JsonClone())
|
.With(h => h.Release = _release.JsonClone())
|
||||||
.With(h => h.ParsedEpisodeInfo = parsedEpisodeInfo)
|
.With(h => h.ParsedMovieInfo = _parsedEpisodeInfo)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
Mocker.GetMock<IPendingReleaseRepository>()
|
Mocker.GetMock<IPendingReleaseRepository>()
|
||||||
@@ -102,7 +99,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
{
|
{
|
||||||
GivenHeldRelease(_parsedEpisodeInfo.Quality);
|
GivenHeldRelease(_parsedEpisodeInfo.Quality);
|
||||||
|
|
||||||
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
|
Subject.Handle(new MovieGrabbedEvent(_remoteEpisode));
|
||||||
|
|
||||||
VerifyDelete();
|
VerifyDelete();
|
||||||
}
|
}
|
||||||
@@ -112,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
{
|
{
|
||||||
GivenHeldRelease(new QualityModel(Quality.SDTV));
|
GivenHeldRelease(new QualityModel(Quality.SDTV));
|
||||||
|
|
||||||
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
|
Subject.Handle(new MovieGrabbedEvent(_remoteEpisode));
|
||||||
|
|
||||||
VerifyDelete();
|
VerifyDelete();
|
||||||
}
|
}
|
||||||
@@ -122,7 +119,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
|||||||
{
|
{
|
||||||
GivenHeldRelease(new QualityModel(Quality.Bluray720p));
|
GivenHeldRelease(new QualityModel(Quality.Bluray720p));
|
||||||
|
|
||||||
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
|
Subject.Handle(new MovieGrabbedEvent(_remoteEpisode));
|
||||||
|
|
||||||
VerifyNoDelete();
|
VerifyNoDelete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,9 @@
|
|||||||
<Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixtureBase.cs" />
|
<Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixtureBase.cs" />
|
||||||
<Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" />
|
<Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" />
|
||||||
<Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" />
|
<Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" />
|
||||||
|
<Compile Include="Download\DownloadClientTests\DownloadStationTests\TorrentDownloadStationFixture.cs" />
|
||||||
|
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SerialNumberProviderFixture.cs" />
|
||||||
|
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SharedFolderResolverFixture.cs" />
|
||||||
<Compile Include="Download\DownloadServiceFixture.cs" />
|
<Compile Include="Download\DownloadServiceFixture.cs" />
|
||||||
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
|
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
|
||||||
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />
|
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />
|
||||||
@@ -381,6 +384,7 @@
|
|||||||
<Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" />
|
<Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" />
|
||||||
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
|
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
|
||||||
<Compile Include="XbmcVersionTests.cs" />
|
<Compile Include="XbmcVersionTests.cs" />
|
||||||
|
<Compile Include="BulkImport\AddMultiMoviesFixture.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
||||||
@@ -572,6 +576,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="ProviderTests\UpdateProviderTests\" />
|
<Folder Include="ProviderTests\UpdateProviderTests\" />
|
||||||
|
<Folder Include="BulkImport\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class AbsoluteEpisodeNumberParserFixture : CoreTest
|
public class AbsoluteEpisodeNumberParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)]
|
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class DailyEpisodeParserFixture : CoreTest
|
public class DailyEpisodeParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase("Conan 2011 04 18 Emma Roberts HDTV XviD BFF", "Conan", 2011, 04, 18)]
|
[TestCase("Conan 2011 04 18 Emma Roberts HDTV XviD BFF", "Conan", 2011, 04, 18)]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class MiniSeriesEpisodeParserFixture : CoreTest
|
public class MiniSeriesEpisodeParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase("The.Kennedys.Part.2.DSR.XviD-SYS", "The Kennedys", 2)]
|
[TestCase("The.Kennedys.Part.2.DSR.XviD-SYS", "The Kennedys", 2)]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class MultiEpisodeParserFixture : CoreTest
|
public class MultiEpisodeParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", "WEEDS", 3, new[] { 1, 2, 3, 4, 5, 6 })]
|
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", "WEEDS", 3, new[] { 1, 2, 3, 4, 5, 6 })]
|
||||||
|
|||||||
@@ -62,5 +62,24 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
Parser.Parser.ParseTitle(postTitle).SeriesTitle.Should().Be(title);
|
Parser.Parser.ParseTitle(postTitle).SeriesTitle.Should().Be(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("The.Man.from.U.N.C.L.E.2015.1080p.BluRay.x264-SPARKS", "The Man from U.N.C.L.E.")]
|
||||||
|
[TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", "1941")]
|
||||||
|
[TestCase("MY MOVIE (2016) [R][Action, Horror][720p.WEB-DL.AVC.8Bit.6ch.AC3].mkv", "MY MOVIE")]
|
||||||
|
[TestCase("R.I.P.D.2013.720p.BluRay.x264-SPARKS", "R.I.P.D.")]
|
||||||
|
[TestCase("V.H.S.2.2013.LIMITED.720p.BluRay.x264-GECKOS", "V.H.S. 2")]
|
||||||
|
[TestCase("This Is A Movie (1999) [IMDB #] <Genre, Genre, Genre> {ACTORS} !DIRECTOR +MORE_SILLY_STUFF_NO_ONE_NEEDS ?", "This Is A Movie")]
|
||||||
|
[TestCase("We Are the Best!.2013.720p.H264.mkv", "We Are the Best!")]
|
||||||
|
[TestCase("(500).Days.Of.Summer.(2009).DTS.1080p.BluRay.x264.NLsubs", "(500) Days Of Summer")]
|
||||||
|
public void should_parse_movie_title(string postTitle, string title)
|
||||||
|
{
|
||||||
|
Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", 1979)]
|
||||||
|
public void should_parse_movie_year(string postTitle, int year)
|
||||||
|
{
|
||||||
|
Parser.Parser.ParseMovieTitle(postTitle).Year.Should().Be(year);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using NzbDrone.Test.Common;
|
|||||||
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class GetEpisodesFixture : TestBase<ParsingService>
|
public class GetEpisodesFixture : TestBase<ParsingService>
|
||||||
{
|
{
|
||||||
private Series _series;
|
private Series _series;
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||||||
public class MapFixture : TestBase<ParsingService>
|
public class MapFixture : TestBase<ParsingService>
|
||||||
{
|
{
|
||||||
private Series _series;
|
private Series _series;
|
||||||
|
private Movie _movie;
|
||||||
|
private ParsedMovieInfo _parsedMovieInfo;
|
||||||
|
private ParsedMovieInfo _wrongYearInfo;
|
||||||
|
private ParsedMovieInfo _romanTitleInfo;
|
||||||
|
private ParsedMovieInfo _alternativeTitleInfo;
|
||||||
|
private MovieSearchCriteria _movieSearchCriteria;
|
||||||
private List<Episode> _episodes;
|
private List<Episode> _episodes;
|
||||||
private ParsedEpisodeInfo _parsedEpisodeInfo;
|
private ParsedEpisodeInfo _parsedEpisodeInfo;
|
||||||
private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria;
|
private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria;
|
||||||
@@ -30,6 +36,13 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||||||
.With(s => s.CleanTitle = "rock")
|
.With(s => s.CleanTitle = "rock")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
_movie = Builder<Movie>.CreateNew()
|
||||||
|
.With(m => m.Title = "Mission Impossible 3")
|
||||||
|
.With(m => m.CleanTitle = "missionimpossible3")
|
||||||
|
.With(m => m.Year = 2006)
|
||||||
|
.With(m => m.AlternativeTitles = new List<string> { "Mission Impossible 3: Same same" })
|
||||||
|
.Build();
|
||||||
|
|
||||||
_episodes = Builder<Episode>.CreateListOfSize(1)
|
_episodes = Builder<Episode>.CreateListOfSize(1)
|
||||||
.All()
|
.All()
|
||||||
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
|
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
|
||||||
@@ -43,6 +56,31 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||||||
EpisodeNumbers = new[] { 1 }
|
EpisodeNumbers = new[] { 1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_parsedMovieInfo = new ParsedMovieInfo
|
||||||
|
{
|
||||||
|
MovieTitle = _movie.Title,
|
||||||
|
Year = _movie.Year,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
_wrongYearInfo = new ParsedMovieInfo
|
||||||
|
{
|
||||||
|
MovieTitle = _movie.Title,
|
||||||
|
Year = 1900,
|
||||||
|
};
|
||||||
|
|
||||||
|
_alternativeTitleInfo = new ParsedMovieInfo
|
||||||
|
{
|
||||||
|
MovieTitle = _movie.AlternativeTitles.First(),
|
||||||
|
Year = _movie.Year,
|
||||||
|
};
|
||||||
|
|
||||||
|
_romanTitleInfo = new ParsedMovieInfo
|
||||||
|
{
|
||||||
|
MovieTitle = "Mission Impossible III",
|
||||||
|
Year = _movie.Year,
|
||||||
|
};
|
||||||
|
|
||||||
_singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
|
_singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
|
||||||
{
|
{
|
||||||
Series = _series,
|
Series = _series,
|
||||||
@@ -50,27 +88,18 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||||||
SeasonNumber = _episodes.First().SeasonNumber,
|
SeasonNumber = _episodes.First().SeasonNumber,
|
||||||
Episodes = _episodes
|
Episodes = _episodes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_movieSearchCriteria = new MovieSearchCriteria
|
||||||
|
{
|
||||||
|
Movie = _movie
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenMatchBySeriesTitle()
|
private void GivenMatchByMovieTitle()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<IMovieService>()
|
||||||
.Setup(s => s.FindByTitle(It.IsAny<string>()))
|
.Setup(s => s.FindByTitle(It.IsAny<string>()))
|
||||||
.Returns(_series);
|
.Returns(_movie);
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenMatchByTvdbId()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Setup(s => s.FindByTvdbId(It.IsAny<Int32>()))
|
|
||||||
.Returns(_series);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenMatchByTvRageId()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Setup(s => s.FindByTvRageId(It.IsAny<int>()))
|
|
||||||
.Returns(_series);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenParseResultSeriesDoesntMatchSearchCriteria()
|
private void GivenParseResultSeriesDoesntMatchSearchCriteria()
|
||||||
@@ -79,121 +108,45 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_lookup_series_by_name()
|
public void should_lookup_Movie_by_name()
|
||||||
{
|
{
|
||||||
GivenMatchBySeriesTitle();
|
GivenMatchByMovieTitle();
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
Subject.Map(_parsedMovieInfo, "", null);
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<IMovieService>()
|
||||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
|
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_use_tvdbid_when_series_title_lookup_fails()
|
public void should_use_search_criteria_movie_title()
|
||||||
{
|
{
|
||||||
GivenMatchByTvdbId();
|
GivenMatchByMovieTitle();
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
Subject.Map(_parsedMovieInfo, "", _movieSearchCriteria);
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTvdbId(It.IsAny<Int32>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_use_tvrageid_when_series_title_lookup_fails()
|
|
||||||
{
|
|
||||||
GivenMatchByTvRageId();
|
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, 0, _series.TvRageId);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_use_tvrageid_when_scene_naming_exception_exists()
|
|
||||||
{
|
|
||||||
GivenMatchByTvRageId();
|
|
||||||
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
|
||||||
.Setup(v => v.FindTvdbId(It.IsAny<string>()))
|
|
||||||
.Returns(10);
|
|
||||||
|
|
||||||
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Never());
|
|
||||||
|
|
||||||
result.Series.Should().BeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_use_search_criteria_series_title()
|
|
||||||
{
|
|
||||||
GivenMatchBySeriesTitle();
|
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<ISeriesService>()
|
||||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_FindByTitle_when_search_criteria_matching_fails()
|
public void should_not_match_with_wrong_year()
|
||||||
{
|
{
|
||||||
GivenParseResultSeriesDoesntMatchSearchCriteria();
|
GivenMatchByMovieTitle();
|
||||||
|
Subject.Map(_wrongYearInfo, "", _movieSearchCriteria).Movie.Should().BeNull();
|
||||||
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_FindByTvdbId_when_search_criteria_and_FindByTitle_matching_fails()
|
public void should_match_alternative_title()
|
||||||
{
|
{
|
||||||
GivenParseResultSeriesDoesntMatchSearchCriteria();
|
Subject.Map(_alternativeTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
|
||||||
|
}
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
|
[Test]
|
||||||
|
public void should_match_roman_title()
|
||||||
|
{
|
||||||
|
Subject.Map(_romanTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
|
||||||
|
}
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTvdbId(It.IsAny<Int32>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_FindByTvRageId_when_search_criteria_and_FindByTitle_matching_fails()
|
|
||||||
{
|
|
||||||
GivenParseResultSeriesDoesntMatchSearchCriteria();
|
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_use_tvdbid_matching_when_alias_is_found()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
|
||||||
.Setup(s => s.FindTvdbId(It.IsAny<string>()))
|
|
||||||
.Returns(_series.TvdbId);
|
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_use_tvrageid_match_from_search_criteria_when_title_match_fails()
|
|
||||||
{
|
|
||||||
GivenParseResultSeriesDoesntMatchSearchCriteria();
|
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]//Is this really necessary with movies? I dont think so
|
||||||
public class PathParserFixture : CoreTest
|
public class PathParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase(@"z:\tv shows\battlestar galactica (2003)\Season 3\S03E05 - Collaborators.mkv", 3, 5)]
|
[TestCase(@"z:\tv shows\battlestar galactica (2003)\Season 3\S03E05 - Collaborators.mkv", 3, 5)]
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
[TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)]
|
[TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)]
|
||||||
[TestCase("the_x-files.9x18.sunshine_days.ac3.ws_dvdrip_xvid-fov.avi", false)]
|
[TestCase("the_x-files.9x18.sunshine_days.ac3.ws_dvdrip_xvid-fov.avi", false)]
|
||||||
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", false)]
|
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", false)]
|
||||||
[TestCase("Hannibal.S01E05.576p.BluRay.DD5.1.x264-HiSD", false)]
|
|
||||||
[TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
|
[TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
|
||||||
[TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
|
[TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
|
||||||
[TestCase("[Doki] Clannad - 02 (848x480 XviD BD MP3) [95360783]", false)]
|
[TestCase("[Doki] Clannad - 02 (848x480 XviD BD MP3) [95360783]", false)]
|
||||||
@@ -215,6 +214,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);
|
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("Movie.Name.2004.576p.BDRip.x264-HANDJOB")]
|
||||||
|
[TestCase("Hannibal.S01E05.576p.BluRay.DD5.1.x264-HiSD")]
|
||||||
|
public void should_parse_bluray576p_quality(string title)
|
||||||
|
{
|
||||||
|
ParseAndVerifyQuality(title, Quality.Bluray576p, false);
|
||||||
|
}
|
||||||
|
|
||||||
//[TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
|
//[TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
|
||||||
//[TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)]
|
//[TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)]
|
||||||
//[TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
|
//[TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
|
||||||
@@ -275,6 +281,15 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension);
|
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "korsub")]
|
||||||
|
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "korsubs")]
|
||||||
|
[TestCase("Movie.Title.2016.1080p.DKSUB.WEBRip.x264.AAC2.0-RADARR", "dksub")]
|
||||||
|
[TestCase("Movie.Title.2016.1080p.DKSUBS.WEBRip.x264.AAC2.0-RADARR", "dksubs")]
|
||||||
|
public void should_parse_hardcoded_subs(string postTitle, string sub)
|
||||||
|
{
|
||||||
|
QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub);
|
||||||
|
}
|
||||||
|
|
||||||
private void ParseAndVerifyQuality(string title, Quality quality, bool proper)
|
private void ParseAndVerifyQuality(string title, Quality quality, bool proper)
|
||||||
{
|
{
|
||||||
var result = QualityParser.ParseQuality(title);
|
var result = QualityParser.ParseQuality(title);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class SeasonParserFixture : CoreTest
|
public class SeasonParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase("30.Rock.Season.04.HDTV.XviD-DIMENSION", "30 Rock", 4)]
|
[TestCase("30.Rock.Season.04.HDTV.XviD-DIMENSION", "30 Rock", 4)]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using NzbDrone.Core.Test.Framework;
|
|||||||
namespace NzbDrone.Core.Test.ParserTests
|
namespace NzbDrone.Core.Test.ParserTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class SeriesTitleInfoFixture : CoreTest
|
public class SeriesTitleInfoFixture : CoreTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Ignore("Series")]
|
||||||
public class SingleEpisodeParserFixture : CoreTest
|
public class SingleEpisodeParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
[TestCase("Sonny.With.a.Chance.S02E15", "Sonny With a Chance", 2, 15)]
|
[TestCase("Sonny.With.a.Chance.S02E15", "Sonny With a Chance", 2, 15)]
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
|
namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class AddSeriesFixture : CoreTest<SeriesService>
|
public class AddSeriesFixture : CoreTest<SeriesService>
|
||||||
{
|
{
|
||||||
private Series fakeSeries;
|
private Series fakeSeries;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
fakeSeries = Builder<Series>.CreateNew().Build();
|
fakeSeries = Builder<Series>.CreateNew().Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void series_added_event_should_have_proper_path()
|
public void series_added_event_should_have_proper_path()
|
||||||
{
|
{
|
||||||
fakeSeries.Path = null;
|
fakeSeries.Path = null;
|
||||||
fakeSeries.RootFolderPath = @"C:\Test\TV";
|
fakeSeries.RootFolderPath = @"C:\Test\TV";
|
||||||
|
|
||||||
Mocker.GetMock<IBuildFileNames>()
|
Mocker.GetMock<IBuildFileNames>()
|
||||||
.Setup(s => s.GetSeriesFolder(fakeSeries, null))
|
.Setup(s => s.GetSeriesFolder(fakeSeries, null))
|
||||||
.Returns(fakeSeries.Title);
|
.Returns(fakeSeries.Title);
|
||||||
|
|
||||||
var series = Subject.AddSeries(fakeSeries);
|
var series = Subject.AddSeries(fakeSeries);
|
||||||
|
|
||||||
series.Path.Should().NotBeNull();
|
series.Path.Should().NotBeNull();
|
||||||
|
|
||||||
VerifyEventPublished<SeriesAddedEvent>();
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(129)]
|
||||||
|
public class add_parsed_movie_info_to_pending_release : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("PendingReleases").AddColumn("ParsedMovieInfo").AsString().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(127)]
|
||||||
|
public class remove_wombles_kickass : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Delete.FromTable("Indexers").Row(new { Implementation = "Wombles" });
|
||||||
|
Delete.FromTable("Indexers").Row(new { Implementation = "KickassTorrents" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(131)]
|
||||||
|
public class make_parsed_episode_info_nullable : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("PendingReleases").AlterColumn("ParsedEpisodeInfo").AsString().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(132)]
|
||||||
|
public class rename_torrent_downloadstation : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Execute.Sql("UPDATE DownloadClients SET Implementation = 'TorrentDownloadStation' WHERE Implementation = 'DownloadStation';");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,19 +91,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
|||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Series.Id, episodeIds);
|
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Movie.Id);
|
||||||
|
|
||||||
if (oldest != null && oldest.Release.AgeMinutes > delay)
|
if (oldest != null && oldest.Release.AgeMinutes > delay)
|
||||||
{
|
{
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}*/
|
}
|
||||||
|
|
||||||
if (subject.Release.AgeMinutes < delay)
|
if (subject.Release.AgeMinutes < delay)
|
||||||
{
|
{
|
||||||
_logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol);
|
_logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol);
|
||||||
return Decision.Reject("Waiting for better quality release");
|
return Decision.Reject("Waiting for better quality release");
|
||||||
} //TODO: Update for movies!
|
}
|
||||||
|
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
@@ -158,14 +158,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
|||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeIds = subject.Episodes.Select(e => e.Id);
|
// var episodeIds = subject.Episodes.Select(e => e.Id);
|
||||||
|
|
||||||
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Series.Id, episodeIds);
|
//var oldest = _pendingReleaseService.OldestPendingRelease(subject.Series.Id, episodeIds);
|
||||||
|
|
||||||
if (oldest != null && oldest.Release.AgeMinutes > delay)
|
//if (oldest != null && oldest.Release.AgeMinutes > delay)
|
||||||
{
|
//{
|
||||||
return Decision.Accept();
|
// return Decision.Accept();
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (subject.Release.AgeMinutes < delay)
|
if (subject.Release.AgeMinutes < delay)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,23 +40,23 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
if (!Settings.SaveMagnetFiles)
|
if (!Settings.SaveMagnetFiles)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Blackhole does not support magnet links.");
|
throw new NotSupportedException("Blackhole does not support magnet links.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var title = remoteEpisode.Release.Title;
|
var title = remoteMovie.Release.Title;
|
||||||
|
|
||||||
title = FileNameBuilder.CleanFileName(title);
|
title = FileNameBuilder.CleanFileName(title);
|
||||||
|
|
||||||
@@ -73,9 +73,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
var title = remoteEpisode.Release.Title;
|
var title = remoteMovie.Release.Title;
|
||||||
|
|
||||||
title = FileNameBuilder.CleanFileName(title);
|
title = FileNameBuilder.CleanFileName(title);
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||||||
return actualHash.ToUpper();
|
return actualHash.ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
var actualHash = _proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
var actualHash = _proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||||
|
|
||||||
@@ -61,12 +61,12 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Name => "Deluge";
|
public override string Name => "Deluge";
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 8112;
|
Port = 8112;
|
||||||
Password = "deluge";
|
Password = "deluge";
|
||||||
MovieCategory = "movie-radarr";
|
MovieCategory = "radarr";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public enum DiskStationApi
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Auth,
|
||||||
|
DownloadStationInfo,
|
||||||
|
DownloadStationTask,
|
||||||
|
FileStationList,
|
||||||
|
DSMInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class DiskStationApiInfo
|
||||||
|
{
|
||||||
|
private string _path;
|
||||||
|
|
||||||
|
public int MaxVersion { get; set; }
|
||||||
|
|
||||||
|
public int MinVersion { get; set; }
|
||||||
|
|
||||||
|
public string Path
|
||||||
|
{
|
||||||
|
get { return _path; }
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_path = value.TrimStart(new char[] { '/', '\\' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class DownloadStationSettingsValidator : AbstractValidator<DownloadStationSettings>
|
||||||
|
{
|
||||||
|
public DownloadStationSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Host).ValidHost();
|
||||||
|
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
|
||||||
|
|
||||||
|
RuleFor(c => c.TvDirectory).Matches(@"^(?!/).+")
|
||||||
|
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
.WithMessage("Cannot start with /");
|
||||||
|
|
||||||
|
RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
|
||||||
|
|
||||||
|
RuleFor(c => c.TvCategory).Empty()
|
||||||
|
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
.WithMessage("Cannot use Category and Directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloadStationSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly DownloadStationSettingsValidator Validator = new DownloadStationSettingsValidator();
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
public string Host { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Username", Type = FieldType.Textbox)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||||
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]
|
||||||
|
public string TvDirectory { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
public bool UseSsl { get; set; }
|
||||||
|
|
||||||
|
public DownloadStationSettings()
|
||||||
|
{
|
||||||
|
this.Host = "127.0.0.1";
|
||||||
|
this.Port = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class DownloadStationTorrent
|
||||||
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public long Size { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public DownloadStationTaskType Type { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "status_extra")]
|
||||||
|
public Dictionary<string, string> StatusExtra { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public DownloadStationTaskStatus Status { get; set; }
|
||||||
|
|
||||||
|
public DownloadStationTorrentAdditional Additional { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return this.Title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DownloadStationTaskType
|
||||||
|
{
|
||||||
|
BT, NZB, http, ftp, eMule
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DownloadStationTaskStatus
|
||||||
|
{
|
||||||
|
Waiting,
|
||||||
|
Downloading,
|
||||||
|
Paused,
|
||||||
|
Finishing,
|
||||||
|
Finished,
|
||||||
|
HashChecking,
|
||||||
|
Seeding,
|
||||||
|
FileHostingWaiting,
|
||||||
|
Extracting,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DownloadStationPriority
|
||||||
|
{
|
||||||
|
Auto,
|
||||||
|
Low,
|
||||||
|
Normal,
|
||||||
|
High
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class DownloadStationTorrentAdditional
|
||||||
|
{
|
||||||
|
public Dictionary<string, string> Detail { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> Transfer { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("File")]
|
||||||
|
public List<DownloadStationTorrentFile> Files { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using static NzbDrone.Core.Download.Clients.DownloadStation.DownloadStationTorrent;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class DownloadStationTorrentFile
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public DownloadStationPriority Priority { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("size")]
|
||||||
|
public long TotalSize { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("size_downloaded")]
|
||||||
|
public long BytesDownloaded { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||||
|
{
|
||||||
|
public interface IDSMInfoProxy
|
||||||
|
{
|
||||||
|
string GetSerialNumber(DownloadStationSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DSMInfoProxy : DiskStationProxyBase, IDSMInfoProxy
|
||||||
|
{
|
||||||
|
public DSMInfoProxy(IHttpClient httpClient, Logger logger) :
|
||||||
|
base(httpClient, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSerialNumber(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>() {
|
||||||
|
{ "api", "SYNO.DSM.Info" },
|
||||||
|
{ "version", "2" },
|
||||||
|
{ "method", "getinfo" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = ProcessRequest<DSMInfoResponse>(DiskStationApi.DSMInfo, arguments, settings, "get serial number");
|
||||||
|
return response.Data.SerialNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||||
|
{
|
||||||
|
public abstract class DiskStationProxyBase
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<DiskStationApi, string> Resources;
|
||||||
|
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
protected readonly Logger _logger;
|
||||||
|
private bool _authenticated;
|
||||||
|
|
||||||
|
static DiskStationProxyBase()
|
||||||
|
{
|
||||||
|
Resources = new Dictionary<DiskStationApi, string>
|
||||||
|
{
|
||||||
|
{ DiskStationApi.Info, "query.cgi" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiskStationProxyBase(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected DiskStationResponse<object> ProcessRequest(DiskStationApi api,
|
||||||
|
Dictionary<string, object> arguments,
|
||||||
|
DownloadStationSettings settings,
|
||||||
|
string operation,
|
||||||
|
HttpMethod method = HttpMethod.GET)
|
||||||
|
{
|
||||||
|
return ProcessRequest<object>(api, arguments, settings, operation, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DiskStationResponse<T> ProcessRequest<T>(DiskStationApi api,
|
||||||
|
Dictionary<string, object> arguments,
|
||||||
|
DownloadStationSettings settings,
|
||||||
|
string operation,
|
||||||
|
HttpMethod method = HttpMethod.GET,
|
||||||
|
int retries = 0) where T : new()
|
||||||
|
{
|
||||||
|
if (retries == 5)
|
||||||
|
{
|
||||||
|
throw new DownloadClientException("Try to process same request more than 5 times");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_authenticated && api != DiskStationApi.Info && api != DiskStationApi.DSMInfo)
|
||||||
|
{
|
||||||
|
AuthenticateClient(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = BuildRequest(settings, api, arguments, method);
|
||||||
|
var response = _httpClient.Execute(request);
|
||||||
|
|
||||||
|
_logger.Debug("Trying to {0}", operation);
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
var responseContent = Json.Deserialize<DiskStationResponse<T>>(response.Content);
|
||||||
|
|
||||||
|
if (responseContent.Success)
|
||||||
|
{
|
||||||
|
return responseContent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (responseContent.Error.SessionError)
|
||||||
|
{
|
||||||
|
_authenticated = false;
|
||||||
|
return ProcessRequest<T>(api, arguments, settings, operation, method, retries++);
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = $"Failed to {operation}. Reason: {responseContent.Error.GetMessage(api)}";
|
||||||
|
_logger.Error(msg);
|
||||||
|
|
||||||
|
throw new DownloadClientException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new HttpException(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AuthenticateClient(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.API.Auth" },
|
||||||
|
{ "version", "1" },
|
||||||
|
{ "method", "login" },
|
||||||
|
{ "account", settings.Username },
|
||||||
|
{ "passwd", settings.Password },
|
||||||
|
{ "format", "cookie" },
|
||||||
|
{ "session", "DownloadStation" },
|
||||||
|
};
|
||||||
|
|
||||||
|
var authLoginRequest = BuildRequest(settings, DiskStationApi.Auth, arguments, HttpMethod.GET);
|
||||||
|
authLoginRequest.StoreResponseCookie = true;
|
||||||
|
|
||||||
|
var response = _httpClient.Execute(authLoginRequest);
|
||||||
|
|
||||||
|
var downloadStationResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content);
|
||||||
|
|
||||||
|
var authResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content);
|
||||||
|
|
||||||
|
_authenticated = authResponse.Success;
|
||||||
|
|
||||||
|
if (!_authenticated)
|
||||||
|
{
|
||||||
|
throw new DownloadClientAuthenticationException(downloadStationResponse.Error.GetMessage(DiskStationApi.Auth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequest BuildRequest(DownloadStationSettings settings, DiskStationApi api, Dictionary<string, object> arguments, HttpMethod method)
|
||||||
|
{
|
||||||
|
if (!Resources.ContainsKey(api))
|
||||||
|
{
|
||||||
|
GetApiVersion(settings, api);
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{Resources[api]}");
|
||||||
|
requestBuilder.Method = method;
|
||||||
|
requestBuilder.LogResponseContent = true;
|
||||||
|
requestBuilder.SuppressHttpError = true;
|
||||||
|
requestBuilder.AllowAutoRedirect = false;
|
||||||
|
|
||||||
|
if (requestBuilder.Method == HttpMethod.POST)
|
||||||
|
{
|
||||||
|
if (api == DiskStationApi.DownloadStationTask && arguments.ContainsKey("file"))
|
||||||
|
{
|
||||||
|
requestBuilder.Headers.ContentType = "multipart/form-data";
|
||||||
|
|
||||||
|
foreach (var arg in arguments)
|
||||||
|
{
|
||||||
|
if (arg.Key == "file")
|
||||||
|
{
|
||||||
|
Dictionary<string, object> file = (Dictionary<string, object>)arg.Value;
|
||||||
|
requestBuilder.AddFormUpload(arg.Key, file["name"].ToString(), (byte[])file["data"]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
requestBuilder.AddFormParameter(arg.Key, arg.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
requestBuilder.Headers.ContentType = "application/json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var arg in arguments)
|
||||||
|
{
|
||||||
|
requestBuilder.AddQueryParam(arg.Key, arg.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IEnumerable<int> GetApiVersion(DownloadStationSettings settings, DiskStationApi api)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.API.Info" },
|
||||||
|
{ "version", "1" },
|
||||||
|
{ "method", "query" },
|
||||||
|
{ "query", "SYNO.API.Auth, SYNO.DownloadStation.Info, SYNO.DownloadStation.Task, SYNO.FileStation.List, SYNO.DSM.Info" },
|
||||||
|
};
|
||||||
|
|
||||||
|
var infoResponse = ProcessRequest<DiskStationApiInfoResponse>(DiskStationApi.Info, arguments, settings, "Get api version");
|
||||||
|
|
||||||
|
//TODO: Refactor this into more elegant code
|
||||||
|
var infoResponeDSAuth = infoResponse.Data["SYNO.API.Auth"];
|
||||||
|
var infoResponeDSInfo = infoResponse.Data["SYNO.DownloadStation.Info"];
|
||||||
|
var infoResponeDSTask = infoResponse.Data["SYNO.DownloadStation.Task"];
|
||||||
|
var infoResponseFSList = infoResponse.Data["SYNO.FileStation.List"];
|
||||||
|
var infoResponseDSMInfo = infoResponse.Data["SYNO.DSM.Info"];
|
||||||
|
|
||||||
|
Resources[DiskStationApi.Auth] = infoResponeDSAuth.Path;
|
||||||
|
Resources[DiskStationApi.DownloadStationInfo] = infoResponeDSInfo.Path;
|
||||||
|
Resources[DiskStationApi.DownloadStationTask] = infoResponeDSTask.Path;
|
||||||
|
Resources[DiskStationApi.FileStationList] = infoResponseFSList.Path;
|
||||||
|
Resources[DiskStationApi.DSMInfo] = infoResponseDSMInfo.Path;
|
||||||
|
|
||||||
|
switch (api)
|
||||||
|
{
|
||||||
|
case DiskStationApi.Auth:
|
||||||
|
return Enumerable.Range(infoResponeDSAuth.MinVersion, infoResponeDSAuth.MaxVersion - infoResponeDSAuth.MinVersion + 1);
|
||||||
|
case DiskStationApi.DownloadStationInfo:
|
||||||
|
return Enumerable.Range(infoResponeDSInfo.MinVersion, infoResponeDSInfo.MaxVersion - infoResponeDSInfo.MinVersion + 1);
|
||||||
|
case DiskStationApi.DownloadStationTask:
|
||||||
|
return Enumerable.Range(infoResponeDSTask.MinVersion, infoResponeDSTask.MaxVersion - infoResponeDSTask.MinVersion + 1);
|
||||||
|
case DiskStationApi.FileStationList:
|
||||||
|
return Enumerable.Range(infoResponseFSList.MinVersion, infoResponseFSList.MaxVersion - infoResponseFSList.MinVersion + 1);
|
||||||
|
case DiskStationApi.DSMInfo:
|
||||||
|
return Enumerable.Range(infoResponseDSMInfo.MinVersion, infoResponseDSMInfo.MaxVersion - infoResponseDSMInfo.MinVersion + 1);
|
||||||
|
default:
|
||||||
|
throw new DownloadClientException("Api not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||||
|
{
|
||||||
|
public interface IDownloadStationProxy
|
||||||
|
{
|
||||||
|
IEnumerable<DownloadStationTorrent> GetTorrents(DownloadStationSettings settings);
|
||||||
|
Dictionary<string, object> GetConfig(DownloadStationSettings settings);
|
||||||
|
void RemoveTorrent(string downloadId, DownloadStationSettings settings);
|
||||||
|
void AddTorrentFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
|
||||||
|
void AddTorrentFromData(byte[] torrentData, string filename, string downloadDirectory, DownloadStationSettings settings);
|
||||||
|
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloadStationProxy : DiskStationProxyBase, IDownloadStationProxy
|
||||||
|
{
|
||||||
|
public DownloadStationProxy(IHttpClient httpClient, Logger logger)
|
||||||
|
: base(httpClient, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTorrentFromData(byte[] torrentData, string filename, string downloadDirectory, DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.DownloadStation.Task" },
|
||||||
|
{ "version", "2" },
|
||||||
|
{ "method", "create" }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (downloadDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
arguments.Add("destination", downloadDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.Add("file", new Dictionary<string, object>() { { "name", filename }, { "data", torrentData } });
|
||||||
|
|
||||||
|
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add torrent from data {filename}", HttpMethod.POST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.DownloadStation.Task" },
|
||||||
|
{ "version", "3" },
|
||||||
|
{ "method", "create" },
|
||||||
|
{ "uri", torrentUrl }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (downloadDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
arguments.Add("destination", downloadDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add torrent from url {torrentUrl}", HttpMethod.GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<DownloadStationTorrent> GetTorrents(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.DownloadStation.Task" },
|
||||||
|
{ "version", "1" },
|
||||||
|
{ "method", "list" },
|
||||||
|
{ "additional", "detail,transfer" }
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = ProcessRequest<DownloadStationTaskInfoResponse>(DiskStationApi.DownloadStationTask, arguments, settings, "get torrents");
|
||||||
|
|
||||||
|
return response.Data.Tasks.Where(t => t.Type == DownloadStationTaskType.BT);
|
||||||
|
}
|
||||||
|
catch (DownloadClientException e)
|
||||||
|
{
|
||||||
|
_logger.Error(e);
|
||||||
|
return new List<DownloadStationTorrent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> GetConfig(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.DownloadStation.Info" },
|
||||||
|
{ "version", "1" },
|
||||||
|
{ "method", "getconfig" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = ProcessRequest<Dictionary<string, object>>(DiskStationApi.DownloadStationInfo, arguments, settings, "get config");
|
||||||
|
|
||||||
|
return response.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveTorrent(string downloadId, DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.DownloadStation.Task" },
|
||||||
|
{ "version", "1" },
|
||||||
|
{ "method", "delete" },
|
||||||
|
{ "id", downloadId },
|
||||||
|
{ "force_complete", false }
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"remove item {downloadId}");
|
||||||
|
_logger.Trace("Item {0} removed from Download Station", downloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
return base.GetApiVersion(settings, DiskStationApi.DownloadStationInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||||
|
{
|
||||||
|
public interface IFileStationProxy
|
||||||
|
{
|
||||||
|
SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings);
|
||||||
|
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
|
||||||
|
FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileStationProxy : DiskStationProxyBase, IFileStationProxy
|
||||||
|
{
|
||||||
|
public FileStationProxy(IHttpClient httpClient, Logger logger)
|
||||||
|
: base(httpClient, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
return base.GetApiVersion(settings, DiskStationApi.FileStationList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var info = GetInfoFileOrDirectory(sharedFolder, settings);
|
||||||
|
|
||||||
|
var physicalPath = info.Additional["real_path"].ToString();
|
||||||
|
|
||||||
|
return new SharedFolderMapping(sharedFolder, physicalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var arguments = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "api", "SYNO.FileStation.List" },
|
||||||
|
{ "version", "2" },
|
||||||
|
{ "method", "getinfo" },
|
||||||
|
{ "path", new [] { path }.ToJson() },
|
||||||
|
{ "additional", $"[\"real_path\"]" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = ProcessRequest<FileStationListResponse>(DiskStationApi.FileStationList, arguments, settings, $"get info of {path}");
|
||||||
|
|
||||||
|
return response.Data.Files.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class DSMInfoResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("serial")]
|
||||||
|
public string SerialNumber { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class DiskStationAuthResponse
|
||||||
|
{
|
||||||
|
public string SId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class DiskStationError
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<int, string> CommonMessages;
|
||||||
|
private static readonly Dictionary<int, string> AuthMessages;
|
||||||
|
private static readonly Dictionary<int, string> DownloadStationTaskMessages;
|
||||||
|
private static readonly Dictionary<int, string> FileStationMessages;
|
||||||
|
|
||||||
|
static DiskStationError()
|
||||||
|
{
|
||||||
|
CommonMessages = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
{ 100, "Unknown error" },
|
||||||
|
{ 101, "Invalid parameter" },
|
||||||
|
{ 102, "The requested API does not exist" },
|
||||||
|
{ 103, "The requested method does not exist" },
|
||||||
|
{ 104, "The requested version does not support the functionality" },
|
||||||
|
{ 105, "The logged in session does not have permission" },
|
||||||
|
{ 106, "Session timeout" },
|
||||||
|
{ 107, "Session interrupted by duplicate login" }
|
||||||
|
};
|
||||||
|
|
||||||
|
AuthMessages = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
{ 400, "No such account or incorrect password" },
|
||||||
|
{ 401, "Account disabled" },
|
||||||
|
{ 402, "Permission denied" },
|
||||||
|
{ 403, "2-step verification code required" },
|
||||||
|
{ 404, "Failed to authenticate 2-step verification code" }
|
||||||
|
};
|
||||||
|
|
||||||
|
DownloadStationTaskMessages = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
{ 400, "File upload failed" },
|
||||||
|
{ 401, "Max number of tasks reached" },
|
||||||
|
{ 402, "Destination denied" },
|
||||||
|
{ 403, "Destination does not exist" },
|
||||||
|
{ 404, "Invalid task id" },
|
||||||
|
{ 405, "Invalid task action" },
|
||||||
|
{ 406, "No default destination" },
|
||||||
|
{ 407, "Set destination failed" },
|
||||||
|
{ 408, "File does not exist" }
|
||||||
|
};
|
||||||
|
|
||||||
|
FileStationMessages = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
{ 400, "Invalid parameter of file operation" },
|
||||||
|
{ 401, "Unknown error of file operation" },
|
||||||
|
{ 402, "System is too busy" },
|
||||||
|
{ 403, "Invalid user does this file operation" },
|
||||||
|
{ 404, "Invalid group does this file operation" },
|
||||||
|
{ 405, "Invalid user and group does this file operation" },
|
||||||
|
{ 406, "Can’t get user/group information from the account server" },
|
||||||
|
{ 407, "Operation not permitted" },
|
||||||
|
{ 408, "No such file or directory" },
|
||||||
|
{ 409, "Non-supported file system" },
|
||||||
|
{ 410, "Failed to connect internet-based file system (ex: CIFS)" },
|
||||||
|
{ 411, "Read-only file system" },
|
||||||
|
{ 412, "Filename too long in the non-encrypted file system" },
|
||||||
|
{ 413, "Filename too long in the encrypted file system" },
|
||||||
|
{ 414, "File already exists" },
|
||||||
|
{ 415, "Disk quota exceeded" },
|
||||||
|
{ 416, "No space left on device" },
|
||||||
|
{ 417, "Input/output error" },
|
||||||
|
{ 418, "Illegal name or path" },
|
||||||
|
{ 419, "Illegal file name" },
|
||||||
|
{ 420, "Illegal file name on FAT file system" },
|
||||||
|
{ 421, "Device or resource busy" },
|
||||||
|
{ 599, "No such task of the file operation" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Code { get; set; }
|
||||||
|
|
||||||
|
public bool SessionError => Code == 105 || Code == 106 || Code == 107;
|
||||||
|
|
||||||
|
public string GetMessage(DiskStationApi api)
|
||||||
|
{
|
||||||
|
if (api == DiskStationApi.Auth && AuthMessages.ContainsKey(Code))
|
||||||
|
{
|
||||||
|
return AuthMessages[Code];
|
||||||
|
}
|
||||||
|
if (api == DiskStationApi.DownloadStationTask && DownloadStationTaskMessages.ContainsKey(Code))
|
||||||
|
{
|
||||||
|
return DownloadStationTaskMessages[Code];
|
||||||
|
}
|
||||||
|
if (api == DiskStationApi.FileStationList && FileStationMessages.ContainsKey(Code))
|
||||||
|
{
|
||||||
|
return FileStationMessages[Code];
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommonMessages[Code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class DiskStationApiInfoResponse : Dictionary<string, DiskStationApiInfo>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class DiskStationResponse<T> where T:new()
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
public DiskStationError Error { get; set; }
|
||||||
|
|
||||||
|
public T Data { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class DownloadStationTaskInfoResponse
|
||||||
|
{
|
||||||
|
public int Offset { get; set; }
|
||||||
|
public List<DownloadStationTorrent> Tasks {get;set;}
|
||||||
|
public int Total { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class FileStationListFileInfoResponse
|
||||||
|
{
|
||||||
|
public bool IsDir { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public Dictionary <string, object> Additional { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
|
||||||
|
{
|
||||||
|
public class FileStationListResponse
|
||||||
|
{
|
||||||
|
public List<FileStationListFileInfoResponse> Files { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Crypto;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public interface ISerialNumberProvider
|
||||||
|
{
|
||||||
|
string GetSerialNumber(DownloadStationSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerialNumberProvider : ISerialNumberProvider
|
||||||
|
{
|
||||||
|
private readonly IDSMInfoProxy _proxy;
|
||||||
|
private ICached<string> _cache;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public SerialNumberProvider(ICacheManager cacheManager,
|
||||||
|
IDSMInfoProxy proxy,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
_cache = cacheManager.GetCache<string>(GetType());
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSerialNumber(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _cache.Get(settings.Host, () => GetHashedSerialNumber(settings), TimeSpan.FromMinutes(5));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Could not get the serial number from Download Station {0}:{1}", settings.Host, settings.Port);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetHashedSerialNumber(DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
var serialNumber = _proxy.GetSerialNumber(settings);
|
||||||
|
return HashConverter.GetHash(serialNumber).ToHexString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class SharedFolderMapping
|
||||||
|
{
|
||||||
|
public OsPath PhysicalPath { get; private set; }
|
||||||
|
public OsPath SharedFolder { get; private set; }
|
||||||
|
|
||||||
|
public SharedFolderMapping(string sharedFolder, string physicalPath)
|
||||||
|
{
|
||||||
|
SharedFolder = new OsPath(sharedFolder);
|
||||||
|
PhysicalPath = new OsPath(physicalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{SharedFolder} -> {PhysicalPath}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public interface ISharedFolderResolver
|
||||||
|
{
|
||||||
|
OsPath RemapToFullPath(OsPath sharedFolderPath, DownloadStationSettings settings, string serialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SharedFolderResolver : ISharedFolderResolver
|
||||||
|
{
|
||||||
|
private readonly IFileStationProxy _proxy;
|
||||||
|
private ICached<SharedFolderMapping> _cache;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public SharedFolderResolver(ICacheManager cacheManager,
|
||||||
|
IFileStationProxy proxy,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
_cache = cacheManager.GetCache<SharedFolderMapping>(GetType());
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedFolderMapping GetPhysicalPath(OsPath sharedFolder, DownloadStationSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _proxy.GetSharedFolderMapping(sharedFolder.FullPath, settings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Failed to get shared folder {0} from Disk Station {1}:{2}", sharedFolder, settings.Host, settings.Port);
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OsPath RemapToFullPath(OsPath sharedFolderPath, DownloadStationSettings settings, string serialNumber)
|
||||||
|
{
|
||||||
|
var index = sharedFolderPath.FullPath.IndexOf('/', 1);
|
||||||
|
var sharedFolder = index == -1 ? sharedFolderPath : new OsPath(sharedFolderPath.FullPath.Substring(0, index));
|
||||||
|
|
||||||
|
var mapping = _cache.Get($"{serialNumber}:{sharedFolder}", () => GetPhysicalPath(sharedFolder, settings), TimeSpan.FromHours(1));
|
||||||
|
|
||||||
|
var fullPath = mapping.PhysicalPath + (sharedFolderPath - mapping.SharedFolder);
|
||||||
|
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||||
|
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||||
|
{
|
||||||
|
public class TorrentDownloadStation : TorrentClientBase<DownloadStationSettings>
|
||||||
|
{
|
||||||
|
protected readonly IDownloadStationProxy _proxy;
|
||||||
|
protected readonly ISharedFolderResolver _sharedFolderResolver;
|
||||||
|
protected readonly ISerialNumberProvider _serialNumberProvider;
|
||||||
|
protected readonly IFileStationProxy _fileStationProxy;
|
||||||
|
|
||||||
|
public TorrentDownloadStation(IDownloadStationProxy proxy,
|
||||||
|
ITorrentFileInfoReader torrentFileInfoReader,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IConfigService configService,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
IRemotePathMappingService remotePathMappingService,
|
||||||
|
Logger logger,
|
||||||
|
ICacheManager cacheManager,
|
||||||
|
ISharedFolderResolver sharedFolderResolver,
|
||||||
|
ISerialNumberProvider serialNumberProvider,
|
||||||
|
IFileStationProxy fileStationProxy)
|
||||||
|
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
_sharedFolderResolver = sharedFolderResolver;
|
||||||
|
_serialNumberProvider = serialNumberProvider;
|
||||||
|
_fileStationProxy = fileStationProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Download Station";
|
||||||
|
|
||||||
|
public override IEnumerable<DownloadClientItem> GetItems()
|
||||||
|
{
|
||||||
|
var torrents = _proxy.GetTorrents(Settings);
|
||||||
|
var serialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||||
|
|
||||||
|
var items = new List<DownloadClientItem>();
|
||||||
|
|
||||||
|
foreach (var torrent in torrents)
|
||||||
|
{
|
||||||
|
var outputPath = new OsPath($"/{torrent.Additional.Detail["destination"]}");
|
||||||
|
|
||||||
|
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
if (!new OsPath($"/{Settings.TvDirectory}").Contains(outputPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var directories = outputPath.FullPath.Split('\\', '/');
|
||||||
|
if (!directories.Contains(Settings.TvCategory))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = new DownloadClientItem()
|
||||||
|
{
|
||||||
|
Category = Settings.TvCategory,
|
||||||
|
DownloadClient = Definition.Name,
|
||||||
|
DownloadId = CreateDownloadId(torrent.Id, serialNumber),
|
||||||
|
Title = torrent.Title,
|
||||||
|
TotalSize = torrent.Size,
|
||||||
|
RemainingSize = GetRemainingSize(torrent),
|
||||||
|
RemainingTime = GetRemainingTime(torrent),
|
||||||
|
Status = GetStatus(torrent),
|
||||||
|
Message = GetMessage(torrent),
|
||||||
|
IsReadOnly = !IsFinished(torrent)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.Status == DownloadItemStatus.Completed || item.Status == DownloadItemStatus.Failed)
|
||||||
|
{
|
||||||
|
item.OutputPath = GetOutputPath(outputPath, torrent, serialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DownloadClientStatus GetStatus()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = GetDownloadDirectory();
|
||||||
|
|
||||||
|
return new DownloadClientStatus
|
||||||
|
{
|
||||||
|
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
|
||||||
|
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(path)) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (DownloadClientException e)
|
||||||
|
{
|
||||||
|
_logger.Debug(e, "Failed to get config from Download Station");
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemoveItem(string downloadId, bool deleteData)
|
||||||
|
{
|
||||||
|
if (deleteData)
|
||||||
|
{
|
||||||
|
DeleteItemData(downloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_proxy.RemoveTorrent(ParseDownloadId(downloadId), Settings);
|
||||||
|
_logger.Debug("{0} removed correctly", downloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTorrent torrent, string serialNumber)
|
||||||
|
{
|
||||||
|
var fullPath = _sharedFolderResolver.RemapToFullPath(outputPath, Settings, serialNumber);
|
||||||
|
|
||||||
|
var remotePath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, fullPath);
|
||||||
|
|
||||||
|
var finalPath = remotePath + torrent.Title;
|
||||||
|
|
||||||
|
return finalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
|
{
|
||||||
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
|
{
|
||||||
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink)
|
||||||
|
{
|
||||||
|
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||||
|
|
||||||
|
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
|
||||||
|
|
||||||
|
var item = _proxy.GetTorrents(Settings).SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink);
|
||||||
|
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
_logger.Debug("{0} added correctly", remoteEpisode);
|
||||||
|
return CreateDownloadId(item.Id, hashedSerialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("No such task {0} in Download Station", magnetLink);
|
||||||
|
|
||||||
|
throw new DownloadClientException("Failed to add magnet task to Download Station");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
|
{
|
||||||
|
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||||
|
|
||||||
|
_proxy.AddTorrentFromData(fileContent, filename, GetDownloadDirectory(), Settings);
|
||||||
|
|
||||||
|
var items = _proxy.GetTorrents(Settings).Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename));
|
||||||
|
|
||||||
|
var item = items.SingleOrDefault();
|
||||||
|
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
_logger.Debug("{0} added correctly", remoteEpisode);
|
||||||
|
return CreateDownloadId(item.Id, hashedSerialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("No such task {0} in Download Station", filename);
|
||||||
|
|
||||||
|
throw new DownloadClientException("Failed to add torrent task to Download Station");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Test(List<ValidationFailure> failures)
|
||||||
|
{
|
||||||
|
failures.AddIfNotNull(TestConnection());
|
||||||
|
if (failures.Any()) return;
|
||||||
|
failures.AddIfNotNull(TestOutputPath());
|
||||||
|
if (failures.Any()) return;
|
||||||
|
failures.AddIfNotNull(TestGetTorrents());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool IsFinished(DownloadStationTorrent torrent)
|
||||||
|
{
|
||||||
|
return torrent.Status == DownloadStationTaskStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string GetMessage(DownloadStationTorrent torrent)
|
||||||
|
{
|
||||||
|
if (torrent.StatusExtra != null)
|
||||||
|
{
|
||||||
|
if (torrent.Status == DownloadStationTaskStatus.Extracting)
|
||||||
|
{
|
||||||
|
return $"Extracting: {int.Parse(torrent.StatusExtra["unzip_progress"])}%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.Status == DownloadStationTaskStatus.Error)
|
||||||
|
{
|
||||||
|
return torrent.StatusExtra["error_detail"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DownloadItemStatus GetStatus(DownloadStationTorrent torrent)
|
||||||
|
{
|
||||||
|
switch (torrent.Status)
|
||||||
|
{
|
||||||
|
case DownloadStationTaskStatus.Waiting:
|
||||||
|
return torrent.Size == 0 || GetRemainingSize(torrent) > 0 ? DownloadItemStatus.Queued : DownloadItemStatus.Completed;
|
||||||
|
case DownloadStationTaskStatus.Paused:
|
||||||
|
return DownloadItemStatus.Paused;
|
||||||
|
case DownloadStationTaskStatus.Finished:
|
||||||
|
case DownloadStationTaskStatus.Seeding:
|
||||||
|
return DownloadItemStatus.Completed;
|
||||||
|
case DownloadStationTaskStatus.Error:
|
||||||
|
return DownloadItemStatus.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DownloadItemStatus.Downloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long GetRemainingSize(DownloadStationTorrent torrent)
|
||||||
|
{
|
||||||
|
var downloadedString = torrent.Additional.Transfer["size_downloaded"];
|
||||||
|
long downloadedSize;
|
||||||
|
|
||||||
|
if (downloadedString.IsNullOrWhiteSpace() || !long.TryParse(downloadedString, out downloadedSize))
|
||||||
|
{
|
||||||
|
_logger.Debug("Torrent {0} has invalid size_downloaded: {1}", torrent.Title, downloadedString);
|
||||||
|
downloadedSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return torrent.Size - Math.Max(0, downloadedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TimeSpan? GetRemainingTime(DownloadStationTorrent torrent)
|
||||||
|
{
|
||||||
|
var speedString = torrent.Additional.Transfer["speed_download"];
|
||||||
|
long downloadSpeed;
|
||||||
|
|
||||||
|
if (speedString.IsNullOrWhiteSpace() || !long.TryParse(speedString, out downloadSpeed))
|
||||||
|
{
|
||||||
|
_logger.Debug("Torrent {0} has invalid speed_download: {1}", torrent.Title, speedString);
|
||||||
|
downloadSpeed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadSpeed <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remainingSize = GetRemainingSize(torrent);
|
||||||
|
|
||||||
|
return TimeSpan.FromSeconds(remainingSize / downloadSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ValidationFailure TestOutputPath()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{GetDownloadDirectory()}", Settings);
|
||||||
|
|
||||||
|
if (!folderInfo.IsDir || folderInfo.Additional == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"{folderInfo.Path} is not a shared folder or it doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationFailure(string.Empty, $"Failed to get output path: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ValidationFailure TestConnection()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ValidateVersion();
|
||||||
|
}
|
||||||
|
catch (DownloadClientAuthenticationException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, ex.Message);
|
||||||
|
return new NzbDroneValidationFailure("Username", "Authentication failure")
|
||||||
|
{
|
||||||
|
DetailedDescription = $"Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex);
|
||||||
|
|
||||||
|
if (ex.Status == WebExceptionStatus.ConnectFailure)
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationFailure("Host", "Unable to connect")
|
||||||
|
{
|
||||||
|
DetailedDescription = "Please verify the hostname and port."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex);
|
||||||
|
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ValidationFailure ValidateVersion()
|
||||||
|
{
|
||||||
|
var versionRange = _proxy.GetApiVersion(Settings);
|
||||||
|
|
||||||
|
_logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max());
|
||||||
|
|
||||||
|
if (!versionRange.Contains(2))
|
||||||
|
{
|
||||||
|
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ValidationFailure TestGetTorrents()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_proxy.GetTorrents(Settings);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationFailure(string.Empty, $"Failed to get the list of torrents: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string ParseDownloadId(string id)
|
||||||
|
{
|
||||||
|
return id.Split(':')[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string CreateDownloadId(string id, string hashedSerialNumber)
|
||||||
|
{
|
||||||
|
return $"{hashedSerialNumber}:{id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string GetDefaultDir()
|
||||||
|
{
|
||||||
|
var config = _proxy.GetConfig(Settings);
|
||||||
|
|
||||||
|
var path = config["default_destination"] as string;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string GetDownloadDirectory()
|
||||||
|
{
|
||||||
|
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return Settings.TvDirectory.TrimStart('/');
|
||||||
|
}
|
||||||
|
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var destDir = GetDefaultDir();
|
||||||
|
|
||||||
|
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -151,22 +151,22 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
_proxy.AddTorrentUri(Settings, magnetLink);
|
_proxy.AddTorrentUri(Settings, magnetLink);
|
||||||
|
|
||||||
return hash.ToUpper();
|
return hash.ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
return _proxy.AddTorrentFile(Settings, fileContent).ToUpper();
|
return _proxy.AddTorrentFile(Settings, fileContent).ToUpper();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,16 +31,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
|||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
||||||
{
|
{
|
||||||
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
|
|
||||||
var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings);
|
|
||||||
|
|
||||||
if (response == null)
|
|
||||||
{
|
|
||||||
throw new DownloadClientException("Failed to add nzb {0}", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
|||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 4321;
|
Port = 4321;
|
||||||
TvCategory = "TV Shows";
|
TvCategory = "Movies";
|
||||||
RecentTvPriority = (int)NzbVortexPriority.Normal;
|
RecentTvPriority = (int)NzbVortexPriority.Normal;
|
||||||
OlderTvPriority = (int)NzbVortexPriority.Normal;
|
OlderTvPriority = (int)NzbVortexPriority.Normal;
|
||||||
}
|
}
|
||||||
@@ -46,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
|||||||
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing releases that aired within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing releases that aired over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
|
|||||||
@@ -31,20 +31,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
|||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
||||||
{
|
{
|
||||||
var category = Settings.TvCategory;
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
|
|
||||||
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
|
|
||||||
|
|
||||||
var addpaused = Settings.AddPaused;
|
|
||||||
|
|
||||||
var response = _proxy.DownloadNzb(fileContents, filename, category, priority, addpaused, Settings);
|
|
||||||
|
|
||||||
if (response == null)
|
|
||||||
{
|
|
||||||
throw new DownloadClientException("Failed to add nzb {0}", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
|||||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing releases that aired within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing releases that aired over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
|||||||
@@ -34,36 +34,15 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
|||||||
|
|
||||||
public override string Download(RemoteEpisode remoteEpisode)
|
public override string Download(RemoteEpisode remoteEpisode)
|
||||||
{
|
{
|
||||||
var url = remoteEpisode.Release.DownloadUrl;
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
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 override string Download(RemoteMovie remoteEpisode)
|
public override string Download(RemoteMovie remoteMovie)
|
||||||
{
|
{
|
||||||
var url = remoteEpisode.Release.DownloadUrl;
|
var url = remoteMovie.Release.DownloadUrl;
|
||||||
var title = remoteEpisode.Release.Title;
|
var title = remoteMovie.Release.Title;
|
||||||
|
|
||||||
if (remoteEpisode.ParsedEpisodeInfo.FullSeason)
|
if (remoteMovie.ParsedEpisodeInfo.FullSeason)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Full season releases are not supported with Pneumatic.");
|
throw new NotSupportedException("Full season releases are not supported with Pneumatic.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, Byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, Byte[] fileContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 9091;
|
Port = 9091;
|
||||||
MovieCategory = "movie-radarr";
|
MovieCategory = "radarr";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
|||||||
@@ -34,17 +34,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
||||||
{
|
{
|
||||||
var category = Settings.TvCategory;
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
|
|
||||||
|
|
||||||
var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings);
|
|
||||||
|
|
||||||
if (response != null && response.Ids.Any())
|
|
||||||
{
|
|
||||||
return response.Ids.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 8080;
|
Port = 8080;
|
||||||
TvCategory = "tv";
|
TvCategory = "movies";
|
||||||
RecentTvPriority = (int)SabnzbdPriority.Default;
|
RecentTvPriority = (int)SabnzbdPriority.Default;
|
||||||
OlderTvPriority = (int)SabnzbdPriority.Default;
|
OlderTvPriority = (int)SabnzbdPriority.Default;
|
||||||
}
|
}
|
||||||
@@ -61,10 +61,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing releases that aired within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing releases that aired over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
|||||||
@@ -137,12 +137,12 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 9091;
|
Port = 9091;
|
||||||
UrlBase = "/transmission/";
|
UrlBase = "/transmission/";
|
||||||
|
MovieCategory = "radarr";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Episodes are not working with Radarr");
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||||
@@ -75,8 +75,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
|||||||
{
|
{
|
||||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||||
|
|
||||||
var tries = 2;
|
var tries = 5;
|
||||||
var retryDelay = 100;
|
var retryDelay = 200;
|
||||||
if (WaitForTorrent(hash, tries, retryDelay))
|
if (WaitForTorrent(hash, tries, retryDelay))
|
||||||
{
|
{
|
||||||
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
|
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
|
||||||
|
|||||||
@@ -38,37 +38,15 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
|||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
_proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings);
|
|
||||||
|
|
||||||
var isRecentEpisode = remoteEpisode.IsRecentEpisode();
|
|
||||||
|
|
||||||
if (isRecentEpisode && Settings.RecentTvPriority == (int)UTorrentPriority.First ||
|
|
||||||
!isRecentEpisode && Settings.OlderTvPriority == (int)UTorrentPriority.First)
|
|
||||||
{
|
|
||||||
_proxy.MoveTorrentToTopInQueue(hash, Settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
throw new DownloadClientException("Episodes are not working with Radarr");
|
||||||
_proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings);
|
|
||||||
|
|
||||||
var isRecentEpisode = remoteEpisode.IsRecentEpisode();
|
|
||||||
|
|
||||||
if (isRecentEpisode && Settings.RecentTvPriority == (int)UTorrentPriority.First ||
|
|
||||||
!isRecentEpisode && Settings.OlderTvPriority == (int)UTorrentPriority.First)
|
|
||||||
{
|
|
||||||
_proxy.MoveTorrentToTopInQueue(hash, Settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||||
{
|
{
|
||||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||||
_proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings);
|
_proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings);
|
||||||
@@ -84,7 +62,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent)
|
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||||
{
|
{
|
||||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||||
_proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings);
|
_proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings);
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
|||||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing releases that aired within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing releases that aired over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
||||||
|
public ParsedMovieInfo ParsedMovieInfo { get; set; }
|
||||||
public ReleaseInfo Release { get; set; }
|
public ReleaseInfo Release { get; set; }
|
||||||
|
|
||||||
//Not persisted
|
//Not persisted
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
{
|
{
|
||||||
void DeleteBySeriesId(int seriesId);
|
void DeleteBySeriesId(int seriesId);
|
||||||
List<PendingRelease> AllBySeriesId(int seriesId);
|
List<PendingRelease> AllBySeriesId(int seriesId);
|
||||||
|
void DeleteByMovieId(int movieId);
|
||||||
|
List<PendingRelease> AllByMovieId(int movieId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PendingReleaseRepository : BasicRepository<PendingRelease>, IPendingReleaseRepository
|
public class PendingReleaseRepository : BasicRepository<PendingRelease>, IPendingReleaseRepository
|
||||||
@@ -26,5 +28,15 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
{
|
{
|
||||||
return Query.Where(p => p.SeriesId == seriesId);
|
return Query.Where(p => p.SeriesId == seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeleteByMovieId(int movieId)
|
||||||
|
{
|
||||||
|
Delete(r => r.MovieId == movieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PendingRelease> AllByMovieId(int movieId)
|
||||||
|
{
|
||||||
|
return Query.Where(p => p.MovieId == movieId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,22 +23,21 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
void Add(DownloadDecision decision);
|
void Add(DownloadDecision decision);
|
||||||
|
|
||||||
List<ReleaseInfo> GetPending();
|
List<ReleaseInfo> GetPending();
|
||||||
List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId);
|
List<RemoteMovie> GetPendingRemoteMovies(int movieId);
|
||||||
List<Queue.Queue> GetPendingQueue();
|
List<Queue.Queue> GetPendingQueue();
|
||||||
Queue.Queue FindPendingQueueItem(int queueId);
|
Queue.Queue FindPendingQueueItem(int queueId);
|
||||||
void RemovePendingQueueItems(int queueId);
|
void RemovePendingQueueItems(int queueId);
|
||||||
RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable<int> episodeIds);
|
RemoteMovie OldestPendingRelease(int movieId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PendingReleaseService : IPendingReleaseService,
|
public class PendingReleaseService : IPendingReleaseService,
|
||||||
IHandle<SeriesDeletedEvent>,
|
|
||||||
IHandle<EpisodeGrabbedEvent>,
|
|
||||||
IHandle<MovieGrabbedEvent>,
|
IHandle<MovieGrabbedEvent>,
|
||||||
|
IHandle<MovieDeletedEvent>,
|
||||||
IHandle<RssSyncCompleteEvent>
|
IHandle<RssSyncCompleteEvent>
|
||||||
{
|
{
|
||||||
private readonly IIndexerStatusService _indexerStatusService;
|
private readonly IIndexerStatusService _indexerStatusService;
|
||||||
private readonly IPendingReleaseRepository _repository;
|
private readonly IPendingReleaseRepository _repository;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly IMovieService _movieService;
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
private readonly IDelayProfileService _delayProfileService;
|
private readonly IDelayProfileService _delayProfileService;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
@@ -48,7 +47,7 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
|
|
||||||
public PendingReleaseService(IIndexerStatusService indexerStatusService,
|
public PendingReleaseService(IIndexerStatusService indexerStatusService,
|
||||||
IPendingReleaseRepository repository,
|
IPendingReleaseRepository repository,
|
||||||
ISeriesService seriesService,
|
IMovieService movieService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
IDelayProfileService delayProfileService,
|
IDelayProfileService delayProfileService,
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
@@ -58,7 +57,7 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
{
|
{
|
||||||
_indexerStatusService = indexerStatusService;
|
_indexerStatusService = indexerStatusService;
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_seriesService = seriesService;
|
_movieService = movieService;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
_delayProfileService = delayProfileService;
|
_delayProfileService = delayProfileService;
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
@@ -72,13 +71,11 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
{
|
{
|
||||||
var alreadyPending = GetPendingReleases();
|
var alreadyPending = GetPendingReleases();
|
||||||
|
|
||||||
var episodeIds = decision.RemoteEpisode.Episodes.Select(e => e.Id);
|
var movieId = decision.RemoteMovie.Movie.Id;
|
||||||
|
|
||||||
var existingReports = alreadyPending.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
|
var existingReports = alreadyPending.Where(r => r.RemoteMovie.Movie.Id == movieId);
|
||||||
.Intersect(episodeIds)
|
|
||||||
.Any());
|
|
||||||
|
|
||||||
if (existingReports.Any(MatchingReleasePredicate(decision.RemoteEpisode.Release)))
|
if (existingReports.Any(MatchingReleasePredicate(decision.RemoteMovie.Release)))
|
||||||
{
|
{
|
||||||
_logger.Debug("This release is already pending, not adding again");
|
_logger.Debug("This release is already pending, not adding again");
|
||||||
return;
|
return;
|
||||||
@@ -107,9 +104,9 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId)
|
public List<RemoteMovie> GetPendingRemoteMovies(int movieId)
|
||||||
{
|
{
|
||||||
return _repository.AllBySeriesId(seriesId).Select(GetRemoteEpisode).ToList();
|
return _repository.AllByMovieId(movieId).Select(GetRemoteMovie).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Queue.Queue> GetPendingQueue()
|
public List<Queue.Queue> GetPendingQueue()
|
||||||
@@ -120,9 +117,9 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
|
|
||||||
foreach (var pendingRelease in GetPendingReleases())
|
foreach (var pendingRelease in GetPendingReleases())
|
||||||
{
|
{
|
||||||
foreach (var episode in pendingRelease.RemoteEpisode.Episodes)
|
//foreach (var episode in pendingRelease.RemoteEpisode.Episodes)
|
||||||
{
|
//{
|
||||||
var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteEpisode));
|
var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie));
|
||||||
|
|
||||||
if (ect < nextRssSync.Value)
|
if (ect < nextRssSync.Value)
|
||||||
{
|
{
|
||||||
@@ -135,21 +132,22 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
|
|
||||||
var queue = new Queue.Queue
|
var queue = new Queue.Queue
|
||||||
{
|
{
|
||||||
Id = GetQueueId(pendingRelease, episode),
|
Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie),
|
||||||
Series = pendingRelease.RemoteEpisode.Series,
|
Series = null,
|
||||||
Episode = episode,
|
Episode = null,
|
||||||
Quality = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Quality,
|
Movie = pendingRelease.RemoteMovie.Movie,
|
||||||
|
Quality = pendingRelease.RemoteMovie.ParsedMovieInfo.Quality,
|
||||||
Title = pendingRelease.Title,
|
Title = pendingRelease.Title,
|
||||||
Size = pendingRelease.RemoteEpisode.Release.Size,
|
Size = pendingRelease.RemoteMovie.Release.Size,
|
||||||
Sizeleft = pendingRelease.RemoteEpisode.Release.Size,
|
Sizeleft = pendingRelease.RemoteMovie.Release.Size,
|
||||||
RemoteEpisode = pendingRelease.RemoteEpisode,
|
RemoteMovie = pendingRelease.RemoteMovie,
|
||||||
Timeleft = ect.Subtract(DateTime.UtcNow),
|
Timeleft = ect.Subtract(DateTime.UtcNow),
|
||||||
EstimatedCompletionTime = ect,
|
EstimatedCompletionTime = ect,
|
||||||
Status = "Pending",
|
Status = "Pending",
|
||||||
Protocol = pendingRelease.RemoteEpisode.Release.DownloadProtocol
|
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol
|
||||||
};
|
};
|
||||||
queued.Add(queue);
|
queued.Add(queue);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Return best quality release for each episode
|
//Return best quality release for each episode
|
||||||
@@ -158,7 +156,7 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
var series = g.First().Series;
|
var series = g.First().Series;
|
||||||
|
|
||||||
return g.OrderByDescending(e => e.Quality, new QualityModelComparer(series.Profile))
|
return g.OrderByDescending(e => e.Quality, new QualityModelComparer(series.Profile))
|
||||||
.ThenBy(q => PrioritizeDownloadProtocol(q.Series, q.Protocol))
|
.ThenBy(q => PrioritizeDownloadProtocol(q.Movie, q.Protocol))
|
||||||
.First();
|
.First();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,20 +171,16 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
public void RemovePendingQueueItems(int queueId)
|
public void RemovePendingQueueItems(int queueId)
|
||||||
{
|
{
|
||||||
var targetItem = FindPendingRelease(queueId);
|
var targetItem = FindPendingRelease(queueId);
|
||||||
var seriesReleases = _repository.AllBySeriesId(targetItem.SeriesId);
|
var movieReleases = _repository.AllByMovieId(targetItem.MovieId);
|
||||||
|
|
||||||
var releasesToRemove = seriesReleases.Where(
|
var releasesToRemove = movieReleases.Where(c => c.ParsedMovieInfo.MovieTitle == targetItem.ParsedMovieInfo.MovieTitle);
|
||||||
c => c.ParsedEpisodeInfo.SeasonNumber == targetItem.ParsedEpisodeInfo.SeasonNumber &&
|
|
||||||
c.ParsedEpisodeInfo.EpisodeNumbers.SequenceEqual(targetItem.ParsedEpisodeInfo.EpisodeNumbers));
|
|
||||||
|
|
||||||
_repository.DeleteMany(releasesToRemove.Select(c => c.Id));
|
_repository.DeleteMany(releasesToRemove.Select(c => c.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable<int> episodeIds)
|
public RemoteMovie OldestPendingRelease(int movieId)
|
||||||
{
|
{
|
||||||
return GetPendingRemoteEpisodes(seriesId).Where(r => r.Episodes.Select(e => e.Id).Intersect(episodeIds).Any())
|
return GetPendingRemoteMovies(movieId).OrderByDescending(p => p.Release.AgeHours).FirstOrDefault();
|
||||||
.OrderByDescending(p => p.Release.AgeHours)
|
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PendingRelease> GetPendingReleases()
|
private List<PendingRelease> GetPendingReleases()
|
||||||
@@ -195,11 +189,11 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
|
|
||||||
foreach (var release in _repository.All())
|
foreach (var release in _repository.All())
|
||||||
{
|
{
|
||||||
var remoteEpisode = GetRemoteEpisode(release);
|
var remoteMovie = GetRemoteMovie(release);
|
||||||
|
|
||||||
if (remoteEpisode == null) continue;
|
if (remoteMovie == null) continue;
|
||||||
|
|
||||||
release.RemoteEpisode = remoteEpisode;
|
release.RemoteMovie = remoteMovie;
|
||||||
|
|
||||||
result.Add(release);
|
result.Add(release);
|
||||||
}
|
}
|
||||||
@@ -207,20 +201,19 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemoteEpisode GetRemoteEpisode(PendingRelease release)
|
private RemoteMovie GetRemoteMovie(PendingRelease release)
|
||||||
{
|
{
|
||||||
var series = _seriesService.GetSeries(release.SeriesId);
|
var movie = _movieService.GetMovie(release.MovieId);
|
||||||
|
|
||||||
//Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up)
|
//Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up)
|
||||||
if (series == null) return null;
|
if (movie == null) return null;
|
||||||
|
|
||||||
var episodes = _parsingService.GetEpisodes(release.ParsedEpisodeInfo, series, true);
|
// var episodes = _parsingService.GetMovie(release.ParsedMovieInfo.MovieTitle);
|
||||||
|
|
||||||
return new RemoteEpisode
|
return new RemoteMovie
|
||||||
{
|
{
|
||||||
Series = series,
|
Movie = movie,
|
||||||
Episodes = episodes,
|
ParsedMovieInfo = release.ParsedMovieInfo,
|
||||||
ParsedEpisodeInfo = release.ParsedEpisodeInfo,
|
|
||||||
Release = release.Release
|
Release = release.Release
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -229,10 +222,10 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
{
|
{
|
||||||
_repository.Insert(new PendingRelease
|
_repository.Insert(new PendingRelease
|
||||||
{
|
{
|
||||||
SeriesId = decision.RemoteEpisode.Series.Id,
|
MovieId = decision.RemoteMovie.Movie.Id,
|
||||||
ParsedEpisodeInfo = decision.RemoteEpisode.ParsedEpisodeInfo,
|
ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo,
|
||||||
Release = decision.RemoteEpisode.Release,
|
Release = decision.RemoteMovie.Release,
|
||||||
Title = decision.RemoteEpisode.Release.Title,
|
Title = decision.RemoteMovie.Release.Title,
|
||||||
Added = DateTime.UtcNow
|
Added = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -252,46 +245,46 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
p.Release.Indexer == release.Indexer;
|
p.Release.Indexer == release.Indexer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetDelay(RemoteEpisode remoteEpisode)
|
private int GetDelay(RemoteMovie remoteMovie)
|
||||||
{
|
{
|
||||||
var delayProfile = _delayProfileService.AllForTags(remoteEpisode.Series.Tags).OrderBy(d => d.Order).First();
|
var delayProfile = _delayProfileService.AllForTags(remoteMovie.Movie.Tags).OrderBy(d => d.Order).First();
|
||||||
var delay = delayProfile.GetProtocolDelay(remoteEpisode.Release.DownloadProtocol);
|
var delay = delayProfile.GetProtocolDelay(remoteMovie.Release.DownloadProtocol);
|
||||||
var minimumAge = _configService.MinimumAge;
|
var minimumAge = _configService.MinimumAge;
|
||||||
|
|
||||||
return new[] { delay, minimumAge }.Max();
|
return new[] { delay, minimumAge }.Max();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveGrabbed(RemoteEpisode remoteEpisode)
|
//private void RemoveGrabbed(RemoteEpisode remoteEpisode)
|
||||||
{
|
//{
|
||||||
var pendingReleases = GetPendingReleases();
|
// var pendingReleases = GetPendingReleases();
|
||||||
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id);
|
// var episodeIds = remoteEpisode.Episodes.Select(e => e.Id);
|
||||||
|
|
||||||
var existingReports = pendingReleases.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
|
// var existingReports = pendingReleases.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
|
||||||
.Intersect(episodeIds)
|
// .Intersect(episodeIds)
|
||||||
.Any())
|
// .Any())
|
||||||
.ToList();
|
// .ToList();
|
||||||
|
|
||||||
if (existingReports.Empty())
|
// if (existingReports.Empty())
|
||||||
{
|
// {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
var profile = remoteEpisode.Series.Profile.Value;
|
// var profile = remoteEpisode.Series.Profile.Value;
|
||||||
|
|
||||||
foreach (var existingReport in existingReports)
|
// foreach (var existingReport in existingReports)
|
||||||
{
|
// {
|
||||||
var compare = new QualityModelComparer(profile).Compare(remoteEpisode.ParsedEpisodeInfo.Quality,
|
// var compare = new QualityModelComparer(profile).Compare(remoteEpisode.ParsedEpisodeInfo.Quality,
|
||||||
existingReport.RemoteEpisode.ParsedEpisodeInfo.Quality);
|
// existingReport.RemoteEpisode.ParsedEpisodeInfo.Quality);
|
||||||
|
|
||||||
//Only remove lower/equal quality pending releases
|
// //Only remove lower/equal quality pending releases
|
||||||
//It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed)
|
// //It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed)
|
||||||
if (compare >= 0)
|
// if (compare >= 0)
|
||||||
{
|
// {
|
||||||
_logger.Debug("Removing previously pending release, as it was grabbed.");
|
// _logger.Debug("Removing previously pending release, as it was grabbed.");
|
||||||
Delete(existingReport);
|
// Delete(existingReport);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
private void RemoveRejected(List<DownloadDecision> rejected)
|
private void RemoveRejected(List<DownloadDecision> rejected)
|
||||||
{
|
{
|
||||||
@@ -300,7 +293,7 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
|
|
||||||
foreach (var rejectedRelease in rejected)
|
foreach (var rejectedRelease in rejected)
|
||||||
{
|
{
|
||||||
var matching = pending.Where(MatchingReleasePredicate(rejectedRelease.RemoteEpisode.Release));
|
var matching = pending.Where(MatchingReleasePredicate(rejectedRelease.RemoteMovie.Release));
|
||||||
|
|
||||||
foreach (var pendingRelease in matching)
|
foreach (var pendingRelease in matching)
|
||||||
{
|
{
|
||||||
@@ -312,17 +305,17 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
|
|
||||||
private PendingRelease FindPendingRelease(int queueId)
|
private PendingRelease FindPendingRelease(int queueId)
|
||||||
{
|
{
|
||||||
return GetPendingReleases().First(p => p.RemoteEpisode.Episodes.Any(e => queueId == GetQueueId(p, e)));
|
return GetPendingReleases().First(p => queueId == GetQueueId(p, p.RemoteMovie.Movie));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetQueueId(PendingRelease pendingRelease, Episode episode)
|
private int GetQueueId(PendingRelease pendingRelease, Movie movie)
|
||||||
{
|
{
|
||||||
return HashConverter.GetHashInt31(string.Format("pending-{0}-ep{1}", pendingRelease.Id, episode.Id));
|
return HashConverter.GetHashInt31(string.Format("pending-{0}-movie{1}", pendingRelease.Id, movie.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int PrioritizeDownloadProtocol(Series series, DownloadProtocol downloadProtocol)
|
private int PrioritizeDownloadProtocol(Movie movie, DownloadProtocol downloadProtocol)
|
||||||
{
|
{
|
||||||
var delayProfile = _delayProfileService.BestForTags(series.Tags);
|
var delayProfile = _delayProfileService.BestForTags(movie.Tags);
|
||||||
|
|
||||||
if (downloadProtocol == delayProfile.PreferredProtocol)
|
if (downloadProtocol == delayProfile.PreferredProtocol)
|
||||||
{
|
{
|
||||||
@@ -332,14 +325,9 @@ namespace NzbDrone.Core.Download.Pending
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(SeriesDeletedEvent message)
|
public void Handle(MovieDeletedEvent message)
|
||||||
{
|
{
|
||||||
_repository.DeleteBySeriesId(message.Series.Id);
|
_repository.DeleteByMovieId(message.Movie.Id);
|
||||||
}
|
|
||||||
|
|
||||||
public void Handle(EpisodeGrabbedEvent message)
|
|
||||||
{
|
|
||||||
RemoveGrabbed(message.Episode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(MovieGrabbedEvent message)
|
public void Handle(MovieGrabbedEvent message)
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
|||||||
mapper.ExecuteNonQuery(@"DELETE FROM Blacklist
|
mapper.ExecuteNonQuery(@"DELETE FROM Blacklist
|
||||||
WHERE Id IN (
|
WHERE Id IN (
|
||||||
SELECT Blacklist.Id FROM Blacklist
|
SELECT Blacklist.Id FROM Blacklist
|
||||||
LEFT OUTER JOIN Series
|
LEFT OUTER JOIN Movies
|
||||||
ON Blacklist.SeriesId = Series.Id
|
ON Blacklist.MovieId = Movies.Id
|
||||||
WHERE Series.Id IS NULL)");
|
WHERE Movies.Id IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
|||||||
mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases
|
mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases
|
||||||
WHERE Id IN (
|
WHERE Id IN (
|
||||||
SELECT PendingReleases.Id FROM PendingReleases
|
SELECT PendingReleases.Id FROM PendingReleases
|
||||||
LEFT OUTER JOIN Series
|
LEFT OUTER JOIN Movies
|
||||||
ON PendingReleases.SeriesId = Series.Id
|
ON PendingReleases.MovieId = Movies.Id
|
||||||
WHERE Series.Id IS NULL)");
|
WHERE Movies.Id IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Instrumentation.Extensions;
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Queue;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
|
||||||
namespace NzbDrone.Core.IndexerSearch
|
namespace NzbDrone.Core.IndexerSearch
|
||||||
{
|
{
|
||||||
@@ -13,17 +17,48 @@ namespace NzbDrone.Core.IndexerSearch
|
|||||||
private readonly IMovieService _movieService;
|
private readonly IMovieService _movieService;
|
||||||
private readonly ISearchForNzb _nzbSearchService;
|
private readonly ISearchForNzb _nzbSearchService;
|
||||||
private readonly IProcessDownloadDecisions _processDownloadDecisions;
|
private readonly IProcessDownloadDecisions _processDownloadDecisions;
|
||||||
|
private readonly IQueueService _queueService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public MovieSearchService(IMovieService movieService,
|
public MovieSearchService(IMovieService movieService,
|
||||||
ISearchForNzb nzbSearchService,
|
ISearchForNzb nzbSearchService,
|
||||||
IProcessDownloadDecisions processDownloadDecisions,
|
IProcessDownloadDecisions processDownloadDecisions,
|
||||||
|
IQueueService queueService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_movieService = movieService;
|
_movieService = movieService;
|
||||||
_nzbSearchService = nzbSearchService;
|
_nzbSearchService = nzbSearchService;
|
||||||
_processDownloadDecisions = processDownloadDecisions;
|
_processDownloadDecisions = processDownloadDecisions;
|
||||||
|
_queueService = queueService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchForMissingMovies(List<Movie> movies, bool userInvokedSearch)
|
||||||
|
{
|
||||||
|
_logger.ProgressInfo("Performing missing search for {0} movies", movies.Count);
|
||||||
|
var downloadedCount = 0;
|
||||||
|
|
||||||
|
foreach (var movieId in movies.GroupBy(e => e.Id))
|
||||||
|
{
|
||||||
|
List<DownloadDecision> decisions;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
decisions = _nzbSearchService.MovieSearch(movieId.Key, userInvokedSearch);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var message = String.Format("Unable to search for missing movie {0}", movieId.Key);
|
||||||
|
_logger.Error(ex, message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||||
|
|
||||||
|
downloadedCount += processed.Grabbed.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.ProgressInfo("Completed missing search for {0} movies. {1} reports downloaded.", movies.Count, downloadedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(MoviesSearchCommand message)
|
public void Execute(MoviesSearchCommand message)
|
||||||
@@ -47,7 +82,9 @@ namespace NzbDrone.Core.IndexerSearch
|
|||||||
|
|
||||||
public void Execute(MissingMoviesSearchCommand message)
|
public void Execute(MissingMoviesSearchCommand message)
|
||||||
{
|
{
|
||||||
var movies = _movieService.MoviesWithoutFiles(new PagingSpec<Movie>
|
List<Movie> movies;
|
||||||
|
|
||||||
|
movies = _movieService.MoviesWithoutFiles(new PagingSpec<Movie>
|
||||||
{
|
{
|
||||||
Page = 1,
|
Page = 1,
|
||||||
PageSize = 100000,
|
PageSize = 100000,
|
||||||
@@ -57,6 +94,14 @@ namespace NzbDrone.Core.IndexerSearch
|
|||||||
v =>
|
v =>
|
||||||
v.Monitored == true
|
v.Monitored == true
|
||||||
}).Records.ToList();
|
}).Records.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
var queue = _queueService.GetQueue().Select(q => q.Movie.Id);
|
||||||
|
var missing = movies.Where(e => !queue.Contains(e.Id)).ToList();
|
||||||
|
|
||||||
|
SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.AwesomeHD
|
|
||||||
{
|
|
||||||
public class Torrent
|
|
||||||
{
|
|
||||||
[XmlElement(ElementName = "id")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
[XmlElement(ElementName = "groupid")]
|
|
||||||
public string GroupId { get; set; }
|
|
||||||
[XmlElement(ElementName = "time")]
|
|
||||||
public DateTime Time { get; set; }
|
|
||||||
[XmlElement(ElementName = "userid")]
|
|
||||||
public string Userid { get; set; }
|
|
||||||
[XmlElement(ElementName = "size")]
|
|
||||||
public long Size { get; set; }
|
|
||||||
[XmlElement(ElementName = "snatched")]
|
|
||||||
public string Snatched { get; set; }
|
|
||||||
[XmlElement(ElementName = "seeders")]
|
|
||||||
public string Seeders { get; set; }
|
|
||||||
[XmlElement(ElementName = "leechers")]
|
|
||||||
public string Leechers { get; set; }
|
|
||||||
[XmlElement(ElementName = "releasegroup")]
|
|
||||||
public string Releasegroup { get; set; }
|
|
||||||
[XmlElement(ElementName = "resolution")]
|
|
||||||
public string Resolution { get; set; }
|
|
||||||
[XmlElement(ElementName = "media")]
|
|
||||||
public string Media { get; set; }
|
|
||||||
[XmlElement(ElementName = "format")]
|
|
||||||
public string Format { get; set; }
|
|
||||||
[XmlElement(ElementName = "encoding")]
|
|
||||||
public string Encoding { get; set; }
|
|
||||||
[XmlElement(ElementName = "audioformat")]
|
|
||||||
public string Audioformat { get; set; }
|
|
||||||
[XmlElement(ElementName = "audiobitrate")]
|
|
||||||
public string Audiobitrate { get; set; }
|
|
||||||
[XmlElement(ElementName = "audiochannels")]
|
|
||||||
public string Audiochannels { get; set; }
|
|
||||||
[XmlElement(ElementName = "subtitles")]
|
|
||||||
public string Subtitles { get; set; }
|
|
||||||
[XmlElement(ElementName = "encodestatus")]
|
|
||||||
public string Encodestatus { get; set; }
|
|
||||||
[XmlElement(ElementName = "freeleech")]
|
|
||||||
public string Freeleech { get; set; }
|
|
||||||
[XmlElement(ElementName = "cover")]
|
|
||||||
public string Cover { get; set; }
|
|
||||||
[XmlElement(ElementName = "smallcover")]
|
|
||||||
public string Smallcover { get; set; }
|
|
||||||
[XmlElement(ElementName = "year")]
|
|
||||||
public string Year { get; set; }
|
|
||||||
[XmlElement(ElementName = "name")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
[XmlElement(ElementName = "imdb")]
|
|
||||||
public string Imdb { get; set; }
|
|
||||||
[XmlElement(ElementName = "type")]
|
|
||||||
public string Type { get; set; }
|
|
||||||
[XmlElement(ElementName = "plotoutline")]
|
|
||||||
public string Plotoutline { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SearchResults
|
|
||||||
{
|
|
||||||
[XmlElement(ElementName = "authkey")]
|
|
||||||
public string AuthKey { get; set; }
|
|
||||||
[XmlElement(ElementName = "torrent")]
|
|
||||||
public List<Torrent> Torrent { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AwesomeHDSearchResponse
|
|
||||||
{
|
|
||||||
[XmlElement(ElementName = "?xml")]
|
|
||||||
public string Xml { get; set; }
|
|
||||||
[XmlElement(ElementName = "searchresults")]
|
|
||||||
public SearchResults SearchResults { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,37 +14,10 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
|
|||||||
public virtual IndexerPageableRequestChain GetRecentRequests()
|
public virtual IndexerPageableRequestChain GetRecentRequests()
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetRequest(null));
|
pageableRequests.Add(GetRequest(null));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
@@ -70,5 +43,30 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using NzbDrone.Core.Parser.Model;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.AwesomeHD
|
namespace NzbDrone.Core.Indexers.AwesomeHD
|
||||||
{
|
{
|
||||||
@@ -31,38 +32,64 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
|
|||||||
indexerResponse.HttpResponse.StatusCode);
|
indexerResponse.HttpResponse.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hacky ¯\_(ツ)_/¯
|
try
|
||||||
XmlDocument doc = new XmlDocument();
|
|
||||||
doc.LoadXml(indexerResponse.Content);
|
|
||||||
|
|
||||||
var json = JsonConvert.SerializeXmlNode(doc);
|
|
||||||
|
|
||||||
Console.WriteLine(json);
|
|
||||||
|
|
||||||
var jsonResponse = JsonConvert.DeserializeObject<AwesomeHDSearchResponse>(json);
|
|
||||||
|
|
||||||
if (jsonResponse == null)
|
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, "Unexpected response from request");
|
var xdoc = XDocument.Parse(indexerResponse.Content);
|
||||||
}
|
var searchResults = xdoc.Descendants("searchresults").Select(x => new
|
||||||
|
|
||||||
foreach (var torrent in jsonResponse.SearchResults.Torrent)
|
|
||||||
{
|
|
||||||
var id = torrent.Id;
|
|
||||||
var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.Audioformat}-{torrent.Releasegroup}";
|
|
||||||
|
|
||||||
torrentInfos.Add(new TorrentInfo()
|
|
||||||
{
|
{
|
||||||
Guid = string.Format("AwesomeHD-{0}", id),
|
AuthKey = x.Element("authkey").Value,
|
||||||
Title = title,
|
}).FirstOrDefault();
|
||||||
Size = torrent.Size,
|
|
||||||
DownloadUrl = GetDownloadUrl(id, jsonResponse.SearchResults.AuthKey, _settings.Passkey),
|
var torrents = xdoc.Descendants("torrent")
|
||||||
InfoUrl = GetInfoUrl(torrent.GroupId, id),
|
.Select(x => new
|
||||||
Seeders = int.Parse(torrent.Seeders),
|
{
|
||||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
Id = x.Element("id").Value,
|
||||||
PublishDate = torrent.Time.ToUniversalTime()
|
Name = x.Element("name").Value,
|
||||||
});
|
Year = x.Element("year").Value,
|
||||||
|
GroupId = x.Element("groupid").Value,
|
||||||
|
Time = DateTime.Parse(x.Element("time").Value),
|
||||||
|
UserId = x.Element("userid").Value,
|
||||||
|
Size = long.Parse(x.Element("size").Value),
|
||||||
|
Snatched = x.Element("snatched").Value,
|
||||||
|
Seeders = x.Element("seeders").Value,
|
||||||
|
Leechers = x.Element("leechers").Value,
|
||||||
|
ReleaseGroup = x.Element("releasegroup").Value,
|
||||||
|
Resolution = x.Element("resolution").Value,
|
||||||
|
Media = x.Element("media").Value,
|
||||||
|
Format = x.Element("format").Value,
|
||||||
|
Encoding = x.Element("encoding").Value,
|
||||||
|
AudioFormat = x.Element("audioformat").Value,
|
||||||
|
AudioBitrate = x.Element("audiobitrate").Value,
|
||||||
|
AudioChannels = x.Element("audiochannels").Value,
|
||||||
|
Subtitles = x.Element("subtitles").Value,
|
||||||
|
EncodeStatus = x.Element("encodestatus").Value,
|
||||||
|
Freeleech = x.Element("freeleech").Value,
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
foreach (var torrent in torrents)
|
||||||
|
{
|
||||||
|
var id = torrent.Id;
|
||||||
|
var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}";
|
||||||
|
|
||||||
|
torrentInfos.Add(new TorrentInfo()
|
||||||
|
{
|
||||||
|
Guid = string.Format("AwesomeHD-{0}", id),
|
||||||
|
Title = title,
|
||||||
|
Size = torrent.Size,
|
||||||
|
DownloadUrl = GetDownloadUrl(id, searchResults.AuthKey, _settings.Passkey),
|
||||||
|
InfoUrl = GetInfoUrl(torrent.GroupId, id),
|
||||||
|
Seeders = int.Parse(torrent.Seeders),
|
||||||
|
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||||
|
PublishDate = torrent.Time.ToUniversalTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (XmlException)
|
||||||
|
{
|
||||||
|
throw new IndexerException(indexerResponse,
|
||||||
|
"An error occurred while processing feed, feed invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return torrentInfos.OrderByDescending(o => ((dynamic)o).Seeders).ToArray();
|
return torrentInfos.OrderByDescending(o => ((dynamic)o).Seeders).ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||||||
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
|
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
|
||||||
yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net"));
|
yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net"));
|
||||||
yield return GetDefinition("Nzbs.org", GetSettings("http://nzbs.org"));
|
yield return GetDefinition("Nzbs.org", GetSettings("http://nzbs.org"));
|
||||||
|
yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me"));
|
||||||
yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com"));
|
yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com"));
|
||||||
yield return GetDefinition("PFmonkey", GetSettings("https://www.pfmonkey.com"));
|
yield return GetDefinition("PFmonkey", GetSettings("https://www.pfmonkey.com"));
|
||||||
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
|
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", $"&q={Parser.Parser.NormalizeTitle(searchCriteria.Movie.Title)}%20{searchCriteria.Movie.Year}"));
|
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", $"&q={System.Web.HttpUtility.UrlPathEncode(Parser.Parser.NormalizeTitle(searchCriteria.Movie.Title))}%20{searchCriteria.Movie.Year}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||||||
[FieldDefinition(1, Label = "API Key")]
|
[FieldDefinition(1, Label = "API Key")]
|
||||||
public string ApiKey { get; set; }
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(2, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows", Advanced = true)]
|
[FieldDefinition(2, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable all categories", Advanced = true)]
|
||||||
public IEnumerable<int> Categories { get; set; }
|
public IEnumerable<int> Categories { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(3, Label = "Anime Categories", HelpText = "Comma Separated list, leave blank to disable anime", Advanced = true)]
|
[FieldDefinition(3, Label = "Anime Categories", HelpText = "Comma Separated list, leave blank to disable anime", Advanced = true)]
|
||||||
|
|||||||
@@ -56,16 +56,8 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
title = $"{title} ✔";
|
title = $"{title} ✔";
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (IsPropertyExist(torrent, "RemasterTitle"))
|
|
||||||
//{
|
|
||||||
// if (torrent.RemasterTitle != null)
|
|
||||||
// {
|
|
||||||
// title = $"{title} - {torrent.RemasterTitle}";
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Only add approved torrents
|
// Only add approved torrents
|
||||||
if (_settings.Approved && torrent.Checked)
|
if (_settings.RequireApproved && torrent.Checked)
|
||||||
{
|
{
|
||||||
torrentInfos.Add(new PassThePopcornInfo()
|
torrentInfos.Add(new PassThePopcornInfo()
|
||||||
{
|
{
|
||||||
@@ -83,7 +75,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Add all torrents
|
// Add all torrents
|
||||||
else if (!_settings.Approved)
|
else if (!_settings.RequireApproved)
|
||||||
{
|
{
|
||||||
torrentInfos.Add(new PassThePopcornInfo()
|
torrentInfos.Add(new PassThePopcornInfo()
|
||||||
{
|
{
|
||||||
@@ -101,7 +93,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Don't add any torrents
|
// Don't add any torrents
|
||||||
else if (_settings.Approved && !torrent.Checked)
|
else if (_settings.RequireApproved && !torrent.Checked)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -109,9 +101,37 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prefer golden
|
// prefer golden
|
||||||
|
if (_settings.Golden)
|
||||||
|
{
|
||||||
|
if (_settings.Scene)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
torrentInfos.OrderByDescending(o => o.PublishDate)
|
||||||
|
.ThenBy(o => ((dynamic)o).Golden ? 0 : 1)
|
||||||
|
.ThenBy(o => ((dynamic) o).Scene ? 0 : 1)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
return
|
||||||
|
torrentInfos.OrderByDescending(o => o.PublishDate)
|
||||||
|
.ThenBy(o => ((dynamic)o).Golden ? 0 : 1)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
// prefer scene
|
// prefer scene
|
||||||
// require approval
|
if (_settings.Scene)
|
||||||
return torrentInfos.OrderBy(o => ((dynamic)o).Golden ? 0 : 1).ThenBy(o => ((dynamic)o).Scene ? 0 : 1).ThenByDescending(o => ((dynamic)o).PublishDate).ToArray();
|
{
|
||||||
|
return
|
||||||
|
torrentInfos.OrderByDescending(o => o.PublishDate)
|
||||||
|
.ThenBy(o => ((dynamic)o).Scene ? 0 : 1)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// order by date
|
||||||
|
return
|
||||||
|
torrentInfos
|
||||||
|
.OrderByDescending(o => o.PublishDate)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDownloadUrl(int torrentId, string authKey, string passKey)
|
private string GetDownloadUrl(int torrentId, string authKey, string passKey)
|
||||||
|
|||||||
@@ -17,14 +17,6 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
public IHttpClient HttpClient { get; set; }
|
public IHttpClient HttpClient { get; set; }
|
||||||
public Logger Logger { get; set; }
|
public Logger Logger { get; set; }
|
||||||
|
|
||||||
//public PassThePopcornRequestGenerator(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
|
|
||||||
//{
|
|
||||||
// _httpClient = httpClient;
|
|
||||||
// _logger = logger;
|
|
||||||
|
|
||||||
// _authCookieCache = cacheManager.GetCache<Dictionary<string, string>>(GetType(), "authCookies");
|
|
||||||
//}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetRecentRequests()
|
public virtual IndexerPageableRequestChain GetRecentRequests()
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
@@ -41,38 +33,22 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
||||||
{
|
{
|
||||||
Authenticate();
|
Authenticate();
|
||||||
|
|
||||||
|
var filter = "";
|
||||||
|
if (searchParameters == null)
|
||||||
|
{
|
||||||
|
if (Settings.RequireGolden)
|
||||||
|
{
|
||||||
|
filter = "&scene=2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var request =
|
var request =
|
||||||
new IndexerRequest(
|
new IndexerRequest(
|
||||||
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?json=noredirect&searchstr={searchParameters}",
|
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?action=advanced&json=noredirect&searchstr={searchParameters}{filter}",
|
||||||
HttpAccept.Json);
|
HttpAccept.Json);
|
||||||
|
|
||||||
var cookies = AuthCookieCache.Find(Settings.BaseUrl.Trim().TrimEnd('/'));
|
var cookies = AuthCookieCache.Find(Settings.BaseUrl.Trim().TrimEnd('/'));
|
||||||
@@ -111,8 +87,6 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
.Accept(HttpAccept.Json)
|
.Accept(HttpAccept.Json)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// authLoginRequest.Method = HttpMethod.POST;
|
|
||||||
|
|
||||||
var response = HttpClient.Execute(authLoginRequest);
|
var response = HttpClient.Execute(authLoginRequest);
|
||||||
var result = Json.Deserialize<PassThePopcornAuthResponse>(response.Content);
|
var result = Json.Deserialize<PassThePopcornAuthResponse>(response.Content);
|
||||||
|
|
||||||
@@ -133,5 +107,32 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
requestBuilder.SetCookies(cookies);
|
requestBuilder.SetCookies(cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||||
|
{
|
||||||
|
return new IndexerPageableRequestChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
public bool Scene { get; set; }
|
public bool Scene { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Require Approved", Type = FieldType.Checkbox, HelpText = "Require staff-approval for releases to be accepted.")]
|
[FieldDefinition(6, Label = "Require Approved", Type = FieldType.Checkbox, HelpText = "Require staff-approval for releases to be accepted.")]
|
||||||
public bool Approved { get; set; }
|
public bool RequireApproved { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Require Golden", Type = FieldType.Checkbox, HelpText = "Require Golden Popcorn-releases for releases to be accepted.")]
|
[FieldDefinition(7, Label = "Require Golden", Type = FieldType.Checkbox, HelpText = "Require Golden Popcorn-releases for releases to be accepted.")]
|
||||||
public bool RequireGolden { get; set; }
|
public bool RequireGolden { get; set; }
|
||||||
|
|||||||
@@ -132,23 +132,23 @@ namespace NzbDrone.Core.MediaCover
|
|||||||
{
|
{
|
||||||
if (e.Status == WebExceptionStatus.ProtocolError)
|
if (e.Status == WebExceptionStatus.ProtocolError)
|
||||||
{
|
{
|
||||||
_logger.Warn(e, "Server returned different code than 200. The poster is probably not available yet.");
|
_logger.Warn(e, string.Format("Couldn't download media cover for {0}, likely the cover doesn't exist for this movie. {1}", movie, e.Message));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Warn(e, string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message));
|
|
||||||
if (retried < 3)
|
|
||||||
{
|
|
||||||
retried = +1;
|
|
||||||
_logger.Warn("Retrying for the {0}. time in ten seconds.", retried);
|
|
||||||
System.Threading.Thread.Sleep(10*1000);
|
|
||||||
EnsureCovers(movie, retried);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Warn(e, "Couldn't download media cover even after retrying five times :(.");
|
_logger.Warn(e, string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message));
|
||||||
|
if (retried < 3)
|
||||||
|
{
|
||||||
|
retried = +1;
|
||||||
|
_logger.Warn("Retrying for the {0}. time in ten seconds.", retried);
|
||||||
|
System.Threading.Thread.Sleep(10 * 1000);
|
||||||
|
EnsureCovers(movie, retried);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Warn(e, "Couldn't download media cover even after retrying five times :(.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -266,7 +266,8 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
{
|
{
|
||||||
if (message.MovieId.HasValue)
|
if (message.MovieId.HasValue)
|
||||||
{
|
{
|
||||||
var series = _movieService.GetMovie(message.MovieId.Value);
|
var movie = _movieService.GetMovie(message.MovieId.Value);
|
||||||
|
Scan(movie);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||||||
private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
|
private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
|
||||||
{
|
{
|
||||||
var directoryInfo = new DirectoryInfo(folder);
|
var directoryInfo = new DirectoryInfo(folder);
|
||||||
var series = _parsingService.GetSeries(directoryInfo.Name);
|
var series = _parsingService.GetMovie(directoryInfo.Name);
|
||||||
|
|
||||||
if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||||
series = trackedDownload.RemoteEpisode.Series;
|
series = trackedDownload.RemoteMovie.Movie;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (series == null)
|
if (series == null)
|
||||||
@@ -119,9 +119,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||||||
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
|
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
|
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
|
||||||
var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
|
var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
|
||||||
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder));
|
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder), false);
|
||||||
|
|
||||||
return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
|
return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,4 +150,41 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
|||||||
public string size { get; set; }
|
public string size { get; set; }
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ListResponseRoot
|
||||||
|
{
|
||||||
|
public string created_by { get; set; }
|
||||||
|
public string description { get; set; }
|
||||||
|
public int favorite_count { get; set; }
|
||||||
|
public string id { get; set; }
|
||||||
|
public Item[] items { get; set; }
|
||||||
|
public int item_count { get; set; }
|
||||||
|
public string iso_639_1 { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public object poster_path { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Item
|
||||||
|
{
|
||||||
|
public string poster_path { get; set; }
|
||||||
|
public bool adult { get; set; }
|
||||||
|
public string overview { get; set; }
|
||||||
|
public string release_date { get; set; }
|
||||||
|
public string original_title { get; set; }
|
||||||
|
public int[] genre_ids { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string media_type { get; set; }
|
||||||
|
public string original_language { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string backdrop_path { get; set; }
|
||||||
|
public float popularity { get; set; }
|
||||||
|
public int vote_count { get; set; }
|
||||||
|
public bool video { get; set; }
|
||||||
|
public float vote_average { get; set; }
|
||||||
|
public string first_air_date { get; set; }
|
||||||
|
public string[] origin_country { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string original_name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user