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

Compare commits

...

29 Commits

Author SHA1 Message Date
Leonardo Galli
81ebbcad70 Now hidden files are ignored :). Fixes #166. 2017-01-11 23:05:11 +01:00
Leonardo Galli
92e9dc6ee1 Fix sorting of unkown release date. 2017-01-11 22:39:07 +01:00
Leonardo Galli
5e8b617625 Sorting now working according to quality in release collection. Fixes #85. 2017-01-11 22:27:37 +01:00
Leonardo Galli
2cbe17151d Correctly check if inCinemas date is present. Creates issue with sorting, but eh. Fixes 140. 2017-01-11 22:02:24 +01:00
Devin Buhl
95bd615718 Merge pull request #169 from Radarr/patch/big-movie-revenues
Problem with Avatar (2009) #168
2017-01-11 15:53:16 -05:00
Leonardo Galli
8e8c4ff497 Update parser to recognize [] and year at the beginning. Fixes #155, fixes #137 and fixes #136. 2017-01-11 21:49:59 +01:00
Devin Buhl
5eddcc1660 Problem with Avatar (2009) #168 2017-01-11 15:48:19 -05:00
Tim Turner
d42165a93a Clean up basic movie naming
Fixes #132
2017-01-11 15:03:55 -05:00
Devin Buhl
99012d8a40 Merge pull request #163 from Radarr/patch/fix-plex-notifs
update plex movie libraries instead of series
2017-01-11 12:35:01 -05:00
Devin Buhl
8bc42c76e4 update plex movie libraries instead of series 2017-01-11 12:25:27 -05:00
Leonardo Galli
b7f72c6259 Merge pull request #160 from Radarr/patch/fix-newznab
fix some spelling mistakes and update the newznab api 'imdbid'
2017-01-11 16:59:09 +01:00
Devin Buhl
64ef8db037 fix some spelling mistakes and update the newznab api 'imdbid' 2017-01-11 10:51:06 -05:00
Leonardo Galli
3b5887bf09 Merge pull request #133 from Radarr/fix-manual-import
Fix Manual Import & Drone Factory
2017-01-11 16:39:09 +01:00
Leonardo Galli
40a75949ba Merge pull request #156 from CHBMB/develop
aarch64 docker container added to readme.
2017-01-11 16:33:36 +01:00
Neil
6747267d19 aarch64 docker container added to readme. 2017-01-11 15:12:26 +00:00
Devin Buhl
13db03f97c Merge pull request #154 from Radarr/patch/remove-fanzub
removed indexer Fanzub - site shutdown
2017-01-11 10:12:03 -05:00
Devin Buhl
cd68eea790 removed indexer Fanzub - site shutdown 2017-01-11 09:40:42 -05:00
Devin Buhl
cfe55d00ae Merge pull request #148 from Radarr/patch/newznab-imdb
search imdbid for usenet indexers that support it
2017-01-11 08:53:28 -05:00
Devin Buhl
1d88313424 #146 search imdbid for usenet indexers that support it 2017-01-11 08:42:29 -05:00
Mike
9513068467 Get rid of unnecessary AppVeyor builds. 2017-01-11 08:57:15 +01:00
Tim Turner
9206258370 Fixes Manual Import and DroneFactory
Automatic Import still doesn't work - waiting for answer from Sonarr
team.
2017-01-10 21:12:47 -05:00
Tim Turner
0fa1509ca6 Manual Import works
Automatically importing still broken
2017-01-10 19:37:17 -05:00
Devin Buhl
c5eb772f7a Merge pull request #130 from Radarr/newznab-patch
Add category 2035 to Newznab providers for WEB-DL search support. #123
2017-01-10 19:14:21 -05:00
Devin Buhl
04fec6d4d8 Add category 2035 to Newznab providers for WEB-DL search support. #123 2017-01-10 19:09:12 -05:00
Devin Buhl
d0b5e380d7 Merge pull request #122 from Radarr/transmission-patch
Fix transmission
2017-01-10 17:43:16 -05:00
Devin Buhl
df69c58d2b Fix transmission 2017-01-10 17:37:23 -05:00
Leonardo Galli
27114c9399 Merge pull request #119 from CHBMB/develop
Update readme.md
2017-01-10 23:25:05 +01:00
Neil
cfca07996b Update readme.md 2017-01-10 22:19:12 +00:00
Leonardo Galli
e97b80f630 Update readme.md 2017-01-10 23:19:00 +01:00
50 changed files with 957 additions and 604 deletions

View File

@@ -35,4 +35,11 @@ artifacts:
cache: cache:
- '%USERPROFILE%\.nuget\packages' - '%USERPROFILE%\.nuget\packages'
- node_modules - node_modules
only_commits:
files:
- src/
- osx/
- gulp/
- logo/

View File

@@ -2,7 +2,7 @@
| Service | Master | Develop | | Service | Master | Develop |
|----------|:---------------------------:|:----------------------------:| |----------|:---------------------------:|:----------------------------:|
| AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | | AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr-usby1/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
| Travis | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) | | Travis | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) |
This fork of Sonarr aims to turn it into something like Couchpotato. This fork of Sonarr aims to turn it into something like Couchpotato.
@@ -25,7 +25,11 @@ This fork of Sonarr aims to turn it into something like Couchpotato.
## Download ## Download
The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases. The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases.
A docker container can be found here: https://hub.docker.com/r/lsiodev/radarr/.
Docker containers from [linuxserver.io](https://linuxserver.io) can be found here.
* [Radarr (x64)](https://hub.docker.com/r/linuxserver/radarr/)
* [Radarr (armhf)](https://hub.docker.com/r/lsioarmhf/radarr/)
* [Radarr (aarch64)](https://hub.docker.com/r/lsioarmhf/radarr-aarch64/)
For more up to date versions (but also sometimes broken), daily builds can be found here: For more up to date versions (but also sometimes broken), daily builds can be found here:
* [OSX](https://leonardogalli.ch/radarr/builds/latest.php?os=osx) * [OSX](https://leonardogalli.ch/radarr/builds/latest.php?os=osx)

View File

@@ -97,7 +97,7 @@ namespace NzbDrone.Api.Indexers
{ {
Guid = releaseInfo.Guid, Guid = releaseInfo.Guid,
Quality = parsedMovieInfo.Quality, Quality = parsedMovieInfo.Quality,
//QualityWeight QualityWeight = parsedMovieInfo.Quality.Quality.Id, //Id kinda hacky for wheight, but what you gonna do? TODO: Fix this shit!
Age = releaseInfo.Age, Age = releaseInfo.Age,
AgeHours = releaseInfo.AgeHours, AgeHours = releaseInfo.AgeHours,
AgeMinutes = releaseInfo.AgeMinutes, AgeMinutes = releaseInfo.AgeMinutes,

View File

@@ -1,51 +0,0 @@
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Fanzub;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.FanzubTests
{
[TestFixture]
public class FanzubFixture : CoreTest<Fanzub>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Fanzub",
Settings = new FanzubSettings()
};
}
[Test]
public void should_parse_recent_feed_from_fanzub()
{
var recentFeed = ReadAllText(@"Files/Indexers/Fanzub/fanzub.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(3);
var releaseInfo = releases.First();
releaseInfo.Title.Should().Be("[Vivid] Hanayamata - 10 [A33D6606]");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
releaseInfo.DownloadUrl.Should().Be("http://fanzub.com/nzb/296464/Vivid%20Hanayamata%20-%2010.nzb");
releaseInfo.InfoUrl.Should().BeNullOrEmpty();
releaseInfo.CommentUrl.Should().BeNullOrEmpty();
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2014/09/13 12:56:53"));
releaseInfo.Size.Should().Be(556246858);
}
}
}

View File

@@ -249,7 +249,6 @@
<Compile Include="IndexerTests\NewznabTests\NewznabFixture.cs" /> <Compile Include="IndexerTests\NewznabTests\NewznabFixture.cs" />
<Compile Include="IndexerTests\NewznabTests\NewznabRequestGeneratorFixture.cs" /> <Compile Include="IndexerTests\NewznabTests\NewznabRequestGeneratorFixture.cs" />
<Compile Include="IndexerTests\NewznabTests\NewznabSettingFixture.cs" /> <Compile Include="IndexerTests\NewznabTests\NewznabSettingFixture.cs" />
<Compile Include="IndexerTests\FanzubTests\FanzubFixture.cs" />
<Compile Include="IndexerTests\OmgwtfnzbsTests\OmgwtfnzbsFixture.cs" /> <Compile Include="IndexerTests\OmgwtfnzbsTests\OmgwtfnzbsFixture.cs" />
<Compile Include="IndexerTests\SeasonSearchFixture.cs" /> <Compile Include="IndexerTests\SeasonSearchFixture.cs" />
<Compile Include="IndexerTests\TestIndexer.cs" /> <Compile Include="IndexerTests\TestIndexer.cs" />

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(114)]
public class remove_fanzub : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "Fanzub" });
}
}
}

View File

@@ -265,7 +265,6 @@ namespace NzbDrone.Core.DecisionEngine
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name); _logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name);
return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS! return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS!
return null;
} }
return null; return null;

View File

@@ -165,6 +165,31 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return hash; return hash;
} }
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
{
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
if (remoteMovie.Release.Age < 14 && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
remoteMovie.Release.Age > 14 && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
{
_proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings);
if (remoteMovie.Release.Age < 14 && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
remoteMovie.Release.Age > 14 && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
protected override void Test(List<ValidationFailure> failures) protected override void Test(List<ValidationFailure> failures)
{ {
failures.AddIfNotNull(TestConnection()); failures.AddIfNotNull(TestConnection());

View File

@@ -56,10 +56,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")] [FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
public string TvDirectory { get; set; } public string TvDirectory { get; set; }
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing movies that we're released within the last 14 days")]
public int RecentTvPriority { get; set; } public int RecentTvPriority { get; set; }
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing movies that we're released over 14 days ago")]
public int OlderTvPriority { get; set; } public int OlderTvPriority { get; set; }
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)] [FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]

View File

@@ -1,30 +0,0 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.Fanzub
{
public class Fanzub : HttpIndexerBase<FanzubSettings>
{
public override string Name => "Fanzub";
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public Fanzub(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new FanzubRequestGenerator() { Settings = Settings };
}
public override IParseIndexerResponse GetParser()
{
return new RssParser() { UseEnclosureUrl = true, UseEnclosureLength = true };
}
}
}

View File

@@ -1,94 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.Fanzub
{
public class FanzubRequestGenerator : IIndexerRequestGenerator
{
private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled);
public FanzubSettings Settings { get; set; }
public int PageSize { get; set; }
public FanzubRequestGenerator()
{
PageSize = 100;
}
public virtual IndexerPageableRequestChain GetRecentRequests()
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(null));
return pageableRequests;
}
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var searchTitles = searchCriteria.QueryTitles.SelectMany(v => GetTitleSearchStrings(v, searchCriteria.AbsoluteEpisodeNumber)).ToList();
pageableRequests.Add(GetPagedRequests(string.Join("|", searchTitles)));
return pageableRequests;
}
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
private IEnumerable<IndexerRequest> GetPagedRequests(string query)
{
var url = new StringBuilder();
url.AppendFormat("{0}?cat=anime&max={1}", Settings.BaseUrl, PageSize);
if (query.IsNotNullOrWhiteSpace())
{
url.AppendFormat("&q={0}", query);
}
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
}
private IEnumerable<string> GetTitleSearchStrings(string title, int absoluteEpisodeNumber)
{
var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" };
return formats.Select(s => "\"" + string.Format(s, CleanTitle(title), absoluteEpisodeNumber) + "\"");
}
private string CleanTitle(string title)
{
return RemoveCharactersRegex.Replace(title, "");
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,33 +0,0 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Fanzub
{
public class FanzubSettingsValidator : AbstractValidator<FanzubSettings>
{
public FanzubSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
}
}
public class FanzubSettings : IProviderConfig
{
private static readonly FanzubSettingsValidator Validator = new FanzubSettingsValidator();
public FanzubSettings()
{
BaseUrl = "http://fanzub.com/rss/";
}
[FieldDefinition(0, Label = "Rss URL", HelpText = "Enter to URL to an Fanzub compatible RSS feed")]
public string BaseUrl { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -106,8 +106,8 @@ namespace NzbDrone.Core.Indexers.Newznab
} }
if (capabilities.SupportedTvSearchParameters != null && if (capabilities.SupportedTvSearchParameters != null &&
new[] { "q", "imdb" }.Any(v => capabilities.SupportedMovieSearchParamters.Contains(v)) && new[] { "q", "imdbid" }.Any(v => capabilities.SupportedMovieSearchParameters.Contains(v)) &&
new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParamters.Contains(v))) new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParameters.Contains(v)))
{ {
return null; return null;
} }

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public int MaxPageSize { get; set; } public int MaxPageSize { get; set; }
public string[] SupportedSearchParameters { get; set; } public string[] SupportedSearchParameters { get; set; }
public string[] SupportedTvSearchParameters { get; set; } public string[] SupportedTvSearchParameters { get; set; }
public string[] SupportedMovieSearchParamters { get; set; } public string[] SupportedMovieSearchParameters { get; set; }
public bool SupportsAggregateIdSearch { get; set; } public bool SupportsAggregateIdSearch { get; set; }
public List<NewznabCategory> Categories { get; set; } public List<NewznabCategory> Categories { get; set; }
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Newznab
DefaultPageSize = 100; DefaultPageSize = 100;
MaxPageSize = 100; MaxPageSize = 100;
SupportedSearchParameters = new[] { "q" }; SupportedSearchParameters = new[] { "q" };
SupportedMovieSearchParamters = new[] { "q", "imdb", "imdbtitle", "imdbyear" }; SupportedMovieSearchParameters = new[] { "q", "imdbid", "imdbtitle", "imdbyear" };
SupportedTvSearchParameters = new[] { "q", "rid", "season", "ep" }; // This should remain 'rid' for older newznab installs. SupportedTvSearchParameters = new[] { "q", "rid", "season", "ep" }; // This should remain 'rid' for older newznab installs.
SupportsAggregateIdSearch = false; SupportsAggregateIdSearch = false;
Categories = new List<NewznabCategory>(); Categories = new List<NewznabCategory>();

View File

@@ -102,11 +102,11 @@ namespace NzbDrone.Core.Indexers.Newznab
var xmlMovieSearch = xmlSearching.Element("movie-search"); var xmlMovieSearch = xmlSearching.Element("movie-search");
if (xmlMovieSearch == null || xmlMovieSearch.Attribute("available").Value != "yes") if (xmlMovieSearch == null || xmlMovieSearch.Attribute("available").Value != "yes")
{ {
capabilities.SupportedMovieSearchParamters = null; capabilities.SupportedMovieSearchParameters = null;
} }
else if (xmlMovieSearch.Attribute("supportedParams") != null) else if (xmlMovieSearch.Attribute("supportedParams") != null)
{ {
capabilities.SupportedMovieSearchParamters = xmlMovieSearch.Attribute("supportedParams").Value.Split(','); capabilities.SupportedMovieSearchParameters = xmlMovieSearch.Attribute("supportedParams").Value.Split(',');
capabilities.SupportsAggregateIdSearch = true; capabilities.SupportsAggregateIdSearch = true;
} }
} }

View File

@@ -91,8 +91,8 @@ namespace NzbDrone.Core.Indexers.Newznab
{ {
var capabilities = _capabilitiesProvider.GetCapabilities(Settings); var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
return capabilities.SupportedMovieSearchParamters != null && return capabilities.SupportedMovieSearchParameters != null &&
capabilities.SupportedMovieSearchParamters.Contains("imdb"); capabilities.SupportedMovieSearchParameters.Contains("imdbid");
} }
} }
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Indexers.Newznab
var capabilities = _capabilitiesProvider.GetCapabilities(Settings); var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
if (capabilities.SupportedMovieSearchParamters != null) if (capabilities.SupportedMovieSearchParameters != null)
{ {
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "movie", "")); pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "movie", ""));
} }
@@ -124,7 +124,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
if(SupportsMovieSearch) if (SupportsMovieSearch)
{ {
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie",
string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY

View File

@@ -48,9 +48,7 @@ namespace NzbDrone.Core.Indexers.Newznab
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
{ {
releaseInfo = base.ProcessItem(item, releaseInfo); releaseInfo = base.ProcessItem(item, releaseInfo);
releaseInfo.ImdbId = GetImdbId(item);
releaseInfo.TvdbId = GetTvdbId(item);
releaseInfo.TvRageId = GetTvRageId(item);
return releaseInfo; return releaseInfo;
} }
@@ -114,27 +112,14 @@ namespace NzbDrone.Core.Indexers.Newznab
return url; return url;
} }
protected virtual int GetTvdbId(XElement item) protected virtual int GetImdbId(XElement item)
{ {
var tvdbIdString = TryGetNewznabAttribute(item, "tvdbid"); var imdbIdString = TryGetNewznabAttribute(item, "imdb");
int tvdbId; int imdbId;
if (!tvdbIdString.IsNullOrWhiteSpace() && int.TryParse(tvdbIdString, out tvdbId)) if (!imdbIdString.IsNullOrWhiteSpace() && int.TryParse(imdbIdString, out imdbId))
{ {
return tvdbId; return imdbId;
}
return 0;
}
protected virtual int GetTvRageId(XElement item)
{
var tvRageIdString = TryGetNewznabAttribute(item, "rageid");
int tvRageId;
if (!tvRageIdString.IsNullOrWhiteSpace() && int.TryParse(tvRageIdString, out tvRageId))
{
return tvRageId;
} }
return 0; return 0;

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public NewznabSettings() public NewznabSettings()
{ {
Categories = new[] { 2030, 2040, 2050 }; Categories = new[] { 2030, 2035, 2040, 2050 };
AnimeCategories = Enumerable.Empty<int>(); AnimeCategories = Enumerable.Empty<int>();
} }

View File

@@ -0,0 +1,17 @@
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class DownloadedMovieScanCommand : Command
{
public override bool SendUpdatesToClient => SendUpdates;
public bool SendUpdates { get; set; }
// Properties used by third-party apps, do not modify.
public string Path { get; set; }
public string DownloadClientId { get; set; }
public ImportMode ImportMode { get; set; }
}
}

View File

@@ -1,265 +1,107 @@
using System.Collections.Generic; using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog; using System.Text;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IDownloadedMovieImportService public class DownloadedMovieCommandService : IExecute<DownloadedMovieScanCommand>
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
}
public class DownloadedMovieImportService : IDownloadedMovieImportService
{ {
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService; private readonly IConfigService _configService;
private readonly IMovieService _movieService;
private readonly IParsingService _parsingService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly IDetectSample _detectSample;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadedMovieImportService(IDiskProvider diskProvider, public DownloadedMovieCommandService(IDownloadedMovieImportService downloadedMovieImportService,
IDiskScanService diskScanService, ITrackedDownloadService trackedDownloadService,
IMovieService movieService, IDiskProvider diskProvider,
IParsingService parsingService, IConfigService configService,
IMakeImportDecision importDecisionMaker, Logger logger)
IImportApprovedMovie importApprovedMovie,
IDetectSample detectSample,
Logger logger)
{ {
_downloadedMovieImportService = downloadedMovieImportService;
_trackedDownloadService = trackedDownloadService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_diskScanService = diskScanService; _configService = configService;
_movieService = movieService;
_parsingService = parsingService;
_importDecisionMaker = importDecisionMaker;
_importApprovedMovie = importApprovedMovie;
_detectSample = detectSample;
_logger = logger; _logger = logger;
} }
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo) private List<ImportResult> ProcessDroneFactoryFolder()
{ {
var results = new List<ImportResult>(); var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName)) if (string.IsNullOrEmpty(downloadedEpisodesFolder))
{ {
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null); _logger.Trace("Drone Factory folder is not configured");
results.AddRange(folderResults);
}
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
{
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
results.AddRange(fileResults);
}
return results;
}
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
{
if (_diskProvider.FolderExists(path))
{
var directoryInfo = new DirectoryInfo(path);
if (movie == null)
{
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
if (_diskProvider.FileExists(path))
{
var fileInfo = new FileInfo(path);
if (movie == null)
{
return ProcessFile(fileInfo, importMode, downloadClientItem);
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
_logger.Error("Import failed, path does not exist or is not accessible by Sonarr: {0}", path);
return new List<ImportResult>();
}
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
{
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
foreach (var videoFile in videoFiles)
{
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
if (episodeParseResult == null)
{
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
return false;
}
var size = _diskProvider.GetFileSize(videoFile);
var quality = QualityParser.ParseQuality(videoFile);
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
{
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
return false;
}
}
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
{
_logger.Warn("RAR file detected, will require manual cleanup");
return false;
}
return true;
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var movie = _parsingService.GetMovie(cleanedUpName);
if (movie == null)
{
_logger.Debug("Unknown Movie {0}", cleanedUpName);
return new List<ImportResult>
{
UnknownMovieResult("Unknown Movie")
};
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (_movieService.MoviePathExists(directoryInfo.FullName))
{
_logger.Warn("Unable to process folder that is mapped to an existing show");
return new List<ImportResult>(); return new List<ImportResult>();
} }
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); if (!_diskProvider.FolderExists(downloadedEpisodesFolder))
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
if (folderInfo != null)
{ {
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality); _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder);
return new List<ImportResult>();
} }
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); return _downloadedMovieImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder));
}
if (downloadClientItem == null) private List<ImportResult> ProcessPath(DownloadedMovieScanCommand message)
{
if (!_diskProvider.FolderExists(message.Path) && !_diskProvider.FileExists(message.Path))
{ {
foreach (var videoFile in videoFiles) _logger.Warn("Folder/File specified for import scan [{0}] doesn't exist.", message.Path);
return new List<ImportResult>();
}
if (message.DownloadClientId.IsNotNullOrWhiteSpace())
{
var trackedDownload = _trackedDownloadService.Find(message.DownloadClientId);
if (trackedDownload != null)
{ {
if (_diskProvider.IsFileLocked(videoFile)) _logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path);
{
return new List<ImportResult> return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
{ }
FileIsLockedResult(videoFile) else
}; {
} _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path);
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
} }
} }
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true); return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); }
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && public void Execute(DownloadedMovieScanCommand message)
importResults.Any(i => i.Result == ImportResultType.Imported) && {
ShouldDeleteFolder(directoryInfo, movie)) List<ImportResult> importResults;
if (message.Path.IsNotNullOrWhiteSpace())
{ {
_logger.Debug("Deleting folder after importing valid files"); importResults = ProcessPath(message);
_diskProvider.DeleteFolder(directoryInfo.FullName, true); }
else
{
importResults = ProcessDroneFactoryFolder();
} }
return importResults; if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
if (movie == null)
{ {
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name); // Atm we don't report it as a command failure, coz that would cause the download to be failed.
// Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later.
return new List<ImportResult> //message.SetMessage("Failed to import");
{
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
};
} }
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
{
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
};
}
if (downloadClientItem == null)
{
if (_diskProvider.IsFileLocked(fileInfo.FullName))
{
return new List<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
}
private string GetCleanedUpFolderName(string folder)
{
folder = folder.Replace("_UNPACK_", "")
.Replace("_FAILED_", "");
return folder;
}
private ImportResult FileIsLockedResult(string videoFile)
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
}
private ImportResult UnknownMovieResult(string message, string videoFile = null)
{
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
} }
} }
} }

View File

@@ -0,0 +1,266 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles.Commands;
namespace NzbDrone.Core.MediaFiles
{
public interface IDownloadedMovieImportService
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
}
public class DownloadedMovieImportService : IDownloadedMovieImportService
{
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly IMovieService _movieService;
private readonly IParsingService _parsingService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly IDetectSample _detectSample;
private readonly Logger _logger;
public DownloadedMovieImportService(IDiskProvider diskProvider,
IDiskScanService diskScanService,
IMovieService movieService,
IParsingService parsingService,
IMakeImportDecision importDecisionMaker,
IImportApprovedMovie importApprovedMovie,
IDetectSample detectSample,
Logger logger)
{
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_movieService = movieService;
_parsingService = parsingService;
_importDecisionMaker = importDecisionMaker;
_importApprovedMovie = importApprovedMovie;
_detectSample = detectSample;
_logger = logger;
}
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
{
var results = new List<ImportResult>();
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
{
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
results.AddRange(folderResults);
}
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
{
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
results.AddRange(fileResults);
}
return results;
}
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
{
if (_diskProvider.FolderExists(path))
{
var directoryInfo = new DirectoryInfo(path);
if (movie == null)
{
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
if (_diskProvider.FileExists(path))
{
var fileInfo = new FileInfo(path);
if (movie == null)
{
return ProcessFile(fileInfo, importMode, downloadClientItem);
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
_logger.Error("Import failed, path does not exist or is not accessible by Radarr: {0}", path);
return new List<ImportResult>();
}
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
{
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
foreach (var videoFile in videoFiles)
{
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
if (episodeParseResult == null)
{
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
return false;
}
var size = _diskProvider.GetFileSize(videoFile);
var quality = QualityParser.ParseQuality(videoFile);
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
{
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
return false;
}
}
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
{
_logger.Warn("RAR file detected, will require manual cleanup");
return false;
}
return true;
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var movie = _parsingService.GetMovie(cleanedUpName);
if (movie == null)
{
_logger.Debug("Unknown Movie {0}", cleanedUpName);
return new List<ImportResult>
{
UnknownMovieResult("Unknown Movie")
};
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (_movieService.MoviePathExists(directoryInfo.FullName))
{
_logger.Warn("Unable to process folder that is mapped to an existing show");
return new List<ImportResult>();
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
if (folderInfo != null)
{
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
if (downloadClientItem == null)
{
foreach (var videoFile in videoFiles)
{
if (_diskProvider.IsFileLocked(videoFile))
{
return new List<ImportResult>
{
FileIsLockedResult(videoFile)
};
}
}
}
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
importResults.Any(i => i.Result == ImportResultType.Imported) &&
ShouldDeleteFolder(directoryInfo, movie))
{
_logger.Debug("Deleting folder after importing valid files");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
}
return importResults;
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
if (movie == null)
{
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name);
return new List<ImportResult>
{
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
};
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
{
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
};
}
if (downloadClientItem == null)
{
if (_diskProvider.IsFileLocked(fileInfo.FullName))
{
return new List<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
}
private string GetCleanedUpFolderName(string folder)
{
folder = folder.Replace("_UNPACK_", "")
.Replace("_FAILED_", "");
return folder;
}
private ImportResult FileIsLockedResult(string videoFile)
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
}
private ImportResult UnknownMovieResult(string message, string videoFile = null)
{
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
}
}
}

View File

@@ -10,5 +10,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List<int> EpisodeIds { get; set; } public List<int> EpisodeIds { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public int MovieId { get; set; }
} }
} }

View File

@@ -17,5 +17,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
public Movie Movie { get; set; }
} }
} }

View File

@@ -30,11 +30,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private readonly IDiskScanService _diskScanService; private readonly IDiskScanService _diskScanService;
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly IMovieService _movieService;
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedEpisodes _importApprovedEpisodes; private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly ITrackedDownloadService _trackedDownloadService; private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
@@ -43,11 +46,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
IDiskScanService diskScanService, IDiskScanService diskScanService,
IMakeImportDecision importDecisionMaker, IMakeImportDecision importDecisionMaker,
ISeriesService seriesService, ISeriesService seriesService,
IMovieService movieService,
IEpisodeService episodeService, IEpisodeService episodeService,
IVideoFileInfoReader videoFileInfoReader, IVideoFileInfoReader videoFileInfoReader,
IImportApprovedEpisodes importApprovedEpisodes, IImportApprovedEpisodes importApprovedEpisodes,
IImportApprovedMovie importApprovedMovie,
ITrackedDownloadService trackedDownloadService, ITrackedDownloadService trackedDownloadService,
IDownloadedEpisodesImportService downloadedEpisodesImportService, IDownloadedEpisodesImportService downloadedEpisodesImportService,
IDownloadedMovieImportService downloadedMovieImportService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
@@ -56,11 +62,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_diskScanService = diskScanService; _diskScanService = diskScanService;
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_seriesService = seriesService; _seriesService = seriesService;
_movieService = movieService;
_episodeService = episodeService; _episodeService = episodeService;
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_importApprovedEpisodes = importApprovedEpisodes; _importApprovedEpisodes = importApprovedEpisodes;
_importApprovedMovie = importApprovedMovie;
_trackedDownloadService = trackedDownloadService; _trackedDownloadService = trackedDownloadService;
_downloadedEpisodesImportService = downloadedEpisodesImportService; _downloadedEpisodesImportService = downloadedEpisodesImportService;
_downloadedMovieImportService = downloadedMovieImportService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@@ -126,62 +135,128 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
var relativeFile = folder.GetRelativePath(file); var relativeFile = folder.GetRelativePath(file);
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); var movie = _parsingService.GetMovie(relativeFile.Split('\\', '/')[0]);
if (series == null) if (movie == null)
{ {
series = _parsingService.GetSeries(relativeFile); movie = _parsingService.GetMovie(relativeFile);
} }
if (series == null && downloadId.IsNotNullOrWhiteSpace()) if (movie == null && downloadId.IsNotNullOrWhiteSpace())
{ {
var trackedDownload = _trackedDownloadService.Find(downloadId); var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series; movie = trackedDownload.RemoteMovie.Movie;
} }
if (series == null) if (movie == null)
{ {
var localEpisode = new LocalEpisode(); var localMovie = new LocalMovie()
localEpisode.Path = file; {
localEpisode.Quality = QualityParser.ParseQuality(file); Path = file,
localEpisode.Size = _diskProvider.GetFileSize(file); Quality = QualityParser.ParseQuality(file),
Size = _diskProvider.GetFileSize(file)
};
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId);
} }
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file}, var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
series, null, SceneSource(series, folder)); movie, null, SceneSource(movie, folder));
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
} }
//private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
//{
// if (folder.IsNullOrWhiteSpace())
// {
// folder = new FileInfo(file).Directory.FullName;
// }
// var relativeFile = folder.GetRelativePath(file);
// var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
// if (series == null)
// {
// series = _parsingService.GetSeries(relativeFile);
// }
// if (series == null && downloadId.IsNotNullOrWhiteSpace())
// {
// var trackedDownload = _trackedDownloadService.Find(downloadId);
// series = trackedDownload.RemoteEpisode.Series;
// }
// if (series == null)
// {
// var localEpisode = new LocalEpisode();
// localEpisode.Path = file;
// localEpisode.Quality = QualityParser.ParseQuality(file);
// localEpisode.Size = _diskProvider.GetFileSize(file);
// return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
// }
// var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
// series, null, SceneSource(series, folder));
// return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
//}
private bool SceneSource(Series series, string folder) private bool SceneSource(Series series, string folder)
{ {
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder)); return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder));
} }
private bool SceneSource(Movie movie, string folder)
{
return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(folder));
}
//private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
//{
// var item = new ManualImportItem();
// item.Path = decision.LocalEpisode.Path;
// item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
// item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
// item.DownloadId = downloadId;
// if (decision.LocalEpisode.Series != null)
// {
// item.Series = decision.LocalEpisode.Series;
// }
// if (decision.LocalEpisode.Episodes.Any())
// {
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
// item.Episodes = decision.LocalEpisode.Episodes;
// }
// item.Quality = decision.LocalEpisode.Quality;
// item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
// item.Rejections = decision.Rejections;
// return item;
//}
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId) private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
{ {
var item = new ManualImportItem(); var item = new ManualImportItem();
item.Path = decision.LocalEpisode.Path; item.Path = decision.LocalMovie.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); item.RelativePath = folder.GetRelativePath(decision.LocalMovie.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); item.Name = Path.GetFileNameWithoutExtension(decision.LocalMovie.Path);
item.DownloadId = downloadId; item.DownloadId = downloadId;
if (decision.LocalEpisode.Series != null) if (decision.LocalMovie.Movie != null)
{ {
item.Series = decision.LocalEpisode.Series; item.Movie = decision.LocalMovie.Movie;
} }
if (decision.LocalEpisode.Episodes.Any()) item.Quality = decision.LocalMovie.Quality;
{ item.Size = _diskProvider.GetFileSize(decision.LocalMovie.Path);
item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
item.Episodes = decision.LocalEpisode.Episodes;
}
item.Quality = decision.LocalEpisode.Quality;
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
item.Rejections = decision.Rejections; item.Rejections = decision.Rejections;
return item; return item;
@@ -199,45 +274,43 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
var file = message.Files[i]; var file = message.Files[i];
var series = _seriesService.GetSeries(file.SeriesId); var movie = _movieService.GetMovie(file.MovieId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds); var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo();
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = series.Path.IsParentPath(file.Path); var existingFile = movie.Path.IsParentPath(file.Path);
var localEpisode = new LocalEpisode var localMovie = new LocalMovie
{ {
ExistingFile = false, ExistingFile = false,
Episodes = episodes,
MediaInfo = mediaInfo, MediaInfo = mediaInfo,
ParsedEpisodeInfo = parsedEpisodeInfo, ParsedMovieInfo = parsedMovieInfo,
Path = file.Path, Path = file.Path,
Quality = file.Quality, Quality = file.Quality,
Series = series, Movie = movie,
Size = 0 Size = 0
}; };
//TODO: Cleanup non-tracked downloads //TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localEpisode); var importDecision = new ImportDecision(localMovie);
if (file.DownloadId.IsNullOrWhiteSpace()) if (file.DownloadId.IsNullOrWhiteSpace())
{ {
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode)); imported.AddRange(_importApprovedMovie.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
} }
else else
{ {
var trackedDownload = _trackedDownloadService.Find(file.DownloadId); var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult); imported.Add(importResult);
importedTrackedDownload.Add(new ManuallyImportedFile importedTrackedDownload.Add(new ManuallyImportedFile
{ {
TrackedDownload = trackedDownload, TrackedDownload = trackedDownload,
ImportResult = importResult ImportResult = importResult
}); });
} }
} }
@@ -249,20 +322,98 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{ {
if (_downloadedEpisodesImportService.ShouldDeleteFolder( if (_downloadedMovieImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) trackedDownload.RemoteMovie.Movie) && !trackedDownload.DownloadItem.IsReadOnly)
{ {
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
} }
} }
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, 1)) //TODO: trackedDownload.RemoteMovie.Movie.Count is always 1?
{ {
trackedDownload.State = TrackedDownloadStage.Imported; trackedDownload.State = TrackedDownloadStage.Imported;
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
} }
} }
} }
//public void Execute(ManualImportCommand message)
//{
// _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
// var imported = new List<ImportResult>();
// var importedTrackedDownload = new List<ManuallyImportedFile>();
// for (int i = 0; i < message.Files.Count; i++)
// {
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
// var file = message.Files[i];
// var series = _seriesService.GetSeries(file.SeriesId);
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
// var existingFile = series.Path.IsParentPath(file.Path);
// var localEpisode = new LocalEpisode
// {
// ExistingFile = false,
// Episodes = episodes,
// MediaInfo = mediaInfo,
// ParsedEpisodeInfo = parsedEpisodeInfo,
// Path = file.Path,
// Quality = file.Quality,
// Series = series,
// Size = 0
// };
// //TODO: Cleanup non-tracked downloads
// var importDecision = new ImportDecision(localEpisode);
// if (file.DownloadId.IsNullOrWhiteSpace())
// {
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
// }
// else
// {
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
// imported.Add(importResult);
// importedTrackedDownload.Add(new ManuallyImportedFile
// {
// TrackedDownload = trackedDownload,
// ImportResult = importResult
// });
// }
// }
// _logger.ProgressTrace("Manually imported {0} files", imported.Count);
// foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
// {
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
// {
// if (_downloadedEpisodesImportService.ShouldDeleteFolder(
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
// {
// _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
// }
// }
// if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
// {
// trackedDownload.State = TrackedDownloadStage.Imported;
// _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
// }
// }
//}
} }
} }

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public Production_Companies[] production_companies { get; set; } public Production_Companies[] production_companies { get; set; }
public Production_Countries[] production_countries { get; set; } public Production_Countries[] production_countries { get; set; }
public string release_date { get; set; } public string release_date { get; set; }
public int revenue { get; set; } public long revenue { get; set; }
public int runtime { get; set; } public int runtime { get; set; }
public Spoken_Languages[] spoken_languages { get; set; } public Spoken_Languages[] spoken_languages { get; set; }
public string status { get; set; } public string status { get; set; }

View File

@@ -189,7 +189,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
var lowerTitle = title.ToLower(); var lowerTitle = title.ToLower();
var parserResult = Parser.Parser.ParseMovieTitle(title); var parserResult = Parser.Parser.ParseMovieTitle(title, true);
var yearTerm = ""; var yearTerm = "";

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Notifications.Plex
{ {
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content) return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
.Sections .Sections
.Where(d => d.Type == "show") .Where(d => d.Type == "movie")
.Select(s => new PlexSection .Select(s => new PlexSection
{ {
Id = s.Id, Id = s.Id,
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.Notifications.Plex
return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response.Content) return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response.Content)
.MediaContainer .MediaContainer
.Sections .Sections
.Where(d => d.Type == "show") .Where(d => d.Type == "movie")
.ToList(); .ToList();
} }

View File

@@ -185,6 +185,7 @@
<Compile Include="Datastore\Migration\004_updated_history.cs" /> <Compile Include="Datastore\Migration\004_updated_history.cs" />
<Compile Include="Datastore\Migration\111_remove_bitmetv.cs" /> <Compile Include="Datastore\Migration\111_remove_bitmetv.cs" />
<Compile Include="Datastore\Migration\112_remove_torrentleech.cs" /> <Compile Include="Datastore\Migration\112_remove_torrentleech.cs" />
<Compile Include="Datastore\Migration\114_remove_fanzub.cs" />
<Compile Include="Datastore\Migration\113_remove_broadcasthenet.cs" /> <Compile Include="Datastore\Migration\113_remove_broadcasthenet.cs" />
<Compile Include="Datastore\Migration\108_update_schedule_interval.cs" /> <Compile Include="Datastore\Migration\108_update_schedule_interval.cs" />
<Compile Include="Datastore\Migration\107_fix_movie_files.cs" /> <Compile Include="Datastore\Migration\107_fix_movie_files.cs" />
@@ -582,9 +583,6 @@
<Compile Include="Indexers\Exceptions\RequestLimitReachedException.cs" /> <Compile Include="Indexers\Exceptions\RequestLimitReachedException.cs" />
<Compile Include="Indexers\Exceptions\UnsupportedFeedException.cs" /> <Compile Include="Indexers\Exceptions\UnsupportedFeedException.cs" />
<Compile Include="Indexers\EzrssTorrentRssParser.cs" /> <Compile Include="Indexers\EzrssTorrentRssParser.cs" />
<Compile Include="Indexers\Fanzub\Fanzub.cs" />
<Compile Include="Indexers\Fanzub\FanzubRequestGenerator.cs" />
<Compile Include="Indexers\Fanzub\FanzubSettings.cs" />
<Compile Include="Indexers\FetchAndParseRssService.cs" /> <Compile Include="Indexers\FetchAndParseRssService.cs" />
<Compile Include="Indexers\PassThePopcorn\PassThePopcorn.cs" /> <Compile Include="Indexers\PassThePopcorn\PassThePopcorn.cs" />
<Compile Include="Indexers\PassThePopcorn\PassThePopcornApi.cs" /> <Compile Include="Indexers\PassThePopcorn\PassThePopcornApi.cs" />
@@ -707,10 +705,12 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" /> <Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" /> <Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" /> <Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedMovieScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" /> <Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" /> <Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
<Compile Include="MediaFiles\DownloadedMovieImportService.cs" />
<Compile Include="MediaFiles\MovieFileMovingService.cs" /> <Compile Include="MediaFiles\MovieFileMovingService.cs" />
<Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" /> <Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" />
<Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" /> <Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" />

View File

@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Parser.Model
public DownloadProtocol DownloadProtocol { get; set; } public DownloadProtocol DownloadProtocol { get; set; }
public int TvdbId { get; set; } public int TvdbId { get; set; }
public int TvRageId { get; set; } public int TvRageId { get; set; }
public int ImdbId { get; set; }
public DateTime PublishDate { get; set; } public DateTime PublishDate { get; set; }
public string Origin { get; set; } public string Origin { get; set; }
@@ -82,6 +83,7 @@ namespace NzbDrone.Core.Parser.Model
stringBuilder.AppendLine("DownloadProtocol: " + DownloadProtocol ?? "Empty"); stringBuilder.AppendLine("DownloadProtocol: " + DownloadProtocol ?? "Empty");
stringBuilder.AppendLine("TvdbId: " + TvdbId ?? "Empty"); stringBuilder.AppendLine("TvdbId: " + TvdbId ?? "Empty");
stringBuilder.AppendLine("TvRageId: " + TvRageId ?? "Empty"); stringBuilder.AppendLine("TvRageId: " + TvRageId ?? "Empty");
stringBuilder.AppendLine("ImdbId: " + ImdbId ?? "Empty");
stringBuilder.AppendLine("PublishDate: " + PublishDate ?? "Empty"); stringBuilder.AppendLine("PublishDate: " + PublishDate ?? "Empty");
return stringBuilder.ToString(); return stringBuilder.ToString();
default: default:

View File

@@ -36,6 +36,15 @@ namespace NzbDrone.Core.Parser
//PassThePopcorn Torrent names: Star.Wars[PassThePopcorn] //PassThePopcorn Torrent names: Star.Wars[PassThePopcorn]
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
//That did not work? Maybe some tool uses [] for years. Who would do that?
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
};
private static readonly Regex[] ReportMovieTitleFolderRegex = new[]
{
//When year comes first.
new Regex(@"^(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?<title>.+?)?$")
}; };
private static readonly Regex[] ReportTitleRegex = new[] private static readonly Regex[] ReportTitleRegex = new[]
@@ -327,7 +336,7 @@ namespace NzbDrone.Core.Parser
{ {
var fileInfo = new FileInfo(path); var fileInfo = new FileInfo(path);
var result = ParseMovieTitle(fileInfo.Name); var result = ParseMovieTitle(fileInfo.Name, true);
if (result == null) if (result == null)
{ {
@@ -345,7 +354,7 @@ namespace NzbDrone.Core.Parser
} }
public static ParsedMovieInfo ParseMovieTitle(string title) public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
{ {
ParsedMovieInfo realResult = null; ParsedMovieInfo realResult = null;
@@ -376,7 +385,14 @@ namespace NzbDrone.Core.Parser
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty); simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty);
foreach (var regex in ReportMovieTitleRegex) var allRegexes = ReportMovieTitleRegex.ToList();
if (isDir)
{
allRegexes.AddRange(ReportMovieTitleFolderRegex);
}
foreach (var regex in allRegexes)
{ {
var match = regex.Matches(simpleTitle); var match = regex.Matches(simpleTitle);

View File

@@ -149,7 +149,11 @@ namespace NzbDrone.Core.RootFolders
foreach (string unmappedFolder in unmappedFolders) foreach (string unmappedFolder in unmappedFolders)
{ {
var di = new DirectoryInfo(unmappedFolder.Normalize()); var di = new DirectoryInfo(unmappedFolder.Normalize());
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName }); if (!di.Attributes.HasFlag(FileAttributes.System) && !di.Attributes.HasFlag(FileAttributes.Hidden))
{
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
}
} }
var setToRemove = SpecialFolders; var setToRemove = SpecialFolders;

View File

@@ -7,10 +7,16 @@ module.exports = TemplatedCell.extend({
var monthNames = ["January", "February", "March", "April", "May", "June", var monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" "July", "August", "September", "October", "November", "December"
]; ];
var cinemasDate = new Date(this.model.get("inCinemas"));
var year = cinemasDate.getFullYear(); this.$el.html("");
var month = monthNames[cinemasDate.getMonth()];
this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work. if (this.model.get("inCinemas")) {
return this; var cinemasDate = new Date(this.model.get("inCinemas"));
var year = cinemasDate.getFullYear();
var month = monthNames[cinemasDate.getMonth()];
this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work.
}
return this;
} }
}); });

View File

@@ -4,5 +4,7 @@ var QualityCellEditor = require('./Edit/QualityCellEditor');
module.exports = TemplatedCell.extend({ module.exports = TemplatedCell.extend({
className : 'quality-cell', className : 'quality-cell',
template : 'Cells/QualityCellTemplate', template : 'Cells/QualityCellTemplate',
editor : QualityCellEditor editor : QualityCellEditor,
});
});

View File

@@ -181,10 +181,13 @@ Handlebars.registerHelper('inCinemas', function() {
var year = d.getFullYear(); var year = d.getFullYear();
return "Available: " + day + ". " + month + " " + year; return "Available: " + day + ". " + month + " " + year;
} }
var cinemasDate = new Date(this.inCinemas); if (this.inCinemas) {
var year = cinemasDate.getFullYear(); var cinemasDate = new Date(this.inCinemas);
var month = monthNames[cinemasDate.getMonth()]; var year = cinemasDate.getFullYear();
return "In Cinemas: " + month + " " + year; var month = monthNames[cinemasDate.getMonth()];
return "In Cinemas: " + month + " " + year;
}
return "To be announced";
}); });
Handlebars.registerHelper('tvRageUrl', function() { Handlebars.registerHelper('tvRageUrl', function() {

View File

@@ -0,0 +1,43 @@
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectMovieLayout = require('../Movie/SelectMovieLayout');
module.exports = NzbDroneCell.extend({
className : 'series-title-cell editable',
events : {
'click' : '_onClick'
},
render : function() {
this.$el.empty();
var movie = this.model.get('movie');
if (movie)
{
this.$el.html(movie.title + " (" + movie.year + ")" );
}
this.delegateEvents();
return this;
},
_onClick : function () {
var view = new SelectMovieLayout();
this.listenTo(view, 'manualimport:selected:movie', this._setMovie);
vent.trigger(vent.Commands.OpenModal2Command, view);
},
_setMovie : function (e) {
if (this.model.has('movie') && e.model.id === this.model.get('movie').id) {
return;
}
this.model.set({
movie : e.model.toJSON()
});
}
});

View File

@@ -16,6 +16,7 @@ var QualityCell = require('./Cells/QualityCell');
var FileSizeCell = require('../Cells/FileSizeCell'); var FileSizeCell = require('../Cells/FileSizeCell');
var ApprovalStatusCell = require('../Cells/ApprovalStatusCell'); var ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
var ManualImportCollection = require('./ManualImportCollection'); var ManualImportCollection = require('./ManualImportCollection');
var MovieCell = require('./Cells/MovieCell');
var Messenger = require('../Shared/Messenger'); var Messenger = require('../Shared/Messenger');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
@@ -49,23 +50,29 @@ module.exports = Marionette.Layout.extend({
sortable : true sortable : true
}, },
{ {
name : 'series', name : 'movie',
label : 'Series', label : 'Movie',
cell : SeriesCell, cell : MovieCell,
sortable : true sortable : true
}, },
{ // {
name : 'seasonNumber', // name : 'series',
label : 'Season', // label : 'Series',
cell : SeasonCell, // cell : SeriesCell,
sortable : true // sortable : true
}, // },
{ // {
name : 'episodes', // name : 'seasonNumber',
label : 'Episode(s)', // label : 'Season',
cell : EpisodesCell, // cell : SeasonCell,
sortable : false // sortable : true
}, // },
// {
// name : 'episodes',
// label : 'Episode(s)',
// cell : EpisodesCell,
// sortable : false
// },
{ {
name : 'quality', name : 'quality',
label : 'Quality', label : 'Quality',
@@ -161,8 +168,8 @@ module.exports = Marionette.Layout.extend({
}, },
_automaticImport : function (e) { _automaticImport : function (e) {
CommandController.Execute('downloadedEpisodesScan', { CommandController.Execute('downloadedMovieScan', {
name : 'downloadedEpisodesScan', name : 'downloadedMovieScan',
path : e.folder path : e.folder
}); });
@@ -176,29 +183,36 @@ module.exports = Marionette.Layout.extend({
return; return;
} }
if (_.any(selected, function (model) { if(_.any(selected, function(model) {
return !model.has('series'); return !model.has('movie');
})) { })) {
this._showErrorMessage('Movie must be chosen for each selected file');
this._showErrorMessage('Series must be chosen for each selected file');
return; return;
} }
if (_.any(selected, function (model) { // if (_.any(selected, function (model) {
return !model.has('seasonNumber'); // return !model.has('series');
})) { // })) {
this._showErrorMessage('Season must be chosen for each selected file'); // this._showErrorMessage('Series must be chosen for each selected file');
return; // return;
} // }
if (_.any(selected, function (model) { // if (_.any(selected, function (model) {
return !model.has('episodes') || model.get('episodes').length === 0; // return !model.has('seasonNumber');
})) { // })) {
this._showErrorMessage('One or more episodes must be chosen for each selected file'); // this._showErrorMessage('Season must be chosen for each selected file');
return; // return;
} // }
// if (_.any(selected, function (model) {
// return !model.has('episodes') || model.get('episodes').length === 0;
// })) {
// this._showErrorMessage('One or more episodes must be chosen for each selected file');
// return;
// }
var importMode = this.ui.importMode.val(); var importMode = this.ui.importMode.val();
@@ -207,8 +221,9 @@ module.exports = Marionette.Layout.extend({
files : _.map(selected, function (file) { files : _.map(selected, function (file) {
return { return {
path : file.get('path'), path : file.get('path'),
seriesId : file.get('series').id, movieId : file.get('movie').id,
episodeIds : _.map(file.get('episodes'), 'id'), // seriesId : file.get('series').id,
// episodeIds : _.map(file.get('episodes'), 'id'),
quality : file.get('quality'), quality : file.get('quality'),
downloadId : file.get('downloadId') downloadId : file.get('downloadId')
}; };

View File

@@ -0,0 +1,101 @@
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var MoviesCollection = require('../../Movies/MoviesCollection');
var SelectRow = require('./SelectMovieRow');
module.exports = Marionette.Layout.extend({
template : 'ManualImport/Movie/SelectMovieLayoutTemplate',
regions : {
movie : '.x-movie'
},
ui : {
filter : '.x-filter'
},
columns : [
{
name : 'title',
label : 'Title',
cell : 'String',
sortValue : 'sortTitle'
}
],
initialize : function() {
this.movieCollection = MoviesCollection.clone();
this._setModelCollection();
this.listenTo(this.movieCollection, 'row:selected', this._onSelected);
this.listenTo(this, 'modal:afterShow', this._setFocus);
},
onRender : function() {
this.movieView = new Backgrid.Grid({
columns : this.columns,
collection : this.movieCollection,
className : 'table table-hover season-grid',
row : SelectRow
});
this.movie.show(this.movieView);
this._setupFilter();
},
_setupFilter : function () {
var self = this;
//TODO: This should be a mixin (same as Add Series searching)
this.ui.filter.keyup(function(e) {
if (_.contains([
9,
16,
17,
18,
19,
20,
33,
34,
35,
36,
37,
38,
39,
40,
91,
92,
93
], e.keyCode)) {
return;
}
self._filter(self.ui.filter.val());
});
},
_filter : function (term) {
this.movieCollection.setFilter(['title', term, 'contains']);
this._setModelCollection();
},
_onSelected : function (e) {
this.trigger('manualimport:selected:movie', { model: e.model });
vent.trigger(vent.Commands.CloseModal2Command);
},
_setFocus : function () {
this.ui.filter.focus();
},
_setModelCollection: function () {
var self = this;
_.each(this.movieCollection.models, function (model) {
model.collection = self.movieCollection;
});
}
});

View File

@@ -0,0 +1,30 @@
<div class="modal-content">
<div class="manual-import-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
Manual Import - Select Movie
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<input type="text" class="form-control x-filter" placeholder="Filter movies" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 x-movie"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,13 @@
var Backgrid = require('backgrid');
module.exports = Backgrid.Row.extend({
className : 'select-row select-series-row',
events : {
'click' : '_onClick'
},
_onClick : function() {
this.model.collection.trigger('row:selected', { model: this.model });
}
});

View File

@@ -4,16 +4,25 @@ var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({ module.exports = Marionette.ItemView.extend({
template : 'ManualImport/Summary/ManualImportSummaryViewTemplate', template : 'ManualImport/Summary/ManualImportSummaryViewTemplate',
// initialize : function (options) {
// var episodes = _.map(options.episodes, function (episode) {
// return episode.toJSON();
// });
// this.templateHelpers = {
// file : options.file,
// series : options.series,
// season : options.season,
// episodes : episodes,
// quality : options.quality
// };
// }
initialize : function (options) { initialize : function (options) {
var episodes = _.map(options.episodes, function (episode) {
return episode.toJSON();
});
this.templateHelpers = { this.templateHelpers = {
file : options.file, file : options.file,
series : options.series, movie : options.movie,
season : options.season,
episodes : episodes,
quality : options.quality quality : options.quality
}; };
} }

View File

@@ -3,16 +3,8 @@
<dt>Path:</dt> <dt>Path:</dt>
<dd>{{file}}</dd> <dd>{{file}}</dd>
<dt>Series:</dt> <dt>Movie:</dt>
<dd>{{series.title}}</dd> <dd>{{movie.title}} ({{movie.year}})</dd>
<dt>Season:</dt>
<dd>{{season.seasonNumber}}</dd>
{{#each episodes}}
<dt>Episode:</dt>
<dd>{{episodeNumber}} - {{title}}</dd>
{{/each}}
<dt>Quality:</dt> <dt>Quality:</dt>
<dd>{{quality.name}}</dd> <dd>{{quality.name}}</dd>

View File

@@ -100,7 +100,18 @@ var Collection = PageableCollection.extend({
return percentOfEpisodes + episodeCount / 1000000; return percentOfEpisodes + episodeCount / 1000000;
} }
}, },
inCinemas : {
sortValue : function(model, attr) {
var monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
if (model.get("inCinemas")) {
return model.get("inCinemas");
}
return "2100-01-01";
}
},
path : { path : {
sortValue : function(model) { sortValue : function(model) {
var path = model.get('path'); var path = model.get('path');

View File

@@ -37,7 +37,7 @@ module.exports = Marionette.Layout.extend({
name : 'edition', name : 'edition',
label : 'Edition', label : 'Edition',
cell : EditionCell, cell : EditionCell,
title : "Edition" title : "Edition",
}, },
{ {
name : 'indexer', name : 'indexer',
@@ -57,7 +57,7 @@ module.exports = Marionette.Layout.extend({
{ {
name : 'quality', name : 'quality',
label : 'Quality', label : 'Quality',
cell : QualityCell cell : QualityCell,
}, },
{ {
name : 'rejections', name : 'rejections',

View File

@@ -1,2 +1 @@
<div id="episode-release-grid" class="table-responsive"></div> <div id="episode-release-grid" class="table-responsive"></div>
<button class="btn x-search-back">Back</button>

View File

@@ -16,7 +16,7 @@ var Collection = PagableCollection.extend({
sortMappings : { sortMappings : {
'quality' : { 'quality' : {
sortKey : 'qualityWeight' sortKey : "qualityWeight"
}, },
'rejections' : { 'rejections' : {
sortValue : function(model) { sortValue : function(model) {
@@ -30,6 +30,9 @@ var Collection = PagableCollection.extend({
return releaseWeight; return releaseWeight;
} }
}, },
"edition" : {
sortKey : "edition"
},
'download' : { 'download' : {
sortKey : 'releaseWeight' sortKey : 'releaseWeight'
}, },

View File

@@ -78,7 +78,7 @@ module.exports = Marionette.Layout.extend({
CommandController.Execute('renameMovieFiles', { CommandController.Execute('renameMovieFiles', {
name : 'renameMovieFiles', name : 'renameMovieFiles',
movieId : this.model.id, movieId : this.model.id,
files : files files : files
}); });

View File

@@ -23,7 +23,7 @@
</div> </div>
<div class="col-sm-2 col-sm-pull-1"> <div class="col-sm-2 col-sm-pull-1">
<input type="number" name="downloadedEpisodesScanInterval" class="form-control" /> <input type="number" name="downloadedMovieScanInterval" class="form-control" />
</div> </div>
</div> </div>
</fieldset> </fieldset>

View File

@@ -1,5 +1,5 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Include Series Title</label> {{!--<label class="col-sm-3 control-label">Include Series Title</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
@@ -36,7 +36,7 @@
</label> </label>
</div> </div>
</div> </div>
</div> </div>--}}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Include Quality</label> <label class="col-sm-3 control-label">Include Quality</label>
@@ -88,7 +88,7 @@
</div> </div>
</div> </div>
<div class="form-group"> {{!--<div class="form-group">
<label class="col-sm-3 control-label">Numbering Style</label> <label class="col-sm-3 control-label">Numbering Style</label>
<div class="col-sm-9"> <div class="col-sm-9">
@@ -99,4 +99,4 @@
<option value="s{season:00}e{episode:00}">s01e05</option> <option value="s{season:00}e{episode:00}">s01e05</option>
</select> </select>
</div> </div>
</div> </div>--}}

View File

@@ -10,6 +10,7 @@ var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout'); var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout');
var ManualImportLayout = require('../../ManualImport/ManualImportLayout'); var ManualImportLayout = require('../../ManualImport/ManualImportLayout');
var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout'); var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout');
var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout');
module.exports = Marionette.AppRouter.extend({ module.exports = Marionette.AppRouter.extend({
initialize : function() { initialize : function() {

View File

@@ -133,7 +133,7 @@ module.exports = Marionette.Layout.extend({
{ {
title : 'Rescan Drone Factory Folder', title : 'Rescan Drone Factory Folder',
icon : 'icon-sonarr-refresh', icon : 'icon-sonarr-refresh',
command : 'downloadedepisodesscan', command : 'downloadedMovieScan',
properties : { sendUpdates : true } properties : { sendUpdates : true }
}, },
{ {