1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-06 13:31:28 -05:00

Compare commits

...

53 Commits

Author SHA1 Message Date
Keivan Beigi
3952ee402b Update readme.md 2015-12-12 11:24:46 -08:00
Mark McDowall
0b3e27cb44 Don't keep dylibs for WIndows and Linux builds 2015-12-09 23:24:50 -08:00
Mark McDowall
4fa4b3507e Fixed: Force grabbing some delayed releases
Closes #984
2015-12-08 22:50:34 -08:00
Mark McDowall
8c211364e2 Fixed: Improved parsing of some multi-episode filenames 2015-12-08 15:26:52 -08:00
Mark McDowall
2d9917d074 Re-order regex to prefer [1x01] over 101 2015-12-06 11:03:11 -08:00
Mark McDowall
d514699ab7 Fixed: Prevent series from being added with an invalid Profile ID
Closes #977
2015-12-05 02:22:22 -08:00
Mark McDowall
dc176a83b3 Update CONTRIBUTING.md 2015-12-01 08:48:41 -08:00
Mark McDowall
69e3516a89 New: Allow Uppercase in Transmission category
Closes #934
2015-11-29 22:01:20 -08:00
Mark McDowall
c8a0f9fa7a Fixed: Saving settings changes 2015-11-26 12:05:37 -08:00
Mark McDowall
c2b9504b15 Merge pull request #931 from Dahlgren/osx-development
Include mediainfo and sqlite3 libraries for Mac
2015-11-24 18:35:07 -08:00
Mark McDowall
2693a3df2e Merge pull request #959 from roguecode/develop
Fixed: Indexer failure log message with local time
2015-11-24 15:41:57 -08:00
Matt
8062466ab8 Changing Indexer failure log message to local from UTC. 2015-11-24 23:42:20 +02:00
Björn Dahlgren
6cde1dd5ae Include mediainfo and sqlite3 libraries for Mac
Enables usage within MonoDevelop and Xamarin Studio including NUnit
2015-11-24 10:21:42 +01:00
Mark McDowall
b6c4a97675 Merge pull request #889 from Sonarr/quality-source
Folder quality when file quality determined by its extension
2015-11-23 23:01:10 -08:00
Mark McDowall
a9444cef30 Fixed: Folder quality when file quality determined by its extension
Closes #603
2015-11-23 23:00:51 -08:00
Mark McDowall
bf217a7093 Merge pull request #754 from Sonarr/real-releases
Support for REAL releases
2015-11-23 22:59:41 -08:00
Mark McDowall
b6b5355261 New: support for REAL releases
Closes #453

New: Added `Quality Real` naming Token
New: Quality Full will add real to file name when applicable
2015-11-23 22:58:53 -08:00
Mark McDowall
bc37084ec4 Merge pull request #928 from Dahlgren/mono-tests
Fixed tests for Mono
2015-11-23 22:50:34 -08:00
Mark McDowall
0a1a30f2af Merge pull request #953 from zetas/nn_preset_nzbcat
New: Newznab Preset for NZBCat
2015-11-23 22:50:15 -08:00
Keivan Beigi
7e023a7944 ConfigServiceFixture shouldn't be touching the DB. 2015-11-23 21:57:01 -08:00
zetas
91f68de8a7 Adding new newznab preset for NZBCat 2015-11-22 07:09:57 -05:00
Björn Dahlgren
994e2a6c57 Fixed failing tests on Mono
Test case unicode characters in escaped format
2015-11-22 01:11:43 +01:00
Mark McDowall
04da2d845a Merge pull request #941 from uzegonemad/hotfix/calendar-legend-width
Give calendar legend ul max width of 100%. Fixes #922
2015-11-18 22:12:39 -08:00
Benjamin Uzelac
d3b87bc3e8 give legend ul max width of 100%
give legend ul max width of 100%
2015-11-18 22:17:05 -06:00
Mark McDowall
554c81f251 Merge pull request #901 from brgaulin/jquery_upgrade
Update jQuery to 1.11.3
2015-11-16 13:44:42 -08:00
Mark McDowall
6de3f9dd0b Merge pull request #942 from uzegonemad/hotfix/calendar-tooltip
Fixed: Hidden calendar tooltips
2015-11-16 13:38:30 -08:00
Mark McDowall
e9692d5b9c Merge pull request #940 from uzegonemad/hotfix/fix-long-path-label
Fixed: Add wrapping to fix long paths in labels
2015-11-16 13:37:26 -08:00
Benjamin Uzelac
1a74990e9b alter calendar tooltip container. fixes #857 2015-11-15 20:50:17 -06:00
Benjamin Uzelac
3b9ac8699d Add wrapping to fix long paths in labels. Fixes #875 2015-11-15 18:25:30 -06:00
Mark McDowall
ea6ae85f7a Fixed: Logging invalid version when failing to connect to Kodi
Closes #927
2015-11-14 22:35:45 -08:00
Mark McDowall
b02b9f026f Fixed: Failing missing episode search when one search fails
Closes #917
2015-11-14 22:35:28 -08:00
Mark McDowall
c419e7b710 Merge pull request #915 from uzegonemad/hotfix/scroll-up-zindex
Fix z-index on scroll to top button
2015-11-14 21:43:37 -08:00
Benjamin Uzelac
cd9132520d fix z-index on scroll to top button 2015-11-05 19:43:53 -06:00
Mark McDowall
08d19df3f7 Fixed: Manual import when quality was not available after failed parsing
Closes #911
2015-11-03 15:12:56 -08:00
Mark McDowall
b34879b4f6 New: Manual search shows error when download fails 2015-11-02 22:46:06 -08:00
Mark McDowall
6b9c4af591 Fixed: Magnet links with torrent blackhole 2015-11-02 22:21:27 -08:00
Mark McDowall
c00c207517 Prevent regrab for all grabs
Fixed: Prevent incorrectly grabbing a similar or identical release for 12 hours
2015-11-01 21:54:49 -08:00
Mark McDowall
6f7fea3591 PFMonkey Newznab preset 2015-11-01 21:35:44 -08:00
Mark McDowall
83eebfe153 Fixed: Stricter parsing of some release filenames 2015-10-29 23:58:56 -07:00
Gaulin, Brendan
fce3f86be7 UI: Update jQuery to 1.11.3 2015-10-29 08:38:21 -04:00
Mark McDowall
2d42c59d70 Fixed: Log download client name when communication fails 2015-10-27 22:19:24 -07:00
Mark McDowall
f0933b9786 Fixed: Test messaging when indexer API returns an error with a message 2015-10-27 22:04:20 -07:00
Mark McDowall
e4e687c2a4 Fixed: Parsing anime series with number in title
Closes #898
2015-10-26 14:56:28 -07:00
Taloth Saldono
44de353b8b Sanitize dognzb apikey in nzb fetch url. 2015-10-25 23:15:21 +01:00
Taloth Saldono
aac4938598 Fixed handling cookies in different system languages.
fixes #896... hopefully
2015-10-25 22:57:37 +01:00
Mark McDowall
d37b24cd0b Better UI messaging when searching for all specials in a series
Fixed: Specials season search UI messaging
Closes #881
2015-10-25 10:10:57 -07:00
Taloth Saldono
c9a36fe4b2 Fixed sorting on Progress in Queue.
Fixes #882
2015-10-22 23:19:24 +02:00
Taloth Saldono
f01a21ce43 Degraded 101 regex to favour S01 regex to match prevent matching 3 digit series title.
Fixes #885
2015-10-22 23:19:22 +02:00
Taloth Saldono
cc72699b8a Fixed: Added verified file transfer mode that doesn't revert to copy. 2015-10-22 23:19:21 +02:00
Mark McDowall
04de0049fe Don't try to process a download client item with an invalid path for the OS 2015-10-21 15:06:54 -07:00
Taloth Saldono
330554edb0 Fixed: External links again open in new windows. 2015-10-20 20:58:56 +02:00
Taloth Saldono
a06a3fa5d6 Fixed: Removal of common suffixes such as [ettv] while parsing.
Fixes #874
2015-10-20 20:12:35 +02:00
Taloth Saldono
e8d6d62fba Warning message when BTN API throw internal server error 2015-10-20 19:31:40 +02:00
67 changed files with 10394 additions and 10042 deletions

View File

@@ -16,7 +16,7 @@ Setup guides, FAQ, the more information we have on the wiki the better.
### Getting started ###
1. Fork Sonarr
2. Clone (develop branch)
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
3. Run `npm install`
4. Run `gulp watch` - Used to compile the UI components and copy them (leave this window open)
5. Compile in Visual Studio

View File

@@ -33,6 +33,7 @@ Function Build()
Write-Host "Removing Mono.Posix.dll"
Remove-Item "$outputFolder\Mono.Posix.dll"
Get-ChildItem $outputFolder -File -Filter "*.dylib" -Recurse | foreach ($_) {Remove-Item $_.Fullname}
Write-Host "##teamcity[progressFinish 'Build']"
}
@@ -233,6 +234,9 @@ Function RunGulp()
Invoke-Expression 'gulp build' -ErrorAction Continue -Verbose
CheckExitCode
Invoke-Expression 'gulp build --phantom' -ErrorAction Continue -Verbose
CheckExitCode
Write-Host "##teamcity[progressFinish 'Running Gulp']"
}

View File

@@ -21,7 +21,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
## Configuring Development Environment: ##
### Requirements ###
- Visual Studio 2013 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
- [Git](http://git-scm.com/downloads)
- [NodeJS](http://nodejs.org/download/)
- [Gulp](http://gulpjs.com)

View File

@@ -30,10 +30,13 @@ namespace NzbDrone.Api.ManualImport
private ManualImportResource AddQualityWeight(ManualImportResource item)
{
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
item.QualityWeight += item.Quality.Revision.Real * 10;
item.QualityWeight += item.Quality.Revision.Version;
if (item.Quality != null)
{
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
item.QualityWeight += item.Quality.Revision.Real * 10;
item.QualityWeight += item.Quality.Revision.Version;
}
return item;
}
}

View File

@@ -10,11 +10,11 @@ using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tv;
using NzbDrone.Api.Validation;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Validation;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Series
@@ -43,7 +43,8 @@ namespace NzbDrone.Api.Series
SeriesPathValidator seriesPathValidator,
SeriesExistsValidator seriesExistsValidator,
DroneFactoryValidator droneFactoryValidator,
SeriesAncestorValidator seriesAncestorValidator
SeriesAncestorValidator seriesAncestorValidator,
ProfileExistsValidator profileExistsValidator
)
: base(signalRBroadcaster)
{
@@ -59,7 +60,7 @@ namespace NzbDrone.Api.Series
UpdateResource = UpdateSeries;
DeleteResource = DeleteSeries;
SharedValidator.RuleFor(s => s.ProfileId).ValidId();
Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.ProfileId));
SharedValidator.RuleFor(s => s.Path)
.Cascade(CascadeMode.StopOnFirstFailure)
@@ -70,6 +71,8 @@ namespace NzbDrone.Api.Series
.SetValidator(seriesAncestorValidator)
.When(s => !s.Path.IsNullOrWhiteSpace());
SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator);
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.Title).NotEmpty();

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Common.Test.DiskTests
{
MonoOnly();
Subject.VerificationMode.Should().Be(DiskTransferVerificationMode.Transactional);
Subject.VerificationMode.Should().Be(DiskTransferVerificationMode.TryTransactional);
}
[Test]
@@ -199,9 +199,6 @@ namespace NzbDrone.Common.Test.DiskTests
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.CopyFile(_sourcePath, _targetPath, false), Times.Once());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetFileSize(It.IsAny<string>()), Times.Never());
}
[Test]
@@ -213,9 +210,6 @@ namespace NzbDrone.Common.Test.DiskTests
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.MoveFile(_sourcePath, _targetPath, false), Times.Once());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetFileSize(It.IsAny<string>()), Times.Never());
}
[Test]
@@ -377,6 +371,52 @@ namespace NzbDrone.Common.Test.DiskTests
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void mode_transactional_should_move_and_verify_if_same_folder()
{
Subject.VerificationMode = DiskTransferVerificationMode.Transactional;
var targetPath = _sourcePath + ".test";
var result = Subject.TransferFile(_sourcePath, targetPath, TransferMode.Move);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Never());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.MoveFile(_sourcePath, targetPath, false), Times.Once());
}
[Test]
public void mode_trytransactional_should_revert_to_verifyonly_if_hardlink_fails()
{
Subject.VerificationMode = DiskTransferVerificationMode.TryTransactional;
WithFailedHardlink();
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Callback(() =>
{
WithExistingFile(_sourcePath, false);
WithExistingFile(_targetPath, true);
});
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Once());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.MoveFile(_sourcePath, _targetPath, false), Times.Once());
}
[Test]
public void mode_transactional_should_delete_old_backup_on_move()
{

View File

@@ -1,17 +1,19 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
using FluentAssertions;
using Moq;
using NLog;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.TPL;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
using NLog;
using NzbDrone.Common.TPL;
using Moq;
using NzbDrone.Common.Http.Dispatchers;
namespace NzbDrone.Common.Test.Http
{
@@ -309,30 +311,43 @@ namespace NzbDrone.Common.Test.Http
.Verify(v => v.PostResponse(It.IsAny<HttpResponse>()), Times.Once());
}
public void should_parse_malformed_cloudflare_cookie()
[TestCase("en-US")]
[TestCase("es-ES")]
public void should_parse_malformed_cloudflare_cookie(string culture)
{
// the date is bad in the below - should be 13-Jul-2016
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
System.Uri.EscapeUriString(malformedCookie);
var origCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
try
{
// the date is bad in the below - should be 13-Jul-2016
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
System.Uri.EscapeUriString(malformedCookie);
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
var responseSet = Subject.Get(requestSet);
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest("http://eu.httpbin.org/get");
var request = new HttpRequest("http://eu.httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request);
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().ContainKey("Cookie");
response.Resource.Headers.Should().ContainKey("Cookie");
var cookie = response.Resource.Headers["Cookie"].ToString();
var cookie = response.Resource.Headers["Cookie"].ToString();
cookie.Should().Contain("__cfduid=d29e686a9d65800021c66faca0a29b4261436890790");
cookie.Should().Contain("__cfduid=d29e686a9d65800021c66faca0a29b4261436890790");
ExceptionVerification.IgnoreErrors();
ExceptionVerification.IgnoreErrors();
}
finally
{
Thread.CurrentThread.CurrentCulture = origCulture;
Thread.CurrentThread.CurrentUICulture = origCulture;
}
}
}

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"http://rss.torrentleech.org/mySecret")]
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
[TestCase(@"{ ""Name"" : ""Server1.Username"", ""Value"" : ""mySecret"" }, { ""Name"" : ""Server1.Password"", ""Value"" : ""mySecret"" }, ")]
@@ -44,6 +45,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("01233210");
}
}
}

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Disk
{
None,
VerifyOnly,
TryTransactional,
Transactional
}
@@ -40,7 +41,7 @@ namespace NzbDrone.Common.Disk
// TODO: Atm we haven't seen partial transfers on windows so we disable verified transfer.
// (If enabled in the future, be sure to check specifically for ReFS, which doesn't support hardlinks.)
VerificationMode = OsInfo.IsWindows ? DiskTransferVerificationMode.VerifyOnly : DiskTransferVerificationMode.Transactional;
VerificationMode = OsInfo.IsWindows ? DiskTransferVerificationMode.VerifyOnly : DiskTransferVerificationMode.TryTransactional;
}
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode, bool verified = true)
@@ -48,11 +49,6 @@ namespace NzbDrone.Common.Disk
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
if (VerificationMode != DiskTransferVerificationMode.Transactional)
{
verified = false;
}
if (!_diskProvider.FolderExists(targetPath))
{
_diskProvider.CreateFolder(targetPath);
@@ -85,12 +81,14 @@ namespace NzbDrone.Common.Disk
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
if (VerificationMode != DiskTransferVerificationMode.Transactional)
if (VerificationMode != DiskTransferVerificationMode.Transactional && VerificationMode != DiskTransferVerificationMode.TryTransactional)
{
verified = false;
}
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
var originalSize = _diskProvider.GetFileSize(sourcePath);
if (sourcePath == targetPath)
{
@@ -127,6 +125,15 @@ namespace NzbDrone.Common.Disk
return TransferMode.None;
}
if (sourcePath.GetParentPath() == targetPath.GetParentPath())
{
if (mode.HasFlag(TransferMode.Move))
{
TryMoveFileVerified(sourcePath, targetPath, originalSize);
return TransferMode.Move;
}
}
if (sourcePath.IsParentPath(targetPath))
{
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
@@ -151,7 +158,7 @@ namespace NzbDrone.Common.Disk
{
if (mode.HasFlag(TransferMode.Copy))
{
if (TryCopyFile(sourcePath, targetPath))
if (TryCopyFileTransactional(sourcePath, targetPath, originalSize))
{
return TransferMode.Copy;
}
@@ -159,7 +166,7 @@ namespace NzbDrone.Common.Disk
if (mode.HasFlag(TransferMode.Move))
{
if (TryMoveFile(sourcePath, targetPath))
if (TryMoveFileTransactional(sourcePath, targetPath, originalSize))
{
return TransferMode.Move;
}
@@ -167,50 +174,18 @@ namespace NzbDrone.Common.Disk
throw new IOException(string.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
}
else if (VerificationMode == DiskTransferVerificationMode.VerifyOnly)
else if (VerificationMode != DiskTransferVerificationMode.None)
{
var originalSize = _diskProvider.GetFileSize(sourcePath);
if (mode.HasFlag(TransferMode.Copy))
{
try
{
_diskProvider.CopyFile(sourcePath, targetPath);
var targetSize = _diskProvider.GetFileSize(targetPath);
if (targetSize != originalSize)
{
throw new IOException(string.Format("File copy incomplete. [{0}] was {1} bytes long instead of {2} bytes.", targetPath, targetSize, originalSize));
}
return TransferMode.Copy;
}
catch
{
RollbackCopy(sourcePath, targetPath);
throw;
}
TryCopyFileVerified(sourcePath, targetPath, originalSize);
return TransferMode.Copy;
}
if (mode.HasFlag(TransferMode.Move))
{
try
{
_diskProvider.MoveFile(sourcePath, targetPath);
var targetSize = _diskProvider.GetFileSize(targetPath);
if (targetSize != originalSize)
{
throw new IOException(string.Format("File copy incomplete, data loss may have occured. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
}
return TransferMode.Move;
}
catch
{
RollbackPartialMove(sourcePath, targetPath);
throw;
}
TryMoveFileVerified(sourcePath, targetPath, originalSize);
return TransferMode.Move;
}
}
else
@@ -310,10 +285,8 @@ namespace NzbDrone.Common.Disk
Thread.Sleep(3000);
}
private bool TryCopyFile(string sourcePath, string targetPath)
private bool TryCopyFileTransactional(string sourcePath, string targetPath, long originalSize)
{
var originalSize = _diskProvider.GetFileSize(sourcePath);
var tempTargetPath = targetPath + ".partial~";
if (_diskProvider.FileExists(tempTargetPath))
@@ -367,10 +340,8 @@ namespace NzbDrone.Common.Disk
return false;
}
private bool TryMoveFile(string sourcePath, string targetPath)
private bool TryMoveFileTransactional(string sourcePath, string targetPath, long originalSize)
{
var originalSize = _diskProvider.GetFileSize(sourcePath);
var backupPath = sourcePath + ".backup~";
var tempTargetPath = targetPath + ".partial~";
@@ -423,16 +394,63 @@ namespace NzbDrone.Common.Disk
}
}
_logger.Trace("Hardlink move failed, reverting to copy.");
if (TryCopyFile(sourcePath, targetPath))
if (VerificationMode == DiskTransferVerificationMode.Transactional)
{
_logger.Trace("Copy succeeded, deleting source.");
_diskProvider.DeleteFile(sourcePath);
_logger.Trace("Hardlink move failed, reverting to copy.");
if (TryCopyFileTransactional(sourcePath, targetPath, originalSize))
{
_logger.Trace("Copy succeeded, deleting source.");
_diskProvider.DeleteFile(sourcePath);
return true;
}
}
else
{
_logger.Trace("Hardlink move failed, reverting to move.");
TryMoveFileVerified(sourcePath, targetPath, originalSize);
return true;
}
_logger.Trace("Copy failed.");
_logger.Trace("Move failed.");
return false;
}
private void TryCopyFileVerified(string sourcePath, string targetPath, long originalSize)
{
try
{
_diskProvider.CopyFile(sourcePath, targetPath);
var targetSize = _diskProvider.GetFileSize(targetPath);
if (targetSize != originalSize)
{
throw new IOException(string.Format("File copy incomplete. [{0}] was {1} bytes long instead of {2} bytes.", targetPath, targetSize, originalSize));
}
}
catch
{
RollbackCopy(sourcePath, targetPath);
throw;
}
}
private void TryMoveFileVerified(string sourcePath, string targetPath, long originalSize)
{
try
{
_diskProvider.MoveFile(sourcePath, targetPath);
var targetSize = _diskProvider.GetFileSize(targetPath);
if (targetSize != originalSize)
{
throw new IOException(string.Format("File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
}
}
catch
{
RollbackPartialMove(sourcePath, targetPath);
throw;
}
}
}
}

View File

@@ -153,6 +153,14 @@ namespace NzbDrone.Common.Disk
}
}
public bool IsValid
{
get
{
return _path.IsPathValid();
}
}
private int GetFileNameIndex()
{
if (_path.Length < 2)

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
@@ -175,9 +176,15 @@ namespace NzbDrone.Common.Http.Dispatchers
// fix up the date if it was malformed
var setCookieClean = ExpiryDate.Replace(setCookie, delegate(Match match)
{
string format = "ddd, dd-MMM-yyyy HH:mm:ss";
DateTime dt = Convert.ToDateTime(match.Groups[2].Value);
return match.Groups[1].Value + dt.ToUniversalTime().ToString(format) + " GMT";
string shortFormat = "ddd, dd-MMM-yy HH:mm:ss";
string longFormat = "ddd, dd-MMM-yyyy HH:mm:ss";
DateTime dt;
if (DateTime.TryParseExact(match.Groups[2].Value, longFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
DateTime.TryParseExact(match.Groups[2].Value, shortFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
DateTime.TryParse(match.Groups[2].Value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt))
return match.Groups[1].Value + dt.ToUniversalTime().ToString(longFormat, CultureInfo.InvariantCulture) + " GMT";
else
return match.Value;
});
return setCookieClean;
}

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Path
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -1,110 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Configuration
{
[TestFixture]
public class ConfigServiceFixture : DbTest<ConfigService, Config>
public class ConfigServiceFixture : TestBase<ConfigService>
{
[SetUp]
public void SetUp()
{
Mocker.SetConstant<IConfigRepository>(Mocker.Resolve<ConfigRepository>());
Db.All<Config>().ForEach(Db.Delete);
}
[Test]
public void Add_new_value_to_database()
{
const string key = "MY_KEY";
const string value = "MY_VALUE";
const string key = "RssSyncInterval";
const int value = 12;
Subject.SetValue(key, value);
Subject.GetValue(key, "").Should().Be(value);
}
Subject.RssSyncInterval = value;
[Test]
public void Get_value_from_database()
{
const string key = "MY_KEY";
const string value = "MY_VALUE";
Db.Insert(new Config { Key = key, Value = value });
Db.Insert(new Config { Key = "Other Key", Value = "OtherValue" });
var result = Subject.GetValue(key, "");
result.Should().Be(value);
AssertUpsert(key, value);
}
[Test]
public void Get_value_should_return_default_when_no_value()
{
const string key = "MY_KEY";
const string value = "MY_VALUE";
var result = Subject.GetValue(key, value);
result.Should().Be(value);
}
[Test]
public void New_value_should_update_old_value_new_value()
{
const string key = "MY_KEY";
const string originalValue = "OLD_VALUE";
const string newValue = "NEW_VALUE";
Db.Insert(new Config { Key = key, Value = originalValue });
Subject.SetValue(key, newValue);
var result = Subject.GetValue(key, "");
result.Should().Be(newValue);
AllStoredModels.Should().HaveCount(1);
}
[Test]
public void New_value_should_update_old_value_same_value()
{
const string key = "MY_KEY";
const string value = "OLD_VALUE";
Subject.SetValue(key, value);
Subject.SetValue(key, value);
var result = Subject.GetValue(key, "");
result.Should().Be(value);
AllStoredModels.Should().HaveCount(1);
Subject.RssSyncInterval.Should().Be(15);
}
[Test]
public void get_value_with_persist_should_store_default_value()
{
const string key = "MY_KEY";
string value = Guid.NewGuid().ToString();
Subject.GetValue(key, value, persist: true).Should().Be(value);
Subject.GetValue(key, string.Empty).Should().Be(value);
var salt = Subject.HmacSalt;
salt.Should().NotBeNullOrWhiteSpace();
AssertUpsert("HmacSalt", salt);
}
[Test]
public void get_value_with_out_persist_should_not_store_default_value()
{
const string key = "MY_KEY";
string value1 = Guid.NewGuid().ToString();
string value2 = Guid.NewGuid().ToString();
var interval = Subject.RssSyncInterval;
interval.Should().Be(15);
Mocker.GetMock<IConfigRepository>().Verify(c => c.Insert(It.IsAny<Config>()), Times.Never());
}
Subject.GetValue(key, value1).Should().Be(value1);
Subject.GetValue(key, value2).Should().Be(value2);
private void AssertUpsert(string key, object value)
{
Mocker.GetMock<IConfigRepository>().Verify(c => c.Upsert(key.ToLowerInvariant(), value.ToString()));
}
[Test]
@@ -114,7 +63,16 @@ namespace NzbDrone.Core.Test.Configuration
var configProvider = Subject;
var allProperties = typeof(ConfigService).GetProperties().Where(p => p.GetSetMethod() != null).ToList();
var keys = new List<string>();
var values = new List<Config>();
Mocker.GetMock<IConfigRepository>().Setup(c => c.Upsert(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, value) =>
{
keys.Add(key);
values.Add(new Config { Key = key, Value = value });
});
Mocker.GetMock<IConfigRepository>().Setup(c => c.All()).Returns(values);
foreach (var propertyInfo in allProperties)
{
@@ -148,8 +106,7 @@ namespace NzbDrone.Core.Test.Configuration
returnValue.Should().Be(value, propertyInfo.Name);
}
AllStoredModels.Should()
.HaveSameCount(allProperties, "two different properties are writing to the same key in db. Copy/Past fail.");
keys.Should().OnlyHaveUniqueItems();
}
}
}

View File

@@ -103,12 +103,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_if_latest_history_has_a_download_id_and_cdh_is_enabled()
{
GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
}
// [Test]
// public void should_return_true_if_latest_history_has_a_download_id_and_cdh_is_enabled()
// {
// GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
// _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
// }
[Test]
public void should_return_true_if_latest_history_item_is_older_than_twelve_hours()

View File

@@ -381,6 +381,30 @@ namespace NzbDrone.Core.Test.Download
AssertCompletedDownload();
}
[Test]
public void should_warn_if_path_is_not_valid_for_windows()
{
WindowsOnly();
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"/invalid/Windows/Path");
Subject.Process(_trackedDownload);
AssertNoAttemptedImport();
}
[Test]
public void should_warn_if_path_is_not_valid_for_linux()
{
MonoOnly();
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\Invalid\Mono\Path");
Subject.Process(_trackedDownload);
AssertNoAttemptedImport();
}
private void AssertNoAttemptedImport()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()

View File

@@ -9,7 +9,9 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.TorrentBlackhole;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
@@ -67,6 +69,21 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Returns(1000000);
}
protected override RemoteEpisode CreateRemoteEpisode()
{
var remoteEpisode = base.CreateRemoteEpisode();
var torrentInfo = new TorrentInfo();
torrentInfo.Title = remoteEpisode.Release.Title;
torrentInfo.DownloadUrl = remoteEpisode.Release.DownloadUrl;
torrentInfo.DownloadProtocol = remoteEpisode.Release.DownloadProtocol;
torrentInfo.MagnetUrl = "magnet:?xt=urn:btih:755248817d32b00cc853e633ecdc48e4c21bff15&dn=Series.S05E10.PROPER.HDTV.x264-DEFiNE%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710";
remoteEpisode.Release = torrentInfo;
return remoteEpisode;
}
[Test]
public void completed_download_should_have_required_properties()
{
@@ -116,6 +133,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
}
[Test]
public void Download_should_throw_if_magnet_and_torrent_url_does_not_exist()
{
var remoteEpisode = CreateRemoteEpisode();
remoteEpisode.Release.DownloadUrl = null;
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteEpisode));
}
[Test]
public void GetItems_should_considered_locked_files_queued()
{

View File

@@ -3,7 +3,9 @@ using System.Collections.Generic;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck.Checks
@@ -25,6 +27,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_error_when_download_client_throws()
{
var downloadClient = Mocker.GetMock<IDownloadClient>();
downloadClient.Setup(s => s.Definition).Returns(new IndexerDefinition{Name = "Test"});
downloadClient.Setup(s => s.GetItems())
.Throws<Exception>();

View File

@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
[TestCase("Hawaii Five-0", Result = "Hawaii+Five+0")]
[TestCase("Franklin & Bash", Result = "Franklin+and+Bash")]
[TestCase("Chicago P.D.", Result = "Chicago+PD")]
[TestCase("Kourtney And Khloé Take The Hamptons", Result = "Kourtney+And+Khloe+Take+The+Hamptons")]
[TestCase("Kourtney And Khlo\u00E9 Take The Hamptons", Result = "Kourtney+And+Khloe+Take+The+Hamptons")]
public string should_replace_some_special_characters(string input)
{
Subject.SceneTitles = new List<string> { input };

View File

@@ -84,8 +84,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
_videoFiles = videoFiles.ToList();
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
.Returns(_videoFiles);
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
.Returns(_videoFiles);
}
[Test]
@@ -180,21 +180,27 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
}
[Test]
public void should_use_file_quality_if_folder_quality_is_lower_than_file_quality()
public void should_use_file_quality_if_file_quality_was_determined_by_name()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo{Quality = new QualityModel(Quality.SDTV)}, true);
var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo{Quality = new QualityModel(Quality.Bluray1080p)}, true);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_use_folder_quality_when_it_is_greater_than_file_quality()
public void should_use_folder_quality_when_file_quality_was_determined_by_the_extension()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = new QualityModel(Quality.Bluray1080p);
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
_localEpisode.Path = _videoFiles.Single();
_localEpisode.Quality.QualitySource = QualitySource.Extension;
_localEpisode.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.SDTV);
var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo { Quality = expectedQuality }, true);

View File

@@ -57,6 +57,11 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_episodeFile.Quality.Revision.Version = 2;
}
private void GivenReal()
{
_episodeFile.Quality.Revision.Real = 1;
}
[Test]
public void should_replace_Series_space_Title()
{
@@ -207,6 +212,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be("Proper");
}
[Test]
public void should_replace_quality_real_with_real()
{
_namingConfig.StandardEpisodeFormat = "{Quality Real}";
GivenReal();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("REAL");
}
[Test]
public void should_replace_all_contents_in_pattern()
{
@@ -617,6 +632,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be("South Park - S15E06 [HDTV-720p Proper]");
}
[Test]
public void should_replace_quality_full_with_quality_title_and_real_when_a_real()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
GivenReal();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p REAL]");
}
[TestCase(' ')]
[TestCase('-')]
[TestCase('.')]

View File

@@ -82,6 +82,9 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[Hatsuyuki] Dragon Ball Kai (2014) - 018 (116) [1280x720][C4A3B16E]", "Dragon Ball Kai (2014)", 18, 0, 0)]
[TestCase("Dragon Ball Kai (2014) - 39 (137) [v2][720p.HDTV][Unison Fansub]", "Dragon Ball Kai (2014)", 39, 0, 0)]
[TestCase("[HorribleSubs] Eyeshield 21 - 101 [480p].mkv", "Eyeshield 21", 101, 0, 0)]
[TestCase("[Cthuyuu].Taimadou.Gakuen.35.Shiken.Shoutai.-.03.[720p.H264.AAC][8AD82C3A]", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
//[TestCase("Taimadou.Gakuen.35.Shiken.Shoutai.-.03.(1280x720.HEVC.AAC)", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
[TestCase("[Cthuyuu] Taimadou Gakuen 35 Shiken Shoutai - 03 [720p H264 AAC][8AD82C3A]", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
//[TestCase("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{

View File

@@ -1,5 +1,4 @@
using System;
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
@@ -14,18 +13,19 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Chuck.S03E17.REAL.PROPER.720p.HDTV.x264-ORENJI-RP", 1)]
[TestCase("Covert.Affairs.S05E09.REAL.PROPER.HDTV.x264-KILLERS", 1)]
[TestCase("Mythbusters.S14E01.REAL.PROPER.720p.HDTV.x264-KILLERS", 1)]
[TestCase("Orange.Is.the.New.Black.s02e06.real.proper.720p.webrip.x264-2hd", 1)]
[TestCase("Top.Gear.S21E07.Super.Duper.Real.Proper.HDTV.x264-FTP", 1)]
[TestCase("Orange.Is.the.New.Black.s02e06.real.proper.720p.webrip.x264-2hd", 0)]
[TestCase("Top.Gear.S21E07.Super.Duper.Real.Proper.HDTV.x264-FTP", 0)]
[TestCase("Top.Gear.S21E07.PROPER.HDTV.x264-RiVER-RP", 0)]
[TestCase("House.S07E11.PROPER.REAL.RERIP.1080p.BluRay.x264-TENEIGHTY", 1)]
[TestCase("[MGS] - Kuragehime - Episode 02v2 - [D8B6C90D]", 0)]
[TestCase("[Hatsuyuki] Tokyo Ghoul - 07 [v2][848x480][23D8F455].avi", 0)]
[TestCase("[DeadFish] Barakamon - 01v3 [720p][AAC]", 0)]
[TestCase("[DeadFish] Momo Kyun Sword - 01v4 [720p][AAC]", 0)]
[TestCase("The Real Housewives of Some Place - S01E01 - Why are we doing this?", 0)]
public void should_parse_reality_from_title(string title, int reality)
{
//TODO: re-enable this when we have a reliable way to determine real
//QualityParser.ParseQuality(title).Revision.Real.Should().Be(reality);
QualityParser.ParseQuality(title).Revision.Real.Should().Be(reality);
}
[TestCase("Chuck.S04E05.HDTV.XviD-LOL", 1)]

View File

@@ -42,7 +42,10 @@ namespace NzbDrone.Core.Test.ParserTests
//[TestCase("Adventure Time - 5x01 - x02 - Finn the Human (2) & Jake the Dog (3)", "Adventure Time", 5, new [] { 1, 2 })]
[TestCase("The Young And The Restless - S42 Ep10718 - Ep10722", "The Young And The Restless", 42, new[] { 10718, 10719, 10720, 10721, 10722 })]
[TestCase("The Young And The Restless - S42 Ep10688 - Ep10692", "The Young And The Restless", 42, new[] { 10688, 10689, 10690, 10691, 10692 })]
//[TestCase("", "", ,new [] { })]
[TestCase("RWBY.S01E02E03.1080p.BluRay.x264-DeBTViD", "RWBY", 1, new [] { 2, 3 })]
[TestCase("grp-zoos01e11e12-1080p", "grp-zoo", 1, new [] { 11, 12 })]
[TestCase("grp-zoo-s01e11e12-1080p", "grp-zoo", 1, new [] { 11, 12 })]
//[TestCase("", "", , new [] { })]
public void should_parse_multiple_episodes(string postTitle, string title, int season, int[] episodes)
{
var result = Parser.Parser.ParseTitle(postTitle);

View File

@@ -37,6 +37,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Match of the Day 2", "matchday2")]
[TestCase("[ www.Torrenting.com ] - Revenge.S03E14.720p.HDTV.X264-DIMENSION", "Revenge")]
[TestCase("Seed S02E09 HDTV x264-2HD [eztv]-[rarbg.com]", "Seed")]
[TestCase("Reno.911.S01.DVDRip.DD2.0.x264-DEEP", "Reno 911")]
public void should_parse_series_name(string postTitle, string title)
{
var result = Parser.Parser.ParseSeriesName(postTitle).CleanSeriesTitle();
@@ -46,7 +47,7 @@ namespace NzbDrone.Core.Test.ParserTests
[Test]
public void should_remove_accents_from_title()
{
const string title = "Carnivŕle";
const string title = "Carniv\u00E0le";
title.CleanSeriesTitle().Should().Be("carnivale");
}

View File

@@ -228,6 +228,24 @@ namespace NzbDrone.Core.Test.ParserTests
}
}
[TestCase("Saturday.Night.Live.Vintage.S10E09.Eddie.Murphy.The.Honeydrippers.1080i.UPSCALE.HDTV.DD5.1.MPEG2-zebra")]
[TestCase("Dexter - S01E01 - Title [HDTV-1080p]")]
[TestCase("[CR] Sailor Moon - 004 [480p][48CE2D0F]")]
[TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA")]
public void should_parse_quality_from_name(string title)
{
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Name);
}
[TestCase("Revolution.S01E02.Chained.Heat.mkv")]
[TestCase("Dexter - S01E01 - Title.avi")]
[TestCase("the_x-files.9x18.sunshine_days.avi")]
[TestCase("[CR] Sailor Moon - 004 [48CE2D0F].avi")]
public void should_parse_quality_from_extension(string title)
{
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension);
}
private void ParseAndVerifyQuality(string title, Quality quality, bool proper)
{
var result = QualityParser.ParseQuality(title);

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series Title S01E01 Episode Title", null)]
[TestCase("The Colbert Report - 2014-06-02 - Thomas Piketty.mkv", null)]
[TestCase("Real Time with Bill Maher S12E17 May 23, 2014.mp4", null)]
[TestCase("Reizen Waes - S01E08 - Transistrië, Zuid-Ossetië en Abchazië SDTV.avi", null)]
[TestCase("Reizen Waes - S01E08 - Transistri\u00EB, Zuid-Osseti\u00EB en Abchazi\u00EB SDTV.avi", null)]
[TestCase("Simpsons 10x11 - Wild Barts Cant Be Broken [rl].avi", null)]
[TestCase("[ www.Torrenting.com ] - Revenge.S03E14.720p.HDTV.X264-DIMENSION", "DIMENSION")]
[TestCase("Seed S02E09 HDTV x264-2HD [eztv]-[rarbg.com]", "2HD")]

View File

@@ -111,6 +111,11 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series Title Season 01 Episode 05 720p", "Series Title", 1, 5)]
//[TestCase("Off the Air - 101 - Animals (460p.x264.vorbis-2.0) [449].mkv", "Off the Air", 1, 1)]
[TestCase("The Young And the Restless - S42 E10713 - 2015-07-20.mp4", "The Young And the Restless", 42, 10713)]
[TestCase("quantico.103.hdtv-lol[ettv].mp4", "quantico", 1, 3)]
[TestCase("Fargo - 01x02 - The Rooster Prince - [itz_theo]", "Fargo", 1, 2)]
[TestCase("Castle (2009) - [06x16] - Room 147.mp4", "Castle (2009)", 6, 16)]
[TestCase("grp-zoos01e11-1080p", "grp-zoo", 1, 11)]
[TestCase("grp-zoo-s01e11-1080p", "grp-zoo", 1, 11)]
//[TestCase("", "", 0, 0)]
public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber)
{

View File

@@ -18,7 +18,6 @@ namespace NzbDrone.Core.Test.TvTests
{
_series = Builder<Series>.CreateNew()
.With(v => v.Status == SeriesStatusType.Continuing)
.With(v => v.LastInfoSync == DateTime.UtcNow.AddHours(-12))
.Build();
Mocker.GetMock<IEpisodeService>()
@@ -45,6 +44,11 @@ namespace NzbDrone.Core.Test.TvTests
_series.LastInfoSync = DateTime.UtcNow.AddDays(-1);
}
private void GivenSeriesLastRefreshedHalfADayAgo()
{
_series.LastInfoSync = DateTime.UtcNow.AddHours(-12);
}
private void GivenSeriesLastRefreshedRecently()
{
_series.LastInfoSync = DateTime.UtcNow.AddHours(-1);
@@ -66,6 +70,8 @@ namespace NzbDrone.Core.Test.TvTests
[Test]
public void should_return_true_if_running_series_last_refreshed_more_than_6_hours_ago()
{
GivenSeriesLastRefreshedHalfADayAgo();
Subject.ShouldRefresh(_series).Should().BeTrue();
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Configuration
public interface IConfigRepository : IBasicRepository<Config>
{
Config Get(string key);
Config Upsert(string key, string value);
}
public class ConfigRepository : BasicRepository<Config>, IConfigRepository
@@ -23,5 +23,19 @@ namespace NzbDrone.Core.Configuration
{
return Query.Where(c => c.Key == key).SingleOrDefault();
}
public Config Upsert(string key, string value)
{
var dbValue = Get(key);
if (dbValue == null)
{
return Insert(new Config {Key = key, Value = value});
}
dbValue.Value = value;
return Update(dbValue);
}
}
}

View File

@@ -30,12 +30,7 @@ namespace NzbDrone.Core.Configuration
_cache = new Dictionary<string, string>();
}
public IEnumerable<Config> All()
{
return _repository.All();
}
public Dictionary<string, object> AllWithDefaults()
private Dictionary<string, object> AllWithDefaults()
{
var dict = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
@@ -45,7 +40,6 @@ namespace NzbDrone.Core.Configuration
foreach (var propertyInfo in properties)
{
var value = propertyInfo.GetValue(this, null);
dict.Add(propertyInfo.Name, value);
}
@@ -65,7 +59,9 @@ namespace NzbDrone.Core.Configuration
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
if (!equal)
{
SetValue(configValue.Key, configValue.Value.ToString());
}
}
_eventAggregator.PublishEvent(new ConfigSavedEvent());
@@ -331,7 +327,7 @@ namespace NzbDrone.Core.Configuration
return Convert.ToInt32(GetValue(key, defaultValue));
}
public T GetValueEnum<T>(string key, T defaultValue)
private T GetValueEnum<T>(string key, T defaultValue)
{
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), true);
}
@@ -346,7 +342,9 @@ namespace NzbDrone.Core.Configuration
string dbValue;
if (_cache.TryGetValue(key, out dbValue) && dbValue != null && !string.IsNullOrEmpty(dbValue))
{
return dbValue;
}
_logger.Trace("Using default config value for '{0}' defaultValue:'{1}'", key, defaultValue);
@@ -354,6 +352,7 @@ namespace NzbDrone.Core.Configuration
{
SetValue(key, defaultValue.ToString());
}
return defaultValue.ToString();
}
@@ -367,44 +366,34 @@ namespace NzbDrone.Core.Configuration
SetValue(key, value.ToString());
}
public void SetValue(string key, string value)
private void SetValue(string key, Enum value)
{
SetValue(key, value.ToString().ToLower());
}
private void SetValue(string key, string value)
{
key = key.ToLowerInvariant();
_logger.Trace("Writing Setting to database. Key:'{0}' Value:'{1}'", key, value);
var dbValue = _repository.Get(key);
if (dbValue == null)
{
_repository.Insert(new Config { Key = key, Value = value });
}
else
{
dbValue.Value = value;
_repository.Update(dbValue);
}
_repository.Upsert(key, value);
ClearCache();
}
public void SetValue(string key, Enum value)
{
SetValue(key, value.ToString().ToLower());
}
private void EnsureCache()
{
lock (_cache)
{
if (!_cache.Any())
{
_cache = All().ToDictionary(c => c.Key.ToLower(), c => c.Value);
var all = _repository.All();
_cache = all.ToDictionary(c => c.Key.ToLower(), c => c.Value);
}
}
}
public static void ClearCache()
private static void ClearCache()
{
lock (_cache)
{

View File

@@ -6,8 +6,6 @@ namespace NzbDrone.Core.Configuration
{
public interface IConfigService
{
IEnumerable<Config> All();
Dictionary<string, object> AllWithDefaults();
void SaveConfigDictionary(Dictionary<string, object> configValues);
bool IsDefined(string key);

View File

@@ -46,18 +46,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
{
var recentBlackhole = mostRecent.DownloadId.IsNullOrWhiteSpace() && mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality);
var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality);
if (!recentBlackhole && cdhEnabled)
if (!recent && cdhEnabled)
{
continue;
}
if (!cutoffUnmet)
{
if (recentBlackhole)
if (recent)
{
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
}
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (!upgradeable)
{
if (recentBlackhole)
if (recent)
{
return Decision.Reject("Recent grab event in history is of equal or higher quality: {0}", mostRecent.Quality);
}

View File

@@ -1,4 +1,4 @@
using System;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
RuleFor(c => c.UrlBase).ValidUrlBase();
RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$").WithMessage("Allowed characters a-z and -");
RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
}
}

View File

@@ -3,6 +3,8 @@ using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.TrackedDownloads;
@@ -72,6 +74,13 @@ namespace NzbDrone.Core.Download
return;
}
if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
(OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
{
trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
return;
}
var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
if (downloadedEpisodesFolder.Contains(downloadItemOutputPath))

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;

View File

@@ -51,8 +51,8 @@ namespace NzbDrone.Core.Download
string magnetUrl = null;
string torrentUrl = null;
if (remoteEpisode.Release.DownloadUrl.StartsWith("magnet:"))
if (remoteEpisode.Release.DownloadUrl.IsNotNullOrWhiteSpace() && remoteEpisode.Release.DownloadUrl.StartsWith("magnet:"))
{
magnetUrl = remoteEpisode.Release.DownloadUrl;
}
@@ -76,11 +76,16 @@ namespace NzbDrone.Core.Download
}
catch (NotSupportedException ex)
{
if (torrentUrl.IsNullOrWhiteSpace())
{
throw new ReleaseDownloadException(remoteEpisode.Release, "Magnet not supported by download client. ({0})", ex.Message);
}
_logger.Debug("Magnet not supported by download client, trying torrent. ({0})", ex.Message);
}
}
if (hash == null && !torrentUrl.IsNullOrWhiteSpace())
if (hash == null && torrentUrl.IsNotNullOrWhiteSpace())
{
hash = DownloadFromWebUrl(remoteEpisode, torrentUrl);
}

View File

@@ -25,17 +25,19 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(), HealthCheckResult.Warning, "No download client is available");
}
try
foreach (var downloadClient in downloadClients)
{
foreach (var downloadClient in downloadClients)
try
{
downloadClient.GetItems();
}
}
catch (Exception ex)
{
_logger.Error("Unable to communicate with download client: ", ex);
return new HealthCheck(GetType(), HealthCheckResult.Error, "Unable to communicate with download client: " + ex.Message);
catch (Exception ex)
{
var message = String.Format("Unable to communicate with {0}.", downloadClient.Definition.Name);
_logger.Error(message, ex);
return new HealthCheck(GetType(), HealthCheckResult.Error, message + " " + ex.Message);
}
}
return new HealthCheck(GetType());

View File

@@ -1,4 +1,4 @@
using System;
using System.Linq;
namespace NzbDrone.Core.IndexerSearch.Definitions
{
@@ -8,6 +8,13 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public override string ToString()
{
var episodeTitles = EpisodeQueryTitles.ToList();
if (episodeTitles.Count > 0)
{
return string.Format("[{0}] Specials", Series.Title);
}
return string.Format("[{0} : {1}]", Series.Title, string.Join(",", EpisodeQueryTitles));
}
}

View File

@@ -50,12 +50,30 @@ namespace NzbDrone.Core.IndexerSearch
if (season.Count() > 1)
{
decisions = _nzbSearchService.SeasonSearch(series.Key, season.Key, true);
try
{
decisions = _nzbSearchService.SeasonSearch(series.Key, season.Key, true);
}
catch (Exception ex)
{
var message = String.Format("Unable to search for missing episodes in season {0} of [{1}]", season.Key, series.Key);
_logger.ErrorException(message, ex);
continue;
}
}
else
{
decisions = _nzbSearchService.EpisodeSearch(season.First());
try
{
decisions = _nzbSearchService.EpisodeSearch(season.First());
}
catch (Exception ex)
{
var message = String.Format("Unable to search for missing episode: [{0}]", season.First());
_logger.ErrorException(message, ex);
continue;
}
}
var processed = _processDownloadDecisions.ProcessDecisions(decisions);

View File

@@ -32,7 +32,13 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
break;
}
var jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerResponse.HttpResponse).Resource;
if (indexerResponse.Content == "Query execution was interrupted")
{
throw new IndexerException(indexerResponse, "Indexer API returned an internal server error");
}
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerResponse.HttpResponse).Resource;
if (jsonResponse.Error != null || jsonResponse.Result == null)
{

View File

@@ -306,13 +306,19 @@ namespace NzbDrone.Core.Indexers
}
catch (UnsupportedFeedException ex)
{
_logger.WarnException("Indexer feed is not supported: " + ex.Message, ex);
_logger.WarnException("Indexer feed is not supported", ex);
return new ValidationFailure(string.Empty, "Indexer feed is not supported: " + ex.Message);
}
catch (IndexerException ex)
{
_logger.WarnException("Unable to connect to indexer", ex);
return new ValidationFailure(string.Empty, "Unable to connect to indexer. " + ex.Message);
}
catch (Exception ex)
{
_logger.WarnException("Unable to connect to indexer: " + ex.Message, ex);
_logger.WarnException("Unable to connect to indexer", ex);
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
}

View File

@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Indexers
IndexerStatus blockedIndexerStatus;
if (blockedIndexers.TryGetValue(indexer.Definition.Id, out blockedIndexerStatus))
{
_logger.Debug("Temporarily ignoring indexer {0} till {1} due to recent failures.", indexer.Definition.Name, blockedIndexerStatus.DisabledTill.Value);
_logger.Debug("Temporarily ignoring indexer {0} till {1} due to recent failures.", indexer.Definition.Name, blockedIndexerStatus.DisabledTill.Value.ToLocalTime());
continue;
}

View File

@@ -51,6 +51,8 @@ namespace NzbDrone.Core.Indexers.Newznab
yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com"));
yield return GetDefinition("nzbplanet.net", GetSettings("https://nzbplanet.net"));
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
yield return GetDefinition("PFmonkey", GetSettings("https://www.pfmonkey.com"));
yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat"));
}
}

View File

@@ -1,14 +1,16 @@
using NzbDrone.Common.Exceptions;
using NzbDrone.Core.Indexers.Exceptions;
namespace NzbDrone.Core.Indexers.Newznab
{
public class NewznabException : NzbDroneException
public class NewznabException : IndexerException
{
public NewznabException(string message, params object[] args) : base(message, args)
public NewznabException(IndexerResponse response, string message, params object[] args)
: base(response, message, args)
{
}
public NewznabException(string message) : base(message)
public NewznabException(IndexerResponse response, string message)
: base(response, message)
{
}
}

View File

@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Indexers.Newznab
throw new RequestLimitReachedException("API limit reached");
}
throw new NewznabException("Newznab error detected: {0}", errorMessage);
throw new NewznabException(indexerResponse, errorMessage);
}
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)

View File

@@ -181,9 +181,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
{
if (folderInfo != null &&
folderInfo.Quality.Quality != Quality.Unknown &&
new QualityModelComparer(series.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
if (folderInfo != null && folderInfo.Quality.Quality != Quality.Unknown && fileQuality.QualitySource == QualitySource.Extension)
{
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
return folderInfo.Quality;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -59,32 +60,27 @@ namespace NzbDrone.Core.Notifications.Xbmc
private XbmcVersion GetJsonVersion(XbmcSettings settings)
{
try
return _xbmcVersionCache.Get(settings.Address, () =>
{
return _xbmcVersionCache.Get(settings.Address, () =>
var response = _proxy.GetJsonVersion(settings);
_logger.Debug("Getting version from response: " + response);
var result = Json.Deserialize<XbmcJsonResult<JObject>>(response);
var versionObject = result.Result.Property("version");
if (versionObject.Value.Type == JTokenType.Integer)
{
return new XbmcVersion((int)versionObject.Value);
}
var response = _proxy.GetJsonVersion(settings);
if (versionObject.Value.Type == JTokenType.Object)
{
return Json.Deserialize<XbmcVersion>(versionObject.Value.ToString());
}
_logger.Debug("Getting version from response: " + response);
var result = Json.Deserialize<XbmcJsonResult<JObject>>(response);
var versionObject = result.Result.Property("version");
if (versionObject.Value.Type == JTokenType.Integer) return new XbmcVersion((int) versionObject.Value);
if (versionObject.Value.Type == JTokenType.Object) return Json.Deserialize<XbmcVersion>(versionObject.Value.ToString());
throw new InvalidCastException("Unknown Version structure!: " + versionObject);
}, TimeSpan.FromHours(12));
}
catch (Exception ex)
{
_logger.DebugException(ex.Message, ex);
}
return new XbmcVersion();
throw new InvalidCastException("Unknown Version structure!: " + versionObject);
}, TimeSpan.FromHours(12));
}
private IApiProvider GetApiProvider(XbmcSettings settings)

View File

@@ -767,6 +767,7 @@
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
<Compile Include="Profiles\ProfileRepository.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\QualitySource.cs" />
<Compile Include="Qualities\Revision.cs" />
<Compile Include="RemotePathMappings\RemotePathMapping.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingRepository.cs" />
@@ -1003,6 +1004,7 @@
<Compile Include="Validation\Paths\SeriesAncestorValidator.cs" />
<Compile Include="Validation\Paths\SeriesExistsValidator.cs" />
<Compile Include="Validation\Paths\SeriesPathValidator.cs" />
<Compile Include="Validation\ProfileExistsValidator.cs" />
<Compile Include="Validation\RuleBuilderExtensions.cs" />
<Compile Include="Validation\UrlValidator.cs" />
</ItemGroup>
@@ -1063,6 +1065,14 @@
<Link>MediaInfo.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Libraries\MediaInfo\libmediainfo.0.dylib">
<Link>libmediainfo.0.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Libraries\Sqlite\libsqlite3.0.dylib">
<Link>libsqlite3.0.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -432,10 +432,12 @@ namespace NzbDrone.Core.Organizer
{
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
var qualityProper = GetQualityProper(series, episodeFile.Quality);
var qualityReal = GetQualityReal(series, episodeFile.Quality);
tokenHandlers["{Quality Full}"] = m => string.Format("{0} {1}", qualityTitle, qualityProper);
tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal);
tokenHandlers["{Quality Title}"] = m => qualityTitle;
tokenHandlers["{Quality Proper}"] = m => qualityProper;
tokenHandlers["{Quality Real}"] = m => qualityReal;
}
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
@@ -708,6 +710,16 @@ namespace NzbDrone.Core.Organizer
return "Proper";
}
return String.Empty;
}
private string GetQualityReal(Series series, QualityModel quality)
{
if (quality.Revision.Real > 0)
{
return "REAL";
}
return string.Empty;
}

View File

@@ -46,6 +46,10 @@ namespace NzbDrone.Core.Parser
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title - Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -55,7 +59,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Absolute Episode Number [Hash]
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])(?:$|\.)",
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with airdate AND season/episode number
@@ -70,10 +74,6 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Supports 103/113 naming
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))+(?<season>(?<!\d+)[1-9])(?<episode>[1-9][0-9]|[0][1-9])(?![a-z]|\d+))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Mini-Series, treated as season 1, episodes are labelled as Part01, Part 01, Part.1
new Regex(@"^(?<title>.+?)(?:\W+(?:(?:Part\W?|(?<!\d+\W+)e)(?<episode>\d{1,2}(?!\d+)))+)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -82,6 +82,10 @@ namespace NzbDrone.Core.Parser
new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:[-_\W](?<![()\[]))+(?:\W?Season\W?)(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)+(?:Episode\W)(?:[-_. ]?(?<episode>(?<!\d+)\d{1,2}(?!\d+)))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Multi-episode release with no space between series title and season (S01E11E12)
new Regex(@"(?:.*(?:^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{2}(?!\d+))(?:E(?<episode>(?<!\d+)\d{2}(?!\d+)))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Single episode season or episode S1E1 or S1-E1
new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)?E(?<episode>(?<!\d+)\d{1,2}(?!\d+))",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -106,6 +110,14 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{4}(?!\d+))(\W+|_|$)(?<extras>EXTRAS|SUBPACK)?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with a title and season/episode in square brackets
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+\[S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+|i|p)))+\])\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Supports 103/113 naming
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))+(?<season>(?<!\d+)[1-9])(?<episode>[1-9][0-9]|[0][1-9])(?![a-z]|\d+))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with airdate
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])(?!\W+[0-3][0-9])",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -124,10 +136,6 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+)\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with a title and season/episode in square brackets
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+\[S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+|i|p)))+\])\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with single digit episode number (S01E1, S01E5E6, etc)
new Regex(@"^(?<title>.*?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d{1}))+)+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -203,6 +211,9 @@ namespace NzbDrone.Core.Parser
private static readonly Regex CleanReleaseGroupRegex = new Regex(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|-(RP|1|NZBGeek|sample)$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex CleanTorrentSuffixRegex = new Regex(@"\[(?:ettv|rartv|rarbg|cttv)\]$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex ReleaseGroupRegex = new Regex(@"-(?<releasegroup>[a-z0-9]+)\b(?<!WEB-DL|480p|720p|1080p)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
@@ -270,9 +281,13 @@ namespace NzbDrone.Core.Parser
var simpleTitle = SimpleTitleRegex.Replace(title, string.Empty);
simpleTitle = RemoveFileExtension(simpleTitle);
// TODO: Quick fix stripping [url] - prefixes.
simpleTitle = WebsitePrefixRegex.Replace(simpleTitle, string.Empty);
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty);
var airDateMatch = AirDateRegex.Match(simpleTitle);
if (airDateMatch.Success)
{

View File

@@ -35,8 +35,8 @@ namespace NzbDrone.Core.Parser
private static readonly Regex VersionRegex = new Regex(@"\dv(?<version>\d)\b|\[v(?<version>\d)\]",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RealRegex = new Regex(@"\b(?<real>)real\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",
RegexOptions.Compiled);
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<_480p>480p|640x480|848x480)|(?<_576p>576p)|(?<_720p>720p|1280x720)|(?<_1080p>1080p|1920x1080))\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -56,8 +56,7 @@ namespace NzbDrone.Core.Parser
name = name.Trim();
var normalizedName = name.Replace('_', ' ').Trim().ToLower();
var result = ParseQualityModifiers(normalizedName);
var result = ParseQualityModifiers(name, normalizedName);
if (RawHDRegex.IsMatch(normalizedName))
{
@@ -276,6 +275,7 @@ namespace NzbDrone.Core.Parser
try
{
result.Quality = MediaFileExtensions.GetQualityForExtension(Path.GetExtension(name));
result.QualitySource = QualitySource.Extension;
}
catch (ArgumentException)
{
@@ -311,7 +311,7 @@ namespace NzbDrone.Core.Parser
return Quality.Unknown;
}
private static QualityModel ParseQualityModifiers(string normalizedName)
private static QualityModel ParseQualityModifiers(string name, string normalizedName)
{
var result = new QualityModel { Quality = Quality.Unknown };
@@ -329,12 +329,12 @@ namespace NzbDrone.Core.Parser
//TODO: re-enable this when we have a reliable way to determine real
//TODO: Only treat it as a real if it comes AFTER the season/epsiode number
// var realRegexResult = RealRegex.Matches(normalizedName);
//
// if (realRegexResult.Count > 0)
// {
// result.Revision.Real = realRegexResult.Count;
// }
var realRegexResult = RealRegex.Matches(name);
if (realRegexResult.Count > 0)
{
result.Revision.Real = realRegexResult.Count;
}
return result;
}

View File

@@ -5,7 +5,7 @@ namespace NzbDrone.Core.Profiles
{
public interface IProfileRepository : IBasicRepository<Profile>
{
bool Exists(int id);
}
public class ProfileRepository : BasicRepository<Profile>, IProfileRepository
@@ -14,5 +14,10 @@ namespace NzbDrone.Core.Profiles
: base(database, eventAggregator)
{
}
public bool Exists(int id)
{
return DataMapper.Query<Profile>().Where(p => p.Id == id).GetRowCount() == 1;
}
}
}

View File

@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Profiles
void Delete(int id);
List<Profile> All();
Profile Get(int id);
bool Exists(int id);
}
public class ProfileService : IProfileService, IHandle<ApplicationStartedEvent>
@@ -61,6 +62,11 @@ namespace NzbDrone.Core.Profiles
return _profileRepository.Get(id);
}
public bool Exists(int id)
{
return _profileRepository.Exists(id);
}
private Profile AddDefaultProfile(string name, Quality cutoff, params Quality[] allowed)
{
var items = Quality.DefaultQualityDefinitions

View File

@@ -1,4 +1,5 @@
using System;
using Newtonsoft.Json;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Qualities
@@ -7,6 +8,9 @@ namespace NzbDrone.Core.Qualities
{
public Quality Quality { get; set; }
public Revision Revision { get; set; }
[JsonIgnore]
public QualitySource QualitySource { get; set; }
public QualityModel()
: this(Quality.Unknown, new Revision())

View File

@@ -0,0 +1,9 @@
namespace NzbDrone.Core.Qualities
{
public enum QualitySource
{
Name,
Extension,
MediaInfo
}
}

View File

@@ -0,0 +1,23 @@
using FluentValidation.Validators;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Core.Validation
{
public class ProfileExistsValidator : PropertyValidator
{
private readonly IProfileService _profileService;
public ProfileExistsValidator(IProfileService profileService)
: base("Profile does not exist")
{
_profileService = profileService;
}
protected override bool IsValid(PropertyValidatorContext context)
{
if (context.PropertyValue == null) return true;
return _profileService.Exists((int)context.PropertyValue);
}
}
}

View File

@@ -32,18 +32,18 @@ namespace NzbDrone.Update.Test
}
[Test]
public void should_call_update_with_corret_path()
public void should_call_update_with_correct_path()
{
const string ProcessPath = @"C:\NzbDrone\nzbdrone.exe";
var ProcessPath = @"C:\NzbDrone\nzbdrone.exe".AsOsAgnostic();
Mocker.GetMock<IProcessProvider>().Setup(c => c.GetProcessById(12))
.Returns(new ProcessInfo() { StartPath = ProcessPath });
Subject.Start(new[] { "12", "" });
Subject.Start(new[] { "12", "", ProcessPath });
Mocker.GetMock<IInstallUpdateService>().Verify(c => c.Start(@"C:\NzbDrone", 12), Times.Once());
Mocker.GetMock<IInstallUpdateService>().Verify(c => c.Start(@"C:\NzbDrone".AsOsAgnostic(), 12), Times.Once());
}

View File

@@ -1,6 +1,7 @@
'use strict';
var $ = require('jquery');
var _ = require('underscore');
var vent = require('../../vent');
var TemplatedCell = require('../../Cells/TemplatedCell');
var RemoveFromQueueView = require('./RemoveFromQueueView');
@@ -40,11 +41,12 @@ module.exports = TemplatedCell.extend({
_grab : function() {
var self = this;
var data = _.omit(this.model.toJSON(), 'series', 'episode');
var promise = $.ajax({
url : window.NzbDrone.ApiRoot + '/queue/grab',
type : 'POST',
data : JSON.stringify(this.model.toJSON())
data : JSON.stringify(data)
});
this.$(this.ui.grab).spinForPromise(promise);

View File

@@ -61,6 +61,19 @@ var QueueCollection = PageableCollection.extend({
return Number.MAX_VALUE;
}
},
sizeleft : {
sortValue : function(model, attr) {
var size = model.get('size');
var sizeleft = model.get('sizeleft');
if (size && sizeleft) {
return sizeleft / size;
}
return 0;
}
}
}
});

View File

@@ -61,7 +61,7 @@ module.exports = Marionette.Layout.extend({
cellValue : 'this'
},
{
name : 'episode',
name : 'sizeleft',
label : 'Progress',
cell : ProgressCell,
cellValue : 'this'

View File

@@ -90,7 +90,7 @@ module.exports = Marionette.ItemView.extend({
element.find('.chart').tooltip({
title : 'Episode is downloading - {0}% {1}'.format(progress.toFixed(1), releaseTitle),
container : '.fc-content-skeleton'
container : '.fc'
});
}
}
@@ -271,7 +271,7 @@ module.exports = Marionette.ItemView.extend({
element.find('.fc-time').after('<span class="status pull-right"><i class="{0}"></i></span>'.format(icon));
element.find('.status').tooltip({
title : tooltip,
container : '.fc-content-skeleton'
container : '.fc'
});
},

View File

@@ -212,7 +212,8 @@
}
.legend-labels {
width : 500px;
max-width : 100%;
width : 500px;
@media (max-width: @screen-xs-min) {
width : 400px;

View File

@@ -81,6 +81,7 @@
.opacity (0.2);
position : fixed;
z-index : 9999;
bottom : 50px;
right : 50px;
display : none;

File diff suppressed because it is too large Load Diff

View File

@@ -14,15 +14,23 @@ module.exports = Backgrid.Cell.extend({
var self = this;
this.$el.html('<i class="icon-sonarr-spinner fa-spin" />');
this.$el.html('<i class="icon-sonarr-spinner fa-spin" title="Adding to download queue" />');
//Using success callback instead of promise so it
//gets called before the sync event is triggered
this.model.save(null, {
var promise = this.model.save(null, {
success : function() {
self.model.set('queued', true);
}
});
promise.fail(function (xhr) {
if (xhr.responseJSON && xhr.responseJSON.message) {
self.$el.html('<i class="icon-sonarr-download-failed" title="{0}" />'.format(xhr.responseJSON.message));
} else {
self.$el.html('<i class="icon-sonarr-download-failed" title="Failed to add to download queue" />');
}
});
},
render : function() {

View File

@@ -433,7 +433,10 @@
.label {
display : inline-block;
margin-bottom : 2px;
padding : 4px 6px 3px 6px
padding : 4px 6px 3px 6px;
max-width : 100%;
white-space : normal;
word-wrap : break-word;
}
}

View File

@@ -25,9 +25,15 @@ var routeBinder = {
var linkElement = $target.closest('a').first();
var href = linkElement.attr('href');
// Set noreferrer for external links.
if (href && href.startsWith('http') && !linkElement.attr('rel')) {
linkElement.attr('rel', 'noreferrer');
if (href && href.startsWith('http')) {
// Set noreferrer for external links.
if (!linkElement.attr('rel')) {
linkElement.attr('rel', 'noreferrer');
}
// Open all external links in new windows.
if (!linkElement.attr('target')) {
linkElement.attr('target', '_blank');
}
}
if (linkElement.hasClass('no-router') || event.type !== 'click') {