Merge branch 'develop'
This commit is contained in:
commit
fd81356097
|
@ -5,7 +5,6 @@ using Nancy.Bootstrapper;
|
||||||
using NzbDrone.Api.Extensions;
|
using NzbDrone.Api.Extensions;
|
||||||
using NzbDrone.Api.Extensions.Pipelines;
|
using NzbDrone.Api.Extensions.Pipelines;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Authentication
|
namespace NzbDrone.Api.Authentication
|
||||||
|
@ -28,11 +27,9 @@ namespace NzbDrone.Api.Authentication
|
||||||
{
|
{
|
||||||
Response response = null;
|
Response response = null;
|
||||||
|
|
||||||
var authorizationHeader = context.Request.Headers.Authorization;
|
var apiKey = GetApiKey(context);
|
||||||
var apiKeyHeader = context.Request.Headers["X-Api-Key"].FirstOrDefault();
|
|
||||||
var apiKey = apiKeyHeader.IsNullOrWhiteSpace() ? authorizationHeader : apiKeyHeader;
|
|
||||||
|
|
||||||
if (context.Request.IsApiRequest() && !ValidApiKey(apiKey))
|
if ((context.Request.IsApiRequest() || context.Request.IsFeedRequest()) && !ValidApiKey(apiKey))
|
||||||
{
|
{
|
||||||
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||||
}
|
}
|
||||||
|
@ -46,5 +43,23 @@ namespace NzbDrone.Api.Authentication
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetApiKey(NancyContext context)
|
||||||
|
{
|
||||||
|
var apiKeyHeader = context.Request.Headers["X-Api-Key"].FirstOrDefault();
|
||||||
|
var apiKeyQueryString = context.Request.Query["ApiKey"];
|
||||||
|
|
||||||
|
if (!apiKeyHeader.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return apiKeyHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKeyQueryString.HasValue)
|
||||||
|
{
|
||||||
|
return apiKeyQueryString.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.Request.Headers.Authorization;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
using Omu.ValueInjecter;
|
using Omu.ValueInjecter;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Config
|
namespace NzbDrone.Api.Config
|
||||||
|
@ -25,8 +27,8 @@ namespace NzbDrone.Api.Config
|
||||||
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled);
|
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled);
|
||||||
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled);
|
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled);
|
||||||
|
|
||||||
SharedValidator.RuleFor(c => c.SslPort).InclusiveBetween(1, 65535).When(c => c.EnableSsl);
|
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
|
||||||
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl);
|
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HostConfigResource GetHostConfig()
|
private HostConfigResource GetHostConfig()
|
||||||
|
|
|
@ -10,6 +10,11 @@ namespace NzbDrone.Api.Extensions
|
||||||
return request.Path.StartsWith("/api/", StringComparison.InvariantCultureIgnoreCase);
|
return request.Path.StartsWith("/api/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsFeedRequest(this Request request)
|
||||||
|
{
|
||||||
|
return request.Path.StartsWith("/feed/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsSignalRRequest(this Request request)
|
public static bool IsSignalRRequest(this Request request)
|
||||||
{
|
{
|
||||||
return request.Path.StartsWith("/signalr/", StringComparison.InvariantCultureIgnoreCase);
|
return request.Path.StartsWith("/signalr/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
|
|
@ -5,7 +5,7 @@ using NzbDrone.Api.REST;
|
||||||
using NzbDrone.Api.Series;
|
using NzbDrone.Api.Series;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
|
|
||||||
namespace NzbDrone.Api.History
|
namespace NzbDrone.Api.History
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using FluentValidation.Validators;
|
using FluentValidation.Validators;
|
||||||
using NzbDrone.Core.Validation.Paths;
|
|
||||||
|
|
||||||
namespace NzbDrone.Api.Validation
|
namespace NzbDrone.Api.Validation
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Text;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
|
|
@ -103,6 +103,7 @@
|
||||||
<Compile Include="Messaging\IMessage.cs" />
|
<Compile Include="Messaging\IMessage.cs" />
|
||||||
<Compile Include="PathEqualityComparer.cs" />
|
<Compile Include="PathEqualityComparer.cs" />
|
||||||
<Compile Include="Processes\INzbDroneProcessProvider.cs" />
|
<Compile Include="Processes\INzbDroneProcessProvider.cs" />
|
||||||
|
<Compile Include="Processes\PidFileProvider.cs" />
|
||||||
<Compile Include="Processes\ProcessOutput.cs" />
|
<Compile Include="Processes\ProcessOutput.cs" />
|
||||||
<Compile Include="RateGate.cs" />
|
<Compile Include="RateGate.cs" />
|
||||||
<Compile Include="Serializer\IntConverter.cs" />
|
<Compile Include="Serializer\IntConverter.cs" />
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Processes
|
||||||
|
{
|
||||||
|
public interface IProvidePidFile
|
||||||
|
{
|
||||||
|
void Write();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PidFileProvider : IProvidePidFile
|
||||||
|
{
|
||||||
|
private readonly IAppFolderInfo _appFolderInfo;
|
||||||
|
private readonly IProcessProvider _processProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public PidFileProvider(IAppFolderInfo appFolderInfo, IProcessProvider processProvider, Logger logger)
|
||||||
|
{
|
||||||
|
_appFolderInfo = appFolderInfo;
|
||||||
|
_processProvider = processProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write()
|
||||||
|
{
|
||||||
|
var filename = Path.Combine(_appFolderInfo.AppDataFolder, "nzbdrone.pid");
|
||||||
|
|
||||||
|
if (OsInfo.IsMono)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(filename, _processProvider.GetCurrentProcess().Id.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error("Unable to write PID file: " + filename, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,14 @@ namespace NzbDrone.Common
|
||||||
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string TrimEnd(this string text, string postfix)
|
||||||
|
{
|
||||||
|
if (text.EndsWith(postfix))
|
||||||
|
text = text.Substring(0, text.Length - postfix.Length);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
public static string CleanSpaces(this string text)
|
public static string CleanSpaces(this string text)
|
||||||
{
|
{
|
||||||
return CollapseSpace.Replace(text, " ").Trim();
|
return CollapseSpace.Replace(text, " ").Trim();
|
||||||
|
|
|
@ -66,5 +66,113 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
Subject.Clean();
|
Subject.Clean();
|
||||||
AllStoredModels.Count.Should().Be(1);
|
AllStoredModels.Count.Should().Be(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_metadata_files_when_they_are_for_the_same_episode_but_different_consumers()
|
||||||
|
{
|
||||||
|
var files = Builder<MetadataFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeMetadata)
|
||||||
|
.With(m => m.EpisodeFileId = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(files);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(files.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_metadata_files_for_different_episode()
|
||||||
|
{
|
||||||
|
var files = Builder<MetadataFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeMetadata)
|
||||||
|
.With(m => m.Consumer = "XbmcMetadata")
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(files);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(files.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_metadata_files_when_they_are_for_the_same_episode_and_consumer()
|
||||||
|
{
|
||||||
|
var files = Builder<MetadataFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeMetadata)
|
||||||
|
.With(m => m.EpisodeFileId = 1)
|
||||||
|
.With(m => m.Consumer = "XbmcMetadata")
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(files);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_metadata_files_when_there_is_only_one_for_that_episode_and_consumer()
|
||||||
|
{
|
||||||
|
var file = Builder<MetadataFile>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(file);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_image_when_they_are_for_the_same_episode_but_different_consumers()
|
||||||
|
{
|
||||||
|
var files = Builder<MetadataFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeImage)
|
||||||
|
.With(m => m.EpisodeFileId = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(files);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(files.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_image_for_different_episode()
|
||||||
|
{
|
||||||
|
var files = Builder<MetadataFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeImage)
|
||||||
|
.With(m => m.Consumer = "XbmcMetadata")
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(files);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(files.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_image_when_they_are_for_the_same_episode_and_consumer()
|
||||||
|
{
|
||||||
|
var files = Builder<MetadataFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeImage)
|
||||||
|
.With(m => m.EpisodeFileId = 1)
|
||||||
|
.With(m => m.Consumer = "XbmcMetadata")
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(files);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_image_when_there_is_only_one_for_that_episode_and_consumer()
|
||||||
|
{
|
||||||
|
var file = Builder<MetadataFile>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(file);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Count.Should().Be(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Metadata;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
using NzbDrone.Core.Metadata.Files;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -81,5 +82,43 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
Subject.Clean();
|
Subject.Clean();
|
||||||
AllStoredModels.Should().HaveCount(1);
|
AllStoredModels.Should().HaveCount(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_episode_metadata_files_that_have_episodefileid_of_zero()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(series);
|
||||||
|
|
||||||
|
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||||
|
.With(m => m.SeriesId = series.Id)
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeMetadata)
|
||||||
|
.With(m => m.EpisodeFileId = 0)
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(metadataFile);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_episode_image_files_that_have_episodefileid_of_zero()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(series);
|
||||||
|
|
||||||
|
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||||
|
.With(m => m.SeriesId = series.Id)
|
||||||
|
.With(m => m.Type = MetadataType.EpisodeImage)
|
||||||
|
.With(m => m.EpisodeFileId = 0)
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(metadataFile);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -405,5 +405,35 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
||||||
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
|
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
|
||||||
.Should().Be("30 Rock - S06E06 - Part 1");
|
.Should().Be("30 Rock - S06E06 - Part 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_replace_double_period_with_single_period()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
|
||||||
|
|
||||||
|
var episode = Builder<Episode>.CreateNew()
|
||||||
|
.With(e => e.Title = "Part 1")
|
||||||
|
.With(e => e.SeasonNumber = 6)
|
||||||
|
.With(e => e.EpisodeNumber = 6)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "Chicago P.D." }, _episodeFile)
|
||||||
|
.Should().Be("Chicago.P.D.S06E06.Part.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_replace_triple_period_with_single_period()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
|
||||||
|
|
||||||
|
var episode = Builder<Episode>.CreateNew()
|
||||||
|
.With(e => e.Title = "Part 1")
|
||||||
|
.With(e => e.SeasonNumber = 6)
|
||||||
|
.With(e => e.EpisodeNumber = 6)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "Chicago P.D.." }, _episodeFile)
|
||||||
|
.Should().Be("Chicago.P.D.S06E06.Part.1");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ParserTests
|
namespace NzbDrone.Core.Test.ParserTests
|
||||||
{
|
{
|
||||||
|
@ -29,10 +30,60 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("THIS SHOULD NEVER PARSE")]
|
[TestCase("THIS SHOULD NEVER PARSE")]
|
||||||
[TestCase("Vh1FvU3bJXw6zs8EEUX4bMo5vbbMdHghxHirc.mkv")]
|
[TestCase("Vh1FvU3bJXw6zs8EEUX4bMo5vbbMdHghxHirc.mkv")]
|
||||||
[TestCase("0e895c37245186812cb08aab1529cf8ee389dd05.mkv")]
|
[TestCase("0e895c37245186812cb08aab1529cf8ee389dd05.mkv")]
|
||||||
|
[TestCase("08bbc153931ce3ca5fcafe1b92d3297285feb061.mkv")]
|
||||||
|
[TestCase("185d86a343e39f3341e35c4dad3ff159")]
|
||||||
public void should_not_parse_crap(string title)
|
public void should_not_parse_crap(string title)
|
||||||
{
|
{
|
||||||
Parser.Parser.ParseTitle(title).Should().BeNull();
|
Parser.Parser.ParseTitle(title).Should().BeNull();
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_parse_md5()
|
||||||
|
{
|
||||||
|
string hash = "CRAPPY TEST SEED";
|
||||||
|
|
||||||
|
var hashAlgo = System.Security.Cryptography.MD5.Create();
|
||||||
|
|
||||||
|
var repetitions = 100;
|
||||||
|
var success = 0;
|
||||||
|
for (int i = 0; i < repetitions; i++)
|
||||||
|
{
|
||||||
|
var hashData = hashAlgo.ComputeHash(System.Text.Encoding.Default.GetBytes(hash));
|
||||||
|
|
||||||
|
hash = BitConverter.ToString(hashData).Replace("-", "");
|
||||||
|
|
||||||
|
if (Parser.Parser.ParseTitle(hash) == null)
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
|
||||||
|
success.Should().Be(repetitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(32)]
|
||||||
|
[TestCase(40)]
|
||||||
|
public void should_not_parse_random(int length)
|
||||||
|
{
|
||||||
|
string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
var hashAlgo = new Random();
|
||||||
|
|
||||||
|
var repetitions = 500;
|
||||||
|
var success = 0;
|
||||||
|
for (int i = 0; i < repetitions; i++)
|
||||||
|
{
|
||||||
|
StringBuilder hash = new StringBuilder(length);
|
||||||
|
|
||||||
|
for (int x = 0; x < length; x++)
|
||||||
|
{
|
||||||
|
hash.Append(charset[hashAlgo.Next() % charset.Length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Parser.Parser.ParseTitle(hash.ToString()) == null)
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
|
||||||
|
success.Should().Be(repetitions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("2020.NZ.2012.16.02.PDTV.XviD-C4TV", "2020nz", 2012, 2, 16)]
|
[TestCase("2020.NZ.2012.16.02.PDTV.XviD-C4TV", "2020nz", 2012, 2, 16)]
|
||||||
[TestCase("2020.NZ.2012.13.02.PDTV.XviD-C4TV", "2020nz", 2012, 2, 13)]
|
[TestCase("2020.NZ.2012.13.02.PDTV.XviD-C4TV", "2020nz", 2012, 2, 13)]
|
||||||
[TestCase("2020.NZ.2011.12.02.PDTV.XviD-C4TV", "2020nz", 2011, 12, 2)]
|
[TestCase("2020.NZ.2011.12.02.PDTV.XviD-C4TV", "2020nz", 2011, 12, 2)]
|
||||||
|
[TestCase("Series Title - 2013-10-30 - Episode Title (1) [HDTV-720p]", "Series Title", 2013, 10, 30)]
|
||||||
public void should_parse_daily_episode(string postTitle, string title, int year, int month, int day)
|
public void should_parse_daily_episode(string postTitle, string title, int year, int month, int day)
|
||||||
{
|
{
|
||||||
var result = Parser.Parser.ParseTitle(postTitle);
|
var result = Parser.Parser.ParseTitle(postTitle);
|
||||||
|
|
|
@ -12,17 +12,31 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
{
|
{
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
@"C:\Test\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury\0e895c3724.mkv".AsOsAgnostic(),
|
@"C:\Test\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury\0e895c37245186812cb08aab1529cf8ee389dd05.mkv".AsOsAgnostic(),
|
||||||
"somehashedrelease",
|
"somehashedrelease",
|
||||||
"WEBDL-720p",
|
"WEBDL-720p",
|
||||||
"Mercury"
|
"Mercury"
|
||||||
},
|
},
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
@"C:\Test\0e895c3724\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury.mkv".AsOsAgnostic(),
|
@"C:\Test\0e895c37245186812cb08aab1529cf8ee389dd05\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury.mkv".AsOsAgnostic(),
|
||||||
"somehashedrelease",
|
"somehashedrelease",
|
||||||
"WEBDL-720p",
|
"WEBDL-720p",
|
||||||
"Mercury"
|
"Mercury"
|
||||||
|
},
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
@"C:\Test\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury.mkv\yrucreM-462.H.0.2CAA.LD-BEW.p027.10E10S.esaeleR.dehsaH.emoS.mkv".AsOsAgnostic(),
|
||||||
|
"somehashedrelease",
|
||||||
|
"WEBDL-720p",
|
||||||
|
"Mercury"
|
||||||
|
},
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
@"C:\Test\Weeds.S01E10.DVDRip.XviD-NZBgeek\AHFMZXGHEWD660.mkv".AsOsAgnostic(),
|
||||||
|
"weeds",
|
||||||
|
"DVD",
|
||||||
|
"NZBgeek"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,17 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_include_extension_in_release_roup()
|
public void should_not_include_extension_in_release_group()
|
||||||
{
|
{
|
||||||
const string path = @"C:\Test\Doctor.Who.2005.s01e01.internal.bdrip.x264-archivist.mkv";
|
const string path = @"C:\Test\Doctor.Who.2005.s01e01.internal.bdrip.x264-archivist.mkv";
|
||||||
|
|
||||||
Parser.Parser.ParsePath(path).ReleaseGroup.Should().Be("archivist");
|
Parser.Parser.ParsePath(path).ReleaseGroup.Should().Be("archivist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("The.Longest.Mystery.S02E04.720p.WEB-DL.AAC2.0.H.264-EVL-RP", "EVL")]
|
||||||
|
public void should_not_include_repost_in_release_group(string title, string expected)
|
||||||
|
{
|
||||||
|
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(49)]
|
||||||
|
public class fix_dognzb_url : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Execute.Sql("UPDATE Indexers SET Settings = replace(Settings, '//dognzb.cr', '//api.dognzb.cr')" +
|
||||||
|
"WHERE Implementation = 'Newznab'" +
|
||||||
|
"AND Settings LIKE '%//dognzb.cr%'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
_logger.Debug("Running cleanup of duplicate metadata files");
|
_logger.Debug("Running cleanup of duplicate metadata files");
|
||||||
|
|
||||||
DeleteDuplicateSeriesMetadata();
|
DeleteDuplicateSeriesMetadata();
|
||||||
|
DeleteDuplicateEpisodeMetadata();
|
||||||
|
DeleteDuplicateEpisodeImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeleteDuplicateSeriesMetadata()
|
private void DeleteDuplicateSeriesMetadata()
|
||||||
|
@ -33,5 +35,31 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
HAVING COUNT(SeriesId) > 1
|
HAVING COUNT(SeriesId) > 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DeleteDuplicateEpisodeMetadata()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||||
|
WHERE Id IN (
|
||||||
|
SELECT Id FROM MetadataFiles
|
||||||
|
WHERE Type = 2
|
||||||
|
GROUP BY EpisodeFileId, Consumer
|
||||||
|
HAVING COUNT(EpisodeFileId) > 1
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteDuplicateEpisodeImages()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||||
|
WHERE Id IN (
|
||||||
|
SELECT Id FROM MetadataFiles
|
||||||
|
WHERE Type = 5
|
||||||
|
GROUP BY EpisodeFileId, Consumer
|
||||||
|
HAVING COUNT(EpisodeFileId) > 1
|
||||||
|
)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
|
||||||
DeleteOrphanedBySeries();
|
DeleteOrphanedBySeries();
|
||||||
DeleteOrphanedByEpisodeFile();
|
DeleteOrphanedByEpisodeFile();
|
||||||
|
DeleteWhereEpisodeFileIsZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeleteOrphanedBySeries()
|
private void DeleteOrphanedBySeries()
|
||||||
|
@ -46,5 +47,16 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
WHERE MetadataFiles.EpisodeFileId > 0
|
WHERE MetadataFiles.EpisodeFileId > 0
|
||||||
AND EpisodeFiles.Id IS NULL)");
|
AND EpisodeFiles.Id IS NULL)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DeleteWhereEpisodeFileIsZero()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||||
|
WHERE Id IN (
|
||||||
|
SELECT Id FROM MetadataFiles
|
||||||
|
WHERE Type IN (2, 5)
|
||||||
|
AND EpisodeFileId = 0)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
@ -13,7 +14,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
{
|
{
|
||||||
public interface IEpisodeSearchService
|
public interface IEpisodeSearchService
|
||||||
{
|
{
|
||||||
void MissingEpisodesAiredAfter(DateTime dateTime);
|
void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable<Int32> grabbed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MissingEpisodeSearchService : IEpisodeSearchService, IExecute<EpisodeSearchCommand>, IExecute<MissingEpisodeSearchCommand>
|
public class MissingEpisodeSearchService : IEpisodeSearchService, IExecute<EpisodeSearchCommand>, IExecute<MissingEpisodeSearchCommand>
|
||||||
|
@ -37,11 +39,12 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MissingEpisodesAiredAfter(DateTime dateTime)
|
public void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable<Int32> grabbed)
|
||||||
{
|
{
|
||||||
var missing = _episodeService.EpisodesBetweenDates(dateTime, DateTime.UtcNow)
|
var missing = _episodeService.EpisodesBetweenDates(dateTime, DateTime.UtcNow)
|
||||||
.Where(e => !e.HasFile &&
|
.Where(e => !e.HasFile &&
|
||||||
!_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id))
|
!_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id) &&
|
||||||
|
!grabbed.Contains(e.Id))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var downloadedCount = 0;
|
var downloadedCount = 0;
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
Enable = false,
|
Enable = false,
|
||||||
Name = "Dognzb.cr",
|
Name = "Dognzb.cr",
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
Settings = GetSettings("https://dognzb.cr", new List<Int32>())
|
Settings = GetSettings("https://api.dognzb.cr", new List<Int32>())
|
||||||
});
|
});
|
||||||
|
|
||||||
list.Add(new IndexerDefinition
|
list.Add(new IndexerDefinition
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.IndexerSearch;
|
using NzbDrone.Core.IndexerSearch;
|
||||||
using NzbDrone.Core.Instrumentation.Extensions;
|
using NzbDrone.Core.Instrumentation.Extensions;
|
||||||
|
@ -11,7 +13,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public interface IRssSyncService
|
public interface IRssSyncService
|
||||||
{
|
{
|
||||||
void Sync();
|
List<DownloadDecision> Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RssSyncService : IRssSyncService, IExecute<RssSyncCommand>
|
public class RssSyncService : IRssSyncService, IExecute<RssSyncCommand>
|
||||||
|
@ -36,7 +38,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Sync()
|
public List<DownloadDecision> Sync()
|
||||||
{
|
{
|
||||||
_logger.ProgressInfo("Starting RSS Sync");
|
_logger.ProgressInfo("Starting RSS Sync");
|
||||||
|
|
||||||
|
@ -45,16 +47,18 @@ namespace NzbDrone.Core.Indexers
|
||||||
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
|
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
|
||||||
|
|
||||||
_logger.ProgressInfo("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, downloaded.Count());
|
_logger.ProgressInfo("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, downloaded.Count());
|
||||||
|
|
||||||
|
return downloaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(RssSyncCommand message)
|
public void Execute(RssSyncCommand message)
|
||||||
{
|
{
|
||||||
Sync();
|
var downloaded = Sync();
|
||||||
|
|
||||||
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
|
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
|
||||||
{
|
{
|
||||||
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
|
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
|
||||||
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1));
|
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), downloaded.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Diagnostics;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -76,7 +78,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoFilesStopwatch = Stopwatch.StartNew();
|
var videoFilesStopwatch = Stopwatch.StartNew();
|
||||||
var mediaFileList = GetVideoFiles(series.Path).ToList();
|
var mediaFileList = GetVideoFiles(series.Path).Where(file => !file.StartsWith(Path.Combine(series.Path, "EXTRAS"))).ToList();
|
||||||
videoFilesStopwatch.Stop();
|
videoFilesStopwatch.Stop();
|
||||||
_logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed);
|
_logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ using NzbDrone.Common;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -172,15 +173,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (ex is UnauthorizedAccessException || ex is InvalidOperationException)
|
|
||||||
{
|
_logger.WarnException("Unable to apply permissions to: " + path, ex);
|
||||||
_logger.Debug("Unable to apply permissions to: ", path);
|
_logger.DebugException(ex.Message, ex);
|
||||||
_logger.DebugException(ex.Message, ex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -247,6 +247,8 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
xws.OmitXmlDeclaration = true;
|
xws.OmitXmlDeclaration = true;
|
||||||
xws.Indent = false;
|
xws.Indent = false;
|
||||||
|
|
||||||
|
var episodeGuideUrl = String.Format("http://www.thetvdb.com/api/1D62F2F90030C444/series/{0}/all/en.zip", series.TvdbId);
|
||||||
|
|
||||||
using (var xw = XmlWriter.Create(sb, xws))
|
using (var xw = XmlWriter.Create(sb, xws))
|
||||||
{
|
{
|
||||||
var tvShow = new XElement("tvshow");
|
var tvShow = new XElement("tvshow");
|
||||||
|
@ -254,10 +256,8 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
tvShow.Add(new XElement("title", series.Title));
|
tvShow.Add(new XElement("title", series.Title));
|
||||||
tvShow.Add(new XElement("rating", (decimal)series.Ratings.Percentage/10));
|
tvShow.Add(new XElement("rating", (decimal)series.Ratings.Percentage/10));
|
||||||
tvShow.Add(new XElement("plot", series.Overview));
|
tvShow.Add(new XElement("plot", series.Overview));
|
||||||
|
tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
|
||||||
//Todo: probably will need to use TVDB to use this feature...
|
tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
|
||||||
// tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
|
|
||||||
// tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
|
|
||||||
tvShow.Add(new XElement("mpaa", series.Certification));
|
tvShow.Add(new XElement("mpaa", series.Certification));
|
||||||
tvShow.Add(new XElement("id", series.TvdbId));
|
tvShow.Add(new XElement("id", series.TvdbId));
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,10 @@ namespace NzbDrone.Core.Metadata
|
||||||
_logger.Debug("Looking for existing metadata in {0}", message.Series.Path);
|
_logger.Debug("Looking for existing metadata in {0}", message.Series.Path);
|
||||||
|
|
||||||
var filesOnDisk = _diskProvider.GetFiles(message.Series.Path, SearchOption.AllDirectories);
|
var filesOnDisk = _diskProvider.GetFiles(message.Series.Path, SearchOption.AllDirectories);
|
||||||
var possibleMetadataFiles = filesOnDisk.Where(c => !MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower())).ToList();
|
|
||||||
|
var possibleMetadataFiles = filesOnDisk.Where(c => !MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower()) &&
|
||||||
|
!c.StartsWith(Path.Combine(message.Series.Path, "EXTRAS"))).ToList();
|
||||||
|
|
||||||
var filteredFiles = _metadataFileService.FilterExistingFiles(possibleMetadataFiles, message.Series);
|
var filteredFiles = _metadataFileService.FilterExistingFiles(possibleMetadataFiles, message.Series);
|
||||||
|
|
||||||
var metadataFiles = new List<MetadataFile>();
|
var metadataFiles = new List<MetadataFile>();
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
{
|
{
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private static readonly Regex CollapseSpaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
|
private static readonly Regex CollapseSpaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
|
||||||
private static readonly Regex InvalidSearchCharRegex = new Regex(@"(?:\*|\(|\)|'|!)", RegexOptions.Compiled);
|
private static readonly Regex InvalidSearchCharRegex = new Regex(@"(?:\*|\(|\)|'|!|@)", RegexOptions.Compiled);
|
||||||
|
|
||||||
public TraktProxy(Logger logger)
|
public TraktProxy(Logger logger)
|
||||||
{
|
{
|
||||||
|
@ -168,8 +168,10 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
phrase = phrase.RemoveAccent().ToLower();
|
phrase = phrase.RemoveAccent().ToLower();
|
||||||
phrase = InvalidSearchCharRegex.Replace(phrase, "");
|
phrase = InvalidSearchCharRegex.Replace(phrase, "");
|
||||||
phrase = CollapseSpaceRegex.Replace(phrase, " ").Trim().ToLower();
|
phrase = CollapseSpaceRegex.Replace(phrase, " ").Trim().ToLower();
|
||||||
|
phrase = phrase.Trim('-');
|
||||||
phrase = HttpUtility.UrlEncode(phrase);
|
phrase = HttpUtility.UrlEncode(phrase);
|
||||||
|
|
||||||
|
|
||||||
return phrase;
|
return phrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
||||||
|
|
||||||
var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString());
|
var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString());
|
||||||
|
|
||||||
Logger.Debug("Getting version from response");
|
Logger.Debug("Getting version from response: " + response);
|
||||||
var result = Json.Deserialize<XbmcJsonResult<JObject>>(response);
|
var result = Json.Deserialize<XbmcJsonResult<JObject>>(response);
|
||||||
|
|
||||||
var versionObject = result.Result.Property("version");
|
var versionObject = result.Result.Property("version");
|
||||||
|
|
|
@ -46,6 +46,8 @@ namespace NzbDrone.Core.Organizer
|
||||||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>\s|\.|-|_)Title\})",
|
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>\s|\.|-|_)Title\})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
private static readonly Regex FilenameCleanupRegex = new Regex(@"\.{2,}", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly char[] EpisodeTitleTrimCharaters = new[] { ' ', '.', '?' };
|
private static readonly char[] EpisodeTitleTrimCharaters = new[] { ' ', '.', '?' };
|
||||||
|
|
||||||
public FileNameBuilder(INamingConfigService namingConfigService,
|
public FileNameBuilder(INamingConfigService namingConfigService,
|
||||||
|
@ -90,6 +92,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
var sortedEpisodes = episodes.OrderBy(e => e.EpisodeNumber).ToList();
|
var sortedEpisodes = episodes.OrderBy(e => e.EpisodeNumber).ToList();
|
||||||
var pattern = namingConfig.StandardEpisodeFormat;
|
var pattern = namingConfig.StandardEpisodeFormat;
|
||||||
|
|
||||||
var episodeTitles = new List<string>
|
var episodeTitles = new List<string>
|
||||||
{
|
{
|
||||||
sortedEpisodes.First().Title.TrimEnd(EpisodeTitleTrimCharaters)
|
sortedEpisodes.First().Title.TrimEnd(EpisodeTitleTrimCharaters)
|
||||||
|
@ -154,7 +157,10 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
|
tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
|
||||||
tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
|
tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
|
||||||
|
|
||||||
return CleanFilename(ReplaceTokens(pattern, tokenValues).Trim());
|
var filename = ReplaceTokens(pattern, tokenValues).Trim();
|
||||||
|
filename = FilenameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString() );
|
||||||
|
|
||||||
|
return CleanFilename(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace NzbDrone.Core.Parser
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
|
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
|
||||||
new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+(?![\da-z]))",
|
new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
||||||
|
@ -59,7 +59,7 @@ namespace NzbDrone.Core.Parser
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Absolute Episode Number [SubGroup]
|
//Anime - Title Absolute Episode Number [SubGroup]
|
||||||
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}))+(?:.+?)\[(?<subgroup>.+?)\](?:\.|$)",
|
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\](?:\.|$)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Supports 103/113 naming
|
//Supports 103/113 naming
|
||||||
|
@ -92,7 +92,7 @@ namespace NzbDrone.Core.Parser
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
||||||
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+(?![\da-z]))\W?(?!\\)",
|
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+)\W?(?!\\)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Absolute Episode Number
|
//Anime - Title Absolute Episode Number
|
||||||
|
@ -100,6 +100,18 @@ namespace NzbDrone.Core.Parser
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Regex[] RejectHashedReleasesRegex = new Regex[]
|
||||||
|
{
|
||||||
|
// Generic match for md5 and mixed-case hashes.
|
||||||
|
new Regex(@"^[0-9a-zA-Z]{32}", RegexOptions.Compiled),
|
||||||
|
|
||||||
|
// Format seen on some NZBGeek releases
|
||||||
|
new Regex(@"^[A-Z]{11}\d{3}$", RegexOptions.Compiled)
|
||||||
|
};
|
||||||
|
|
||||||
|
//Regex to detect whether the title was reversed.
|
||||||
|
private static readonly Regex ReversedTitleRegex = new Regex(@"\.p027\.|\.p0801\.|\.\d{2}E\d{2}S\.", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^)(a|an|the|and|or|of)(?:\b|_))|\W|_",
|
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^)(a|an|the|and|or|of)(?:\b|_))|\W|_",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
@ -155,6 +167,17 @@ namespace NzbDrone.Core.Parser
|
||||||
if (!ValidateBeforeParsing(title)) return null;
|
if (!ValidateBeforeParsing(title)) return null;
|
||||||
|
|
||||||
Logger.Debug("Parsing string '{0}'", title);
|
Logger.Debug("Parsing string '{0}'", title);
|
||||||
|
|
||||||
|
if (ReversedTitleRegex.IsMatch(title))
|
||||||
|
{
|
||||||
|
var titleWithoutExtension = RemoveFileExtension(title).ToCharArray();
|
||||||
|
Array.Reverse(titleWithoutExtension);
|
||||||
|
|
||||||
|
title = new string(titleWithoutExtension) + title.Substring(titleWithoutExtension.Length);
|
||||||
|
|
||||||
|
Logger.Debug("Reversed name detected. Converted to '{0}'", title);
|
||||||
|
}
|
||||||
|
|
||||||
var simpleTitle = SimpleTitleRegex.Replace(title, String.Empty);
|
var simpleTitle = SimpleTitleRegex.Replace(title, String.Empty);
|
||||||
|
|
||||||
foreach (var regex in ReportTitleRegex)
|
foreach (var regex in ReportTitleRegex)
|
||||||
|
@ -245,10 +268,9 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
title = title.Trim();
|
title = title.Trim();
|
||||||
|
|
||||||
if (!title.ContainsInvalidPathChars() && MediaFiles.MediaFileExtensions.Extensions.Contains(Path.GetExtension(title).ToLower()))
|
title = RemoveFileExtension(title);
|
||||||
{
|
|
||||||
title = Path.GetFileNameWithoutExtension(title).Trim();
|
title = title.TrimEnd("-RP");
|
||||||
}
|
|
||||||
|
|
||||||
var index = title.LastIndexOf('-');
|
var index = title.LastIndexOf('-');
|
||||||
|
|
||||||
|
@ -275,6 +297,19 @@ namespace NzbDrone.Core.Parser
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string RemoveFileExtension(string title)
|
||||||
|
{
|
||||||
|
if (!title.ContainsInvalidPathChars())
|
||||||
|
{
|
||||||
|
if (MediaFiles.MediaFileExtensions.Extensions.Contains(Path.GetExtension(title).ToLower()))
|
||||||
|
{
|
||||||
|
title = Path.Combine(Path.GetDirectoryName(title), Path.GetFileNameWithoutExtension(title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
private static SeriesTitleInfo GetSeriesTitleInfo(string title)
|
private static SeriesTitleInfo GetSeriesTitleInfo(string title)
|
||||||
{
|
{
|
||||||
var seriesTitleInfo = new SeriesTitleInfo();
|
var seriesTitleInfo = new SeriesTitleInfo();
|
||||||
|
@ -511,6 +546,14 @@ namespace NzbDrone.Core.Parser
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var titleWithoutExtension = RemoveFileExtension(title);
|
||||||
|
|
||||||
|
if (RejectHashedReleasesRegex.Any(v => v.IsMatch(titleWithoutExtension)))
|
||||||
|
{
|
||||||
|
Logger.Debug("Rejected Hashed Release Title: " + title);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.DataAugmentation.Scene;
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
|
|
@ -97,6 +97,8 @@ namespace NzbDrone.Core.Tv
|
||||||
|
|
||||||
public Series FindByTitle(string title)
|
public Series FindByTitle(string title)
|
||||||
{
|
{
|
||||||
|
title = Parser.Parser.CleanSeriesTitle(title);
|
||||||
|
|
||||||
var tvdbId = _sceneMappingService.GetTvDbId(title);
|
var tvdbId = _sceneMappingService.GetTvDbId(title);
|
||||||
|
|
||||||
if (tvdbId.HasValue)
|
if (tvdbId.HasValue)
|
||||||
|
@ -104,7 +106,7 @@ namespace NzbDrone.Core.Tv
|
||||||
return FindByTvdbId(tvdbId.Value);
|
return FindByTvdbId(tvdbId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _seriesRepository.FindByTitle(Parser.Parser.CleanSeriesTitle(title));
|
return _seriesRepository.FindByTitle(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Series FindByTitleInexact(string title)
|
public Series FindByTitleInexact(string title)
|
||||||
|
|
|
@ -26,5 +26,10 @@ namespace NzbDrone.Core.Validation
|
||||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
return ruleBuilder.SetValidator(new RegularExpressionValidator("^http(?:s)?://[a-z0-9-.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that");
|
return ruleBuilder.SetValidator(new RegularExpressionValidator("^http(?:s)?://[a-z0-9-.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IRuleBuilderOptions<T, int> ValidPort<T>(this IRuleBuilder<T, int> ruleBuilder)
|
||||||
|
{
|
||||||
|
return ruleBuilder.SetValidator(new InclusiveBetweenValidator(0, 65535));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ namespace NzbDrone.Host.AccessControl
|
||||||
public class SslAdapter : ISslAdapter
|
public class SslAdapter : ISslAdapter
|
||||||
{
|
{
|
||||||
private const string APP_ID = "C2172AF4-F9A6-4D91-BAEE-C2E4EE680613";
|
private const string APP_ID = "C2172AF4-F9A6-4D91-BAEE-C2E4EE680613";
|
||||||
|
private static readonly Regex CertificateHashRegex = new Regex(@"^\s+(?:Certificate Hash\s+:\s+)(?<hash>\w+)", RegexOptions.Compiled);
|
||||||
|
|
||||||
private readonly INetshProvider _netshProvider;
|
private readonly INetshProvider _netshProvider;
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
@ -54,7 +56,32 @@ namespace NzbDrone.Host.AccessControl
|
||||||
|
|
||||||
if (output == null || !output.Standard.Any()) return false;
|
if (output == null || !output.Standard.Any()) return false;
|
||||||
|
|
||||||
|
var hashLine = output.Standard.SingleOrDefault(line => CertificateHashRegex.IsMatch(line));
|
||||||
|
|
||||||
|
if (hashLine != null)
|
||||||
|
{
|
||||||
|
var match = CertificateHashRegex.Match(hashLine);
|
||||||
|
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
if (match.Groups["hash"].Value != _configFileProvider.SslCertHash)
|
||||||
|
{
|
||||||
|
Unregister();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return output.Standard.Any(line => line.Contains(ipPort));
|
return output.Standard.Any(line => line.Contains(ipPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Unregister()
|
||||||
|
{
|
||||||
|
var ipPort = "0.0.0.0:" + _configFileProvider.SslPort;
|
||||||
|
var arguments = String.Format("http delete sslcert ipport={0}", ipPort);
|
||||||
|
|
||||||
|
_netshProvider.Run(arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ namespace NzbDrone.Host
|
||||||
private readonly PriorityMonitor _priorityMonitor;
|
private readonly PriorityMonitor _priorityMonitor;
|
||||||
private readonly IStartupContext _startupContext;
|
private readonly IStartupContext _startupContext;
|
||||||
private readonly IBrowserService _browserService;
|
private readonly IBrowserService _browserService;
|
||||||
private readonly IProcessProvider _processProvider;
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public NzbDroneServiceFactory(IConfigFileProvider configFileProvider,
|
public NzbDroneServiceFactory(IConfigFileProvider configFileProvider,
|
||||||
|
@ -33,7 +32,6 @@ namespace NzbDrone.Host
|
||||||
PriorityMonitor priorityMonitor,
|
PriorityMonitor priorityMonitor,
|
||||||
IStartupContext startupContext,
|
IStartupContext startupContext,
|
||||||
IBrowserService browserService,
|
IBrowserService browserService,
|
||||||
IProcessProvider processProvider,
|
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_configFileProvider = configFileProvider;
|
_configFileProvider = configFileProvider;
|
||||||
|
@ -42,7 +40,6 @@ namespace NzbDrone.Host
|
||||||
_priorityMonitor = priorityMonitor;
|
_priorityMonitor = priorityMonitor;
|
||||||
_startupContext = startupContext;
|
_startupContext = startupContext;
|
||||||
_browserService = browserService;
|
_browserService = browserService;
|
||||||
_processProvider = processProvider;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ namespace NzbDrone.Host
|
||||||
|
|
||||||
_container = MainAppContainerBuilder.BuildContainer(startupContext);
|
_container = MainAppContainerBuilder.BuildContainer(startupContext);
|
||||||
_container.Resolve<IAppFolderFactory>().Register();
|
_container.Resolve<IAppFolderFactory>().Register();
|
||||||
|
_container.Resolve<IProvidePidFile>().Write();
|
||||||
|
|
||||||
var appMode = GetApplicationMode(startupContext);
|
var appMode = GetApplicationMode(startupContext);
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,13 @@ define(
|
||||||
|
|
||||||
this.$el.addClass(this.className);
|
this.$el.addClass(this.className);
|
||||||
|
|
||||||
this.ui.seriesSearch.keypress(function () {
|
this.ui.seriesSearch.keyup(function (e) {
|
||||||
|
|
||||||
|
//Ignore special keys: http://www.javascripter.net/faq/keycodes.htm
|
||||||
|
if (_.contains([9, 16, 17, 18, 19, 20, 33, 34, 35, 36, 37, 38, 39, 40, 91, 92, 93 ], e.keyCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.searchResult.close();
|
self.searchResult.close();
|
||||||
self._abortExistingSearch();
|
self._abortExistingSearch();
|
||||||
self.throttledSearch({
|
self.throttledSearch({
|
||||||
|
|
|
@ -2,17 +2,19 @@
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'underscore',
|
||||||
'Cells/ToggleCell',
|
'Cells/ToggleCell',
|
||||||
'Series/SeriesCollection',
|
'Series/SeriesCollection',
|
||||||
'Shared/Messenger'
|
'Shared/Messenger'
|
||||||
], function (ToggleCell, SeriesCollection, Messenger) {
|
], function (_, ToggleCell, SeriesCollection, Messenger) {
|
||||||
return ToggleCell.extend({
|
return ToggleCell.extend({
|
||||||
|
|
||||||
className: 'toggle-cell episode-monitored',
|
className: 'toggle-cell episode-monitored',
|
||||||
|
|
||||||
_originalOnClick: ToggleCell.prototype._onClick,
|
_originalOnClick: ToggleCell.prototype._onClick,
|
||||||
|
|
||||||
_onClick: function () {
|
_onClick: function (e) {
|
||||||
|
|
||||||
var series = SeriesCollection.get(this.model.get('seriesId'));
|
var series = SeriesCollection.get(this.model.get('seriesId'));
|
||||||
|
|
||||||
if (!series.get('monitored')) {
|
if (!series.get('monitored')) {
|
||||||
|
@ -25,7 +27,41 @@ define(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this._selectRange();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._originalOnClick.apply(this, arguments);
|
this._originalOnClick.apply(this, arguments);
|
||||||
|
this.model.episodeCollection.lastToggled = this.model;
|
||||||
|
},
|
||||||
|
|
||||||
|
_selectRange: function () {
|
||||||
|
var episodeCollection = this.model.episodeCollection;
|
||||||
|
var lastToggled = episodeCollection.lastToggled;
|
||||||
|
|
||||||
|
if (!lastToggled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIndex = episodeCollection.indexOf(this.model);
|
||||||
|
var lastIndex = episodeCollection.indexOf(lastToggled);
|
||||||
|
|
||||||
|
var low = Math.min(currentIndex, lastIndex);
|
||||||
|
var high = Math.max(currentIndex, lastIndex);
|
||||||
|
var range = _.range(low + 1, high);
|
||||||
|
|
||||||
|
_.each(range, function (index) {
|
||||||
|
var model = episodeCollection.at(index);
|
||||||
|
|
||||||
|
model.set('monitored', lastToggled.get('monitored'));
|
||||||
|
model.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.model.set('monitored', lastToggled.get('monitored'));
|
||||||
|
this.model.save();
|
||||||
|
this.model.episodeCollection.lastToggled = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,8 +24,8 @@ define(
|
||||||
this.$('i').addClass('icon-spinner icon-spin');
|
this.$('i').addClass('icon-spinner icon-spin');
|
||||||
|
|
||||||
this.model.save().always(function () {
|
this.model.save().always(function () {
|
||||||
self.render();
|
self.render();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
@import "../Content/Bootstrap/variables";
|
@import "../Content/Bootstrap/variables";
|
||||||
@import "../Content/Bootstrap/buttons";
|
@import "../Content/Bootstrap/buttons";
|
||||||
@import "../Shared/Styles/clickable";
|
@import "../Shared/Styles/clickable";
|
||||||
|
@import "../Content/mixins";
|
||||||
|
|
||||||
.episode-title-cell {
|
.episode-title-cell {
|
||||||
.btn-link;
|
.btn-link;
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
|
|
||||||
.toggle-cell{
|
.toggle-cell{
|
||||||
.clickable();
|
.clickable();
|
||||||
|
.not-selectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.approval-status-cell {
|
.approval-status-cell {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
.selectable() {
|
||||||
|
-moz-user-select : all;
|
||||||
|
-webkit-user-select : all;
|
||||||
|
-ms-user-select : all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-selectable() {
|
||||||
|
-moz-user-select : none;
|
||||||
|
-webkit-user-select : none;
|
||||||
|
-ms-user-select : none;
|
||||||
|
}
|
|
@ -92,11 +92,19 @@ define(
|
||||||
|
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
|
|
||||||
|
|
||||||
if (!options.episodeCollection) {
|
if (!options.episodeCollection) {
|
||||||
throw 'episodeCollection is needed';
|
throw 'episodeCollection is needed';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.episodeCollection = options.episodeCollection.bySeason(this.model.get('seasonNumber'));
|
this.episodeCollection = options.episodeCollection.bySeason(this.model.get('seasonNumber'));
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.episodeCollection.each(function (model) {
|
||||||
|
model.episodeCollection = self.episodeCollection;
|
||||||
|
});
|
||||||
|
|
||||||
this.series = options.series;
|
this.series = options.series;
|
||||||
|
|
||||||
this.showingEpisodes = this._shouldShowEpisodes();
|
this.showingEpisodes = this._shouldShowEpisodes();
|
||||||
|
@ -249,6 +257,34 @@ define(
|
||||||
|
|
||||||
this.templateHelpers.showingEpisodes = this.showingEpisodes;
|
this.templateHelpers.showingEpisodes = this.showingEpisodes;
|
||||||
this.render();
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
_episodeMonitoredToggled: function (options) {
|
||||||
|
var model = options.model;
|
||||||
|
var shiftKey = options.shiftKey;
|
||||||
|
|
||||||
|
if (!this.episodeCollection.get(model.get('id'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastToggled = this.episodeCollection.lastToggled;
|
||||||
|
|
||||||
|
if (!lastToggled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIndex = this.episodeCollection.indexOf(model);
|
||||||
|
var lastIndex = this.episodeCollection.indexOf(lastToggled);
|
||||||
|
|
||||||
|
var low = Math.min(currentIndex, lastIndex);
|
||||||
|
var high = Math.max(currentIndex, lastIndex);
|
||||||
|
var range = _.range(low + 1, high);
|
||||||
|
|
||||||
|
this.episodeCollection.lastToggled = model;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -178,6 +178,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.series-season {
|
.series-season {
|
||||||
|
|
||||||
.episode-number-cell {
|
.episode-number-cell {
|
||||||
width : 22px;
|
width : 22px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if_windows}}
|
||||||
<div class="control-group advanced-setting">
|
<div class="control-group advanced-setting">
|
||||||
<label class="control-label">SSL Cert Hash</label>
|
<label class="control-label">SSL Cert Hash</label>
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
<input type="text" name="sslCertHash"/>
|
<input type="text" name="sslCertHash"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{/if_windows}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
|
|
@ -27,6 +27,7 @@ define(
|
||||||
'click .x-delete' : '_deleteNotification',
|
'click .x-delete' : '_deleteNotification',
|
||||||
'click .x-back' : '_back',
|
'click .x-back' : '_back',
|
||||||
'click .x-test' : '_test',
|
'click .x-test' : '_test',
|
||||||
|
'click .x-cancel' : '_cancel',
|
||||||
'change .x-on-download': '_onDownloadChanged'
|
'change .x-on-download': '_onDownloadChanged'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -63,12 +64,23 @@ define(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_cancel: function () {
|
||||||
|
if (this.model.isNew()) {
|
||||||
|
this.model.destroy();
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_deleteNotification: function () {
|
_deleteNotification: function () {
|
||||||
var view = new DeleteView({ model: this.model });
|
var view = new DeleteView({ model: this.model });
|
||||||
AppLayout.modalRegion.show(view);
|
AppLayout.modalRegion.show(view);
|
||||||
},
|
},
|
||||||
|
|
||||||
_back: function () {
|
_back: function () {
|
||||||
|
if (this.model.isNew()) {
|
||||||
|
this.model.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
require('Settings/Notifications/SchemaModal').open(this.notificationCollection);
|
require('Settings/Notifications/SchemaModal').open(this.notificationCollection);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
|
<button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
|
||||||
<button class="btn" data-dismiss="modal">cancel</button>
|
<button class="btn x-cancel">cancel</button>
|
||||||
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-primary x-save">save</button>
|
<button class="btn btn-primary x-save">save</button>
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
<label class="checkbox toggle well">
|
<label class="checkbox toggle well">
|
||||||
<input type="checkbox" class="x-advanced-settings"/>
|
<input type="checkbox" class="x-advanced-settings"/>
|
||||||
<p>
|
<p>
|
||||||
<span>Show</span>
|
<span>Shown</span>
|
||||||
<span>Hide</span>
|
<span>Hidden</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="btn btn-warning slide-button"/>
|
<div class="btn btn-warning slide-button"/>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -8,11 +8,11 @@ define(
|
||||||
var vent = new Backbone.Wreqr.EventAggregator();
|
var vent = new Backbone.Wreqr.EventAggregator();
|
||||||
|
|
||||||
vent.Events = {
|
vent.Events = {
|
||||||
SeriesAdded : 'series:added',
|
SeriesAdded : 'series:added',
|
||||||
SeriesDeleted : 'series:deleted',
|
SeriesDeleted : 'series:deleted',
|
||||||
CommandComplete : 'command:complete',
|
CommandComplete : 'command:complete',
|
||||||
ServerUpdated : 'server:updated',
|
ServerUpdated : 'server:updated',
|
||||||
EpisodeFileDeleted: 'episodefile:deleted'
|
EpisodeFileDeleted : 'episodefile:deleted'
|
||||||
};
|
};
|
||||||
|
|
||||||
vent.Commands = {
|
vent.Commands = {
|
||||||
|
|
Loading…
Reference in New Issue