mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ebbcad70 | ||
|
|
92e9dc6ee1 | ||
|
|
5e8b617625 | ||
|
|
2cbe17151d | ||
|
|
95bd615718 | ||
|
|
8e8c4ff497 | ||
|
|
5eddcc1660 | ||
|
|
d42165a93a | ||
|
|
99012d8a40 | ||
|
|
8bc42c76e4 | ||
|
|
b7f72c6259 | ||
|
|
64ef8db037 | ||
|
|
3b5887bf09 | ||
|
|
40a75949ba | ||
|
|
6747267d19 | ||
|
|
13db03f97c | ||
|
|
cd68eea790 | ||
|
|
cfe55d00ae | ||
|
|
1d88313424 | ||
|
|
9513068467 | ||
|
|
9206258370 | ||
|
|
0fa1509ca6 | ||
|
|
c5eb772f7a | ||
|
|
04fec6d4d8 | ||
|
|
d0b5e380d7 | ||
|
|
df69c58d2b | ||
|
|
27114c9399 | ||
|
|
cfca07996b | ||
|
|
e97b80f630 |
@@ -35,4 +35,11 @@ artifacts:
|
|||||||
|
|
||||||
cache:
|
cache:
|
||||||
- '%USERPROFILE%\.nuget\packages'
|
- '%USERPROFILE%\.nuget\packages'
|
||||||
- node_modules
|
- node_modules
|
||||||
|
|
||||||
|
only_commits:
|
||||||
|
files:
|
||||||
|
- src/
|
||||||
|
- osx/
|
||||||
|
- gulp/
|
||||||
|
- logo/
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
| Service | Master | Develop |
|
| Service | Master | Develop |
|
||||||
|----------|:---------------------------:|:----------------------------:|
|
|----------|:---------------------------:|:----------------------------:|
|
||||||
| AppVeyor | [](https://ci.appveyor.com/project/galli-leo/Radarr) | [](https://ci.appveyor.com/project/galli-leo/Radarr) |
|
| AppVeyor | [](https://ci.appveyor.com/project/galli-leo/Radarr) | [](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
|
||||||
| Travis | [](https://travis-ci.org/galli-leo/Radarr) | [](https://travis-ci.org/galli-leo/Radarr) |
|
| Travis | [](https://travis-ci.org/galli-leo/Radarr) | [](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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
||||||
|
|||||||
14
src/NzbDrone.Core/Datastore/Migration/114_remove_fanzub.cs
Normal file
14
src/NzbDrone.Core/Datastore/Migration/114_remove_fanzub.cs
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
266
src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs
Normal file
266
src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
43
src/UI/ManualImport/Cells/MovieCell.js
Normal file
43
src/UI/ManualImport/Cells/MovieCell.js
Normal 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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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')
|
||||||
};
|
};
|
||||||
|
|||||||
101
src/UI/ManualImport/Movie/SelectMovieLayout.js
Normal file
101
src/UI/ManualImport/Movie/SelectMovieLayout.js
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
30
src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs
Normal file
30
src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs
Normal 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">×</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>
|
||||||
|
|
||||||
|
|
||||||
13
src/UI/ManualImport/Movie/SelectMovieRow.js
Normal file
13
src/UI/ManualImport/Movie/SelectMovieRow.js
Normal 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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>--}}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user