diff --git a/Exceptron.Client/ExceptronClient.cs b/Exceptron.Client/ExceptronClient.cs index b49af3bc4b..149a739d7f 100644 --- a/Exceptron.Client/ExceptronClient.cs +++ b/Exceptron.Client/ExceptronClient.cs @@ -149,7 +149,11 @@ public ExceptionResponse SubmitException(ExceptionData exceptionData) SetHttpInfo(exceptionData, report); SetEnviromentInfo(report); - return RestClient.Put(Configuration.Host, report); + var exceptionResponse = RestClient.Put(Configuration.Host, report); + + exceptionData.Exception.Data["et"] = exceptionResponse.RefId; + + return exceptionResponse; } catch (Exception e) { diff --git a/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj b/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj index 7fa8111c14..ddf2f38e70 100644 --- a/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj +++ b/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj @@ -36,9 +36,9 @@ ..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll - + False - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll diff --git a/NzbDrone.Api.Test/packages.config b/NzbDrone.Api.Test/packages.config index 139196ed31..ce6653d253 100644 --- a/NzbDrone.Api.Test/packages.config +++ b/NzbDrone.Api.Test/packages.config @@ -1,8 +1,8 @@  - + + - \ No newline at end of file diff --git a/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs b/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs index b97f639436..da9fac5322 100644 --- a/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs +++ b/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs @@ -25,7 +25,7 @@ protected override string Map(string resourceUrl) public override bool CanHandle(string resourceUrl) { - return resourceUrl.StartsWith("/Content") || resourceUrl.EndsWith(".js") || resourceUrl.EndsWith(".css"); + return resourceUrl.StartsWith("/Content") || resourceUrl.EndsWith(".js") || resourceUrl.EndsWith(".css") || resourceUrl.EndsWith(".ico"); } } } \ No newline at end of file diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index 5210ef0a60..d0a03bd178 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -164,6 +164,7 @@ + diff --git a/NzbDrone.Api/RootFolders/RootFolderModule.cs b/NzbDrone.Api/RootFolders/RootFolderModule.cs index 5c13a736c2..7bcc4aa52e 100644 --- a/NzbDrone.Api/RootFolders/RootFolderModule.cs +++ b/NzbDrone.Api/RootFolders/RootFolderModule.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NzbDrone.Core.RootFolders; using NzbDrone.Api.Mapping; +using NzbDrone.Api.Validation; namespace NzbDrone.Api.RootFolders { @@ -17,6 +18,8 @@ public RootFolderModule(IRootFolderService rootFolderService) GetResourceById = GetRootFolder; CreateResource = CreateRootFolder; DeleteResource = DeleteFolder; + + SharedValidator.RuleFor(c=>c.Path).IsValidPath(); } private RootFolderResource GetRootFolder(int id) diff --git a/NzbDrone.Api/RootFolders/RootFolderResource.cs b/NzbDrone.Api/RootFolders/RootFolderResource.cs index 05af828c1c..43fcbe6670 100644 --- a/NzbDrone.Api/RootFolders/RootFolderResource.cs +++ b/NzbDrone.Api/RootFolders/RootFolderResource.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Api.RootFolders public class RootFolderResource : RestResource { public String Path { get; set; } - public Int64 FreeSpace { get; set; } + public Int64? FreeSpace { get; set; } public List UnmappedFolders { get; set; } } diff --git a/NzbDrone.Api/Series/SeriesModule.cs b/NzbDrone.Api/Series/SeriesModule.cs index 57caeb7a63..fe7b820a6e 100644 --- a/NzbDrone.Api/Series/SeriesModule.cs +++ b/NzbDrone.Api/Series/SeriesModule.cs @@ -31,10 +31,10 @@ public SeriesModule(ISeriesService seriesService, ISeriesStatisticsService serie SharedValidator.RuleFor(s => s.QualityProfileId).ValidId(); - PutValidator.RuleFor(s => s.Path).NotEmpty(); + PutValidator.RuleFor(s => s.Path).IsValidPath(); - PostValidator.RuleFor(s => s.Path).NotEmpty().When(s => String.IsNullOrEmpty(s.RootFolderPath)); - PostValidator.RuleFor(s => s.RootFolderPath).NotEmpty().When(s => String.IsNullOrEmpty(s.Path)); + PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => String.IsNullOrEmpty(s.RootFolderPath)); + PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => String.IsNullOrEmpty(s.Path)); PostValidator.RuleFor(s => s.Title).NotEmpty(); } diff --git a/NzbDrone.Api/Update/UpdateModule.cs b/NzbDrone.Api/Update/UpdateModule.cs index 6fdb1ca50e..8a95efa4b2 100644 --- a/NzbDrone.Api/Update/UpdateModule.cs +++ b/NzbDrone.Api/Update/UpdateModule.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; using NzbDrone.Api.REST; using NzbDrone.Core.Update; using NzbDrone.Api.Mapping; @@ -32,7 +33,13 @@ private List GetAvailableUpdate() public class UpdateResource : RestResource { + public String Id { get; set; } + + [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] public Version Version { get; set; } + + public String Branch { get; set; } + public DateTime ReleaseDate { get; set; } public String FileName { get; set; } public String Url { get; set; } } diff --git a/NzbDrone.Api/Validation/PathValidator.cs b/NzbDrone.Api/Validation/PathValidator.cs new file mode 100644 index 0000000000..f7cf37eaba --- /dev/null +++ b/NzbDrone.Api/Validation/PathValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation.Validators; +using NzbDrone.Common; + +namespace NzbDrone.Api.Validation +{ + public class PathValidator : PropertyValidator + { + public PathValidator() + : base("Invalid Path") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return false; + return context.PropertyValue.ToString().IsPathValid(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/Validation/RuleBuilderExtensions.cs b/NzbDrone.Api/Validation/RuleBuilderExtensions.cs index 23dc34c776..b142f5a56c 100644 --- a/NzbDrone.Api/Validation/RuleBuilderExtensions.cs +++ b/NzbDrone.Api/Validation/RuleBuilderExtensions.cs @@ -1,4 +1,6 @@ -using System.Text.RegularExpressions; +using System; +using System.Linq.Expressions; +using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Validators; @@ -20,5 +22,10 @@ public static IRuleBuilderOptions HaveHttpProtocol(this IRuleBuild { return ruleBuilder.SetValidator(new RegularExpressionValidator("^http(s)?://", RegexOptions.IgnoreCase)).WithMessage("must start with http:// or https://"); } + + public static IRuleBuilderOptions IsValidPath(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.SetValidator(new PathValidator()); + } } } \ No newline at end of file diff --git a/NzbDrone.App.Test/NzbDrone.Host.Test.csproj b/NzbDrone.App.Test/NzbDrone.Host.Test.csproj index a2dbe66803..3581eb9cb0 100644 --- a/NzbDrone.App.Test/NzbDrone.Host.Test.csproj +++ b/NzbDrone.App.Test/NzbDrone.Host.Test.csproj @@ -37,9 +37,9 @@ ..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll - + False - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll diff --git a/NzbDrone.App.Test/packages.config b/NzbDrone.App.Test/packages.config index 3e6b4e0946..6f925b437c 100644 --- a/NzbDrone.App.Test/packages.config +++ b/NzbDrone.App.Test/packages.config @@ -1,6 +1,6 @@  - + diff --git a/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs b/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs new file mode 100644 index 0000000000..8359ad4819 --- /dev/null +++ b/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Common.Test.DiskProviderTests +{ + [TestFixture] + public class IsParentFixture : TestBase + { + private string _parent = @"C:\Test".AsOsAgnostic(); + + [Test] + public void should_return_false_when_not_a_child() + { + var path = @"C:\Another Folder".AsOsAgnostic(); + + Subject.IsParent(_parent, path).Should().BeFalse(); + } + + [Test] + public void should_return_true_when_folder_is_parent_of_another_folder() + { + var path = @"C:\Test\TV".AsOsAgnostic(); + + Subject.IsParent(_parent, path).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_folder_is_parent_of_a_file() + { + var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic(); + + Subject.IsParent(_parent, path).Should().BeTrue(); + } + } +} diff --git a/NzbDrone.Common.Test/DiskProviderTests/IsParentFixture.cs b/NzbDrone.Common.Test/DiskProviderTests/IsParentFixture.cs index 8359ad4819..b29f601803 100644 --- a/NzbDrone.Common.Test/DiskProviderTests/IsParentFixture.cs +++ b/NzbDrone.Common.Test/DiskProviderTests/IsParentFixture.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.IO; using FluentAssertions; using NUnit.Framework; using NzbDrone.Test.Common; @@ -9,32 +6,38 @@ namespace NzbDrone.Common.Test.DiskProviderTests { [TestFixture] - public class IsParentFixture : TestBase + public class FreeSpaceFixture : TestBase { - private string _parent = @"C:\Test".AsOsAgnostic(); - [Test] - public void should_return_false_when_not_a_child() + public void should_get_free_space_for_folder() { - var path = @"C:\Another Folder".AsOsAgnostic(); + var path = @"C:\".AsOsAgnostic(); - Subject.IsParent(_parent, path).Should().BeFalse(); + Subject.GetAvailableSpace(path).Should().NotBe(0); } [Test] - public void should_return_true_when_folder_is_parent_of_another_folder() + public void should_get_free_space_for_folder_that_doesnt_exist() { - var path = @"C:\Test\TV".AsOsAgnostic(); + var path = @"C:\".AsOsAgnostic(); - Subject.IsParent(_parent, path).Should().BeTrue(); + Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0); + } + + + [Test] + public void should_get_free_space_for_drive_that_doesnt_exist() + { + WindowsOnly(); + + Assert.Throws(() => Subject.GetAvailableSpace("J:\\").Should().NotBe(0)); } [Test] - public void should_return_true_when_folder_is_parent_of_a_file() + public void should_be_able_to_check_space_on_ramdrive() { - var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic(); - - Subject.IsParent(_parent, path).Should().BeTrue(); + LinuxOnly(); + Subject.GetAvailableSpace("/run/").Should().NotBe(0); } } } diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 1dd3ccd786..4c22a7aebd 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -34,9 +34,9 @@ MinimumRecommendedRules.ruleset - + False - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll @@ -62,6 +62,7 @@ + diff --git a/NzbDrone.Common.Test/PathExtensionFixture.cs b/NzbDrone.Common.Test/PathExtensionFixture.cs index 7393447278..907d13d4c2 100644 --- a/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -102,10 +102,18 @@ public void normalize_path_exception_null() } [Test] - public void get_actual_casing_for_none_existing_file_should_throw() + public void get_actual_casing_for_none_existing_file_return_partially_fixed_result() { WindowsOnly(); - Assert.Throws(() => "C:\\InValidFolder\\invalidfile.exe".GetActualCasing()); + "C:\\WINDOWS\\invalidfile.exe".GetActualCasing().Should().Be("C:\\Windows\\invalidfile.exe"); + } + + + [Test] + public void get_actual_casing_for_none_existing_folder_return_partially_fixed_result() + { + WindowsOnly(); + "C:\\WINDOWS\\SYSTEM32\\FAKEFOLDER\\invalidfile.exe".GetActualCasing().Should().Be("C:\\Windows\\System32\\FAKEFOLDER\\invalidfile.exe"); } [Test] @@ -117,14 +125,7 @@ public void get_actual_casing_should_return_actual_casing_for_local_file_in_wind path.ToLower().GetActualCasing().Should().Be(path); } - [Test] - public void get_actual_casing_should_return_origibal_value_in_linux() - { - LinuxOnly(); - var path = Process.GetCurrentProcess().MainModule.FileName; - path.GetActualCasing().Should().Be(path); - path.GetActualCasing().Should().Be(path); - } + [Test] public void get_actual_casing_should_return_actual_casing_for_local_dir_in_windows() diff --git a/NzbDrone.Common.Test/packages.config b/NzbDrone.Common.Test/packages.config index c03d2b5be7..01e492eefb 100644 --- a/NzbDrone.Common.Test/packages.config +++ b/NzbDrone.Common.Test/packages.config @@ -1,6 +1,6 @@  - + diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs index 062cd79d73..851ba4550a 100644 --- a/NzbDrone.Common/DiskProvider.cs +++ b/NzbDrone.Common/DiskProvider.cs @@ -17,7 +17,6 @@ public interface IDiskProvider DateTime GetLastFolderWrite(string path); DateTime GetLastFileWrite(string path); void EnsureFolder(string path); - bool FolderExists(string path, bool caseSensitive); bool FolderExists(string path); bool FileExists(string path); bool FileExists(string path, bool caseSensitive); @@ -32,7 +31,7 @@ public interface IDiskProvider void MoveFile(string source, string destination); void DeleteFolder(string path, bool recursive); void InheritFolderPermissions(string filename); - long GetAvilableSpace(string path); + long? GetAvailableSpace(string path); string ReadAllText(string filePath); void WriteAllText(string filename, string contents); void FileSetLastWriteTimeUtc(string path, DateTime dateTime); @@ -113,16 +112,6 @@ public bool FolderExists(string path) return Directory.Exists(path); } - public bool FolderExists(string path, bool caseSensitive) - { - if (caseSensitive) - { - return FolderExists(path) && path == path.GetActualCasing(); - } - - return FolderExists(path); - } - public bool FileExists(string path) { Ensure.That(() => path).IsValidPath(); @@ -289,27 +278,38 @@ public void InheritFolderPermissions(string filename) File.SetAccessControl(filename, fs); } - public long GetAvilableSpace(string path) + public long? GetAvailableSpace(string path) { Ensure.That(() => path).IsValidPath(); - if (OsInfo.IsLinux) - { - var driveInfo = DriveInfo.GetDrives().SingleOrDefault(c => c.IsReady && path.StartsWith(c.Name, StringComparison.CurrentCultureIgnoreCase)); - - if (driveInfo == null) - { - throw new DirectoryNotFoundException(path); - } - - return driveInfo.AvailableFreeSpace; - } - var root = GetPathRoot(path); if (!FolderExists(root)) throw new DirectoryNotFoundException(root); + if (OsInfo.IsLinux) + { + var drives = DriveInfo.GetDrives(); + + foreach (var drive in drives) + { + try + { + if (drive.IsReady && path.StartsWith(drive.Name, StringComparison.CurrentCultureIgnoreCase)) + { + return drive.AvailableFreeSpace; + } + } + catch (InvalidOperationException e) + { + Logger.ErrorException("Couldn't get free space for " + path, e); + } + } + + return null; + } + + return DriveFreeSpaceEx(root); } diff --git a/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs b/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs index 9a02758940..0cd5954649 100644 --- a/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs +++ b/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs @@ -95,8 +95,7 @@ public static Param IsRelativePath(this Param param) return param; } - private static readonly Regex windowsInvalidPathRegex = new Regex(@"[/*<>""|]", RegexOptions.Compiled); - private static readonly Regex windowsPathRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled); + [DebuggerStepThrough] public static Param IsValidPath(this Param param) @@ -104,31 +103,14 @@ public static Param IsValidPath(this Param param) if (string.IsNullOrWhiteSpace(param.Value)) throw ExceptionFactory.CreateForParamValidation(param.Name, ExceptionMessages.EnsureExtensions_IsNotNullOrWhiteSpace); + if (param.Value.IsPathValid()) return param; + if (OsInfo.IsLinux) { - if (!param.Value.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value)); - } + throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value)); } - else - { - if (windowsInvalidPathRegex.IsMatch(param.Value)) - { - throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. It contains invalid characters", param.Value)); - } - - //Network path - if (param.Value.StartsWith(Path.DirectorySeparatorChar.ToString())) return param; - - if (!windowsPathRegex.IsMatch(param.Value)) - { - throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value)); - } - } - - - return param; + + throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value)); } } } diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index 42d7d09034..c1b17236fa 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -93,6 +93,7 @@ + diff --git a/NzbDrone.Common/PathExtensions.cs b/NzbDrone.Common/PathExtensions.cs index 15e3b8a08f..15687895fe 100644 --- a/NzbDrone.Common/PathExtensions.cs +++ b/NzbDrone.Common/PathExtensions.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text.RegularExpressions; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnvironmentInfo; @@ -45,6 +46,29 @@ public static bool PathEquals(this string firstPath, string secondPath) return String.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath(), StringComparison.InvariantCultureIgnoreCase); } + private static readonly Regex WindowsPathWithDriveRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled); + + public static bool IsPathValid(this string path) + { + if (path.ContainsInvalidPathChars() || string.IsNullOrWhiteSpace(path)) + { + return false; + } + + if (OsInfo.IsLinux) + { + return path.StartsWith(Path.DirectorySeparatorChar.ToString()); + } + + if (path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path)) + { + return true; + } + + return false; + } + + public static bool ContainsInvalidPathChars(this string text) { return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0; @@ -58,28 +82,40 @@ private static string GetProperCapitalization(DirectoryInfo dirInfo) //Drive letter return dirInfo.Name.ToUpper(); } - return Path.Combine(GetProperCapitalization(parentDirInfo), parentDirInfo.GetDirectories(dirInfo.Name)[0].Name); + + var folderName = dirInfo.Name; + + if (dirInfo.Exists) + { + folderName = parentDirInfo.GetDirectories(dirInfo.Name)[0].Name; + } + + return Path.Combine(GetProperCapitalization(parentDirInfo), folderName); } public static string GetActualCasing(this string path) { - var attributes = File.GetAttributes(path); - if (OsInfo.IsLinux || path.StartsWith("\\")) { return path; } - if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) + if (Directory.Exists(path) && (File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory) { return GetProperCapitalization(new DirectoryInfo(path)); } var fileInfo = new FileInfo(path); + var dirInfo = fileInfo.Directory; + var fileName = fileInfo.Name; - DirectoryInfo dirInfo = fileInfo.Directory; - return Path.Combine(GetProperCapitalization(dirInfo), dirInfo.GetFiles(fileInfo.Name)[0].Name); + if (dirInfo != null && fileInfo.Exists) + { + fileName = dirInfo.GetFiles(fileInfo.Name)[0].Name; + } + + return Path.Combine(GetProperCapitalization(dirInfo), fileName); } public static string GetAppDataPath(this IAppFolderInfo appFolderInfo) diff --git a/NzbDrone.Common/Serializer/Json.cs b/NzbDrone.Common/Serializer/Json.cs index 59c58946ac..866776ec9f 100644 --- a/NzbDrone.Common/Serializer/Json.cs +++ b/NzbDrone.Common/Serializer/Json.cs @@ -40,7 +40,6 @@ public static string ToJson(this object obj) return JsonConvert.SerializeObject(obj); } - public static void Serialize(TModel model, TextWriter outputStream) { var jsonTextWriter = new JsonTextWriter(outputStream); @@ -52,7 +51,5 @@ public static void Serialize(TModel model, Stream outputStream) { Serialize(model, new StreamWriter(outputStream)); } - - } } \ No newline at end of file diff --git a/NzbDrone.Common/Services.cs b/NzbDrone.Common/Services.cs new file mode 100644 index 0000000000..0824c4735e --- /dev/null +++ b/NzbDrone.Common/Services.cs @@ -0,0 +1,15 @@ +using System; + +namespace NzbDrone.Common +{ + public class Services + { + public static String RootUrl + { + get + { + return "http://services.nzbdrone.com"; + } + } + } +} diff --git a/NzbDrone.Console/ConsoleApp.cs b/NzbDrone.Console/ConsoleApp.cs index 586027a54c..3119cc811a 100644 --- a/NzbDrone.Console/ConsoleApp.cs +++ b/NzbDrone.Console/ConsoleApp.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Host; @@ -18,7 +19,6 @@ public static void Main(string[] args) } catch (Exception e) { - System.Console.WriteLine(e.ToString()); System.Console.ReadLine(); } diff --git a/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs b/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs index 64c5cbc793..98ad8b5042 100644 --- a/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs +++ b/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs @@ -15,14 +15,6 @@ public class SceneMappingProxyFixture : CoreTest { private const string SCENE_MAPPING_URL = "http://services.nzbdrone.com/SceneMapping/Active"; - [SetUp] - public void Setup() - { - Mocker.GetMock().SetupGet(s => s.ServiceRootUrl) - .Returns("http://services.nzbdrone.com"); - - } - [Test] public void fetch_should_return_list_of_mappings() { diff --git a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs index d052503a9c..813c1a45f8 100644 --- a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs +++ b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs @@ -44,17 +44,17 @@ public void wombles_rss() [Test] - [Explicit("needs newznab api key")] public void nzbsorg_rss() { var indexer = new Newznab(); indexer.Settings = new NewznabSettings { - ApiKey = "", + ApiKey = "64d61d3cfd4b75e51d01cbc7c6a78275", Url = "http://nzbs.org" }; indexer.InstanceDefinition = new IndexerDefinition(); + indexer.InstanceDefinition.Name = "nzbs.org"; var result = Subject.FetchRss(indexer); diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs index 7d2948ff88..47804bde08 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs +++ b/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs @@ -54,7 +54,7 @@ private void GivenFileSize(long size) private void GivenFreeSpace(long size) { Mocker.GetMock() - .Setup(s => s.GetAvilableSpace(It.IsAny())) + .Setup(s => s.GetAvailableSpace(It.IsAny())) .Returns(size); } @@ -96,7 +96,7 @@ public void should_use_series_paths_parent_for_free_space_check() Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Mocker.GetMock() - .Verify(v => v.GetAvilableSpace(_rootFolder), Times.Once()); + .Verify(v => v.GetAvailableSpace(_rootFolder), Times.Once()); } } } diff --git a/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs b/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs index 9e34a5882a..a7d13ff0c3 100644 --- a/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs +++ b/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs @@ -53,8 +53,7 @@ public void Setup() } Mocker.GetMock() - .Setup(s => s.UpgradeEpisodeFile(It.IsAny(), It.IsAny())) - .Returns(new EpisodeFile()); + .Setup(s => s.UpgradeEpisodeFile(It.IsAny(), It.IsAny())); } [Test] diff --git a/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs b/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs index dbdf35b561..80b4dc855e 100644 --- a/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs +++ b/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs @@ -60,8 +60,7 @@ private void GivenEpisodeFiles() private void GivenMovedFiles() { Mocker.GetMock() - .Setup(s => s.MoveEpisodeFile(It.IsAny(), _series)) - .Returns(_episodeFiles.First()); + .Setup(s => s.MoveEpisodeFile(It.IsAny(), _series)); } [Test] diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index b3e34350c8..524d84b1db 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -43,9 +43,9 @@ ..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll - + False - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll False @@ -186,6 +186,7 @@ + diff --git a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 30c7282728..192ff7380b 100644 --- a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -353,6 +353,7 @@ public void parse_series_name(string postTitle, string title) [TestCase("H.Polukatoikia.S03E13.Greek.PDTV.XviD-Ouzo", Language.Greek)] [TestCase("Burn.Notice.S04E15.Brotherly.Love.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP", Language.German)] [TestCase("Ray Donovan - S01E01.720p.HDtv.x264-Evolve (NLsub)", Language.Norwegian)] + [TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD", Language.French)] public void parse_language(string postTitle, Language language) { var result = Parser.Parser.ParseTitle(postTitle); diff --git a/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index 7f433c41ea..a1a8c60f03 100644 --- a/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -12,74 +12,118 @@ namespace NzbDrone.Core.Test.ParserTests public class QualityParserFixture : CoreTest { - public static object[] QualityParserCases = + public static object[] SdtvCases = { - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", Quality.DVD, false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", Quality.DVD, false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", Quality.DVD, false }, - new object[] { "Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", Quality.HDTV720p, false }, - new object[] { "Chuck S11E03 has no periods or extension HDTV", Quality.SDTV, false }, - new object[] { "Chuck.S04E05.HDTV.XviD-LOL", Quality.SDTV, false }, - new object[] { "The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", Quality.DVD, false }, - new object[] { "The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", Quality.DVD, false }, - new object[] { "The.Girls.Next.Door.S03E06.HDTV-WiDE", Quality.SDTV, false }, - new object[] { "Degrassi.S10E27.WS.DSR.XviD-2HD", Quality.SDTV, false }, - new object[] { "Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", Quality.WEBDL720p, false }, - new object[] { "Sonny.With.a.Chance.S02E15.720p", Quality.HDTV720p, false }, - new object[] { "Sonny.With.a.Chance.S02E15.mkv", Quality.HDTV720p, false }, - new object[] { "Sonny.With.a.Chance.S02E15.avi", Quality.SDTV, false }, - new object[] { "Sonny.With.a.Chance.S02E15.xvid", Quality.SDTV, false }, - new object[] { "Sonny.With.a.Chance.S02E15.divx", Quality.SDTV, false }, - new object[] { "Sonny.With.a.Chance.S02E15", Quality.Unknown, false }, - new object[] { "Chuck - S01E04 - So Old - Playdate - 720p TV.mkv", Quality.HDTV720p, false }, - new object[] { "Chuck - S22E03 - MoneyBART - HD TV.mkv", Quality.HDTV720p, false }, - new object[] { "Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv", Quality.Bluray720p, false }, - new object[] { "Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", Quality.Bluray1080p, false }, - new object[] { "Chuck - S11E06 - D-Yikes! - 720p WEB-DL.mkv", Quality.WEBDL720p, false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", Quality.DVD, false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", Quality.DVD, false }, - new object[] { "Law & Order: Special Victims Unit - 11x11 - Quickie", Quality.Unknown, false }, - new object[] { "S07E23 - [HDTV-720p].mkv ", Quality.HDTV720p, false }, - new object[] { "S07E23 - [WEBDL].mkv ", Quality.WEBDL720p, false }, - new object[] { "S07E23.mkv ", Quality.HDTV720p, false }, - new object[] { "S07E23 .avi ", Quality.SDTV, false }, - new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", Quality.DVD, false }, - new object[] { "WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", Quality.Bluray720p, false }, - new object[] { "The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", Quality.RAWHD, false }, - new object[] { "Nikita S02E01 HDTV XviD 2HD", Quality.SDTV, false }, - new object[] { "Gossip Girl S05E11 PROPER HDTV XviD 2HD", Quality.SDTV, true }, - new object[] { "The Jonathan Ross Show S02E08 HDTV x264 FTP", Quality.SDTV, false }, - new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-TLA", Quality.SDTV, false }, - new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA", Quality.SDTV, true }, - new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", Quality.DVD, true }, - new object[] { "Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", Quality.HDTV720p, true }, - new object[] { "The Real Housewives of Vancouver S01E04 DSR x264 2HD", Quality.SDTV, false }, - new object[] { "Vanguard S01E04 Mexicos Death Train DSR x264 MiNDTHEGAP", Quality.SDTV, false }, - new object[] { "Vanguard S01E04 Mexicos Death Train 720p WEB DL", Quality.WEBDL720p, false }, - new object[] { "Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", Quality.WEBDL720p, false }, - new object[] { "Castle S04E22 720p WEB DL DD5 1 H 264 NFHD", Quality.WEBDL720p, false }, - new object[] { "Fringe S04E22 720p WEB-DL DD5.1 H264-EbP.mkv", Quality.WEBDL720p, false }, - new object[] { "CSI NY S09E03 1080p WEB DL DD5 1 H264 NFHD", Quality.WEBDL1080p, false }, - new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 NFHD", Quality.WEBDL1080p, false }, - new object[] { "Criminal.Minds.S08E01.1080p.WEB-DL.DD5.1.H264-NFHD", Quality.WEBDL1080p, false }, - new object[] { "Its.Always.Sunny.in.Philadelphia.S08E01.1080p.WEB-DL.proper.AAC2.0.H.264", Quality.WEBDL1080p, true }, - new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 REPACK NFHD", Quality.WEBDL1080p, true }, - new object[] { "Glee.S04E09.Swan.Song.1080p.WEB-DL.DD5.1.H.264-ECI", Quality.WEBDL1080p, false }, - new object[] { "Elementary.S01E10.The.Leviathan.480p.WEB-DL.x264-mSD", Quality.WEBDL480p, false }, - new object[] { "Glee.S04E10.Glee.Actually.480p.WEB-DL.x264-mSD", Quality.WEBDL480p, false }, - new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", Quality.WEBDL480p, false }, - new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.1080p.WEB-DL.DD5.1.H.264", Quality.WEBDL1080p, false }, - new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.X264-QCF", Quality.HDTV1080p, false }, - new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.x264-QCF", Quality.HDTV1080p, false }, - new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.proper.X264-QCF", Quality.HDTV1080p, true }, - new object[] { "Dexter - S01E01 - Title [HDTV]", Quality.HDTV720p, false }, - new object[] { "Dexter - S01E01 - Title [HDTV-720p]", Quality.HDTV720p, false }, - new object[] { "Dexter - S01E01 - Title [HDTV-1080p]", Quality.HDTV1080p, false }, - new object[] { "POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", Quality.RAWHD, false }, - new object[] { "How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", Quality.RAWHD, false }, - new object[] { "Arrested.Development.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", Quality.WEBDL1080p, false }, - new object[] { "Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", Quality.WEBDL720p, false }, - new object[] { "Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", Quality.Bluray1080p, false } + new object[] { "S07E23 .avi ", false }, + new object[] {"The.Shield.S01E13.x264-CtrlSD", false}, + new object[] { "Nikita S02E01 HDTV XviD 2HD", false }, + new object[] { "Gossip Girl S05E11 PROPER HDTV XviD 2HD", true }, + new object[] { "The Jonathan Ross Show S02E08 HDTV x264 FTP", false }, + new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-TLA", false }, + new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA", true }, + new object[] { "The Real Housewives of Vancouver S01E04 DSR x264 2HD", false }, + new object[] { "Vanguard S01E04 Mexicos Death Train DSR x264 MiNDTHEGAP", false }, + new object[] { "Chuck S11E03 has no periods or extension HDTV", false }, + new object[] { "Chuck.S04E05.HDTV.XviD-LOL", false }, + new object[] { "Sonny.With.a.Chance.S02E15.avi", false }, + new object[] { "Sonny.With.a.Chance.S02E15.xvid", false }, + new object[] { "Sonny.With.a.Chance.S02E15.divx", false }, + new object[] { "The.Girls.Next.Door.S03E06.HDTV-WiDE", false }, + new object[] { "Degrassi.S10E27.WS.DSR.XviD-2HD", false }, + }; + + public static object[] DvdCases = + { + new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", true }, + new object[] { "The.Shield.S01E13.NTSC.x264-CtrlSD", false }, + new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", false }, + new object[] { "WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false }, + new object[] { "WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false }, + new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false }, + new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false }, + new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", false }, + new object[] { "The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", false }, + new object[] { "The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", false }, + new object[] { "the.shield.1x13.circles.ws.xvidvd-tns", false} + }; + + public static object[] Webdl480pCases = + { + new object[] { "Elementary.S01E10.The.Leviathan.480p.WEB-DL.x264-mSD", false }, + new object[] { "Glee.S04E10.Glee.Actually.480p.WEB-DL.x264-mSD", false }, + new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", false }, + }; + + public static object[] Hdtv720pCases = + { + new object[] { "Dexter - S01E01 - Title [HDTV]", false }, + new object[] { "Dexter - S01E01 - Title [HDTV-720p]", false }, + new object[] { "Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", true }, + new object[] { "Sonny.With.a.Chance.S02E15.720p", false }, + new object[] { "S07E23 - [HDTV-720p].mkv ", false }, + new object[] { "Chuck - S22E03 - MoneyBART - HD TV.mkv", false }, + new object[] { "S07E23.mkv ", false }, + new object[] { "Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", false }, + new object[] { "Sonny.With.a.Chance.S02E15.mkv", false }, + }; + + public static object[] Hdtv1080pCases = + { + new object[] { "Under the Dome S01E10 Let the Games Begin 1080p", false }, + new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.X264-QCF", false }, + new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.x264-QCF", false }, + new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.proper.X264-QCF", true }, + new object[] { "Dexter - S01E01 - Title [HDTV-1080p]", false }, + }; + + public static object[] Webdl720pCases = + { + new object[] { "Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", false }, + new object[] { "Vanguard S01E04 Mexicos Death Train 720p WEB DL", false }, + new object[] { "Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", false }, + new object[] { "Castle S04E22 720p WEB DL DD5 1 H 264 NFHD", false }, + new object[] { "Chuck - S11E06 - D-Yikes! - 720p WEB-DL.mkv", false }, + new object[] { "Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", false }, + new object[] { "S07E23 - [WEBDL].mkv ", false }, + new object[] { "Fringe S04E22 720p WEB-DL DD5.1 H264-EbP.mkv", false }, + }; + + public static object[] Webdl1080pCases = + { + new object[] { "Arrested.Development.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", false }, + new object[] { "CSI NY S09E03 1080p WEB DL DD5 1 H264 NFHD", false }, + new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 NFHD", false }, + new object[] { "Criminal.Minds.S08E01.1080p.WEB-DL.DD5.1.H264-NFHD", false }, + new object[] { "Its.Always.Sunny.in.Philadelphia.S08E01.1080p.WEB-DL.proper.AAC2.0.H.264", true }, + new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 REPACK NFHD", true }, + new object[] { "Glee.S04E09.Swan.Song.1080p.WEB-DL.DD5.1.H.264-ECI", false }, + new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.1080p.WEB-DL.DD5.1.H.264", false }, + }; + + public static object[] Bluray720pCases = + { + new object[] { "WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", false }, + new object[] { "Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv", false }, + }; + + public static object[] Bluray1080pCases = + { + new object[] { "Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", false }, + new object[] { "Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", false }, + }; + + public static object[] RawCases = + { + new object[] { "POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false }, + new object[] { "How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false }, + new object[] { "The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false }, + }; + + public static object[] UnknownCases = + { + new object[] { "Sonny.With.a.Chance.S02E15", false }, + new object[] { "Law & Order: Special Victims Unit - 11x11 - Quickie", false }, + }; public static object[] SelfQualityParserCases = @@ -94,20 +138,100 @@ public class QualityParserFixture : CoreTest new object[] { Quality.Bluray1080p } }; - [Test, TestCaseSource("QualityParserCases")] - public void quality_parse(string postTitle, Quality quality, bool proper) + [Test, TestCaseSource("SdtvCases")] + public void should_parse_sdtv_quality(string postTitle, bool proper) { - var result = Parser.Parser.ParseTitle(postTitle); - result.Quality.Quality.Should().Be(quality); - result.Quality.Proper.Should().Be(proper); + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.SDTV); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("DvdCases")] + public void should_parse_dvd_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.DVD); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Webdl480pCases")] + public void should_parse_webdl480p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.WEBDL480p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Hdtv720pCases")] + public void should_parse_hdtv720p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.HDTV720p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Hdtv1080pCases")] + public void should_parse_hdtv1080p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.HDTV1080p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Webdl720pCases")] + public void should_parse_webdl720p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.WEBDL720p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Webdl1080pCases")] + public void should_parse_webdl1080p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.WEBDL1080p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Bluray720pCases")] + public void should_parse_bluray720p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.Bluray720p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("Bluray1080pCases")] + public void should_parse_bluray1080p_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.Bluray1080p); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("RawCases")] + public void should_parse_raw_quality(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.RAWHD); + result.Proper.Should().Be(proper); + } + + [Test, TestCaseSource("UnknownCases")] + public void quality_parse(string postTitle, bool proper) + { + var result = Parser.QualityParser.ParseQuality(postTitle); + result.Quality.Should().Be(Quality.Unknown); + result.Proper.Should().Be(proper); } [Test, TestCaseSource("SelfQualityParserCases")] public void parsing_our_own_quality_enum(Quality quality) { var fileName = String.Format("My series S01E01 [{0}]", quality); - var result = Parser.Parser.ParseTitle(fileName); - result.Quality.Quality.Should().Be(quality); + var result = Parser.QualityParser.ParseQuality(fileName); + result.Quality.Should().Be(quality); } } } diff --git a/NzbDrone.Core.Test/ProviderTests/DiskProviderTests/FreeDiskSpaceFixture.cs b/NzbDrone.Core.Test/ProviderTests/DiskProviderTests/FreeDiskSpaceFixture.cs index 4584770ca6..849cf820aa 100644 --- a/NzbDrone.Core.Test/ProviderTests/DiskProviderTests/FreeDiskSpaceFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/DiskProviderTests/FreeDiskSpaceFixture.cs @@ -14,7 +14,7 @@ public class FreeDiskSpaceFixture : CoreTest [Test] public void should_return_free_disk_space() { - var result = Subject.GetAvilableSpace(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); + var result = Subject.GetAvailableSpace(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); result.Should().BeGreaterThan(0); } @@ -23,7 +23,7 @@ public void should_be_able_to_get_space_on_unc() { WindowsOnly(); - var result = Subject.GetAvilableSpace(@"\\localhost\c$\Windows"); + var result = Subject.GetAvailableSpace(@"\\localhost\c$\Windows"); result.Should().BeGreaterThan(0); } @@ -32,13 +32,13 @@ public void should_throw_if_drive_doesnt_exist() { WindowsOnly(); - Assert.Throws(() => Subject.GetAvilableSpace(@"Z:\NOT_A_REAL_PATH\DOES_NOT_EXIST".AsOsAgnostic())); + Assert.Throws(() => Subject.GetAvailableSpace(@"Z:\NOT_A_REAL_PATH\DOES_NOT_EXIST".AsOsAgnostic())); } [Test] public void should_be_able_to_get_space_on_folder_that_doesnt_exist() { - var result = Subject.GetAvilableSpace(@"C:\I_DO_NOT_EXIST".AsOsAgnostic()); + var result = Subject.GetAvailableSpace(@"C:\I_DO_NOT_EXIST".AsOsAgnostic()); result.Should().BeGreaterThan(0); } } diff --git a/NzbDrone.Core.Test/RootFolderTests/FreeSpaceOnDrivesFixture.cs b/NzbDrone.Core.Test/RootFolderTests/FreeSpaceOnDrivesFixture.cs index ff3458ab10..ec5ade7210 100644 --- a/NzbDrone.Core.Test/RootFolderTests/FreeSpaceOnDrivesFixture.cs +++ b/NzbDrone.Core.Test/RootFolderTests/FreeSpaceOnDrivesFixture.cs @@ -30,7 +30,7 @@ public void should_return_one_drive_when_only_one_root_dir_exists() .Returns(@"C:\"); Mocker.GetMock() - .Setup(s => s.GetAvilableSpace(@"C:\")) + .Setup(s => s.GetAvailableSpace(@"C:\")) .Returns(123456); var result = Subject.FreeSpaceOnDrives(); @@ -51,7 +51,7 @@ public void should_return_one_drive_when_two_rootDirs_on_the_same_drive_exist() .Returns(@"C:\"); Mocker.GetMock() - .Setup(s => s.GetAvilableSpace(@"C:\")) + .Setup(s => s.GetAvailableSpace(@"C:\")) .Returns(123456); var result = Subject.FreeSpaceOnDrives(); @@ -76,7 +76,7 @@ public void should_return_two_drives_when_two_rootDirs_on_the_different_drive_ex .Returns(@"D:\"); Mocker.GetMock() - .Setup(s => s.GetAvilableSpace(It.IsAny())) + .Setup(s => s.GetAvailableSpace(It.IsAny())) .Returns(123456); var result = Subject.FreeSpaceOnDrives(); @@ -96,7 +96,7 @@ public void should_skip_rootDir_if_not_found_on_disk() .Returns(@"C:\"); Mocker.GetMock() - .Setup(s => s.GetAvilableSpace(It.IsAny())) + .Setup(s => s.GetAvailableSpace(It.IsAny())) .Throws(new DirectoryNotFoundException()); var result = Subject.FreeSpaceOnDrives(); diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs new file mode 100644 index 0000000000..d31c2b6794 --- /dev/null +++ b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Tv.Events; + +namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests +{ + [TestFixture] + public class HandleEpisodeInfoDeletedEventFixture : CoreTest + { + private List _seasons; + private List _episodes; + + [SetUp] + public void Setup() + { + _seasons = Builder + .CreateListOfSize(1) + .All() + .With(s => s.SeriesId = 1) + .Build() + .ToList(); + + _episodes = Builder + .CreateListOfSize(1) + .All() + .With(e => e.SeasonNumber = _seasons.First().SeasonNumber) + .With(s => s.SeriesId = _seasons.First().SeasonNumber) + .Build() + .ToList(); + + Mocker.GetMock() + .Setup(s => s.GetSeasonBySeries(It.IsAny())) + .Returns(_seasons); + + Mocker.GetMock() + .Setup(s => s.GetEpisodesBySeason(It.IsAny(), _seasons.First().SeasonNumber)) + .Returns(_episodes); + } + + private void GivenAbandonedSeason() + { + Mocker.GetMock() + .Setup(s => s.GetEpisodesBySeason(It.IsAny(), _seasons.First().SeasonNumber)) + .Returns(new List()); + } + + [Test] + public void should_not_delete_when_season_is_still_valid() + { + Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); + + Mocker.GetMock() + .Verify(v => v.Delete(It.IsAny()), Times.Never()); + } + + [Test] + public void should_delete_season_if_no_episodes_exist_in_that_season() + { + GivenAbandonedSeason(); + + Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); + + Mocker.GetMock() + .Verify(v => v.Delete(It.IsAny()), Times.Once()); + } + + [Test] + public void should_only_delete_a_season_once() + { + _episodes = Builder + .CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = _seasons.First().SeasonNumber) + .With(s => s.SeriesId = _seasons.First().SeasonNumber) + .Build() + .ToList(); + + GivenAbandonedSeason(); + + Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); + + Mocker.GetMock() + .Verify(v => v.Delete(It.IsAny()), Times.Once()); + } + } +} diff --git a/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs b/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs index bbf0c5b9c8..138e3b404c 100644 --- a/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs +++ b/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs @@ -16,13 +16,7 @@ public void should_get_list_of_available_updates() Mocker.GetMock().SetupGet(c => c.Branch).Returns("master"); - var updates = Subject.GetAvailablePackages().ToList(); - - updates.Should().NotBeEmpty(); - updates.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.FileName)); - updates.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Url)); - updates.Should().OnlyContain(c => c.Version != null); - updates.Should().OnlyContain(c => c.Version.Major == 2); + Subject.GetLatestUpdate().Should().BeNull(); } } } diff --git a/NzbDrone.Core.Test/packages.config b/NzbDrone.Core.Test/packages.config index 0881c7fe5d..b751901cdb 100644 --- a/NzbDrone.Core.Test/packages.config +++ b/NzbDrone.Core.Test/packages.config @@ -2,7 +2,7 @@ - + diff --git a/NzbDrone.Core/Configuration/ConfigService.cs b/NzbDrone.Core/Configuration/ConfigService.cs index 0d9189fc61..e3ca0317bf 100644 --- a/NzbDrone.Core/Configuration/ConfigService.cs +++ b/NzbDrone.Core/Configuration/ConfigService.cs @@ -172,11 +172,6 @@ public string BlackholeFolder set { SetValue("BlackholeFolder", value); } } - public string ServiceRootUrl - { - get { return "http://services.nzbdrone.com"; } - } - public string PneumaticFolder { get { return GetValue("PneumaticFolder", String.Empty); } diff --git a/NzbDrone.Core/Configuration/IConfigService.cs b/NzbDrone.Core/Configuration/IConfigService.cs index 07e70d25b0..d84ed272be 100644 --- a/NzbDrone.Core/Configuration/IConfigService.cs +++ b/NzbDrone.Core/Configuration/IConfigService.cs @@ -25,7 +25,6 @@ public interface IConfigService int Retention { get; set; } DownloadClientType DownloadClient { get; set; } string BlackholeFolder { get; set; } - string ServiceRootUrl { get; } string PneumaticFolder { get; set; } string RecycleBin { get; set; } String NzbgetUsername { get; set; } diff --git a/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs b/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs index fc2a591a27..f59ff9ee9d 100644 --- a/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs +++ b/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs @@ -3,7 +3,6 @@ using NLog; using NzbDrone.Common; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Configuration; namespace NzbDrone.Core.DataAugmentation.DailySeries { @@ -17,13 +16,11 @@ public interface IDailySeriesDataProxy public class DailySeriesDataProxy : IDailySeriesDataProxy { private readonly IHttpProvider _httpProvider; - private readonly IConfigService _configService; private readonly Logger _logger; - public DailySeriesDataProxy(IHttpProvider httpProvider, IConfigService configService, Logger logger) + public DailySeriesDataProxy(IHttpProvider httpProvider, Logger logger) { _httpProvider = httpProvider; - _configService = configService; _logger = logger; } @@ -31,7 +28,7 @@ public IEnumerable GetDailySeriesIds() { try { - var dailySeriesIds = _httpProvider.DownloadString(_configService.ServiceRootUrl + "/DailySeries/AllIds"); + var dailySeriesIds = _httpProvider.DownloadString(Services.RootUrl + "/DailySeries/AllIds"); var seriesIds = Json.Deserialize>(dailySeriesIds); @@ -49,7 +46,7 @@ public bool IsDailySeries(int tvdbid) { try { - var result = _httpProvider.DownloadString(_configService.ServiceRootUrl + "/DailySeries/Check?seriesId=" + tvdbid); + var result = _httpProvider.DownloadString(Services.RootUrl + "/DailySeries/Check?seriesId=" + tvdbid); return Convert.ToBoolean(result); } catch (Exception ex) diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs index c38cb4a58f..2447a96ec7 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs @@ -13,19 +13,15 @@ public interface ISceneMappingProxy public class SceneMappingProxy : ISceneMappingProxy { private readonly IHttpProvider _httpProvider; - private readonly IConfigService _configService; - - public SceneMappingProxy(IHttpProvider httpProvider, IConfigService configService) + public SceneMappingProxy(IHttpProvider httpProvider) { _httpProvider = httpProvider; - _configService = configService; - } public List Fetch() { - var mappingsJson = _httpProvider.DownloadString(_configService.ServiceRootUrl + "/SceneMapping/Active"); + var mappingsJson = _httpProvider.DownloadString(Services.RootUrl + "/SceneMapping/Active"); return Json.Deserialize>(mappingsJson); } } diff --git a/NzbDrone.Core/Datastore/Migration/016_updated_imported_history_item.cs b/NzbDrone.Core/Datastore/Migration/016_updated_imported_history_item.cs new file mode 100644 index 0000000000..7a2c50e717 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/016_updated_imported_history_item.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(16)] + public class updated_imported_history_item : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.Sql(@"UPDATE HISTORY SET Data = replace( Data, '""Path""', '""ImportedPath""' ) WHERE EventType=3"); + } + } +} diff --git a/NzbDrone.Core/Datastore/Migration/017_reset_scene_names.cs b/NzbDrone.Core/Datastore/Migration/017_reset_scene_names.cs new file mode 100644 index 0000000000..e2e3a21d66 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/017_reset_scene_names.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(17)] + public class reset_scene_names : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + //we were storing new file name as scene name. + Execute.Sql(@"UPDATE EpisodeFiles SET SceneName = NULL where SceneName != NULL"); + } + } +} diff --git a/NzbDrone.Core/History/HistoryService.cs b/NzbDrone.Core/History/HistoryService.cs index 40f637fcfc..c0752eb2a1 100644 --- a/NzbDrone.Core/History/HistoryService.cs +++ b/NzbDrone.Core/History/HistoryService.cs @@ -62,7 +62,7 @@ public void Handle(EpisodeGrabbedEvent message) { var history = new History { - EventType = HistoryEventType.Grabbed, + EventType = HistoryEventType.Grabbed, Date = DateTime.UtcNow, Quality = message.Episode.ParsedEpisodeInfo.Quality, SourceTitle = message.Episode.Report.Title, @@ -81,20 +81,22 @@ public void Handle(EpisodeGrabbedEvent message) public void Handle(EpisodeImportedEvent message) { - foreach (var episode in message.EpisodeFile.Episodes.Value) + foreach (var episode in message.DroppedEpisode.Episodes) { var history = new History { EventType = HistoryEventType.DownloadFolderImported, Date = DateTime.UtcNow, - Quality = message.EpisodeFile.Quality, - SourceTitle = message.EpisodeFile.Path, - SeriesId = message.EpisodeFile.SeriesId, + Quality = message.DroppedEpisode.Quality, + SourceTitle = message.ImportedEpisode.SceneName, + SeriesId = message.ImportedEpisode.SeriesId, EpisodeId = episode.Id }; - history.Data.Add("Path", message.EpisodeFile.Path); - history.Data.Add("Filename", Path.GetFileNameWithoutExtension(message.EpisodeFile.Path)); + //Won't have a value since we publish this event before saving to DB. + //history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); + history.Data.Add("DroppedPath", message.DroppedEpisode.Path); + history.Data.Add("ImportedPath", message.ImportedEpisode.Path); _historyRepository.Insert(history); } diff --git a/NzbDrone.Core/Indexers/BasicRssParser.cs b/NzbDrone.Core/Indexers/BasicRssParser.cs index d298892485..84c61f296d 100644 --- a/NzbDrone.Core/Indexers/BasicRssParser.cs +++ b/NzbDrone.Core/Indexers/BasicRssParser.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Xml; using System.Xml.Linq; using NLog; using NzbDrone.Core.Parser.Model; @@ -12,7 +13,7 @@ namespace NzbDrone.Core.Indexers { public interface IParseFeed { - IEnumerable Process(Stream source, string url); + IEnumerable Process(string xml, string url); } public class BasicRssParser : IParseFeed @@ -24,34 +25,37 @@ public BasicRssParser() _logger = LogManager.GetCurrentClassLogger(); } - public IEnumerable Process(Stream source, string url) + public IEnumerable Process(string xml, string url) { - var document = XDocument.Load(source); - var items = document.Descendants("item"); - - var result = new List(); - - foreach (var item in items) + using (var xmlTextReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { ProhibitDtd = false, IgnoreComments = true })) { - try - { - var reportInfo = ParseFeedItem(item); - if (reportInfo != null) - { - reportInfo.NzbUrl = GetNzbUrl(item); - reportInfo.NzbInfoUrl = GetNzbInfoUrl(item); + var document = XDocument.Load(xmlTextReader); + var items = document.Descendants("item"); - result.Add(reportInfo); + var result = new List(); + + foreach (var item in items) + { + try + { + var reportInfo = ParseFeedItem(item); + if (reportInfo != null) + { + reportInfo.NzbUrl = GetNzbUrl(item); + reportInfo.NzbInfoUrl = GetNzbInfoUrl(item); + + result.Add(reportInfo); + } + } + catch (Exception itemEx) + { + itemEx.Data.Add("Item", item.Title()); + _logger.ErrorException("An error occurred while processing feed item from " + url, itemEx); } } - catch (Exception itemEx) - { - itemEx.Data.Add("Item", item.Title()); - _logger.ErrorException("An error occurred while processing feed item from " + url, itemEx); - } - } - return result; + return result; + } } diff --git a/NzbDrone.Core/Indexers/IndexerFetchService.cs b/NzbDrone.Core/Indexers/IndexerFetchService.cs index 6c46032b64..2e585f8621 100644 --- a/NzbDrone.Core/Indexers/IndexerFetchService.cs +++ b/NzbDrone.Core/Indexers/IndexerFetchService.cs @@ -106,19 +106,26 @@ private List Fetch(IIndexer indexer, IEnumerable urls) try { _logger.Trace("Downloading Feed " + url); - var stream = _httpProvider.DownloadStream(url); - result.AddRange(indexer.Parser.Process(stream, url)); + var xml = _httpProvider.DownloadString(url); + if (!string.IsNullOrWhiteSpace(xml)) + { + result.AddRange(indexer.Parser.Process(xml, url)); + } + else + { + _logger.Warn("{0} returned empty response.", url); + } + } catch (WebException webException) { - if (webException.Message.Contains("503") || webException.Message.Contains("timed out")) + if (webException.Message.Contains("502") || webException.Message.Contains("503") || webException.Message.Contains("timed out")) { _logger.Warn("{0} server is currently unavailable. {1} {2}", indexer.Name, url, webException.Message); } else { - webException.Data.Add("FeedUrl", url); - _logger.WarnException("An error occurred while processing feed. " + url, webException); + _logger.Warn("{0} {1} {2}", indexer.Name, url, webException.Message); } } catch (Exception feedEx) diff --git a/NzbDrone.Core/Instrumentation/LogRepository.cs b/NzbDrone.Core/Instrumentation/LogRepository.cs index 5fe755580e..0a00aa27b5 100644 --- a/NzbDrone.Core/Instrumentation/LogRepository.cs +++ b/NzbDrone.Core/Instrumentation/LogRepository.cs @@ -18,7 +18,7 @@ public LogRepository(IDatabase database, IMessageAggregator messageAggregator) public void Trim() { - var trimDate = DateTime.UtcNow.AddDays(-15).Date; + var trimDate = DateTime.UtcNow.AddDays(-7).Date; Delete(c => c.Time <= trimDate); } } diff --git a/NzbDrone.Core/MediaCover/MediaCoverService.cs b/NzbDrone.Core/MediaCover/MediaCoverService.cs index 99f10e4e70..e3d247bfa7 100644 --- a/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -44,30 +44,31 @@ private void EnsureCovers(Series series) foreach (var cover in series.Images) { var fileName = GetCoverPath(series.Id, cover.CoverType); - if (!_coverExistsSpecification.AlreadyExists(cover.Url, fileName)) + try { - DownloadCover(series, cover); + if (!_coverExistsSpecification.AlreadyExists(cover.Url, fileName)) + { + DownloadCover(series, cover); + } + } + catch (WebException e) + { + _logger.Warn(string.Format("Couldn't download media cover for {0}. {1}", series, e.Message)); + } + catch (Exception e) + { + _logger.ErrorException("Couldn't download media cover for " + series, e); } } } private void DownloadCover(Series series, MediaCover cover) { - try - { - var fileName = GetCoverPath(series.Id, cover.CoverType); + var fileName = GetCoverPath(series.Id, cover.CoverType); + + _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url); + _httpProvider.DownloadFile(cover.Url, fileName); - _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url); - _httpProvider.DownloadFile(cover.Url, fileName); - } - catch (WebException e) - { - _logger.Warn("Couldn't download media cover for " + series); - } - catch (Exception e) - { - _logger.ErrorException("Couldn't download media cover for " + series, e); - } } public void HandleAsync(SeriesDeletedEvent message) diff --git a/NzbDrone.Core/MediaFiles/DiskScanService.cs b/NzbDrone.Core/MediaFiles/DiskScanService.cs index 17d7e6dc2c..0fb6901fe8 100644 --- a/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -24,9 +24,9 @@ public class DiskScanService : private const string EXTENSIONS = //XBMC - ".m4v .3gp .nsv .ts .ty .strm .rm .rmvb .m3u .ifo .mov .qt .divx .xvid .bivx .vob .nrg .img" + + ".m4v .3gp .nsv .ts .ty .strm .rm .rmvb .m3u .ifo .mov .qt .divx .xvid .bivx .vob .nrg .img " + ".iso .pva .wmv .asf .asx .ogm .m2v .avi .bin .dat .dvr-ms .mpg .mpeg .mp4 .mkv .avc .vp3 " + - ".svq3 .nuv .viv .dv .fli .flv .wpl" + + ".svq3 .nuv .viv .dv .fli .flv .wpl " + //Other ".m2ts"; diff --git a/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index e46bf3b26e..ef230443b5 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -13,8 +13,8 @@ namespace NzbDrone.Core.MediaFiles { public interface IMoveEpisodeFiles { - EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series); - EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode); + string MoveEpisodeFile(EpisodeFile episodeFile, Series series); + string MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode); } public class MoveEpisodeFiles : IMoveEpisodeFiles @@ -38,27 +38,26 @@ public MoveEpisodeFiles(IEpisodeService episodeService, _logger = logger; } - public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series) + public string MoveEpisodeFile(EpisodeFile episodeFile, Series series) { var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id); var newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile); - var destinationFilename = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); + var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); + MoveFile(episodeFile, filePath); - return MoveFile(episodeFile, destinationFilename); + return filePath; } - public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) + public string MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) { var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile); - var destinationFilename = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); - episodeFile = MoveFile(episodeFile, destinationFilename); + var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); + MoveFile(episodeFile, filePath); - _messageAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode)); - - return episodeFile; + return filePath; } - private EpisodeFile MoveFile(EpisodeFile episodeFile, string destinationFilename) + private void MoveFile(EpisodeFile episodeFile, string destinationFilename) { if (!_diskProvider.FileExists(episodeFile.Path)) { @@ -85,10 +84,6 @@ private EpisodeFile MoveFile(EpisodeFile episodeFile, string destinationFilename _logger.Debug("Unable to apply folder permissions to: ", destinationFilename); _logger.TraceException(ex.Message, ex); } - - episodeFile.Path = destinationFilename; - - return episodeFile; } } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 0757b14956..733b8580d4 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -13,7 +13,7 @@ public interface IImportApprovedEpisodes { List Import(List decisions, bool newDownloads = false); } - + public class ImportApprovedEpisodes : IImportApprovedEpisodes { private readonly IUpgradeMediaFiles _episodeFileUpgrader; @@ -62,15 +62,17 @@ public List Import(List decisions, bool newDownl episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Quality = localEpisode.Quality; episodeFile.SeasonNumber = localEpisode.SeasonNumber; - episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); episodeFile.Episodes = localEpisode.Episodes; + if (newDownload) { - episodeFile = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode); - _messageAggregator.PublishEvent(new EpisodeImportedEvent(episodeFile)); + episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); + episodeFile.Path = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode); + _messageAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile)); + _messageAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode)); } - + _mediaFileService.Add(episodeFile); imported.Add(importDecision); } diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs index 833e368a37..9c499d8fb4 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs @@ -24,7 +24,7 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode) try { var path = Directory.GetParent(localEpisode.Series.Path); - var freeSpace = _diskProvider.GetAvilableSpace(path.FullName); + var freeSpace = _diskProvider.GetAvailableSpace(path.FullName); if (freeSpace < localEpisode.Size + 100.Megabytes()) { diff --git a/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs b/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs index 9c8e59cdc9..2f166b069f 100644 --- a/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs +++ b/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs @@ -1,14 +1,17 @@ using NzbDrone.Common.Messaging; +using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.MediaFiles.Events { public class EpisodeImportedEvent : IEvent { - public EpisodeFile EpisodeFile { get; private set; } + public LocalEpisode DroppedEpisode { get; private set; } + public EpisodeFile ImportedEpisode { get; private set; } - public EpisodeImportedEvent(EpisodeFile episodeFile) + public EpisodeImportedEvent(LocalEpisode droppedEpisode, EpisodeFile importedEpisode) { - EpisodeFile = episodeFile; + DroppedEpisode = droppedEpisode; + ImportedEpisode = importedEpisode; } } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs index f6737a9ef9..7912039e71 100644 --- a/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs +++ b/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs @@ -34,14 +34,12 @@ private void RenameFiles(List episodeFiles, Series series) { var renamed = new List(); - foreach (var file in episodeFiles) + foreach (var episodeFile in episodeFiles) { try { - var episodeFile = file; - _logger.Trace("Renaming episode file: {0}", episodeFile); - episodeFile = _episodeFileMover.MoveEpisodeFile(episodeFile, series); + episodeFile.Path = _episodeFileMover.MoveEpisodeFile(episodeFile, series); _mediaFileService.Update(episodeFile); renamed.Add(episodeFile); @@ -54,7 +52,7 @@ private void RenameFiles(List episodeFiles, Series series) } catch (Exception ex) { - _logger.ErrorException("Failed to rename file: " + file.Path, ex); + _logger.ErrorException("Failed to rename file: " + episodeFile.Path, ex); } } diff --git a/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index c272e91977..75ee6869ee 100644 --- a/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -7,7 +7,7 @@ namespace NzbDrone.Core.MediaFiles { public interface IUpgradeMediaFiles { - EpisodeFile UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode); + string UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode); } public class UpgradeMediaFileService : IUpgradeMediaFiles @@ -31,7 +31,7 @@ public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider, _logger = logger; } - public EpisodeFile UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) + public string UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) { var existingFiles = localEpisode.Episodes .Where(e => e.EpisodeFileId > 0) diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 15ded77294..3f95a1ab6b 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -158,6 +158,8 @@ + + @@ -342,6 +344,7 @@ + @@ -350,6 +353,7 @@ + @@ -493,6 +497,7 @@ + diff --git a/NzbDrone.Core/Parser/Model/RemoteEpisode.cs b/NzbDrone.Core/Parser/Model/RemoteEpisode.cs index cc7459e4f1..70b0015114 100644 --- a/NzbDrone.Core/Parser/Model/RemoteEpisode.cs +++ b/NzbDrone.Core/Parser/Model/RemoteEpisode.cs @@ -17,7 +17,7 @@ public class RemoteEpisode public bool IsRecentEpisode() { - return Episodes.Any(e => e.AirDateUtc >= DateTime.Today.AddDays(-14)); + return Episodes.Any(e => e.AirDateUtc >= DateTime.UtcNow.Date.AddDays(-14)); } public override string ToString() diff --git a/NzbDrone.Core/Parser/Parser.cs b/NzbDrone.Core/Parser/Parser.cs index 2f88fe81e4..3178b50847 100644 --- a/NzbDrone.Core/Parser/Parser.cs +++ b/NzbDrone.Core/Parser/Parser.cs @@ -74,7 +74,8 @@ public static class Parser private static readonly Regex MultiPartCleanupRegex = new Regex(@"\(\d+\)$", RegexOptions.Compiled); - private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?ita|italian)|(?german\b)|(?flemish)|(?greek)(?:\W|_)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?ita|italian)|(?german\b)|(?flemish)|(?greek)|(?(?:\W|_)FR)(?:\W|_)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); public static ParsedEpisodeInfo ParsePath(string path) { @@ -119,7 +120,7 @@ public static ParsedEpisodeInfo ParseTitle(string title) break; result.Language = ParseLanguage(title); - result.Quality = ParseQuality(title); + result.Quality = QualityParser.ParseQuality(title); return result; } } @@ -240,155 +241,6 @@ public static string ParseSeriesName(string title) return parseResult.SeriesTitle; } - private static QualityModel ParseQuality(string name) - { - Logger.Trace("Trying to parse quality for {0}", name); - - name = name.Trim(); - var normalizedName = CleanSeriesTitle(name); - var result = new QualityModel { Quality = Quality.Unknown }; - result.Proper = (normalizedName.Contains("proper") || normalizedName.Contains("repack")); - - if ((normalizedName.Contains("dvd") && !normalizedName.Contains("avcdvd")) || normalizedName.Contains("bdrip") || normalizedName.Contains("brrip")) - { - result.Quality = Quality.DVD; - return result; - } - - if (normalizedName.Contains("xvid") || normalizedName.Contains("divx") || normalizedName.Contains("dsr")) - { - if (normalizedName.Contains("bluray")) - { - result.Quality = Quality.DVD; - return result; - } - - result.Quality = Quality.SDTV; - return result; - } - - if (normalizedName.Contains("bluray")) - { - if (normalizedName.Contains("720p")) - { - result.Quality = Quality.Bluray720p; - return result; - } - - if (normalizedName.Contains("1080p")) - { - result.Quality = Quality.Bluray1080p; - return result; - } - - result.Quality = Quality.Bluray720p; - return result; - } - if (normalizedName.Contains("webdl") || normalizedName.Contains("webrip")) - { - if (normalizedName.Contains("1080p")) - { - result.Quality = Quality.WEBDL1080p; - return result; - } - - if (normalizedName.Contains("720p")) - { - result.Quality = Quality.WEBDL720p; - return result; - } - - if (name.Contains("[WEBDL]")) - { - result.Quality = Quality.WEBDL720p; - return result; - } - - result.Quality = Quality.WEBDL480p; - return result; - } - - if (normalizedName.Contains("trollhd") || normalizedName.Contains("rawhd")) - { - result.Quality = Quality.RAWHD; - return result; - } - - if (normalizedName.Contains("x264") || normalizedName.Contains("h264") || normalizedName.Contains("720p")) - { - if (normalizedName.Contains("1080p")) - { - result.Quality = Quality.HDTV1080p; - return result; - } - - result.Quality = Quality.HDTV720p; - return result; - } - - //Based on extension - if (result.Quality == Quality.Unknown && !name.ContainsInvalidPathChars()) - { - try - { - switch (Path.GetExtension(name).ToLower()) - { - case ".avi": - case ".xvid": - case ".divx": - case ".wmv": - case ".mp4": - case ".mpg": - case ".mpeg": - case ".mov": - case ".rm": - case ".rmvb": - case ".flv": - case ".dvr-ms": - case ".ogm": - case ".strm": - { - result.Quality = Quality.SDTV; - break; - } - case ".mkv": - case ".ts": - { - result.Quality = Quality.HDTV720p; - break; - } - } - } - catch (ArgumentException) - { - //Swallow exception for cases where string contains illegal - //path characters. - } - } - - if (name.Contains("[HDTV]")) - { - result.Quality = Quality.HDTV720p; - return result; - } - - if (normalizedName.Contains("hdtv") && normalizedName.Contains("1080p")) - { - result.Quality = Quality.HDTV1080p; - return result; - } - - if ((normalizedName.Contains("sdtv") || normalizedName.Contains("pdtv") || - (result.Quality == Quality.Unknown && normalizedName.Contains("hdtv"))) && - !normalizedName.Contains("mpeg")) - { - result.Quality = Quality.SDTV; - return result; - } - - return result; - } - private static Language ParseLanguage(string title) { var lowerTitle = title.ToLower(); @@ -461,6 +313,9 @@ private static Language ParseLanguage(string title) if (match.Groups["greek"].Captures.Cast().Any()) return Language.Greek; + if (match.Groups["french"].Success) + return Language.French; + return Language.English; } diff --git a/NzbDrone.Core/Parser/QualityParser.cs b/NzbDrone.Core/Parser/QualityParser.cs new file mode 100644 index 0000000000..8cd3077e2a --- /dev/null +++ b/NzbDrone.Core/Parser/QualityParser.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Parser +{ + public class QualityParser + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private static readonly Regex SourceRegex = new Regex(@"(?BluRay)| + (?WEB-DL|WEBDL|WEB\sDL|WEB\-DL|WebRip)| + (?HDTV)| + (?BDRiP)|(?BRRip)|(?\bDVD\b|DVDRip|NTSC|PAL|xvidvd)| + (?WS\sDSR|WS_DSR|WS\.DSR|DSR)|(?PDTV)|(?SDTV)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + private static readonly Regex ResolutionRegex = new Regex(@"(?<_480p>480p)|(?<_720p>720p)|(?<_1080p>1080p)", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex CodecRegex = new Regex(@"(?x264)|(?h264)|(?XvidHD)|(?Xvid)|(?divx)", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static QualityModel ParseQuality(string name) + { + Logger.Trace("Trying to parse quality for {0}", name); + + name = name.Trim(); + var normalizedName = name.CleanSeriesTitle(); + var result = new QualityModel { Quality = Quality.Unknown }; + result.Proper = (normalizedName.Contains("proper") || normalizedName.Contains("repack")); + + if (normalizedName.Contains("trollhd") || normalizedName.Contains("rawhd")) + { + result.Quality = Quality.RAWHD; + return result; + } + + var sourceMatch = SourceRegex.Match(name); + var resolution = ParseResolution(name); + var codecRegex = CodecRegex.Match(name); + + if (sourceMatch.Groups["bluray"].Success) + { + if (codecRegex.Groups["xvid"].Success || codecRegex.Groups["divx"].Success) + { + result.Quality = Quality.DVD; + return result; + } + + if (resolution == Resolution._1080p) + { + result.Quality = Quality.Bluray1080p; + return result; + } + + result.Quality = Quality.Bluray720p; + return result; + } + + if (sourceMatch.Groups["webdl"].Success) + { + if (resolution == Resolution._1080p) + { + result.Quality = Quality.WEBDL1080p; + return result; + } + + if (resolution == Resolution._720p) + { + result.Quality = Quality.WEBDL720p; + return result; + } + + if (name.Contains("[WEBDL]")) + { + result.Quality = Quality.WEBDL720p; + return result; + } + + result.Quality = Quality.WEBDL480p; + return result; + } + + if (sourceMatch.Groups["hdtv"].Success) + { + if (resolution == Resolution._1080p) + { + result.Quality = Quality.HDTV1080p; + return result; + } + + if (resolution == Resolution._720p) + { + result.Quality = Quality.HDTV720p; + return result; + } + + if (name.Contains("[HDTV]")) + { + result.Quality = Quality.HDTV720p; + return result; + } + + result.Quality = Quality.SDTV; + return result; + } + + if (sourceMatch.Groups["dvd"].Success || + sourceMatch.Groups["bdrip"].Success || + sourceMatch.Groups["brrip"].Success) + { + result.Quality = Quality.DVD; + return result; + } + + if (sourceMatch.Groups["pdtv"].Success || + sourceMatch.Groups["sdtv"].Success || + sourceMatch.Groups["dsr"].Success) + { + result.Quality = Quality.SDTV; + return result; + } + + if (resolution == Resolution._1080p) + { + result.Quality = Quality.HDTV1080p; + return result; + } + + if (resolution == Resolution._720p) + { + result.Quality = Quality.HDTV720p; + return result; + } + + if (codecRegex.Groups["x264"].Success) + { + result.Quality = Quality.SDTV; + return result; + } + + //Based on extension + if (result.Quality == Quality.Unknown && !name.ContainsInvalidPathChars()) + { + try + { + switch (Path.GetExtension(name).ToLower()) + { + case ".avi": + case ".xvid": + case ".divx": + case ".wmv": + case ".mp4": + case ".mpg": + case ".mpeg": + case ".mov": + case ".rm": + case ".rmvb": + case ".flv": + case ".dvr-ms": + case ".ogm": + case ".strm": + { + result.Quality = Quality.SDTV; + break; + } + case ".mkv": + case ".ts": + { + result.Quality = Quality.HDTV720p; + break; + } + } + } + catch (ArgumentException) + { + //Swallow exception for cases where string contains illegal + //path characters. + } + } + + return result; + } + + private static Resolution ParseResolution(string name) + { + var match = ResolutionRegex.Match(name); + + if (!match.Success) return Resolution.Unknown; + if (match.Groups["_480p"].Success) return Resolution._480p; + if (match.Groups["_720p"].Success) return Resolution._720p; + if (match.Groups["_1080p"].Success) return Resolution._1080p; + + return Resolution.Unknown; + } + } + + public enum Resolution + { + _480p, + _720p, + _1080p, + Unknown + } +} diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs index 0881f4edc4..931793dfb6 100644 --- a/NzbDrone.Core/Providers/XemProvider.cs +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common.Cache; using NzbDrone.Common.Messaging; +using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; @@ -12,31 +14,37 @@ public interface IXemProvider { void UpdateMappings(); void UpdateMappings(int seriesId); + void UpdateMappings(Series series); void PerformUpdate(Series series); } - public class XemProvider : IXemProvider, IExecute, IHandle + public class XemProvider : IXemProvider, IExecute, IHandle, IHandleAsync { private readonly IEpisodeService _episodeService; private readonly IXemCommunicationProvider _xemCommunicationProvider; private readonly ISeriesService _seriesService; + private readonly ICached _cache; private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - public XemProvider(IEpisodeService episodeService, IXemCommunicationProvider xemCommunicationProvider, ISeriesService seriesService) + public XemProvider(IEpisodeService episodeService, + IXemCommunicationProvider xemCommunicationProvider, + ISeriesService seriesService, ICacheManger cacheManger) { if (seriesService == null) throw new ArgumentNullException("seriesService"); _episodeService = episodeService; _xemCommunicationProvider = xemCommunicationProvider; _seriesService = seriesService; + _cache = cacheManger.GetCache(GetType()); } public void UpdateMappings() { _logger.Trace("Starting scene numbering update"); + try { - var ids = _xemCommunicationProvider.GetXemSeriesIds(); + var ids = GetXemSeriesIds(); var series = _seriesService.GetAllSeries(); var wantedSeries = series.Where(s => ids.Contains(s.TvdbId)).ToList(); @@ -64,12 +72,15 @@ public void UpdateMappings(int seriesId) _logger.Trace("Series could not be found: {0}", seriesId); return; } + + UpdateMappings(series); + } - var xemIds = _xemCommunicationProvider.GetXemSeriesIds(); - - if (!xemIds.Contains(series.TvdbId)) + public void UpdateMappings(Series series) + { + if (!_cache.Find(series.TvdbId.ToString())) { - _logger.Trace("Xem doesn't have a mapping for this series: {0}", series.TvdbId); + _logger.Trace("Scene numbering is not available for {0} [{1}]", series.Title, series.TvdbId); return; } @@ -125,6 +136,20 @@ public void PerformUpdate(Series series) } } + private List GetXemSeriesIds() + { + _cache.Clear(); + + var ids = _xemCommunicationProvider.GetXemSeriesIds(); + + foreach (var id in ids) + { + _cache.Set(id.ToString(), true); + } + + return ids; + } + public void Execute(UpdateXemMappingsCommand message) { if (message.SeriesId.HasValue) @@ -139,7 +164,12 @@ public void Execute(UpdateXemMappingsCommand message) public void Handle(SeriesUpdatedEvent message) { - PerformUpdate(message.Series); + UpdateMappings(message.Series); + } + + public void HandleAsync(ApplicationStartedEvent message) + { + GetXemSeriesIds(); } } } diff --git a/NzbDrone.Core/RootFolders/RootFolder.cs b/NzbDrone.Core/RootFolders/RootFolder.cs index e197251930..8232653237 100644 --- a/NzbDrone.Core/RootFolders/RootFolder.cs +++ b/NzbDrone.Core/RootFolders/RootFolder.cs @@ -8,7 +8,7 @@ public class RootFolder : ModelBase { public string Path { get; set; } - public long FreeSpace { get; set; } + public long? FreeSpace { get; set; } public List UnmappedFolders { get; set; } } diff --git a/NzbDrone.Core/RootFolders/RootFolderService.cs b/NzbDrone.Core/RootFolders/RootFolderService.cs index 00e1a9cf05..160cd3c9fa 100644 --- a/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -17,7 +17,7 @@ public interface IRootFolderService RootFolder Add(RootFolder rootDir); void Remove(int id); List GetUnmappedFolders(string path); - Dictionary FreeSpaceOnDrives(); + Dictionary FreeSpaceOnDrives(); RootFolder Get(int id); } @@ -55,7 +55,7 @@ public List AllWithUnmappedFolders() { if (_diskProvider.FolderExists(folder.Path)) { - folder.FreeSpace = _diskProvider.GetAvilableSpace(folder.Path); + folder.FreeSpace = _diskProvider.GetAvailableSpace(folder.Path); folder.UnmappedFolders = GetUnmappedFolders(folder.Path); } }); @@ -82,7 +82,7 @@ public RootFolder Add(RootFolder rootFolder) _rootFolderRepository.Insert(rootFolder); - rootFolder.FreeSpace = _diskProvider.GetAvilableSpace(rootFolder.Path); + rootFolder.FreeSpace = _diskProvider.GetAvailableSpace(rootFolder.Path); rootFolder.UnmappedFolders = GetUnmappedFolders(rootFolder.Path); return rootFolder; } @@ -126,9 +126,9 @@ public List GetUnmappedFolders(string path) return results; } - public Dictionary FreeSpaceOnDrives() + public Dictionary FreeSpaceOnDrives() { - var freeSpace = new Dictionary(); + var freeSpace = new Dictionary(); var rootDirs = All(); @@ -140,7 +140,7 @@ public Dictionary FreeSpaceOnDrives() { try { - freeSpace.Add(pathRoot, _diskProvider.GetAvilableSpace(rootDir.Path)); + freeSpace.Add(pathRoot, _diskProvider.GetAvailableSpace(rootDir.Path)); } catch (Exception ex) { @@ -155,7 +155,7 @@ public Dictionary FreeSpaceOnDrives() public RootFolder Get(int id) { var rootFolder = _rootFolderRepository.Get(id); - rootFolder.FreeSpace = _diskProvider.GetAvilableSpace(rootFolder.Path); + rootFolder.FreeSpace = _diskProvider.GetAvailableSpace(rootFolder.Path); rootFolder.UnmappedFolders = GetUnmappedFolders(rootFolder.Path); return rootFolder; } diff --git a/NzbDrone.Core/Tv/Events/EpisodeInfoDeletedEvent.cs b/NzbDrone.Core/Tv/Events/EpisodeInfoDeletedEvent.cs new file mode 100644 index 0000000000..864445e58e --- /dev/null +++ b/NzbDrone.Core/Tv/Events/EpisodeInfoDeletedEvent.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Tv.Events +{ + public class EpisodeInfoDeletedEvent : IEvent + { + public ReadOnlyCollection Episodes { get; private set; } + + public EpisodeInfoDeletedEvent(IList episodes) + { + Episodes = new ReadOnlyCollection(episodes); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/NzbDrone.Core/Tv/RefreshEpisodeService.cs index 14140ba842..c62d8a5d23 100644 --- a/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -34,7 +34,7 @@ public void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisode var successCount = 0; var failCount = 0; - var existinEpisodes = _episodeService.GetEpisodeBySeries(series.Id); + var existingEpisodes = _episodeService.GetEpisodeBySeries(series.Id); var seasons = _seasonService.GetSeasonsBySeries(series.Id); var updateList = new List(); @@ -44,11 +44,11 @@ public void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisode { try { - var episodeToUpdate = existinEpisodes.SingleOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); + var episodeToUpdate = existingEpisodes.SingleOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); if (episodeToUpdate != null) { - existinEpisodes.Remove(episodeToUpdate); + existingEpisodes.Remove(episodeToUpdate); updateList.Add(episodeToUpdate); } else @@ -82,11 +82,10 @@ public void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisode AdjustMultiEpisodeAirTime(series, allEpisodes); - _episodeService.DeleteMany(existinEpisodes); + _episodeService.DeleteMany(existingEpisodes); _episodeService.UpdateMany(updateList); _episodeService.InsertMany(newList); - if (newList.Any()) { _messageAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series)); @@ -97,6 +96,11 @@ public void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisode _messageAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList)); } + if (existingEpisodes.Any()) + { + _messageAggregator.PublishEvent(new EpisodeInfoDeletedEvent(updateList)); + } + if (failCount != 0) { _logger.Info("Finished episode refresh for series: {0}. Successful: {1} - Failed: {2} ", diff --git a/NzbDrone.Core/Tv/SeasonService.cs b/NzbDrone.Core/Tv/SeasonService.cs index 8cb3d0c757..fddfc9f0f1 100644 Binary files a/NzbDrone.Core/Tv/SeasonService.cs and b/NzbDrone.Core/Tv/SeasonService.cs differ diff --git a/NzbDrone.Core/Update/UpdateCheckService.cs b/NzbDrone.Core/Update/UpdateCheckService.cs index 36d43ea440..313f1ef58d 100644 --- a/NzbDrone.Core/Update/UpdateCheckService.cs +++ b/NzbDrone.Core/Update/UpdateCheckService.cs @@ -26,7 +26,7 @@ public UpdatePackage AvailableUpdate() { var latestAvailable = _updatePackageProvider.GetLatestUpdate(); - if (latestAvailable == null || latestAvailable.Version <= BuildInfo.Version) + if (latestAvailable == null) { _logger.Debug("No update available."); return null; diff --git a/NzbDrone.Core/Update/UpdatePackage.cs b/NzbDrone.Core/Update/UpdatePackage.cs index e600e2a830..ce038ac999 100644 --- a/NzbDrone.Core/Update/UpdatePackage.cs +++ b/NzbDrone.Core/Update/UpdatePackage.cs @@ -1,11 +1,18 @@ using System; +using Newtonsoft.Json; namespace NzbDrone.Core.Update { public class UpdatePackage { - public string Url { get; set; } - public string FileName { get; set; } + public String Id { get; set; } + + [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] public Version Version { get; set; } + + public String Branch { get; set; } + public DateTime ReleaseDate { get; set; } + public String FileName { get; set; } + public String Url { get; set; } } } diff --git a/NzbDrone.Core/Update/UpdatePackageAvailable.cs b/NzbDrone.Core/Update/UpdatePackageAvailable.cs new file mode 100644 index 0000000000..1d76e22d70 --- /dev/null +++ b/NzbDrone.Core/Update/UpdatePackageAvailable.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Update +{ + public class UpdatePackageAvailable + { + public Boolean Available { get; set; } + public UpdatePackage UpdatePackage { get; set; } + } +} diff --git a/NzbDrone.Core/Update/UpdatePackageProvider.cs b/NzbDrone.Core/Update/UpdatePackageProvider.cs index f85dc6e66f..e045a89fd2 100644 --- a/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Newtonsoft.Json; using NLog; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; @@ -11,56 +12,30 @@ namespace NzbDrone.Core.Update { public interface IUpdatePackageProvider { - IEnumerable GetAvailablePackages(); UpdatePackage GetLatestUpdate(); } public class UpdatePackageProvider : IUpdatePackageProvider { - private readonly IConfigFileProvider _configService; + private readonly IConfigFileProvider _configFileProvider; private readonly IHttpProvider _httpProvider; private readonly Logger _logger; - private static readonly Regex ParseRegex = new Regex(@"(?:\>)(?NzbDrone.+?(?(?<=\.)\d+\.\d+\.\d+\.\d+).+?)(?:\<\/a\>)", - RegexOptions.IgnoreCase); - - public UpdatePackageProvider(IConfigFileProvider configService, IHttpProvider httpProvider, Logger logger) + public UpdatePackageProvider(IConfigFileProvider configFileProvider, IHttpProvider httpProvider, Logger logger) { - _configService = configService; + _configFileProvider = configFileProvider; _httpProvider = httpProvider; _logger = logger; } - public IEnumerable GetAvailablePackages() - { - var updateList = new List(); - - var branch = _configService.Branch; - var version = BuildInfo.Version; - var updateUrl = String.Format("http://update.nzbdrone.com/v{0}/{1}/", version.Major, branch); - - _logger.Debug("Getting a list of updates from {0}", updateUrl); - - var rawUpdateList = _httpProvider.DownloadString(updateUrl); - var matches = ParseRegex.Matches(rawUpdateList); - - foreach (Match match in matches) - { - var updatePackage = new UpdatePackage(); - updatePackage.FileName = match.Groups["filename"].Value; - updatePackage.Url = updateUrl + updatePackage.FileName; - updatePackage.Version = new Version(match.Groups["version"].Value); - updateList.Add(updatePackage); - } - - _logger.Debug("Found {0} update packages", updateUrl.Length); - - return updateList; - } - public UpdatePackage GetLatestUpdate() { - return GetAvailablePackages().OrderByDescending(c => c.Version).FirstOrDefault(); + var url = String.Format("{0}/v1/update/{1}?version={2}", Services.RootUrl, _configFileProvider.Branch, BuildInfo.Version); + var update = JsonConvert.DeserializeObject(_httpProvider.DownloadString(url)); + + if (!update.Available) return null; + + return update.UpdatePackage; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Validation/RuleBuilderExtensions.cs b/NzbDrone.Core/Validation/RuleBuilderExtensions.cs index 6401fb4753..da8609e844 100644 --- a/NzbDrone.Core/Validation/RuleBuilderExtensions.cs +++ b/NzbDrone.Core/Validation/RuleBuilderExtensions.cs @@ -23,6 +23,7 @@ public static IRuleBuilderOptions HaveHttpProtocol(this IRuleBuild public static IRuleBuilderOptions ValidRootUrl(this IRuleBuilder ruleBuilder) { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); return ruleBuilder.SetValidator(new RegularExpressionValidator("^http(?:s)?://[a-z0-9-.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that"); } } diff --git a/NzbDrone.Host/AccessControl/FirewallAdapter.cs b/NzbDrone.Host/AccessControl/FirewallAdapter.cs index 96fb876795..72d1944467 100644 --- a/NzbDrone.Host/AccessControl/FirewallAdapter.cs +++ b/NzbDrone.Host/AccessControl/FirewallAdapter.cs @@ -1,6 +1,7 @@ using System; using NetFwTypeLib; using NLog; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; namespace NzbDrone.Host.AccessControl @@ -31,9 +32,6 @@ public void MakeAccessible() return; } - CloseFirewallPort(); - - //Open the new port OpenFirewallPort(_configFileProvider.Port); } } @@ -91,38 +89,10 @@ private void OpenFirewallPort(int portNumber) } } - private void CloseFirewallPort() - { - try - { - var netFwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr", false); - var mgr = (INetFwMgr)Activator.CreateInstance(netFwMgrType); - var ports = mgr.LocalPolicy.CurrentProfile.GloballyOpenPorts; - - var portNumber = 8989; - - foreach (INetFwOpenPort p in ports) - { - if (p.Name == "NzbDrone") - { - portNumber = p.Port; - break; - } - } - - if (portNumber != _configFileProvider.Port) - { - ports.Remove(portNumber, NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP); - } - } - catch (Exception ex) - { - _logger.WarnException("Failed to close port in firewall for NzbDrone", ex); - } - } - private bool IsFirewallEnabled() { + if (OsInfo.IsLinux) return false; + try { var netFwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr", false); @@ -135,7 +105,5 @@ private bool IsFirewallEnabled() return false; } } - - } } diff --git a/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/NzbDrone.Host/AccessControl/UrlAclAdapter.cs index c7e1480d64..e536fffbd0 100644 --- a/NzbDrone.Host/AccessControl/UrlAclAdapter.cs +++ b/NzbDrone.Host/AccessControl/UrlAclAdapter.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Host.AccessControl public interface IUrlAclAdapter { void RefreshRegistration(); + string UrlAcl { get; } } public class UrlAclAdapter : IUrlAclAdapter @@ -24,18 +25,25 @@ public UrlAclAdapter(IProcessProvider processProvider, IConfigFileProvider confi _logger = logger; } + public string UrlAcl + { + get + { + return "http://*:" + _configFileProvider.Port + "/"; + } + } + public void RefreshRegistration() { if (OsInfo.Version.Major < 6) return; - RegisterUrl(_configFileProvider.Port); + RegisterUrl(); } - - private void RegisterUrl(int portNumber) + private void RegisterUrl() { - var arguments = String.Format("http add urlacl http://*:{0}/ sddl=D:(A;;GX;;;S-1-1-0)", portNumber); + var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", UrlAcl); RunNetsh(arguments); } diff --git a/NzbDrone.Host/ApplicationServer.cs b/NzbDrone.Host/ApplicationServer.cs index b1abc00c57..d6e1e36259 100644 --- a/NzbDrone.Host/ApplicationServer.cs +++ b/NzbDrone.Host/ApplicationServer.cs @@ -4,7 +4,6 @@ using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; -using NzbDrone.Host.AccessControl; using NzbDrone.Host.Owin; namespace NzbDrone.Host @@ -23,13 +22,10 @@ public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory private readonly IProcessProvider _processProvider; private readonly PriorityMonitor _priorityMonitor; private readonly IStartupArguments _startupArguments; - private readonly IFirewallAdapter _firewallAdapter; - private readonly IUrlAclAdapter _urlAclAdapter; private readonly Logger _logger; public NzbDroneServiceFactory(IConfigFileProvider configFileProvider, IHostController hostController, IRuntimeInfo runtimeInfo, - IProcessProvider processProvider, PriorityMonitor priorityMonitor, IStartupArguments startupArguments, - IFirewallAdapter firewallAdapter, IUrlAclAdapter urlAclAdapter, Logger logger) + IProcessProvider processProvider, PriorityMonitor priorityMonitor, IStartupArguments startupArguments, Logger logger) { _configFileProvider = configFileProvider; _hostController = hostController; @@ -37,8 +33,6 @@ public NzbDroneServiceFactory(IConfigFileProvider configFileProvider, IHostContr _processProvider = processProvider; _priorityMonitor = priorityMonitor; _startupArguments = startupArguments; - _firewallAdapter = firewallAdapter; - _urlAclAdapter = urlAclAdapter; _logger = logger; } @@ -49,11 +43,6 @@ protected override void OnStart(string[] args) public void Start() { - if (OsInfo.IsWindows && _runtimeInfo.IsAdmin) - { - _urlAclAdapter.RefreshRegistration(); - _firewallAdapter.MakeAccessible(); - } _hostController.StartServer(); if (!_startupArguments.Flags.Contains(StartupArguments.NO_BROWSER) && diff --git a/NzbDrone.Host/MainAppContainerBuilder.cs b/NzbDrone.Host/MainAppContainerBuilder.cs index 51f47d4159..e12fc91531 100644 --- a/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/NzbDrone.Host/MainAppContainerBuilder.cs @@ -1,4 +1,5 @@ -using Nancy.Bootstrapper; +using System; +using Nancy.Bootstrapper; using NzbDrone.Api; using NzbDrone.Api.SignalR; using NzbDrone.Common.Composition; @@ -6,6 +7,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Organizer; using NzbDrone.Core.RootFolders; +using NzbDrone.Host.Owin; namespace NzbDrone.Host { diff --git a/NzbDrone.Host/NzbDrone.Host.csproj b/NzbDrone.Host/NzbDrone.Host.csproj index 2eb6bcda70..c625626eae 100644 --- a/NzbDrone.Host/NzbDrone.Host.csproj +++ b/NzbDrone.Host/NzbDrone.Host.csproj @@ -122,6 +122,9 @@ + + + diff --git a/NzbDrone.Host/Owin/IHostController.cs b/NzbDrone.Host/Owin/IHostController.cs index 9df77feb49..026bff89e5 100644 --- a/NzbDrone.Host/Owin/IHostController.cs +++ b/NzbDrone.Host/Owin/IHostController.cs @@ -4,7 +4,6 @@ public interface IHostController { string AppUrl { get; } void StartServer(); - void RestartServer(); void StopServer(); } } \ No newline at end of file diff --git a/NzbDrone.Host/Owin/NlogTextWriter.cs b/NzbDrone.Host/Owin/NlogTextWriter.cs new file mode 100644 index 0000000000..d4cd4da7d8 --- /dev/null +++ b/NzbDrone.Host/Owin/NlogTextWriter.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Text; +using NLog; + +namespace NzbDrone.Host.Owin +{ + public class NlogTextWriter : TextWriter + { + private readonly Logger logger = LogManager.GetCurrentClassLogger(); + + + public override Encoding Encoding + { + get + { + return Encoding.Default; + } + } + + public override void Write(char value) + { + logger.Trace(value); + } + + public override void Write(char[] buffer) + { + logger.Trace(buffer); + } + + public override void Write(string value) + { + logger.Trace(value); + } + + public override void Write(char[] buffer, int index, int count) + { + logger.Trace(buffer); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Host/Owin/OwinHostController.cs b/NzbDrone.Host/Owin/OwinHostController.cs index 0055883b67..9968b22bb2 100644 --- a/NzbDrone.Host/Owin/OwinHostController.cs +++ b/NzbDrone.Host/Owin/OwinHostController.cs @@ -3,8 +3,10 @@ using System.Linq; using Microsoft.Owin.Hosting; using NLog; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Security; using NzbDrone.Core.Configuration; +using NzbDrone.Host.AccessControl; using NzbDrone.Host.Owin.MiddleWare; using Owin; @@ -14,13 +16,20 @@ public class OwinHostController : IHostController { private readonly IConfigFileProvider _configFileProvider; private readonly IEnumerable _owinMiddleWares; + private readonly IRuntimeInfo _runtimeInfo; + private readonly IUrlAclAdapter _urlAclAdapter; + private readonly IFirewallAdapter _firewallAdapter; private readonly Logger _logger; private IDisposable _host; - public OwinHostController(IConfigFileProvider configFileProvider, IEnumerable owinMiddleWares, Logger logger) + public OwinHostController(IConfigFileProvider configFileProvider, IEnumerable owinMiddleWares, + IRuntimeInfo runtimeInfo, IUrlAclAdapter urlAclAdapter, IFirewallAdapter firewallAdapter, Logger logger) { _configFileProvider = configFileProvider; _owinMiddleWares = owinMiddleWares; + _runtimeInfo = runtimeInfo; + _urlAclAdapter = urlAclAdapter; + _firewallAdapter = firewallAdapter; _logger = logger; } @@ -28,16 +37,20 @@ public void StartServer() { IgnoreCertErrorPolicy.Register(); - var url = "http://*:" + _configFileProvider.Port; + if (OsInfo.IsWindows && _runtimeInfo.IsAdmin) + { + _urlAclAdapter.RefreshRegistration(); + _firewallAdapter.MakeAccessible(); + } - var options = new StartOptions(url) + var options = new StartOptions(_urlAclAdapter.UrlAcl) { ServerFactory = "Microsoft.Owin.Host.HttpListener" }; - _logger.Info("starting server on {0}", url); + _logger.Info("starting server on {0}", _urlAclAdapter.UrlAcl); - _host = WebApp.Start(options, BuildApp); + _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp); } private void BuildApp(IAppBuilder appBuilder) @@ -56,14 +69,6 @@ public string AppUrl get { return string.Format("http://localhost:{0}", _configFileProvider.Port); } } - public void RestartServer() - { - _logger.Warn("Attempting to restart server."); - - StopServer(); - StartServer(); - } - public void StopServer() { if (_host == null) return; diff --git a/NzbDrone.Host/Owin/OwinServiceProvider.cs b/NzbDrone.Host/Owin/OwinServiceProvider.cs new file mode 100644 index 0000000000..cffea738f2 --- /dev/null +++ b/NzbDrone.Host/Owin/OwinServiceProvider.cs @@ -0,0 +1,16 @@ +using Microsoft.Owin.Hosting.Services; +using Microsoft.Owin.Hosting.Tracing; + +namespace NzbDrone.Host.Owin +{ + public static class OwinServiceProviderFactory + { + public static ServiceProvider Create() + { + var provider = (ServiceProvider)ServicesFactory.Create(); + provider.Add(typeof(ITraceOutputFactory), typeof(OwinTraceOutputFactory)); + + return provider; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs b/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs new file mode 100644 index 0000000000..9a0f8997d6 --- /dev/null +++ b/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs @@ -0,0 +1,14 @@ +using System.IO; +using Microsoft.Owin.Hosting.Tracing; + +namespace NzbDrone.Host.Owin +{ + public class OwinTraceOutputFactory : ITraceOutputFactory + { + + public TextWriter Create(string outputFile) + { + return new NlogTextWriter(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs b/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs index 411023cacb..f4d7fe8c10 100644 --- a/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs +++ b/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using NzbDrone.Api.Series; using System.Linq; +using NzbDrone.Test.Common; namespace NzbDrone.Integration.Test { @@ -15,7 +16,7 @@ private SeriesResource GivenSeriesWithEpisodes() var series = Series.Lookup("archer").First(); series.QualityProfileId = 1; - series.Path = @"C:\Test\Archer"; + series.Path = @"C:\Test\Archer".AsOsAgnostic(); series = Series.Post(series); @@ -57,4 +58,4 @@ public void should_be_able_to_set_monitor_status_via_api() Episodes.Put(updatedEpisode).Monitored.Should().BeFalse(); } } -} \ No newline at end of file +} diff --git a/NzbDrone.Integration.Test/IntegrationTest.cs b/NzbDrone.Integration.Test/IntegrationTest.cs index 3ef126a98d..b858e4ff34 100644 --- a/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/NzbDrone.Integration.Test/IntegrationTest.cs @@ -1,4 +1,5 @@ -using NLog; +using System.Runtime.CompilerServices; +using NLog; using NLog.Config; using NLog.Targets; using NUnit.Framework; @@ -39,6 +40,7 @@ public IntegrationTest() LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget)); } + //[TestFixtureSetUp] [SetUp] public void SmokeTestSetup() { @@ -63,6 +65,7 @@ private void InitRestClients() NamingConfig = new ClientBase(RestClient, "config/naming"); } + //[TestFixtureTearDown] [TearDown] public void SmokeTestTearDown() { diff --git a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj index 2ffdbc7a97..5762f6877f 100644 --- a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj +++ b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj @@ -33,8 +33,9 @@ MinimumRecommendedRules.ruleset - - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + + False + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll False diff --git a/NzbDrone.Integration.Test/NzbDroneRunner.cs b/NzbDrone.Integration.Test/NzbDroneRunner.cs index 988c472780..5955166ffd 100644 --- a/NzbDrone.Integration.Test/NzbDroneRunner.cs +++ b/NzbDrone.Integration.Test/NzbDroneRunner.cs @@ -53,11 +53,17 @@ public void Start() Assert.Fail("Process has exited"); } - if (_restClient.Get(new RestRequest("system/status")).ResponseStatus == ResponseStatus.Completed) + + var statusCall = _restClient.Get(new RestRequest("system/status")); + + if (statusCall.ResponseStatus == ResponseStatus.Completed) { + Console.WriteLine("NzbDrone is started. Running Tests"); return; } + Console.WriteLine("Waiting for NzbDrone to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException); + Thread.Sleep(500); } } diff --git a/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs b/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs index cd1b29cf04..8a888fffa6 100644 --- a/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs +++ b/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs @@ -58,5 +58,17 @@ public void should_have_no_root_folder_initially() RootFolders.All().Should().BeEmpty(); } + + [Test] + public void invalid_path_should_return_bad_request() + { + var rootFolder = new RootFolderResource + { + Path = "invalid_path" + }; + + var postResponse = RootFolders.InvalidPost(rootFolder); + postResponse.Should().NotBeEmpty(); + } } } \ No newline at end of file diff --git a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs b/NzbDrone.Integration.Test/SeasonIntegrationTests.cs index c101460309..7a2458b4f4 100644 --- a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs +++ b/NzbDrone.Integration.Test/SeasonIntegrationTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using NzbDrone.Api.Series; using System.Linq; +using NzbDrone.Test.Common; namespace NzbDrone.Integration.Test { @@ -15,7 +16,7 @@ private SeriesResource GivenSeriesWithEpisodes() var series = Series.Lookup("archer").First(); series.QualityProfileId = 1; - series.Path = @"C:\Test\Archer"; + series.Path = @"C:\Test\Archer".AsOsAgnostic(); series = Series.Post(series); @@ -57,4 +58,4 @@ public void should_be_able_to_set_monitor_status_via_api() Seasons.Put(updatedSeason).Monitored.Should().BeFalse(); } } -} \ No newline at end of file +} diff --git a/NzbDrone.Integration.Test/SeriesIntegrationTest.cs b/NzbDrone.Integration.Test/SeriesIntegrationTest.cs index 85ef89050b..df6dce79db 100644 --- a/NzbDrone.Integration.Test/SeriesIntegrationTest.cs +++ b/NzbDrone.Integration.Test/SeriesIntegrationTest.cs @@ -3,18 +3,13 @@ using NUnit.Framework; using NzbDrone.Api.Series; using System.Linq; +using NzbDrone.Test.Common; namespace NzbDrone.Integration.Test { [TestFixture] public class SeriesIntegrationTest : IntegrationTest { - [Test] - public void should_have_no_series_on_start_application() - { - Series.All().Should().BeEmpty(); - } - [Test] public void series_lookup_on_trakt() { @@ -37,7 +32,7 @@ public void should_be_able_to_add_and_delete_series() var series = Series.Lookup("archer").First(); series.QualityProfileId = 1; - series.Path = @"C:\Test\Archer"; + series.Path = @"C:\Test\Archer".AsOsAgnostic(); series = Series.Post(series); @@ -57,7 +52,7 @@ public void should_be_able_to_find_series_by_id() var series = Series.Lookup("90210").First(); series.QualityProfileId = 1; - series.Path = @"C:\Test\90210"; + series.Path = @"C:\Test\90210".AsOsAgnostic(); series = Series.Post(series); diff --git a/NzbDrone.Integration.Test/packages.config b/NzbDrone.Integration.Test/packages.config index f0ee04f810..268fde156a 100644 --- a/NzbDrone.Integration.Test/packages.config +++ b/NzbDrone.Integration.Test/packages.config @@ -1,6 +1,6 @@  - + diff --git a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj index b3bb03f0c9..4e85ec162a 100644 --- a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj +++ b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj @@ -34,8 +34,9 @@ MinimumRecommendedRules.ruleset - - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + + False + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll ..\packages\CommonServiceLocator.1.0\lib\NET35\Microsoft.Practices.ServiceLocation.dll diff --git a/NzbDrone.Test.Common/packages.config b/NzbDrone.Test.Common/packages.config index 8c1697cb30..6880e0bf84 100644 --- a/NzbDrone.Test.Common/packages.config +++ b/NzbDrone.Test.Common/packages.config @@ -1,7 +1,7 @@  - + diff --git a/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj b/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj index 2401ae6d27..3dbc57b352 100644 --- a/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj +++ b/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj @@ -43,9 +43,9 @@ ..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll - + False - ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll + ..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll diff --git a/NzbDrone.Update.Test/packages.config b/NzbDrone.Update.Test/packages.config index 3e6b4e0946..6f925b437c 100644 --- a/NzbDrone.Update.Test/packages.config +++ b/NzbDrone.Update.Test/packages.config @@ -1,6 +1,6 @@  - + diff --git a/NzbDrone/NzbDrone.csproj b/NzbDrone/NzbDrone.csproj index b599ed47b0..5d45930cd6 100644 --- a/NzbDrone/NzbDrone.csproj +++ b/NzbDrone/NzbDrone.csproj @@ -83,6 +83,10 @@ False ..\packages\Newtonsoft.Json.5.0.6\lib\net40\Newtonsoft.Json.dll + + False + ..\packages\NLog.2.0.1.2\lib\net40\NLog.dll + False ..\packages\Owin.1.0\lib\net40\Owin.dll diff --git a/NzbDrone/WindowsApp.cs b/NzbDrone/WindowsApp.cs index 0ded14d614..4d4dc9d69a 100644 --- a/NzbDrone/WindowsApp.cs +++ b/NzbDrone/WindowsApp.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Forms; +using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Host; using NzbDrone.SysTray; @@ -8,6 +9,7 @@ namespace NzbDrone { public static class WindowsApp { + public static void Main(string[] args) { try diff --git a/NzbDrone/packages.config b/NzbDrone/packages.config index 907d39c5ab..19f3a3d8bb 100644 --- a/NzbDrone/packages.config +++ b/NzbDrone/packages.config @@ -5,5 +5,6 @@ + \ No newline at end of file diff --git a/UI/AddSeries/RootFolders/Collection.js b/UI/AddSeries/RootFolders/Collection.js index 8f20ac92ab..74b9b45550 100644 --- a/UI/AddSeries/RootFolders/Collection.js +++ b/UI/AddSeries/RootFolders/Collection.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict'; define( [ 'backbone', diff --git a/UI/AddSeries/RootFolders/Layout.js b/UI/AddSeries/RootFolders/Layout.js index 89388b2c24..3a75c0f9ff 100644 --- a/UI/AddSeries/RootFolders/Layout.js +++ b/UI/AddSeries/RootFolders/Layout.js @@ -7,10 +7,11 @@ define( 'AddSeries/RootFolders/Collection', 'AddSeries/RootFolders/Model', 'Shared/LoadingView', + 'Mixins/AsValidatedView', 'Mixins/AutoComplete' - ], function (Marionette, RootFolderCollectionView, RootFolderCollection, RootFolderModel, LoadingView) { + ], function (Marionette, RootFolderCollectionView, RootFolderCollection, RootFolderModel, LoadingView, AsValidatedView) { - return Marionette.Layout.extend({ + var layout = Marionette.Layout.extend({ template: 'AddSeries/RootFolders/LayoutTemplate', ui: { @@ -55,12 +56,16 @@ define( Path: this.ui.pathInput.val() }); - RootFolderCollection.add(newDir); + this.bindToModelValidation(newDir); newDir.save().done(function () { + RootFolderCollection.add(newDir); self.trigger('folderSelected', {model: newDir}); }); } }); + + return AsValidatedView.apply(layout); + }); diff --git a/UI/AddSeries/RootFolders/LayoutTemplate.html b/UI/AddSeries/RootFolders/LayoutTemplate.html index 2a5d5f3505..a62ef128d9 100644 --- a/UI/AddSeries/RootFolders/LayoutTemplate.html +++ b/UI/AddSeries/RootFolders/LayoutTemplate.html @@ -3,10 +3,13 @@

Select Folder

@@ -79,7 +79,7 @@ - + diff --git a/UI/Settings/DownloadClient/PneumaticViewTemplate.html b/UI/Settings/DownloadClient/PneumaticViewTemplate.html index 791b212ba1..8fc612fc3d 100644 --- a/UI/Settings/DownloadClient/PneumaticViewTemplate.html +++ b/UI/Settings/DownloadClient/PneumaticViewTemplate.html @@ -6,7 +6,7 @@
- +
diff --git a/UI/Settings/DownloadClient/SabViewTemplate.html b/UI/Settings/DownloadClient/SabViewTemplate.html index 8bdd39afd0..25ae6e74a1 100644 --- a/UI/Settings/DownloadClient/SabViewTemplate.html +++ b/UI/Settings/DownloadClient/SabViewTemplate.html @@ -70,11 +70,11 @@ - + - + @@ -92,7 +92,7 @@ - + diff --git a/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html b/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html index 021a494d7b..f45c97dda2 100644 --- a/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html +++ b/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html @@ -12,9 +12,6 @@
- - -
diff --git a/UI/jQuery/jquery.validation.js b/UI/jQuery/jquery.validation.js index fda5087c52..ca03d0aa79 100644 --- a/UI/jQuery/jquery.validation.js +++ b/UI/jQuery/jquery.validation.js @@ -8,6 +8,10 @@ define( var validationName = error.propertyName.toLowerCase(); + this.find('.validation-errors') + .addClass('alert alert-error') + .append('
' + error.errorMessage + '
'); + var input = this.find('[name]').filter(function () { return this.name.toLowerCase() === validationName; }); @@ -40,11 +44,12 @@ define( }; $.fn.addFormError = function (error) { - this.find('.control-group').parent().prepend('
'+ error.errorMessage +'
') + this.find('.control-group').parent().prepend('
' + error.errorMessage + '
') }; $.fn.removeAllErrors = function () { this.find('.error').removeClass('error'); + this.find('.validation-errors').removeClass('alert').removeClass('alert-error').html(''); this.find('.validation-error').remove(); return this.find('.help-inline.error-message').remove(); }; diff --git a/debian/changelog b/debian/changelog index e15e9a293c..eb8f841a67 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ - nzbdrone (2.0.12-1) unstable; urgency=low -2 -3 * Initial release -4 -5 -- kay.one Mon, 22 Mar 2010 00:37:31 +0100 \ No newline at end of file +nzbdrone {version} {branch}; urgency=low + + * Automatic Release. + + -- NzbDrone Mon, 26 Aug 2013 00:00:00 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000000..45a4fb75db --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control index 174fecb48b..e7f6a7b11d 100644 --- a/debian/control +++ b/debian/control @@ -1,14 +1,12 @@ -Package: nzbdrone -Standards-Version: 2.0.0.226 -Architecture: all -Maintainer: Keivan Beigi -Depends: mono-runtime (>= 2.10.1) Section: web Priority: optional - +Maintainer: NzbDrone +Source: nzbdrone Homepage: http://www.nzbdrone.com Vcs-Git: git@github.com:NzbDrone/NzbDrone.git Vcs-Browser: https://github.com/NzbDrone/NzbDrone -Description: NZBDrone is a PVR for newsgroup users. -It can monitor multiple RSS feeds for new episodes of your favourite shows and will grab, sorts and renames them. It can also be configured to automatically upgrade the quality of files already downloaded if a better quality format becomes available. \ No newline at end of file +Package: nzbdrone +Architecture: all +Depends: libmono-cil-dev (>= 2.10.1) +Description: NZBDrone is a PVR for newsgroup users diff --git a/debian/copyright b/debian/copyright old mode 100644 new mode 100755 index 28e595dc8c..cf281685c1 --- a/debian/copyright +++ b/debian/copyright @@ -1,25 +1,24 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: nzbdrone -Source: https://github.com/NzbDrone/NzbDrone - -Files: * -Copyright: 2010-2013 Keivan Beigi - 2010-2013 Mark McDowall - -License: GPL-3.0+ - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". \ No newline at end of file +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: nzbdrone +Source: https://github.com/NzbDrone/NzbDrone + +Files: * +Copyright: 2010-2013 NzbDrone + +License: GPL-3.0+ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". diff --git a/debian/files b/debian/files deleted file mode 100644 index 5957c8b623..0000000000 --- a/debian/files +++ /dev/null @@ -1 +0,0 @@ -nzbdrone_226_i386.deb unknown extra diff --git a/debian/install b/debian/install old mode 100644 new mode 100755 index 46a453a03c..106b06a9b5 --- a/debian/install +++ b/debian/install @@ -1,2 +1 @@ -nzbdrone_226/* opt/nzbdrone -../nzbdrone.desktop usr/share/applications +nzbdrone_bin/* opt/NzbDrone diff --git a/debian/rules b/debian/rules old mode 100644 new mode 100755 index fcc2b6104d..b760bee7f4 --- a/debian/rules +++ b/debian/rules @@ -7,7 +7,7 @@ # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. -export DH_VERBOSE=1 +#export DH_VERBOSE=1 %: dh $@