1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

63 Commits

Author SHA1 Message Date
Tim Turner 89e5001bad Patch/bulk import tests (#833)
* Ensure duplicates are not imported

* Clean up some unnecessary code

* Added some fancy tests for bulk import.
2017-02-22 11:21:05 +01:00
Tim Turner 056fb154a8 Patch/bulk import qol (#785)
* Filter out existing movies upon import
* Update collection based on what is imported
* Ensure root folders are loaded before collectionview

TODO:
* Ensure grid region exists
* Return information about what wasn't imported
* Filter collection based on duplicates
2017-02-21 15:31:31 -05:00
Leonardo Galli 3edc2b80cf Update .travis.yml 2017-02-20 22:27:07 +01:00
Mihai Blaga a1745cd02e URL Encode for newznab query strings, closes #818 (#819)
Closes #818
2017-02-20 11:17:28 -05:00
Mitchell Cash 1c0f9b64ca Rename Sonarr to Radarr in DownloadStation client (#812) 2017-02-20 11:15:53 -05:00
hotio 7d6a518f30 Update notification logos (#804) 2017-02-20 11:15:26 -05:00
Ryan Matthews 9f2fcebc24 Fixes error message for MovieExistsValidator to state the movie doesn't exist (#723) (#808)
Fixes #723
2017-02-19 19:05:37 +01:00
Ryan Matthews 91295f50b0 Set PROWL application to Radarr (#770) (#807) 2017-02-19 18:55:01 +01:00
Devin Buhl 68bf97f52c TMDb Lists should be working now :) (#775)
* Remove un-used imports

* Some small update to Net Import
2017-02-16 14:40:04 -05:00
Devin Buhl 363048e68e Roll back some code on Net Import (#772) 2017-02-16 13:29:21 -05:00
Marcelo Castagna 9c20c0b889 Check to see if output path is right when DownloadClient.Test is invoked (#768)
changed to string interpolation a few lines
2017-02-16 08:22:02 -05:00
Devin Buhl 50891e5dd7 TMDb Filtering Options: Rating, Min Votes, Min Vote Ave, Original Language, TMDb Genre Ids (CSV), (#765) 2017-02-16 04:46:35 -05:00
Devin Buhl f393a95501 Small consistancy updates to PTP and AwesomeHD (#758) 2017-02-15 18:02:24 -05:00
Devin Buhl a68dd6d2f7 Patch/onedr0p (#757)
* Fix double ? in log

* Rename Download Station to TorrentDownloadStation

* Rename Trakt, TMDb, and set Enable sync default to false

* Added omgwtfnzbs Newznab prefix

* Update Media info for Windows/macOS to 0.7.92.1
2017-02-15 17:37:23 -05:00
Marcelo Castagna dadf6708ab Handle download data diskstation (#744)
* Delete data manually after finished seeding

* Removed try/catch
2017-02-14 09:29:47 -05:00
Tim Turner 7a86c78896 Merged patch/update-files-tab-onChange into develop 2017-02-13 20:08:31 -05:00
Tim Turner 81688399c0 When refreshing movie, refresh Files tab 2017-02-13 20:08:07 -05:00
Devin Buhl 1e28a2e5d4 Feature/Add TMDb Functionality (#739)
* Inital TMDb List, needs paging support and user lists, private or public

* Clean up Base

* TMDb grabs upto 5 pages for import, update validation, added minimum vote average

* Added logic for MovieLinksTemplate

* Clean up a bit

* Add Public Lists
2017-02-13 09:11:20 -05:00
zductiv c5bb259555 add downloaded quality column to movie editor (#738) 2017-02-13 08:25:37 -05:00
Devin Buhl 0d5d75d6ea Update ISSUE_TEMPLATE.md 2017-02-13 08:14:37 -05:00
Devin Buhl 5bae9bbbcc Update PULL_REQUEST_TEMPLATE.md 2017-02-13 08:08:37 -05:00
Devin Buhl a3e681078f Clean up Trakt a little (#735) 2017-02-13 00:38:19 -05:00
Devin Buhl 758228e159 Update dl-clients (#732) 2017-02-12 20:07:57 -05:00
Devin Buhl 1b900a006f Add Synology Download Station (#725)
* Add Synology Download Station

* Update DownloadStation Client from Sonarr
2017-02-12 19:44:47 -05:00
Leonardo Galli 9b5c5169ef fix pending release table. 2017-02-12 16:33:36 +01:00
Devin Buhl e78a55ac6e Fix Hardcoded .DKSubs. (#726)
* Fix Hardcoded .DKSubs.

* Added tests
2017-02-12 09:19:48 -05:00
Devin Buhl e82cf70399 NetImport - Do not allow TV Series / Mini-Series (works with IMDb) #699 (#727)
* Do not allow TV Series / Mini-Series (works with IMDb) #699

* Ignore Case
2017-02-12 09:16:15 -05:00
Devin Buhl e7d65ee4ae Patch/re add ghost migrations (#724)
* Remove Wombles and Kickass Data from tables

* And that's why you build
2017-02-12 07:08:39 -05:00
Devin Buhl 1db3669afa Patch/onedr0p (#716)
* Alter IMDb lists to what was requested: #697

* Update Pending Release

* Tests (these need to be updated further)

* Alter table migration, movieId already exists

* Update HouseKeeping for pending release

* Fix migratiom and pendingrelease housekeeping
2017-02-12 06:57:07 -05:00
Mitchell Cash 93e55b7575 Increase timeout when waiting for rTorrent to finish adding torrent (#721)
Increase timeout when waiting for rTorrent to finish adding torrent
2017-02-12 03:07:38 -05:00
Devin Buhl f850c65b56 Update ISSUE_TEMPLATE.md 2017-02-11 18:29:56 -05:00
Leonardo Galli 297348fffe Merged branch develop into develop 2017-02-11 23:25:35 +01:00
Leonardo Galli 07ff6558d1 Hopefully fixes a lot of null reference bugs in BulkImport 2017-02-11 23:25:27 +01:00
Leonardo Galli 85843efcb0 Added test for ! 2017-02-11 23:24:10 +01:00
zductiv 3d4b1c3be5 changed sort options to match UI (#707) 2017-02-11 15:04:19 -05:00
Tim Turner a3f389af5e Fix RescanMovie command for single movie
Partial fix for #669
2017-02-11 14:38:17 -05:00
Leonardo Galli 1aeb3c6fd6 Should fix blacklist items disappearing.
Fixes #653
2017-02-11 18:32:54 +01:00
Leonardo Galli 6ab6c016c0 Fix manual import for when downloaded movies are in a folder 2017-02-11 18:26:35 +01:00
Leonardo Galli a1961603d7 Merged branch develop into develop 2017-02-11 17:40:31 +01:00
Leonardo Galli 50ac95dec5 Update parser tests. 2017-02-11 17:40:01 +01:00
Leonardo Galli a16e46cf38 Fix parsing with lower bluray qualities.
Fixes #706
2017-02-11 16:59:15 +01:00
zductiv ea33b75764 search all missing movie works - missing tab only (#710) 2017-02-11 08:48:02 -05:00
Devin Buhl 31e657d052 Limit TMDb requests when importing via IMDBid (#703) 2017-02-10 23:03:24 -05:00
Leonardo Galli fe0dfef83c Fixes issue with movies with same name but different years being downloaded. 2017-02-10 19:40:10 +01:00
Leonardo Galli bc1a47ff5a Fixed a few parser issues. Also added some tests.
Fixes #549
2017-02-10 19:00:16 +01:00
Leonardo Galli 4e8089dd42 Cutoff tab actually working now. 2017-02-10 17:30:35 +01:00
geogolem 6dc9f90a8b fix trakt links for movies (like sonarr for shows) (#690) 2017-02-10 04:07:38 -05:00
Devin Buhl 1aae3ae2b5 Fixed Sorting In Wanted and Cutoff (#693) 2017-02-10 04:04:50 -05:00
Devin Buhl 75436bcce4 Pass at seeing if this works on linux now (#692) 2017-02-10 03:17:28 -05:00
zductiv 61df3ef40e Small UI changes (#691)
* Update FileManagementViewTemplate.hbs

* Update PermissionsViewTemplate.hbs

* modified UI, removed old episode references
2017-02-10 03:14:51 -05:00
Devin Buhl f45aab27d1 Add required flag for PTP (#688) 2017-02-10 01:35:47 -05:00
Devin Buhl f477c46406 Wanted & Missing (#687)
* Remove Season Pass, Update Header name, remove useless function

* Cutoff Tab now works
2017-02-10 00:15:41 -05:00
Devin Buhl 2af07d7e0d * Make Missing/Wanted Work again (#686)
* Make Missing/Wanted Load
2017-02-09 22:52:13 -05:00
Devin Buhl df691488a9 Fixed MovieMissingModule failed while processing [MovieDownloadedEvent] 2017-02-09 16:52:35 -05:00
Tim Turner 800e7ae508 UI Enhancements for Manual Import (#681)
Fixes #589
Fixes #632
2017-02-09 16:25:51 -05:00
Leonardo Galli fcf156293e May be fix loading view? Idk. 2017-02-09 16:48:14 +01:00
Tim Turner 94f44a0eb7 Display loading view when changing page size 2017-02-08 17:00:37 -05:00
Leonardo Galli 1e2c28f67a Merged branch develop into develop 2017-02-08 22:14:04 +01:00
Leonardo Galli 62b45f7ea7 Fix paging breaking in bulk import. 2017-02-08 22:11:11 +01:00
Devin Buhl f577590ad6 Fix ordering in PTP, should prefer GP releases (#667)
* Fix ordering in PTP, should prefer GP releases

* Apply more checks
2017-02-08 14:26:11 -05:00
Devin Buhl 0941247f63 Patch/onedr0p updates (#664)
* Remove button for require GP in PassThePopcorn

* Fix AwesomeHD when search results yeild 1 torrent result

* Add try/catch block
2017-02-08 10:36:07 -05:00
schumi2004 3170060f37 Make Movie Title and Status sortable on Wanted tab (#662) 2017-02-08 09:22:49 -05:00
Leonardo Galli 35b384439f Bulk Import. (#583)
* First pass at bulk import.

* First pass at paging implementation for bulk import.

* Another pass at UI for bulk import

WHY WON'T THE ROWS SELECT?!?!

* Paging mostly done. UI needs to show loading still.

* Fix for selection

* fixes.

* Add caching to bulk import

* Tried to fix paging.

* Fix has next

* Fix link error.

* Pageable now works almost perfectly.

Collection now works really nicely when paged. Also movies from different pages can be added no problemo.

* /bulk-import: ProfileCell

Various other QoL changes

* Profile selection works now

Still kinda hacky

* Default monitored to true.

* Add Monitor Cell

Update styling, added path tooltip as well

* Update model when changing tmdbId

Ensure monitor status doesn't change as well

* Added spinner feedback for tmdbid cell.

* /bulk-import: Add page-size selector
2017-02-07 19:09:36 -05:00
199 changed files with 4852 additions and 1290 deletions
+8 -5
View File
@@ -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 -4
View File
@@ -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
View File
@@ -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);
}
}
}
+11 -9
View File
@@ -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>
+18 -2
View File
@@ -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)
+1 -1
View File
@@ -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;
}
}
}
+4 -34
View File
@@ -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()
@@ -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);
}
}
}
@@ -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");
}
}
}
@@ -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);
}
}
}
@@ -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
}
}
@@ -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; }
}
}
@@ -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, "Cant 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];
}
}
}
@@ -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; }
}
}
@@ -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; }
}
}
@@ -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; }
}
}
@@ -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