Compare commits

..

26 Commits

Author SHA1 Message Date
Bogdan
b3ddf2f9cd Improve logging when no releases were found 2023-05-28 02:04:39 +03:00
Bogdan
d9ce9eb0b2 Add defaults definitions for indexer proxies 2023-05-28 01:52:38 +03:00
bakerboy448
29ab1801db Fixed a really important spelling mistake
(cherry picked from commit b510201b43f6bc5e6774119ebbd7b8a0d89ee487)
2023-05-27 13:08:16 +03:00
Mark McDowall
19ff73dad0 Fixed: Don't log handled exceptions in API
(cherry picked from commit 59f2e5b65dd7352aad92b33adefa6cf5ca79a0de)
2023-05-27 13:03:36 +03:00
Bogdan
c455f1a113 New: (BakaBT) Add freeleech only option 2023-05-26 20:45:23 +03:00
Qstick
b8793d8783 Remove mono process detection
(cherry picked from commit 5a046026725084bc880a7b63d7105dcf4d882128)
2023-05-26 16:51:33 +03:00
Bogdan
ce34940287 Ensuring backward compatibility with older versions on first sync 2023-05-26 09:54:51 +03:00
Bogdan
dcb19a66b0 New: Add minimum version checks for applications 2023-05-26 09:54:51 +03:00
Weblate
b3bc92e60e Translated using Weblate (Indonesian)
Currently translated at 3.5% (18 of 514 strings)

Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translation: Servarr/Prowlarr
2023-05-25 13:38:21 +03:00
bakerboy448
1b17d38564 Fixed: (Animedia) Description Language 2023-05-24 23:17:34 -05:00
Bogdan
d8c7361205 Convert typeof to nameof 2023-05-24 19:25:08 +03:00
Bogdan
7a0dd0bc0d Fixed: (AnimeTorrents) Replace non-word chars with wildcard in search term 2023-05-24 00:15:13 +03:00
Mark McDowall
c02bfb5930 Fixed: Don't rollback file move if destination already exists
(cherry picked from commit f05405fe1ce4c78a8c75e27920c863c5b83686bd)
(cherry picked from commit 8ab040f612ee04dac4813a08cdeaddd446a64dc9)
2023-05-23 20:16:53 +03:00
Weblate
d0fbb1f49a Translated using Weblate (French)
Currently translated at 98.6% (507 of 514 strings)

Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-05-23 10:36:59 +03:00
Bogdan
aafdefe2f0 Fixed: (RuTracker) Improve the error message for failed logins 2023-05-22 14:12:41 +03:00
Bogdan
96234c0fe1 Fixed: (SceneTime) Update categories 2023-05-21 22:13:11 +03:00
Bogdan
8b5648d7bd Fix spelling "Use languages from Torznab/Newznab attributes if given"
(cherry picked from commit de3bfb7c5ab03e527dca1be3ef4a664dce266db6)
2023-05-21 21:15:20 +03:00
Qstick
1fc79f9e9b New: Use languages from Torznab/Newznab attributes if given
(cherry picked from commit 9c5a07f62a6e32832c10c80813cd3b98c5859989)
2023-05-21 21:13:48 +03:00
S0me6uy
ec40761757 New: Signal Notifications
(cherry picked from commit 59dd3b11271a63ea16f0e32a596dba8e9b9d1096)
2023-05-21 21:01:46 +03:00
Bogdan
0a8e4eb092 New: Improve validation messages
(cherry picked from commit a117001de673e80abd90d54a34a7c86292b3a649)
2023-05-21 20:59:31 +03:00
Bogdan
ade961fad5 Minor CS improvements in NzbDroneValidation
(cherry picked from commit 6118afa339621509aad55caf27b05e89bd0b8c74)
2023-05-21 20:58:25 +03:00
Bogdan
81b1c0e445 Add tests and ignore 0 in GetFullImdbId 2023-05-21 11:53:06 +03:00
Bogdan
0fe54ed36a Fix tests in IndexerServiceFixture 2023-05-21 11:52:54 +03:00
Bogdan
337828ff9c Bump version to 1.5.1 2023-05-21 10:15:52 +03:00
Mark McDowall
fb34294d2e Fixed: Exception when request to SABnzbd times out
(cherry picked from commit f946d78153b85ad726a06a1140143c8beac8766d)
2023-05-21 10:10:17 +03:00
Mark McDowall
931e3cf42d Cleanup TorrentDownloadStation
Fixed: Don't move seeding torrents in Synology Download Station

(cherry picked from commit 3cd33d3f44097b4cb4fb291bca70a0aa53c4b844)
2023-05-21 10:09:21 +03:00
66 changed files with 600 additions and 100 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.5.0'
majorVersion: '1.5.1'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View File

@@ -13,7 +13,7 @@ const messages = [
'Loading humorous message... Please Wait',
'I could\'ve been faster in Python',
'Don\'t forget to rewind your tracks',
'Congratulations! you are the 1000th visitor.',
'Congratulations! You are the 1000th visitor.',
'HELP! I\'m being held hostage and forced to write these stupid lines!',
'RE-calibrating the internet...',
'I\'ll be here all week',

View File

@@ -351,6 +351,26 @@ namespace NzbDrone.Common.Test.DiskTests
.Verify(v => v.DeleteFile(_targetPath), Times.Once());
}
[Test]
public void should_not_rollback_move_on_partial_if_destination_already_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Callback(() =>
{
WithExistingFile(_targetPath, true, 900);
});
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Throws(new FileAlreadyExistsException("File already exists", _targetPath));
Assert.Throws<FileAlreadyExistsException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFile(_targetPath), Times.Never());
}
[Test]
public void should_log_error_if_rollback_partialmove_fails()
{

View File

@@ -264,6 +264,11 @@ namespace NzbDrone.Common.Disk
protected virtual void MoveFileInternal(string source, string destination)
{
if (File.Exists(destination))
{
throw new FileAlreadyExistsException("File already exists", destination);
}
File.Move(source, destination);
}

View File

@@ -500,9 +500,13 @@ namespace NzbDrone.Common.Disk
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
catch (Exception ex)
{
RollbackPartialMove(sourcePath, targetPath);
if (ex is not FileAlreadyExistsException)
{
RollbackPartialMove(sourcePath, targetPath);
}
throw;
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Common.Disk
{
public class FileAlreadyExistsException : Exception
{
public string Filename { get; set; }
public FileAlreadyExistsException(string message, string filename)
: base(message)
{
Filename = filename;
}
}
}

View File

@@ -313,7 +313,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
processInfo.StartPath = GetExeFileName(process);
processInfo.StartPath = process.MainModule.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited)
{
@@ -328,16 +328,6 @@ namespace NzbDrone.Common.Processes
return processInfo;
}
private static string GetExeFileName(Process process)
{
if (process.MainModule.FileName != "mono.exe")
{
return process.MainModule.FileName;
}
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
}
private List<Process> GetProcessesByName(string name)
{
//TODO: move this to an OS specific class

View File

@@ -6,6 +6,7 @@ using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests
{
@@ -31,13 +32,15 @@ namespace NzbDrone.Core.Test.IndexerTests
Mocker.SetConstant<IIndexerRepository>(repo);
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
existingIndexers.ConfigContract = typeof(NewznabSettings).Name;
existingIndexers.ConfigContract = nameof(NewznabSettings);
repo.Insert(existingIndexers);
Subject.Handle(new ApplicationStartedEvent());
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

@@ -63,5 +63,22 @@ namespace NzbDrone.Core.Test.ParserTests
{
ParseUtil.GetLongFromString(original).Should().Be(parsedInt);
}
[TestCase("tt0183790", "tt0183790")]
[TestCase("0183790", "tt0183790")]
[TestCase("183790", "tt0183790")]
[TestCase("tt10001870", "tt10001870")]
[TestCase("10001870", "tt10001870")]
[TestCase("tt", null)]
[TestCase("tt0", null)]
[TestCase("abc", null)]
[TestCase("abc0", null)]
[TestCase("0", null)]
[TestCase("", null)]
[TestCase(null, null)]
public void should_parse_full_imdb_id_from_string(string input, string expected)
{
ParseUtil.GetFullImdbId(input).Should().Be(expected);
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Lidarr
{
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}"));
}
return new ValidationResult(failures);
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Lidarr
public override List<AppIndexerMap> GetIndexerMappings()
{
var indexers = _lidarrV1Proxy.GetIndexers(Settings)
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
.Where(i => i.Implementation is "Newznab" or "Torznab");
var mappings = new List<AppIndexerMap>();
@@ -174,7 +173,13 @@ namespace NzbDrone.Core.Applications.Lidarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
if (id == 0)
{
// Ensuring backward compatibility with older versions on first sync
syncFields.AddRange(new List<string> { "earlyReleaseLimit", "additionalParameters" });
}
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");

View File

@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Lidarr
public class LidarrV1Proxy : ILidarrV1Proxy
{
private static Version MinimumApplicationVersion => new (1, 0, 2, 0);
private const string AppApiRoute = "/api/v1";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Lidarr
try
{
Execute<LidarrIndexer>(request);
var applicationVersion = _httpClient.Post<LidarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version");
}
if (new Version(applicationVersion) < MinimumApplicationVersion)
{
return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
}
catch (HttpException ex)
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Radarr
{
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}"));
}
return new ValidationResult(failures);
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Radarr
public override List<AppIndexerMap> GetIndexerMappings()
{
var indexers = _radarrV3Proxy.GetIndexers(Settings)
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
.Where(i => i.Implementation is "Newznab" or "Torznab");
var mappings = new List<AppIndexerMap>();
@@ -174,7 +173,13 @@ namespace NzbDrone.Core.Applications.Radarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
if (id == 0)
{
// Ensuring backward compatibility with older versions on first sync
syncFields.AddRange(new List<string> { "multiLanguages", "removeYear", "requiredFlags", "additionalParameters" });
}
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");

View File

@@ -23,8 +23,12 @@ namespace NzbDrone.Core.Applications.Radarr
public class RadarrV3Proxy : IRadarrV3Proxy
{
private static Version MinimumApplicationV4Version => new (4, 0, 4, 0);
private static Version MinimumApplicationV3Version => new (3, 1, 1, 0);
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -102,7 +106,29 @@ namespace NzbDrone.Core.Applications.Radarr
try
{
Execute<RadarrIndexer>(request);
var applicationVersion = _httpClient.Post<RadarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
return new ValidationFailure(string.Empty, "Failed to fetch Radarr version");
}
var version = new Version(applicationVersion);
if (version.Major == 3)
{
if (version < MinimumApplicationV3Version)
{
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
}
else
{
if (version < MinimumApplicationV4Version)
{
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
}
}
catch (HttpException ex)
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Sonarr
{
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}"));
}
return new ValidationResult(failures);
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Sonarr
public override List<AppIndexerMap> GetIndexerMappings()
{
var indexers = _sonarrV3Proxy.GetIndexers(Settings)
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
.Where(i => i.Implementation is "Newznab" or "Torznab");
var mappings = new List<AppIndexerMap>();
@@ -176,7 +175,13 @@ namespace NzbDrone.Core.Applications.Sonarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
if (id == 0)
{
// Ensuring backward compatibility with older versions on first sync
syncFields.AddRange(new List<string> { "animeStandardFormatSearch", "additionalParameters" });
}
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");

View File

@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Sonarr
public class SonarrV3Proxy : ISonarrV3Proxy
{
private static Version MinimumApplicationVersion => new (3, 0, 5, 0);
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Sonarr
try
{
Execute<SonarrIndexer>(request);
var applicationVersion = _httpClient.Post<SonarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version");
}
if (new Version(applicationVersion) < MinimumApplicationVersion)
{
return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
}
catch (HttpException ex)
{

View File

@@ -109,11 +109,6 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
return torrent.Status == DownloadStationTaskStatus.Finished;
}
protected bool IsCompleted(DownloadStationTask torrent)
{
return torrent.Status == DownloadStationTaskStatus.Seeding || IsFinished(torrent) || (torrent.Status == DownloadStationTaskStatus.Waiting && torrent.Size != 0 && GetRemainingSize(torrent) <= 0);
}
protected string GetMessage(DownloadStationTask torrent)
{
if (torrent.StatusExtra != null)

View File

@@ -1,5 +1,6 @@
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Extensions;
@@ -208,6 +209,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
throw new DownloadClientException("Unable to connect to SABnzbd, {0}", ex, ex.Message);
}
catch (HttpRequestException ex)
{
throw new DownloadClientUnavailableException("Unable to connect to SABnzbd, {0}", ex, ex.Message);
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.TrustFailure)

View File

@@ -14,7 +14,20 @@ namespace NzbDrone.Core.IndexerProxies
public Type ConfigContract => typeof(TSettings);
public IEnumerable<ProviderDefinition> DefaultDefinitions => new List<ProviderDefinition>();
public IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
var config = (IProviderConfig)new TSettings();
yield return new IndexerProxyDefinition
{
Name = GetType().Name,
Implementation = GetType().Name,
Settings = config
};
}
}
public ProviderDefinition Definition { get; set; }
public abstract ValidationResult Test();

View File

@@ -57,7 +57,7 @@ namespace NzbDrone.Core.IndexerSearch
{
var searchSpec = Get<MovieSearchCriteria>(request, indexerIds, interactiveSearch);
var imdbId = ParseUtil.GetImdbID(request.imdbid);
var imdbId = ParseUtil.GetImdbId(request.imdbid);
searchSpec.ImdbId = imdbId?.ToString("D7");
searchSpec.TmdbId = request.tmdbid;
@@ -87,7 +87,7 @@ namespace NzbDrone.Core.IndexerSearch
{
var searchSpec = Get<TvSearchCriteria>(request, indexerIds, interactiveSearch);
var imdbId = ParseUtil.GetImdbID(request.imdbid);
var imdbId = ParseUtil.GetImdbId(request.imdbid);
searchSpec.ImdbId = imdbId?.ToString("D7");
searchSpec.Season = request.season;

View File

@@ -210,8 +210,8 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var searchUrl = _settings.BaseUrl + "ajax/torrents_data.php";
// replace non-alphanumeric characters with % (wildcard)
var searchString = Regex.Replace(term.Trim(), "[^a-zA-Z0-9]+", "%");
// replace non-word characters with % (wildcard)
var searchString = Regex.Replace(term.Trim(), @"[\W]+", "%");
var page = searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0 ? (int)(searchCriteria.Offset / searchCriteria.Limit) + 1 : 1;
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Indexers.Definitions
yield return new IndexerRequest(requestBuilder.Build());
}
private IEnumerable<string> GetTrackerCategories(string term, SearchCriteriaBase searchCriteria)
private List<string> GetTrackerCategories(string term, SearchCriteriaBase searchCriteria)
{
var searchTerm = term.Trim();

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "Animedia";
public override string[] IndexerUrls => new[] { "https://tt.animedia.tv/" };
public override string Description => "Animedia is russian anime voiceover group and eponymous anime tracker.";
public override string Description => "Animedia is RUSSIAN anime voiceover group and eponymous anime tracker.";
public override string Language => "ru-RU";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;

View File

@@ -246,7 +246,7 @@ namespace NzbDrone.Core.Indexers.Definitions
// TODO: TMDb is also available
var qImdb = row.QuerySelector("a[href^=\"https://www.imdb.com\"]");
var imdb = qImdb != null ? ParseUtil.GetImdbID(qImdb.GetAttribute("href").Split('/').Last()) : null;
var imdb = qImdb != null ? ParseUtil.GetImdbId(qImdb.GetAttribute("href").Split('/').Last()) : null;
var release = new TorrentInfo
{

View File

@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
if (row.MovieTvinfo != null)
{
release.ImdbId = ParseUtil.GetImdbID(row.MovieTvinfo.Imdb).GetValueOrDefault();
release.ImdbId = ParseUtil.GetImdbId(row.MovieTvinfo.Imdb).GetValueOrDefault();
release.TmdbId = row.MovieTvinfo.Tmdb.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.MovieTvinfo.Tmdb, out var tmdbResult) ? tmdbResult : 0;
release.TvdbId = row.MovieTvinfo.Tvdb.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.MovieTvinfo.Tvdb, out var tvdbResult) ? tvdbResult : 0;
}

View File

@@ -254,6 +254,14 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var row in rows)
{
var downloadVolumeFactor = row.QuerySelector("span.freeleech") != null ? 0 : 1;
// Skip non-freeleech results when freeleech only is set
if (_settings.FreeleechOnly && downloadVolumeFactor != 0)
{
continue;
}
var qTitleLink = row.QuerySelector("a.title, a.alt_title");
if (qTitleLink == null)
{
@@ -357,7 +365,7 @@ namespace NzbDrone.Core.Indexers.Definitions
release.PublishDate = DateTime.ParseExact(dateStr, "dd MMM yy", CultureInfo.InvariantCulture);
}
release.DownloadVolumeFactor = row.QuerySelector("span.freeleech") != null ? 0 : 1;
release.DownloadVolumeFactor = downloadVolumeFactor;
release.UploadVolumeFactor = 1;
releaseInfos.Add(release);
@@ -400,13 +408,21 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BakaBTSettings : UserPassTorrentBaseSettings
{
[FieldDefinition(4, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
public BakaBTSettings()
{
FreeleechOnly = false;
}
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(5, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
public bool AddRomajiTitle { get; set; }
[FieldDefinition(5, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
[FieldDefinition(6, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
public bool AppendSeason { get; set; }
[FieldDefinition(6, Label = "Adult Content", Type = FieldType.Checkbox, HelpText = "Allow Adult Content (Must be enabled in BakaBT settings)")]
[FieldDefinition(7, Label = "Adult Content", Type = FieldType.Checkbox, HelpText = "Allow Adult Content (Must be enabled in BakaBT settings)")]
public bool AdultContent { get; set; }
}
}

View File

@@ -209,7 +209,7 @@ namespace NzbDrone.Core.Indexers.Definitions
// BHD can return crazy values for tmdb
var tmdbId = row.TmdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.TmdbId.Split("/")[1], out var tmdbResult) ? tmdbResult : 0;
var imdbId = ParseUtil.GetImdbID(row.ImdbId).GetValueOrDefault();
var imdbId = ParseUtil.GetImdbId(row.ImdbId).GetValueOrDefault();
var release = new TorrentInfo
{

View File

@@ -192,7 +192,7 @@ public class GreatPosterWallParser : GazelleParser
MinimumSeedTime = 172800 // 48 hours
};
var imdbId = ParseUtil.GetImdbID(result.ImdbId);
var imdbId = ParseUtil.GetImdbId(result.ImdbId);
if (imdbId != null)
{
release.ImdbId = (int)imdbId;

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
{
var pageableRequests = new IndexerPageableRequestChain();
var query = new TorrentQuery();
var imdbId = ParseUtil.GetImdbID(searchCriteria.ImdbId).GetValueOrDefault(0);
var imdbId = ParseUtil.GetImdbId(searchCriteria.ImdbId).GetValueOrDefault(0);
if (searchCriteria.Categories?.Length > 0)
{
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
var pageableRequests = new IndexerPageableRequestChain();
var query = new TorrentQuery();
var tvdbId = searchCriteria.TvdbId.GetValueOrDefault(0);
var imdbId = ParseUtil.GetImdbID(searchCriteria.ImdbId).GetValueOrDefault(0);
var imdbId = ParseUtil.GetImdbId(searchCriteria.ImdbId).GetValueOrDefault(0);
if (searchCriteria.Categories?.Length > 0)
{

View File

@@ -282,7 +282,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var imdbLink = row.QuerySelector("td:nth-child(2) a[href*=imdb]");
if (imdbLink != null)
{
release.ImdbId = ParseUtil.GetImdbID(imdbLink.GetAttribute("href").Split('/').Last()).GetValueOrDefault();
release.ImdbId = ParseUtil.GetImdbId(imdbLink.GetAttribute("href").Split('/').Last()).GetValueOrDefault();
}
//"July 11, 2015, 13:34:09", "Today|Yesterday at 20:04:23"

View File

@@ -328,7 +328,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
var imdbLink = row.QuerySelector("a[href*=\"www.imdb.com/title/\"]")?.GetAttribute("href");
var imdb = !string.IsNullOrWhiteSpace(imdbLink) ? ParseUtil.GetImdbID(imdbLink) : null;
var imdb = !string.IsNullOrWhiteSpace(imdbLink) ? ParseUtil.GetImdbId(imdbLink) : null;
var flags = new HashSet<IndexerFlag>();

View File

@@ -126,6 +126,28 @@ namespace NzbDrone.Core.Indexers.Headphones
return results;
}
protected override List<string> GetLanguages(XElement item)
{
var languages = TryGetMultipleNewznabAttributes(item, "language");
var results = new List<string>();
// Try to find <language> elements for some indexers that suck at following the rules.
if (languages.Count == 0)
{
languages = item.Elements("language").Select(e => e.Value).ToList();
}
foreach (var language in languages)
{
if (language.IsNotNullOrWhiteSpace())
{
results.Add(language);
}
}
return results;
}
protected override long GetSize(XElement item)
{
long size;

View File

@@ -154,6 +154,28 @@ namespace NzbDrone.Core.Indexers.Newznab
return results;
}
protected override List<string> GetLanguages(XElement item)
{
var languages = TryGetMultipleNewznabAttributes(item, "language");
var results = new List<string>();
// Try to find <language> elements for some indexers that suck at following the rules.
if (languages.Count == 0)
{
languages = item.Elements("language").Select(e => e.Value).ToList();
}
foreach (var language in languages)
{
if (language.IsNotNullOrWhiteSpace())
{
results.Add(language);
}
}
return results;
}
protected override long GetSize(XElement item)
{
long size;

View File

@@ -313,7 +313,7 @@ public class NorBitsParser : IParseIndexerResponse
}
var imdbLink = row.QuerySelector("a[href*=\"imdb.com/title/tt\"]")?.GetAttribute("href");
release.ImdbId = ParseUtil.GetImdbID(imdbLink) ?? 0;
release.ImdbId = ParseUtil.GetImdbId(imdbLink) ?? 0;
if (row.QuerySelector("img[title=\"100% freeleech\"]") != null)
{

View File

@@ -180,7 +180,7 @@ public class PixelHDParser : IParseIndexerResponse
var groupName = group.QuerySelector("strong:has(a[title=\"View Torrent\"])")?.TextContent.Replace(" ]", "]");
var imdbLink = group.QuerySelector("a[href*=\"imdb.com/title/tt\"]")?.GetAttribute("href");
var imdbId = ParseUtil.GetImdbID(imdbLink) ?? 0;
var imdbId = ParseUtil.GetImdbId(imdbLink) ?? 0;
var rows = group.QuerySelectorAll("tr.group_torrent:has(a[href^=\"torrents.php?id=\"])");
foreach (var row in rows)

View File

@@ -286,7 +286,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var category = row.QuerySelector(".br_type > a").GetAttribute("href").Replace("browse.php?cat=", string.Empty);
var qImdb = row.QuerySelector("a[href*=\"www.imdb.com/\"]");
var imdb = qImdb != null ? ParseUtil.GetImdbID(qImdb.GetAttribute("href").Split('/').Last()) : null;
var imdb = qImdb != null ? ParseUtil.GetImdbId(qImdb.GetAttribute("href").Split('/').Last()) : null;
var release = new TorrentInfo
{

View File

@@ -104,7 +104,11 @@ namespace NzbDrone.Core.Indexers.Definitions
if (!response.Content.Contains("id=\"logged-in-username\""))
{
throw new IndexerAuthException("RuTracker Auth Failed");
var parser = new HtmlParser();
var document = await parser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector("h4.warnColor1.tCenter.mrg_16, div.msg-main")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "RuTracker Auth Failed");
}
cookies = response.GetCookies();

View File

@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Files = item.Value<int>("numfiles"),
Seeders = item.Value<int>("seeders"),
Peers = item.Value<int>("leechers") + item.Value<int>("seeders"),
ImdbId = ParseUtil.GetImdbID(item.Value<string>("imdbid")) ?? 0,
ImdbId = ParseUtil.GetImdbId(item.Value<string>("imdbid")) ?? 0,
MinimumRatio = 1,
MinimumSeedTime = 0,
DownloadVolumeFactor = dlVolumeFactor,

View File

@@ -87,25 +87,26 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(57, NewznabStandardCategory.MoviesSD, "Movies SD");
caps.Categories.AddCategoryMapping(59, NewznabStandardCategory.MoviesHD, "Movies HD");
caps.Categories.AddCategoryMapping(64, NewznabStandardCategory.Movies3D, "Movies 3D");
caps.Categories.AddCategoryMapping(82, NewznabStandardCategory.MoviesOther, "Movies CAM-TS");
caps.Categories.AddCategoryMapping(82, NewznabStandardCategory.MoviesOther, "Movies CAM/TS");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesUHD, "Movies UHD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD, "TV UHD");
caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.TV, "TV Packs");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TVHD, "TV HD");
caps.Categories.AddCategoryMapping(77, NewznabStandardCategory.TVSD, "TV SD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Games PC ISO");
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV ANIME");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Games PC-ISO");
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.ConsoleXBox, "Games XBOX");
caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.ConsoleWii, "Games Wii");
caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.ConsoleNDS, "Games Nintendo DS");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.ConsolePS4, "Games/PS");
caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.ConsoleNDS, "Games Nintendo");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.ConsolePS4, "Games PS");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.ConsoleOther, "Games Dreamcast");
caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.PCMac, "Mac/Linux");
caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.PC0day, "Apps");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PCMobileOther, "Mobile Apps");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Books, "Books and Magazines");
caps.Categories.AddCategoryMapping(65, NewznabStandardCategory.BooksComics, "Books Comic");
caps.Categories.AddCategoryMapping(65, NewznabStandardCategory.BooksComics, "Books Comics");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Audio, "Music");
caps.Categories.AddCategoryMapping(116, NewznabStandardCategory.Audio, "Music Pack");
caps.Categories.AddCategoryMapping(116, NewznabStandardCategory.Audio, "Music Packs");
caps.Flags = new List<IndexerFlag>
{

View File

@@ -262,7 +262,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Title = CleanTitle(torrent.Name),
Description = torrent.ShortDescription,
Size = torrent.Size,
ImdbId = ParseUtil.GetImdbID(torrent.ImdbId).GetValueOrDefault(),
ImdbId = ParseUtil.GetImdbId(torrent.ImdbId).GetValueOrDefault(),
DownloadUrl = $"{_settings.BaseUrl}api/torrent/{torrent.Id}/download",
PosterUrl = torrent.Poster,
InfoUrl = torrent.Url,

View File

@@ -244,7 +244,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var downloadMultiplier = (double?)row["download-multiplier"] ?? 1;
var link = new Uri(_settings.BaseUrl + "download.php/" + torrentId + "/" + torrentId + ".torrent");
var publishDate = DateTimeUtil.UnixTimestampToDateTime((long)row.ctime).ToLocalTime();
var imdb = ParseUtil.GetImdbID(imdbId) ?? 0;
var imdb = ParseUtil.GetImdbId(imdbId) ?? 0;
var release = new TorrentInfo
{

View File

@@ -125,6 +125,28 @@ namespace NzbDrone.Core.Indexers.Torznab
return ParseUrl(item.TryGetValue("comments"));
}
protected override List<string> GetLanguages(XElement item)
{
var languages = TryGetMultipleTorznabAttributes(item, "language");
var results = new List<string>();
// Try to find <language> elements for some indexers that suck at following the rules.
if (languages.Count == 0)
{
languages = item.Elements("language").Select(e => e.Value).ToList();
}
foreach (var language in languages)
{
if (language.IsNotNullOrWhiteSpace())
{
results.Add(language);
}
}
return results;
}
protected override long GetSize(XElement item)
{
long size;

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
Files = row.Attributes.Files,
Grabs = row.Attributes.Grabs,
Seeders = row.Attributes.Seeders,
ImdbId = ParseUtil.GetImdbID(row.Attributes.ImdbId).GetValueOrDefault(),
ImdbId = ParseUtil.GetImdbId(row.Attributes.ImdbId).GetValueOrDefault(),
TmdbId = row.Attributes.TmdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.Attributes.TmdbId),
TvdbId = row.Attributes.TvdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.Attributes.TvdbId),
Peers = row.Attributes.Leechers + row.Attributes.Seeders,

View File

@@ -418,7 +418,7 @@ namespace NzbDrone.Core.Indexers
if (releases.Count == 0)
{
_logger.Trace(response.Content);
_logger.Trace("No releases found. Response: {0}", response.Content);
}
return new IndexerQueryResult

View File

@@ -166,6 +166,7 @@ namespace NzbDrone.Core.Indexers
releaseInfo.InfoUrl = GetInfoUrl(item);
releaseInfo.CommentUrl = GetCommentUrl(item);
releaseInfo.Categories = GetCategory(item);
releaseInfo.Languages = GetLanguages(item);
try
{
@@ -237,6 +238,11 @@ namespace NzbDrone.Core.Indexers
return ParseUrl((string)item.Element("comments"));
}
protected virtual List<string> GetLanguages(XElement item)
{
return new List<string>();
}
protected virtual long GetSize(XElement item)
{
if (UseEnclosureLength)

View File

@@ -12,7 +12,7 @@
"Events": "Événements",
"Edit": "Éditer",
"DownloadClientStatusCheckAllClientMessage": "Aucun client de téléchargement n'est disponible en raison d'échecs",
"DownloadClients": "Clients téléchargement",
"DownloadClients": "Clients télécharg.",
"Dates": "Dates",
"Date": "Date",
"Delete": "Supprimer",

View File

@@ -10,5 +10,11 @@
"Apply": "Terapkan",
"Authentication": "Autentikasi",
"About": "Tentang",
"Add": "Tambah"
"Add": "Tambah",
"AppDataDirectory": "Direktori AppData",
"Automatic": "Otomatis",
"AutomaticSearch": "Penelusuran Otomatis",
"BackupNow": "Cadangkan Sekarang",
"Cancel": "Batal",
"ChangeHasNotBeenSavedYet": "Perubahan belum disimpan"
}

View File

@@ -0,0 +1,16 @@
using System;
namespace NzbDrone.Core.Notifications.Signal
{
public class SignalInvalidResponseException : Exception
{
public SignalInvalidResponseException()
{
}
public SignalInvalidResponseException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Notifications.Signal
{
public class Signal : NotificationBase<SignalSettings>
{
private readonly ISignalProxy _proxy;
public Signal(ISignalProxy proxy)
{
_proxy = proxy;
}
public override string Name => "Signal";
public override string Link => "https://signal.org/";
public override void OnGrab(GrabMessage grabMessage)
{
_proxy.SendNotification(RELEASE_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings);
}
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings);
}
public override void OnHealthRestored(HealthCheck.HealthCheck previousCheck)
{
_proxy.SendNotification(HEALTH_RESTORED_TITLE, $"The following issue is now resolved: {previousCheck.Message}", Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace NzbDrone.Core.Notifications.Signal
{
public class SignalError
{
public string Error { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace NzbDrone.Core.Notifications.Signal
{
public class SignalPayload
{
public string Message { get; set; }
public string Number { get; set; }
public string[] Recipients { get; set; }
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Net;
using System.Text;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Notifications.Signal
{
public interface ISignalProxy
{
void SendNotification(string title, string message, SignalSettings settings);
ValidationFailure Test(SignalSettings settings);
}
public class SignalProxy : ISignalProxy
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public SignalProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, SignalSettings settings)
{
var text = new StringBuilder();
text.AppendLine(title);
text.AppendLine(message);
var urlSignalAPI = HttpRequestBuilder.BuildBaseUrl(
settings.UseSsl,
settings.Host,
settings.Port,
"/v2/send");
var requestBuilder = new HttpRequestBuilder(urlSignalAPI).Post();
if (settings.AuthUsername.IsNotNullOrWhiteSpace() && settings.AuthPassword.IsNotNullOrWhiteSpace())
{
requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.AuthUsername, settings.AuthPassword);
}
var request = requestBuilder.Build();
request.Headers.ContentType = "application/json";
var payload = new SignalPayload
{
Message = text.ToString(),
Number = settings.SenderNumber,
Recipients = new[] { settings.ReceiverId }
};
request.SetContent(payload.ToJson());
_httpClient.Post(request);
}
public ValidationFailure Test(SignalSettings settings)
{
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
SendNotification(title, body, settings);
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message: {0}", ex.Message);
return new ValidationFailure("Host", $"Unable to send test message: {ex.Message}");
}
catch (HttpException ex)
{
_logger.Error(ex, "Unable to send test message: {0}", ex.Message);
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
if (ex.Response.Content.ContainsIgnoreCase("400 The plain HTTP request was sent to HTTPS port"))
{
return new ValidationFailure("UseSsl", "SSL seems to be required");
}
var error = Json.Deserialize<SignalError>(ex.Response.Content);
var property = "Host";
if (error.Error.ContainsIgnoreCase("Invalid group id"))
{
property = "ReceiverId";
}
else if (error.Error.ContainsIgnoreCase("Invalid account"))
{
property = "SenderNumber";
}
return new ValidationFailure(property, $"Unable to send test message: {error.Error}");
}
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
return new ValidationFailure("AuthUsername", "Login/Password invalid");
}
return new ValidationFailure("Host", $"Unable to send test message: {ex.Message}");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message: {0}", ex.Message);
return new ValidationFailure("Host", $"Unable to send test message: {ex.Message}");
}
return null;
}
}
}

View File

@@ -0,0 +1,49 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Signal
{
public class SignalSettingsValidator : AbstractValidator<SignalSettings>
{
public SignalSettingsValidator()
{
RuleFor(c => c.Host).NotEmpty();
RuleFor(c => c.Port).NotEmpty();
RuleFor(c => c.SenderNumber).NotEmpty();
RuleFor(c => c.ReceiverId).NotEmpty();
}
}
public class SignalSettings : IProviderConfig
{
private static readonly SignalSettingsValidator Validator = new ();
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "localhost")]
public string Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "8080")]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection.")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Sender Number", Privacy = PrivacyLevel.ApiKey, HelpText = "Phone number of the sender register in signal-api")]
public string SenderNumber { get; set; }
[FieldDefinition(4, Label = "Group ID / PhoneNumber", HelpText = "GroupID / PhoneNumber of the receiver")]
public string ReceiverId { get; set; }
[FieldDefinition(5, Label = "Login", Privacy = PrivacyLevel.UserName, HelpText = "Username used to authenticate requests toward signal-api")]
public string AuthUsername { get; set; }
[FieldDefinition(6, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "Password used to authenticate requests toward signal-api")]
public string AuthPassword { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Parser
{
public static class ParseUtil
{
private static readonly Regex ImdbId = new Regex(@"^(?:tt)?(\d{1,8})$", RegexOptions.Compiled);
private static readonly Regex ImdbIdRegex = new (@"^(?:tt)?(\d{1,8})$", RegexOptions.Compiled);
public static string NormalizeMultiSpaces(string s) =>
new Regex(@"\s+").Replace(s.Trim(), " ");
@@ -85,14 +85,15 @@ namespace NzbDrone.Core.Parser
return CoerceLong(extractedLong);
}
public static int? GetImdbID(string imdbstr)
public static int? GetImdbId(string value)
{
if (imdbstr == null)
if (value == null)
{
return null;
}
var match = ImdbId.Match(imdbstr);
var match = ImdbIdRegex.Match(value);
if (!match.Success)
{
return null;
@@ -101,17 +102,16 @@ namespace NzbDrone.Core.Parser
return int.Parse(match.Groups[1].Value, NumberStyles.Any, CultureInfo.InvariantCulture);
}
public static string GetFullImdbId(string imdbstr)
public static string GetFullImdbId(string value)
{
var imdbid = GetImdbID(imdbstr);
if (imdbid == null)
var imdbId = GetImdbId(value);
if (imdbId is null or 0)
{
return null;
}
var imdbLen = ((int)imdbid > 9999999) ? "D8" : "D7";
return "tt" + ((int)imdbid).ToString(imdbLen);
return $"tt{imdbId.GetValueOrDefault():D7}";
}
public static string GetArgumentFromQueryString(string url, string argument)

View File

@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Validation
{
public class FolderValidator : PropertyValidator
{
protected override string GetDefaultMessageTemplate() => "Invalid Path";
protected override string GetDefaultMessageTemplate() => "Invalid Path: '{path}'";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -15,6 +15,8 @@ namespace NzbDrone.Core.Validation
return false;
}
context.MessageFormatter.AppendArgument("path", context.PropertyValue.ToString());
return context.PropertyValue.ToString().IsPathValid(PathValidationType.CurrentOs);
}
}

View File

@@ -26,8 +26,7 @@ namespace NzbDrone.Core.Validation
{
foreach (var item in list)
{
var extended = item as NzbDroneValidationFailure;
if (extended != null && extended.IsWarning)
if (item is NzbDroneValidationFailure { IsWarning: true })
{
continue;
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Validation
CustomState = validationFailure.CustomState;
var state = validationFailure.CustomState as NzbDroneValidationState;
IsWarning = state != null && state.IsWarning;
IsWarning = state is { IsWarning: true };
}
}
}

View File

@@ -26,8 +26,7 @@ namespace NzbDrone.Core.Validation
foreach (var failureBase in failures)
{
var failure = failureBase as NzbDroneValidationFailure;
if (failure == null)
if (failureBase is not NzbDroneValidationFailure failure)
{
failure = new NzbDroneValidationFailure(failureBase);
}

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Validation.Paths
_diskProvider = diskProvider;
}
protected override string GetDefaultMessageTemplate() => "File does not exist";
protected override string GetDefaultMessageTemplate() => "File '{file}' does not exist";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -21,6 +21,8 @@ namespace NzbDrone.Core.Validation.Paths
return false;
}
context.MessageFormatter.AppendArgument("file", context.PropertyValue.ToString());
return _diskProvider.FileExists(context.PropertyValue.ToString());
}
}

View File

@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Validation.Paths
_diskProvider = diskProvider;
}
protected override string GetDefaultMessageTemplate() => $"Folder is not writable by user {Environment.UserName}";
protected override string GetDefaultMessageTemplate() => "Folder '{path}' is not writable by user '{user}'";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -22,6 +22,9 @@ namespace NzbDrone.Core.Validation.Paths
return false;
}
context.MessageFormatter.AppendArgument("path", context.PropertyValue.ToString());
context.MessageFormatter.AppendArgument("user", Environment.UserName);
return _diskProvider.FolderWritable(context.PropertyValue.ToString());
}
}

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Validation.Paths
_diskProvider = diskProvider;
}
protected override string GetDefaultMessageTemplate() => "Path does not exist";
protected override string GetDefaultMessageTemplate() => "Path '{path}' does not exist";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -21,6 +21,8 @@ namespace NzbDrone.Core.Validation.Paths
return false;
}
context.MessageFormatter.AppendArgument("path", context.PropertyValue.ToString());
return _diskProvider.FolderExists(context.PropertyValue.ToString());
}
}

View File

@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Validation.Paths
public class PathValidator : PropertyValidator
{
protected override string GetDefaultMessageTemplate() => "Invalid Path";
protected override string GetDefaultMessageTemplate() => "Invalid Path: '{path}'";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -24,6 +24,8 @@ namespace NzbDrone.Core.Validation.Paths
return false;
}
context.MessageFormatter.AppendArgument("path", context.PropertyValue.ToString());
return context.PropertyValue.ToString().IsPathValid(PathValidationType.CurrentOs);
}
}

View File

@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Validation.Paths
_appFolderInfo = appFolderInfo;
}
protected override string GetDefaultMessageTemplate() => "Path cannot be {relationship} the start up folder";
protected override string GetDefaultMessageTemplate() => "Path '{path}' cannot be {relationship} the start up folder";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -24,6 +24,7 @@ namespace NzbDrone.Core.Validation.Paths
var startupFolder = _appFolderInfo.StartUpFolder;
var folder = context.PropertyValue.ToString();
context.MessageFormatter.AppendArgument("path", folder);
if (startupFolder.PathEquals(folder))
{

View File

@@ -6,11 +6,12 @@ namespace NzbDrone.Core.Validation.Paths
{
public class SystemFolderValidator : PropertyValidator
{
protected override string GetDefaultMessageTemplate() => "Is {relationship} system folder {systemFolder}";
protected override string GetDefaultMessageTemplate() => "Path '{path}' is {relationship} system folder {systemFolder}";
protected override bool IsValid(PropertyValidatorContext context)
{
var folder = context.PropertyValue.ToString();
context.MessageFormatter.AppendArgument("path", folder);
foreach (var systemFolder in SystemFolders.GetSystemFolders())
{

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Validation
public class UrlValidator : PropertyValidator
{
protected override string GetDefaultMessageTemplate() => "Invalid Url";
protected override string GetDefaultMessageTemplate() => "Invalid Url: '{url}'";
protected override bool IsValid(PropertyValidatorContext context)
{
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Validation
return false;
}
context.MessageFormatter.AppendArgument("url", context.PropertyValue.ToString());
return context.PropertyValue.ToString().IsValidUrl();
}
}

View File

@@ -14,6 +14,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using Microsoft.Extensions.Logging;
using NLog;
using Npgsql;
using NzbDrone.Common.Composition.Extensions;
@@ -24,6 +25,7 @@ using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions;
namespace NzbDrone.Host
@@ -141,6 +143,10 @@ namespace NzbDrone.Host
return new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.ConfigureLogging(logging =>
{
logging.AddFilter("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", LogLevel.None);
})
.ConfigureContainer<IContainer>(c =>
{
c.AutoAddServices(Bootstrap.ASSEMBLIES)

View File

@@ -16,7 +16,7 @@ namespace NzbDrone.Integration.Test.ApiTests
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
indexers.Where(c => c.ConfigContract == typeof(NullConfig).Name).Should().OnlyContain(c => c.Enable);
indexers.Where(c => c.ConfigContract == nameof(NullConfig)).Should().OnlyContain(c => c.Enable);
}
}
}

View File

@@ -112,7 +112,7 @@ namespace Prowlarr.Api.V1.Commands
{
BroadcastResourceChange(ModelAction.Updated, pendingUpdate);
if (pendingUpdate.Name == typeof(MessagingCleanupCommand).Name.Replace("Command", "") &&
if (pendingUpdate.Name == nameof(MessagingCleanupCommand).Replace("Command", "") &&
pendingUpdate.Status == CommandStatus.Completed)
{
BroadcastResourceChange(ModelAction.Sync);