mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-16 21:35:04 -04:00
Compare commits
26 Commits
v0.4.0.183
...
v0.4.2.187
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cec304a0be | ||
|
|
06f3c8e151 | ||
|
|
91eb65bd6c | ||
|
|
832080cb36 | ||
|
|
f9c731627f | ||
|
|
83344fb6f4 | ||
|
|
59b7435820 | ||
|
|
d2c1ffa761 | ||
|
|
5436d4f800 | ||
|
|
86fe19a5dd | ||
|
|
473405ceeb | ||
|
|
cac2729230 | ||
|
|
9b1f9abfac | ||
|
|
76285a8ccd | ||
|
|
a6b499e4a5 | ||
|
|
5ee95e3cc2 | ||
|
|
654d2dbad3 | ||
|
|
67ae7e32df | ||
|
|
d52516b700 | ||
|
|
326a7b5e16 | ||
|
|
313a0b459a | ||
|
|
2ffe88bf6a | ||
|
|
a311d13805 | ||
|
|
0e2d15cb73 | ||
|
|
d1949d24e0 | ||
|
|
cdb1f163f8 |
@@ -9,13 +9,13 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.4.0'
|
||||
majorVersion: '0.4.2'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.201'
|
||||
dotnetVersion: '6.0.301'
|
||||
innoVersion: '6.2.0'
|
||||
nodeVersion: '16.x'
|
||||
windowsImage: 'windows-2022'
|
||||
@@ -506,6 +506,58 @@ stages:
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: '$(testName) Unit Tests'
|
||||
failTaskOnFailedTests: true
|
||||
|
||||
- job: Unit_LinuxCore_Postgres
|
||||
displayName: Unit Native LinuxCore with Postgres Database
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||
artifactName: LinuxCoreTests
|
||||
Prowlarr__Postgres__Host: 'localhost'
|
||||
Prowlarr__Postgres__Port: '5432'
|
||||
Prowlarr__Postgres__User: 'prowlarr'
|
||||
Prowlarr__Postgres__Password: 'prowlarr'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
|
||||
timeoutInMinutes: 10
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: $(artifactName)
|
||||
targetPath: $(testsFolder)
|
||||
- bash: find ${TESTSFOLDER} -name "Prowlarr.Test.Dummy" -exec chmod a+x {} \;
|
||||
displayName: Make Test Dummy Executable
|
||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||
- bash: |
|
||||
docker run -d --name=postgres14 \
|
||||
-e POSTGRES_PASSWORD=prowlarr \
|
||||
-e POSTGRES_USER=prowlarr \
|
||||
-p 5432:5432/tcp \
|
||||
postgres:14
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
ls -lR ${TESTSFOLDER}
|
||||
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||
displayName: Run Tests
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||
failTaskOnFailedTests: true
|
||||
|
||||
- stage: Integration
|
||||
displayName: Integration
|
||||
@@ -590,6 +642,67 @@ stages:
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_LinuxCore_Postgres
|
||||
displayName: Integration Native LinuxCore with Postgres Database
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||
Prowlarr__Postgres__Host: 'localhost'
|
||||
Prowlarr__Postgres__Port: '5432'
|
||||
Prowlarr__Postgres__User: 'prowlarr'
|
||||
Prowlarr__Postgres__Password: 'prowlarr'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'LinuxCoreTests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '**/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
mkdir -p ./bin/
|
||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Prowlarr/. ./bin/
|
||||
displayName: Move Package Contents
|
||||
- bash: |
|
||||
docker run -d --name=postgres14 \
|
||||
-e POSTGRES_PASSWORD=prowlarr \
|
||||
-e POSTGRES_USER=prowlarr \
|
||||
-p 5432:5432/tcp \
|
||||
postgres:14
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||
displayName: Run Integration Tests
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_FreeBSD
|
||||
displayName: Integration Native FreeBSD
|
||||
dependsOn: Prepare
|
||||
|
||||
@@ -24,6 +24,7 @@ const searchOptions = [
|
||||
const seriesTokens = [
|
||||
{ token: '{ImdbId:tt1234567}', example: 'tt12345' },
|
||||
{ token: '{TvdbId:12345}', example: '12345' },
|
||||
{ token: '{TmdbId:12345}', example: '12345' },
|
||||
{ token: '{TvMazeId:12345}', example: '54321' },
|
||||
{ token: '{Season:00}', example: '01' },
|
||||
{ token: '{Episode:00}', example: '01' }
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"@microsoft/signalr": "6.0.3",
|
||||
"@microsoft/signalr": "6.0.6",
|
||||
"@sentry/browser": "6.19.2",
|
||||
"@sentry/integrations": "6.19.2",
|
||||
"chart.js": "3.7.1",
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test
|
||||
|
||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||
_runner.KillAll();
|
||||
_runner.Start();
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
||||
where TSubject : class, IDiskProvider
|
||||
{
|
||||
[Test]
|
||||
public void writealltext_should_truncate_existing()
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
Subject.WriteAllText(file, "A pretty long string");
|
||||
Subject.WriteAllText(file, "A short string");
|
||||
Subject.ReadAllText(file).Should().Be("A short string");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Retry(5)]
|
||||
public void directory_exist_should_be_able_to_find_existing_folder()
|
||||
|
||||
@@ -402,6 +402,40 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_detect_caseinsensitive_parents()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var root = new DirectoryInfo(GetTempFilePath());
|
||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||
var destination = new DirectoryInfo(root.FullName + "a/series");
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_detect_caseinsensitive_folder()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var root = new DirectoryInfo(GetTempFilePath());
|
||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_ignore_nfs_temp_file()
|
||||
{
|
||||
@@ -451,6 +485,42 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_detect_caseinsensitive_parents()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var root = new DirectoryInfo(GetTempFilePath());
|
||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||
var destination = new DirectoryInfo(root.FullName + "a/series");
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_rename_caseinsensitive_folder()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var root = new DirectoryInfo(GetTempFilePath());
|
||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
||||
|
||||
source.FullName.GetActualCasing().Should().Be(destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_destination_is_readonly()
|
||||
{
|
||||
@@ -553,6 +623,23 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MirrorFolder_should_handle_trailing_slash()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var source = new DirectoryInfo(GetTempFilePath());
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
|
||||
var count = Subject.MirrorFolder(source.FullName + Path.DirectorySeparatorChar, destination.FullName);
|
||||
|
||||
count.Should().Equals(3);
|
||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
||||
{
|
||||
@@ -752,6 +839,10 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
||||
.Callback<string>(v => Directory.CreateDirectory(v));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFolder(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
|
||||
.Callback<string, string, bool>((v, r, b) => Directory.Move(v, r));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
||||
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
|
||||
|
||||
@@ -28,14 +28,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
|
||||
}
|
||||
|
||||
[Ignore("Docker")]
|
||||
[Test]
|
||||
public void should_be_able_to_check_space_on_ramdrive()
|
||||
{
|
||||
PosixOnly();
|
||||
Subject.GetAvailableSpace("/run/").Should().NotBe(0);
|
||||
}
|
||||
|
||||
[Ignore("Docker")]
|
||||
[Test]
|
||||
public void should_return_free_disk_space()
|
||||
@@ -44,35 +36,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
result.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_able_to_get_space_on_unc()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
var result = Subject.GetAvailableSpace(@"\\localhost\c$\Windows");
|
||||
result.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_drive_doesnt_exist()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
// Find a drive that doesn't exist.
|
||||
for (char driveletter = 'Z'; driveletter > 'D'; driveletter--)
|
||||
{
|
||||
if (new DriveInfo(driveletter.ToString()).IsReady)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.Throws<DirectoryNotFoundException>(() => Subject.GetAvailableSpace(driveletter + @":\NOT_A_REAL_PATH\DOES_NOT_EXIST".AsOsAgnostic()));
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Inconclusive("No drive available for testing.");
|
||||
}
|
||||
|
||||
[Ignore("Docker")]
|
||||
[Test]
|
||||
public void should_be_able_to_get_space_on_folder_that_doesnt_exist()
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Indexer and Download Client Responses
|
||||
|
||||
// avistaz response
|
||||
[TestCase(@"""download"":""https:\/\/avistaz.to\/rss\/download\/2b51db35e1910123321025a12b9933d2\/tb51db35e1910123321025a12b9933d2.torrent"",")]
|
||||
[TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")]
|
||||
[TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")]
|
||||
|
||||
// animebytes response
|
||||
[TestCase(@"""Link"":""https:\/\/animebytes.tv\/torrent\/994064\/download\/tb51db35e1910123321025a12b9933d2"",")]
|
||||
[TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")]
|
||||
|
||||
// danish bytes response
|
||||
[TestCase(@",""rsskey"":""2b51db35e1910123321025a12b9933d2"",")]
|
||||
@@ -86,7 +86,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// BroadcastheNet
|
||||
[TestCase(@"method: ""getTorrents"", ""params"": [ ""mySecret"",")]
|
||||
[TestCase(@"getTorrents(""mySecret"", [asdfasdf], 100, 0)")]
|
||||
[TestCase(@"""DownloadURL"":""https:\/\/broadcasthe.net\/torrents.php?action=download&id=123&authkey=mySecret&torrent_pass=mySecret""")]
|
||||
[TestCase(@"""DownloadURL"":""https://broadcasthe.net/torrents.php?action=download&id=123&authkey=mySecret&torrent_pass=mySecret""")]
|
||||
|
||||
// Webhooks - Notifiarr
|
||||
[TestCase(@"https://xxx.yyy/api/v1/notification/prowlarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace NzbDrone.Common.Test
|
||||
var processStarted = new ManualResetEventSlim();
|
||||
|
||||
string suffix;
|
||||
if (OsInfo.IsWindows || PlatformInfo.IsMono)
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
suffix = ".exe";
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ using DryIoc.Microsoft.DependencyInjection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Composition.Extensions;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -29,7 +31,8 @@ namespace NzbDrone.Common.Test
|
||||
.AddDummyDatabase()
|
||||
.AddStartupContext(new StartupContext("first", "second"));
|
||||
|
||||
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
||||
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
||||
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
||||
|
||||
var serviceProvider = container.GetServiceProvider();
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace NzbDrone.Common.Disk
|
||||
public abstract long? GetAvailableSpace(string path);
|
||||
public abstract void InheritFolderPermissions(string filename);
|
||||
public abstract void SetEveryonePermissions(string filename);
|
||||
public abstract void SetPermissions(string path, string mask);
|
||||
public abstract void SetFilePermissions(string path, string mask, string group);
|
||||
public abstract void SetPermissions(string path, string mask, string group);
|
||||
public abstract void CopyPermissions(string sourcePath, string targetPath);
|
||||
public abstract long? GetTotalSize(string path);
|
||||
|
||||
@@ -130,7 +131,7 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
var testPath = Path.Combine(path, "prowlarr_write_test.txt");
|
||||
var testContent = string.Format("This file was created to verify if '{0}' is writable. It should've been automatically deleted. Feel free to delete it.", path);
|
||||
File.WriteAllText(testPath, testContent);
|
||||
WriteAllText(testPath, testContent);
|
||||
File.Delete(testPath);
|
||||
return true;
|
||||
}
|
||||
@@ -258,17 +259,6 @@ namespace NzbDrone.Common.Disk
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
|
||||
if (source.PathEquals(destination))
|
||||
{
|
||||
throw new IOException(string.Format("Source and destination can't be the same {0}", source));
|
||||
}
|
||||
|
||||
if (FolderExists(destination) && overwrite)
|
||||
{
|
||||
DeleteFolder(destination, true);
|
||||
}
|
||||
|
||||
RemoveReadOnlyFolder(source);
|
||||
Directory.Move(source, destination);
|
||||
}
|
||||
|
||||
@@ -310,7 +300,16 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
Ensure.That(filename, () => filename).IsValidPath();
|
||||
RemoveReadOnly(filename);
|
||||
File.WriteAllText(filename, contents);
|
||||
|
||||
// File.WriteAllText is broken on net core when writing to some CIFS mounts
|
||||
// This workaround from https://github.com/dotnet/runtime/issues/42790#issuecomment-700362617
|
||||
using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
using (var writer = new StreamWriter(fs))
|
||||
{
|
||||
writer.Write(contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void FolderSetLastWriteTime(string path, DateTime dateTime)
|
||||
@@ -550,7 +549,7 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsValidFilePermissionMask(string mask)
|
||||
public virtual bool IsValidFolderPermissionMask(string mask)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
@@ -27,11 +26,56 @@ namespace NzbDrone.Common.Disk
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private string ResolveRealParentPath(string path)
|
||||
{
|
||||
var parentPath = path.GetParentPath();
|
||||
if (!_diskProvider.FolderExists(parentPath))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
var realParentPath = parentPath.GetActualCasing();
|
||||
|
||||
var partialChildPath = path.Substring(parentPath.Length);
|
||||
|
||||
return realParentPath + partialChildPath;
|
||||
}
|
||||
|
||||
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
|
||||
{
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
sourcePath = ResolveRealParentPath(sourcePath);
|
||||
targetPath = ResolveRealParentPath(targetPath);
|
||||
|
||||
_logger.Debug("{0} Directory [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||
|
||||
if (sourcePath == targetPath)
|
||||
{
|
||||
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
||||
}
|
||||
|
||||
if (mode == TransferMode.Move && sourcePath.PathEquals(targetPath, StringComparison.InvariantCultureIgnoreCase) && _diskProvider.FolderExists(targetPath))
|
||||
{
|
||||
// Move folder out of the way to allow case-insensitive renames
|
||||
var tempPath = sourcePath + ".backup~";
|
||||
_logger.Trace("Rename Intermediate Directory [{0}] > [{1}]", sourcePath, tempPath);
|
||||
_diskProvider.MoveFolder(sourcePath, tempPath);
|
||||
|
||||
if (!_diskProvider.FolderExists(targetPath))
|
||||
{
|
||||
_logger.Trace("Rename Intermediate Directory [{0}] > [{1}]", tempPath, targetPath);
|
||||
_logger.Debug("Rename Directory [{0}] > [{1}]", sourcePath, targetPath);
|
||||
_diskProvider.MoveFolder(tempPath, targetPath);
|
||||
return mode;
|
||||
}
|
||||
|
||||
// There were two separate folders, revert the intermediate rename and let the recursion deal with it
|
||||
_logger.Trace("Rename Intermediate Directory [{0}] > [{1}]", tempPath, sourcePath);
|
||||
_diskProvider.MoveFolder(tempPath, sourcePath);
|
||||
}
|
||||
|
||||
if (mode == TransferMode.Move && !_diskProvider.FolderExists(targetPath))
|
||||
{
|
||||
var sourceMount = _diskProvider.GetMount(sourcePath);
|
||||
@@ -40,7 +84,7 @@ namespace NzbDrone.Common.Disk
|
||||
// If we're on the same mount, do a simple folder move.
|
||||
if (sourceMount != null && targetMount != null && sourceMount.RootDirectory == targetMount.RootDirectory)
|
||||
{
|
||||
_logger.Debug("Move Directory [{0}] > [{1}]", sourcePath, targetPath);
|
||||
_logger.Debug("Rename Directory [{0}] > [{1}]", sourcePath, targetPath);
|
||||
_diskProvider.MoveFolder(sourcePath, targetPath);
|
||||
return mode;
|
||||
}
|
||||
@@ -79,6 +123,13 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
var totalSize = _diskProvider.GetFileInfos(sourcePath).Sum(v => v.Length);
|
||||
|
||||
if (totalSize > (100 * 1024L * 1024L))
|
||||
{
|
||||
throw new IOException($"Large files still exist in {sourcePath} after folder move, not deleting source folder");
|
||||
}
|
||||
|
||||
_diskProvider.DeleteFolder(sourcePath, true);
|
||||
}
|
||||
|
||||
@@ -92,7 +143,10 @@ namespace NzbDrone.Common.Disk
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
_logger.Debug("Mirror [{0}] > [{1}]", sourcePath, targetPath);
|
||||
sourcePath = ResolveRealParentPath(sourcePath);
|
||||
targetPath = ResolveRealParentPath(targetPath);
|
||||
|
||||
_logger.Debug("Mirror Folder [{0}] > [{1}]", sourcePath, targetPath);
|
||||
|
||||
if (!_diskProvider.FolderExists(targetPath))
|
||||
{
|
||||
@@ -204,6 +258,9 @@ namespace NzbDrone.Common.Disk
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
sourcePath = ResolveRealParentPath(sourcePath);
|
||||
targetPath = ResolveRealParentPath(targetPath);
|
||||
|
||||
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace NzbDrone.Common.Disk
|
||||
long? GetAvailableSpace(string path);
|
||||
void InheritFolderPermissions(string filename);
|
||||
void SetEveryonePermissions(string filename);
|
||||
void SetPermissions(string path, string mask);
|
||||
void SetFilePermissions(string path, string mask, string group);
|
||||
void SetPermissions(string path, string mask, string group);
|
||||
void CopyPermissions(string sourcePath, string targetPath);
|
||||
long? GetTotalSize(string path);
|
||||
DateTime FolderGetCreationTime(string path);
|
||||
@@ -56,6 +57,6 @@ namespace NzbDrone.Common.Disk
|
||||
List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
|
||||
void RemoveEmptySubfolders(string path);
|
||||
void SaveStream(Stream stream, string path);
|
||||
bool IsValidFilePermissionMask(string mask);
|
||||
bool IsValidFolderPermissionMask(string mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,79 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public static class LongPathSupport
|
||||
{
|
||||
private static int MAX_PATH;
|
||||
private static int MAX_NAME;
|
||||
|
||||
public static void Enable()
|
||||
{
|
||||
// Mono has an issue with enabling long path support via app.config.
|
||||
// This works for both mono and .net on Windows.
|
||||
AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false);
|
||||
AppContext.SetSwitch("Switch.System.IO.BlockLongPaths", false);
|
||||
|
||||
DetectLongPathLimits();
|
||||
}
|
||||
|
||||
private static void DetectLongPathLimits()
|
||||
{
|
||||
if (!int.TryParse(Environment.GetEnvironmentVariable("MAX_PATH"), out MAX_PATH))
|
||||
{
|
||||
if (OsInfo.IsLinux)
|
||||
{
|
||||
MAX_PATH = 4096;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Windows paths can be up to 32,767 characters long, but each component of the path must be less than 255.
|
||||
// If the OS does not have Long Path enabled, then the following will throw an exception
|
||||
// ref: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
|
||||
Path.GetDirectoryName($@"C:\{new string('a', 254)}\{new string('a', 254)}");
|
||||
MAX_PATH = 4096;
|
||||
}
|
||||
catch
|
||||
{
|
||||
MAX_PATH = 260 - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!int.TryParse(Environment.GetEnvironmentVariable("MAX_NAME"), out MAX_NAME))
|
||||
{
|
||||
MAX_NAME = 255;
|
||||
}
|
||||
}
|
||||
|
||||
public static int MaxFilePathLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MAX_PATH == 0)
|
||||
{
|
||||
DetectLongPathLimits();
|
||||
}
|
||||
|
||||
return MAX_PATH;
|
||||
}
|
||||
}
|
||||
|
||||
public static int MaxFileNameLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MAX_NAME == 0)
|
||||
{
|
||||
DetectLongPathLimits();
|
||||
}
|
||||
|
||||
return MAX_NAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Indexer Responses
|
||||
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}\\\/rss\\\/download\\\/(?<secret>[^&=]+?)\\\/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?:animebytes)\.[a-z]{2,3}\\\/torrent\\\/[0-9]+\\\/download\\\/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
|
||||
@@ -8,47 +10,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
|
||||
{
|
||||
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
|
||||
|
||||
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return logBuilder.Property("Sentry", fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
|
||||
}
|
||||
|
||||
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
|
||||
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
|
||||
{
|
||||
SentryLogger.Log(level)
|
||||
.CopyLogEvent(logBuilder.LogEventInfo)
|
||||
SentryLogger.ForLogEvent(level)
|
||||
.CopyLogEvent(logBuilder.LogEvent)
|
||||
.SentryFingerprint(fingerprint)
|
||||
.Write();
|
||||
.Log();
|
||||
|
||||
return logBuilder.Property("Sentry", null);
|
||||
return logBuilder.Property<string>("Sentry", null);
|
||||
}
|
||||
|
||||
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
|
||||
private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
|
||||
{
|
||||
return logBuilder.LoggerName(logEvent.LoggerName)
|
||||
.TimeStamp(logEvent.TimeStamp)
|
||||
return logBuilder.TimeStamp(logEvent.TimeStamp)
|
||||
.Message(logEvent.Message, logEvent.Parameters)
|
||||
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
|
||||
.Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
|
||||
.Exception(logEvent.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Targets;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public class NzbDroneFileTarget : FileTarget
|
||||
{
|
||||
protected override string GetFormattedMessage(LogEventInfo logEvent)
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
return CleanseLogMessage.Cleanse(Layout.Render(logEvent));
|
||||
var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
var appFolderInfo = new AppFolderInfo(startupContext);
|
||||
|
||||
RegisterGlobalFilters();
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
RegisterDebugger();
|
||||
@@ -97,10 +99,21 @@ namespace NzbDrone.Common.Instrumentation
|
||||
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
var loggingRule = new LoggingRule("*", LogLevel.Trace, target);
|
||||
|
||||
LogManager.Configuration.AddTarget("debugger", target);
|
||||
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
||||
}
|
||||
|
||||
private static void RegisterGlobalFilters()
|
||||
{
|
||||
LogManager.Setup().LoadConfiguration(c =>
|
||||
{
|
||||
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
||||
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterConsole()
|
||||
{
|
||||
var level = LogLevel.Trace;
|
||||
|
||||
@@ -127,7 +127,18 @@ namespace NzbDrone.Common.Processes
|
||||
try
|
||||
{
|
||||
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
||||
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
||||
|
||||
var key = environmentVariable.Key.ToString();
|
||||
var value = environmentVariable.Value?.ToString();
|
||||
|
||||
if (startInfo.EnvironmentVariables.ContainsKey(key))
|
||||
{
|
||||
startInfo.EnvironmentVariables[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo.EnvironmentVariables.Add(key, value);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="Sentry" Version="3.15.0" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.18.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
public void SingleOrDefault_should_return_null_on_empty_db()
|
||||
{
|
||||
Mocker.Resolve<IDatabase>()
|
||||
.OpenConnection().Query<IndexerDefinition>("SELECT * FROM Indexers")
|
||||
.OpenConnection().Query<IndexerDefinition>("SELECT * FROM \"Indexers\"")
|
||||
.SingleOrDefault()
|
||||
.Should()
|
||||
.BeNull();
|
||||
|
||||
@@ -5,10 +5,14 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using NzbDrone.Test.Common.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Test.Framework
|
||||
{
|
||||
@@ -49,6 +53,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||
public abstract class DbTest : CoreTest
|
||||
{
|
||||
private ITestDatabase _db;
|
||||
private DatabaseType _databaseType;
|
||||
|
||||
protected virtual MigrationType MigrationType => MigrationType.Main;
|
||||
|
||||
@@ -101,17 +106,39 @@ namespace NzbDrone.Core.Test.Framework
|
||||
|
||||
private IDatabase CreateDatabase(MigrationContext migrationContext)
|
||||
{
|
||||
if (_databaseType == DatabaseType.PostgreSQL)
|
||||
{
|
||||
CreatePostgresDb();
|
||||
}
|
||||
|
||||
var factory = Mocker.Resolve<DbFactory>();
|
||||
|
||||
// If a special migration test or log migration then create new
|
||||
if (migrationContext.BeforeMigration != null)
|
||||
if (migrationContext.BeforeMigration != null || _databaseType == DatabaseType.PostgreSQL)
|
||||
{
|
||||
return factory.Create(migrationContext);
|
||||
}
|
||||
|
||||
return CreateSqliteDatabase(factory, migrationContext);
|
||||
}
|
||||
|
||||
private void CreatePostgresDb()
|
||||
{
|
||||
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
||||
PostgresDatabase.Create(options, MigrationType);
|
||||
}
|
||||
|
||||
private void DropPostgresDb()
|
||||
{
|
||||
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
||||
PostgresDatabase.Drop(options, MigrationType);
|
||||
}
|
||||
|
||||
private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext)
|
||||
{
|
||||
// Otherwise try to use a cached migrated db
|
||||
var cachedDb = GetCachedDatabase(migrationContext.MigrationType);
|
||||
var testDb = GetTestDb(migrationContext.MigrationType);
|
||||
var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType);
|
||||
var testDb = GetTestSqliteDb(migrationContext.MigrationType);
|
||||
if (File.Exists(cachedDb))
|
||||
{
|
||||
TestLogger.Info($"Using cached initial database {cachedDb}");
|
||||
@@ -131,12 +158,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCachedDatabase(MigrationType type)
|
||||
{
|
||||
return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db");
|
||||
}
|
||||
|
||||
private string GetTestDb(MigrationType type)
|
||||
private string GetTestSqliteDb(MigrationType type)
|
||||
{
|
||||
return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase();
|
||||
}
|
||||
@@ -151,6 +173,13 @@ namespace NzbDrone.Core.Test.Framework
|
||||
WithTempAsAppPath();
|
||||
SetupLogging();
|
||||
|
||||
// populate the possible postgres options
|
||||
var postgresOptions = PostgresDatabase.GetTestOptions();
|
||||
_databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
|
||||
|
||||
// Set up remaining container services
|
||||
Mocker.SetConstant(Options.Create(postgresOptions));
|
||||
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
|
||||
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
||||
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
||||
|
||||
@@ -171,11 +200,17 @@ namespace NzbDrone.Core.Test.Framework
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
SQLiteConnection.ClearAllPools();
|
||||
NpgsqlConnection.ClearAllPools();
|
||||
|
||||
if (TestFolderInfo != null)
|
||||
{
|
||||
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
||||
}
|
||||
|
||||
if (_databaseType == DatabaseType.PostgreSQL)
|
||||
{
|
||||
DropPostgresDb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using NzbDrone.Test.Common.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Test
|
||||
{
|
||||
@@ -10,13 +12,13 @@ namespace NzbDrone.Core.Test
|
||||
[OneTimeTearDown]
|
||||
public void ClearCachedDatabase()
|
||||
{
|
||||
var mainCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Main.db");
|
||||
var mainCache = SqliteDatabase.GetCachedDb(MigrationType.Main);
|
||||
if (File.Exists(mainCache))
|
||||
{
|
||||
File.Delete(mainCache);
|
||||
}
|
||||
|
||||
var logCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Log.db");
|
||||
var logCache = SqliteDatabase.GetCachedDb(MigrationType.Log);
|
||||
if (File.Exists(logCache))
|
||||
{
|
||||
File.Delete(logCache);
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||
where T : ModelBase, new();
|
||||
IDirectDataMapper GetDirectDataMapper();
|
||||
IDbConnection OpenConnection();
|
||||
DatabaseType DatabaseType { get; }
|
||||
}
|
||||
|
||||
public class TestDatabase : ITestDatabase
|
||||
@@ -30,6 +31,8 @@ namespace NzbDrone.Core.Test.Framework
|
||||
private readonly IDatabase _dbConnection;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public DatabaseType DatabaseType => _dbConnection.DatabaseType;
|
||||
|
||||
public TestDatabase(IDatabase dbConnection)
|
||||
{
|
||||
_eventAggregator = new Mock<IEventAggregator>().Object;
|
||||
|
||||
@@ -41,7 +41,8 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
|
||||
Subject.Clean();
|
||||
|
||||
AllStoredModels.ToList().ForEach(t => t.LastExecution.Should().Be(expectedTime));
|
||||
// BeCloseTo handles Postgres rounding times
|
||||
AllStoredModels.ToList().ForEach(t => t.LastExecution.Should().BeCloseTo(expectedTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers.Cardigann;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.CardigannTests
|
||||
{
|
||||
public class ApplyGoTemplateTextFixture : CoreTest<CardigannBase>
|
||||
{
|
||||
private Dictionary<string, object> _variables;
|
||||
private CardigannDefinition _definition;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_variables = new Dictionary<string, object>
|
||||
{
|
||||
[".Config.sitelink"] = "https://somesite.com/",
|
||||
[".True"] = "True",
|
||||
[".False"] = null,
|
||||
[".Today.Year"] = DateTime.Today.Year.ToString(),
|
||||
[".Categories"] = new string[] { "tv", "movies" }
|
||||
};
|
||||
|
||||
_definition = Builder<CardigannDefinition>.CreateNew()
|
||||
.With(x => x.Encoding = "UTF-8")
|
||||
.With(x => x.Links = new List<string>
|
||||
{
|
||||
"https://somesite.com/"
|
||||
})
|
||||
.With(x => x.Caps = new CapabilitiesBlock
|
||||
{
|
||||
Modes = new Dictionary<string, List<string>>
|
||||
{
|
||||
{ "search", new List<string> { "q" } }
|
||||
}
|
||||
})
|
||||
.Build();
|
||||
|
||||
Mocker.SetConstant<CardigannDefinition>(_definition);
|
||||
}
|
||||
|
||||
[TestCase("{{ range .Categories}}&categories[]={{.}}{{end}}", "&categories[]=tv&categories[]=movies")]
|
||||
[TestCase("{{ range $i, $e := .Categories}}&categories[{{$i}}]={{.}}{{end}}", "&categories[0]=tv&categories[1]=movies")]
|
||||
[TestCase("{{ range $index, $element := .Categories}}&categories[{{$index}}]={{.}}+postIndex[{{$index}}]{{end}}", "&categories[0]=tv+postIndex[0]&categories[1]=movies+postIndex[1]")]
|
||||
public void should_handle_range_statements(string template, string expected)
|
||||
{
|
||||
var result = Subject.ApplyGoTemplateText(template, _variables);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("{{ re_replace .Query.Keywords \"[^a-zA-Z0-9]+\" \"%\" }}", "abc%def")]
|
||||
public void should_handle_re_replace_statements(string template, string expected)
|
||||
{
|
||||
_variables[".Query.Keywords"] = string.Join(" ", new List<string> { "abc", "def" });
|
||||
|
||||
var result = Subject.ApplyGoTemplateText(template, _variables);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("{{ join .Categories \", \" }}", "tv, movies")]
|
||||
public void should_handle_join_statements(string template, string expected)
|
||||
{
|
||||
var result = Subject.ApplyGoTemplateText(template, _variables);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("{{ .Today.Year }}", "2022")]
|
||||
public void should_handle_variables_statements(string template, string expected)
|
||||
{
|
||||
var result = Subject.ApplyGoTemplateText(template, _variables);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("{{if .False }}0{{else}}1{{end}}", "1")]
|
||||
[TestCase("{{if .True }}0{{else}}1{{end}}", "0")]
|
||||
public void should_handle_if_statements(string template, string expected)
|
||||
{
|
||||
var result = Subject.ApplyGoTemplateText(template, _variables);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.SecurityTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ProtectionServiceFixture : CoreTest<ProtectionService>
|
||||
{
|
||||
private string _protectionKey;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_protectionKey = Guid.NewGuid().ToString().Replace("-", "");
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.DownloadProtectionKey)
|
||||
.Returns(_protectionKey);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_encrypt_and_decrypt_string()
|
||||
{
|
||||
const string plainText = "https://prowlarr.com";
|
||||
|
||||
var encrypted = Subject.Protect(plainText);
|
||||
var decrypted = Subject.UnProtect(encrypted);
|
||||
|
||||
decrypted.Should().Be(plainText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,10 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
.Setup(c => c.FolderWritable(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update.exe"))))
|
||||
.Returns(true);
|
||||
|
||||
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
|
||||
}
|
||||
|
||||
@@ -149,7 +153,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_start_update_client()
|
||||
public void should_start_update_client_if_updater_exists()
|
||||
{
|
||||
Subject.Execute(new ApplicationUpdateCommand());
|
||||
|
||||
@@ -157,6 +161,21 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
.Verify(c => c.Start(It.IsAny<string>(), It.Is<string>(s => s.StartsWith("12")), null, null, null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_with_warning_if_updater_doesnt_exists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update.exe"))))
|
||||
.Returns(false);
|
||||
|
||||
Subject.Execute(new ApplicationUpdateCommand());
|
||||
|
||||
Mocker.GetMock<IProcessProvider>()
|
||||
.Verify(c => c.Start(It.IsAny<string>(), It.IsAny<string>(), null, null, null), Times.Never());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_without_error_or_warnings_when_no_updates_are_available()
|
||||
{
|
||||
|
||||
@@ -28,10 +28,13 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
}
|
||||
|
||||
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
|
||||
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
|
||||
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders;
|
||||
@@ -40,8 +43,8 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value);
|
||||
var seedTimeCompare = seedTime == otherSeedTime;
|
||||
|
||||
var discographySeedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
|
||||
var otherDiscographySeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
|
||||
var discographySeedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value);
|
||||
var otherDiscographySeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value);
|
||||
var discographySeedTimeCompare = discographySeedTime == otherDiscographySeedTime;
|
||||
|
||||
var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
|
||||
@@ -55,7 +58,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
other.Id == Id &&
|
||||
apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
|
||||
apiKey && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
}
|
||||
|
||||
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
|
||||
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
|
||||
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders;
|
||||
@@ -51,7 +54,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
other.Id == Id &&
|
||||
apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
|
||||
apiKey && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
}
|
||||
|
||||
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
|
||||
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
|
||||
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders;
|
||||
@@ -40,8 +43,8 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value);
|
||||
var seedTimeCompare = seedTime == otherSeedTime;
|
||||
|
||||
var discographySeedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
|
||||
var otherDiscographySeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
|
||||
var discographySeedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value);
|
||||
var otherDiscographySeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value);
|
||||
var discographySeedTimeCompare = discographySeedTime == otherDiscographySeedTime;
|
||||
|
||||
var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
|
||||
@@ -55,7 +58,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
other.Id == Id &&
|
||||
apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
|
||||
apiKey && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,14 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
}
|
||||
|
||||
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
|
||||
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
|
||||
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
|
||||
var animeCats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "animeCategories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value);
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders;
|
||||
@@ -56,7 +59,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
other.Id == Id &&
|
||||
apiKey && apiPath && baseUrl && cats && animeCats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare;
|
||||
apiKey && apiPathCompare && baseUrl && cats && animeCats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
}
|
||||
|
||||
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
|
||||
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
|
||||
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders;
|
||||
@@ -51,7 +54,7 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
other.Id == Id &&
|
||||
apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
|
||||
apiKey && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -67,6 +69,7 @@ namespace NzbDrone.Core.Configuration
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly ICached<string> _cache;
|
||||
private readonly PostgresOptions _postgresOptions;
|
||||
|
||||
private readonly string _configFile;
|
||||
private static readonly Regex HiddenCharacterRegex = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -76,12 +79,14 @@ namespace NzbDrone.Core.Configuration
|
||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
||||
ICacheManager cacheManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IDiskProvider diskProvider)
|
||||
IDiskProvider diskProvider,
|
||||
IOptions<PostgresOptions> postgresOptions)
|
||||
{
|
||||
_cache = cacheManager.GetCache<string>(GetType());
|
||||
_eventAggregator = eventAggregator;
|
||||
_diskProvider = diskProvider;
|
||||
_configFile = appFolderInfo.GetConfigPath();
|
||||
_postgresOptions = postgresOptions.Value;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetConfigDictionary()
|
||||
@@ -195,13 +200,13 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
public string PostgresHost => GetValue("PostgresHost", string.Empty, persist: false);
|
||||
public string PostgresUser => GetValue("PostgresUser", string.Empty, persist: false);
|
||||
public string PostgresPassword => GetValue("PostgresPassword", string.Empty, persist: false);
|
||||
public string PostgresMainDb => GetValue("PostgresMainDb", "prowlarr-main", persist: false);
|
||||
public string PostgresLogDb => GetValue("PostgresLogDb", "prowlarr-log", persist: false);
|
||||
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
||||
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "prowlarr-main", persist: false);
|
||||
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "prowlarr-log", persist: false);
|
||||
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
||||
public string Theme => GetValue("Theme", "light", persist: false);
|
||||
public int PostgresPort => GetValueInt("PostgresPort", 5432, persist: false);
|
||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||
|
||||
@@ -167,14 +167,12 @@ namespace NzbDrone.Core.Datastore
|
||||
}
|
||||
}
|
||||
|
||||
if (_database.DatabaseType == DatabaseType.SQLite)
|
||||
{
|
||||
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
||||
}
|
||||
else
|
||||
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||
{
|
||||
return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\"";
|
||||
}
|
||||
|
||||
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
||||
}
|
||||
|
||||
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "ThePirateBay" });
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "TorrentLeech" });
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "TorrentSeeds" });
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "TorrentParadiseMI" });
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "YTS" });
|
||||
|
||||
//Change settings to shared classes
|
||||
|
||||
@@ -3,7 +3,7 @@ using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(18)]
|
||||
[Migration(018)]
|
||||
public class minimum_seeders : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
|
||||
15
src/NzbDrone.Core/Datastore/Migration/019_remove_showrss.cs
Normal file
15
src/NzbDrone.Core/Datastore/Migration/019_remove_showrss.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(019)]
|
||||
public class remove_showrss : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
// Remove, YML version exists
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "ShowRSS" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(020)]
|
||||
public class remove_torrentparadiseml : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
// Remove, 017 incorrectly removes this using "TorrentParadiseMI"
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "TorrentParadiseMl" });
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/NzbDrone.Core/Datastore/PostgresOptions.cs
Normal file
26
src/NzbDrone.Core/Datastore/PostgresOptions.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public class PostgresOptions
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string User { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string MainDb { get; set; }
|
||||
public string LogDb { get; set; }
|
||||
|
||||
public static PostgresOptions GetOptions()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
var postgresOptions = new PostgresOptions();
|
||||
config.GetSection("Prowlarr:Postgres").Bind(postgresOptions);
|
||||
|
||||
return postgresOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,7 +313,20 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
_sb.Append(" = ANY (");
|
||||
|
||||
Visit(list);
|
||||
// hardcode the integer list if it exists to bypass parameter limit
|
||||
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
|
||||
{
|
||||
var items = (IEnumerable<int>)value;
|
||||
_sb.Append("('{");
|
||||
_sb.Append(string.Join(", ", items));
|
||||
_sb.Append("}')");
|
||||
|
||||
_gotConcreteValue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Visit(list);
|
||||
}
|
||||
|
||||
_sb.Append("))");
|
||||
}
|
||||
@@ -324,7 +337,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
Visit(body.Object);
|
||||
|
||||
_sb.Append(" LIKE '%' || ");
|
||||
_sb.Append(" ILIKE '%' || ");
|
||||
|
||||
Visit(body.Arguments[0]);
|
||||
|
||||
@@ -337,7 +350,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
Visit(body.Object);
|
||||
|
||||
_sb.Append(" LIKE ");
|
||||
_sb.Append(" ILIKE ");
|
||||
|
||||
Visit(body.Arguments[0]);
|
||||
|
||||
@@ -350,7 +363,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
Visit(body.Object);
|
||||
|
||||
_sb.Append(" LIKE '%' || ");
|
||||
_sb.Append(" ILIKE '%' || ");
|
||||
|
||||
Visit(body.Arguments[0]);
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public async Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title)
|
||||
{
|
||||
_logger.Trace("Attempting download of {0}", link);
|
||||
var url = new Uri(link);
|
||||
|
||||
// Limit grabs to 2 per second.
|
||||
|
||||
@@ -77,12 +77,15 @@ namespace NzbDrone.Core.HealthCheck
|
||||
.ToDictionary(g => g.Key, g => g.ToArray());
|
||||
}
|
||||
|
||||
private void PerformHealthCheck(IProvideHealthCheck[] healthChecks)
|
||||
private void PerformHealthCheck(IProvideHealthCheck[] healthChecks, bool performServerChecks = false)
|
||||
{
|
||||
var results = healthChecks.Select(c => c.Check())
|
||||
.ToList();
|
||||
|
||||
results.AddRange(_serverSideNotificationService.GetServerChecks());
|
||||
if (performServerChecks)
|
||||
{
|
||||
results.AddRange(_serverSideNotificationService.GetServerChecks());
|
||||
}
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
@@ -108,17 +111,17 @@ namespace NzbDrone.Core.HealthCheck
|
||||
{
|
||||
if (message.Trigger == CommandTrigger.Manual)
|
||||
{
|
||||
PerformHealthCheck(_healthChecks);
|
||||
PerformHealthCheck(_healthChecks, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformHealthCheck(_scheduledHealthChecks);
|
||||
PerformHealthCheck(_scheduledHealthChecks, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleAsync(ApplicationStartedEvent message)
|
||||
{
|
||||
PerformHealthCheck(_startupHealthChecks);
|
||||
PerformHealthCheck(_startupHealthChecks, true);
|
||||
}
|
||||
|
||||
public void HandleAsync(IEvent message)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -23,24 +24,37 @@ namespace NzbDrone.Core.HealthCheck
|
||||
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ServerSideNotificationService(IHttpClient client, IConfigFileProvider configFileProvider, IProwlarrCloudRequestBuilder cloudRequestBuilder, Logger logger)
|
||||
private readonly ICached<List<HealthCheck>> _cache;
|
||||
|
||||
public ServerSideNotificationService(IHttpClient client,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IProwlarrCloudRequestBuilder cloudRequestBuilder,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_client = client;
|
||||
_configFileProvider = configFileProvider;
|
||||
_cloudRequestBuilder = cloudRequestBuilder.Services;
|
||||
_logger = logger;
|
||||
|
||||
_cache = cacheManager.GetCache<List<HealthCheck>>(GetType());
|
||||
}
|
||||
|
||||
public List<HealthCheck> GetServerChecks()
|
||||
{
|
||||
return _cache.Get("ServerChecks", () => RetrieveServerChecks(), TimeSpan.FromHours(2));
|
||||
}
|
||||
|
||||
private List<HealthCheck> RetrieveServerChecks()
|
||||
{
|
||||
var request = _cloudRequestBuilder.Create()
|
||||
.Resource("/notification")
|
||||
.AddQueryParam("version", BuildInfo.Version)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
|
||||
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("branch", _configFileProvider.Branch)
|
||||
.Build();
|
||||
.Resource("/notification")
|
||||
.AddQueryParam("version", BuildInfo.Version)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
|
||||
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("branch", _configFileProvider.Branch)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
_logger.Trace("Getting server side health notifications");
|
||||
|
||||
@@ -136,6 +136,7 @@ namespace NzbDrone.Core.History
|
||||
{
|
||||
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).FullImdbId ?? string.Empty);
|
||||
history.Data.Add("TvdbId", ((TvSearchCriteria)message.Query).TvdbId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("TmdbId", ((TvSearchCriteria)message.Query).TmdbId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("TraktId", ((TvSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("RId", ((TvSearchCriteria)message.Query).RId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("TvMazeId", ((TvSearchCriteria)message.Query).TvMazeId?.ToString() ?? string.Empty);
|
||||
|
||||
@@ -98,6 +98,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
GetNabElement("imdb", r.ImdbId.ToString("D7"), protocol),
|
||||
GetNabElement("tmdbid", r.TmdbId, protocol),
|
||||
GetNabElement("traktid", r.TraktId, protocol),
|
||||
GetNabElement("doubanid", r.DoubanId, protocol),
|
||||
GetNabElement("seeders", t.Seeders, protocol),
|
||||
GetNabElement("files", r.Files, protocol),
|
||||
GetNabElement("grabs", r.Grabs, protocol),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
@@ -14,17 +16,19 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||
private readonly IAnalyticsService _analyticsService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ReleaseAnalyticsService(IHttpClient httpClient, IProwlarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService)
|
||||
public ReleaseAnalyticsService(IHttpClient httpClient, IProwlarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService, Logger logger)
|
||||
{
|
||||
_analyticsService = analyticsService;
|
||||
_requestBuilder = requestBuilder.Releases;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void HandleAsync(IndexerQueryEvent message)
|
||||
{
|
||||
if (_analyticsService.IsEnabled)
|
||||
if (_analyticsService.IsEnabled && message.QueryResult?.Releases != null)
|
||||
{
|
||||
var request = _requestBuilder.Create().Resource("release/push").Build();
|
||||
request.Method = HttpMethod.Post;
|
||||
@@ -34,14 +38,21 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
var body = message.QueryResult.Releases.Select(x => new
|
||||
{
|
||||
Title = x.Title,
|
||||
Categories = x.Categories.Where(c => c.Id < 10000).Select(c => c.Id),
|
||||
Categories = x.Categories?.Where(c => c.Id < 10000).Select(c => c.Id) ?? new List<int>(),
|
||||
Protocol = x.DownloadProtocol.ToString(),
|
||||
Size = x.Size,
|
||||
PublishDate = x.PublishDate
|
||||
});
|
||||
|
||||
request.SetContent(body.ToJson());
|
||||
_httpClient.Post(request);
|
||||
try
|
||||
{
|
||||
request.SetContent(body.ToJson());
|
||||
_httpClient.Post(request);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Trace("Analytics push failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
/* Update Service will fall back if version # does not exist for an indexer per Ta */
|
||||
|
||||
private const string DEFINITION_BRANCH = "master";
|
||||
private const int DEFINITION_VERSION = 5;
|
||||
private const int DEFINITION_VERSION = 6;
|
||||
|
||||
//Used when moving yml to C#
|
||||
private readonly List<string> _defintionBlocklist = new List<string>()
|
||||
|
||||
@@ -6,11 +6,9 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
@@ -18,8 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
protected readonly IndexerCapabilitiesCategories _categories = new IndexerCapabilitiesCategories();
|
||||
protected readonly List<string> _defaultCategories = new List<string>();
|
||||
|
||||
protected readonly string[] OptionalFields = new string[] { "imdb", "imdbid", "rageid", "tmdbid", "tvdbid", "poster", "banner", "description" };
|
||||
protected readonly string[] OptionalFields = new string[] { "imdb", "imdbid", "rageid", "tmdbid", "tvdbid", "poster", "banner", "description", "doubanid" };
|
||||
|
||||
protected static readonly string[] _SupportedLogicFunctions =
|
||||
{
|
||||
@@ -338,9 +338,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
return variables;
|
||||
}
|
||||
|
||||
protected delegate string TemplateTextModifier(string str);
|
||||
public delegate string TemplateTextModifier(string str);
|
||||
|
||||
protected string ApplyGoTemplateText(string template, Dictionary<string, object> variables = null, TemplateTextModifier modifier = null)
|
||||
public string ApplyGoTemplateText(string template, Dictionary<string, object> variables = null, TemplateTextModifier modifier = null)
|
||||
{
|
||||
if (variables == null)
|
||||
{
|
||||
@@ -520,7 +520,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
|
||||
// handle range expression
|
||||
var rangeRegex = new Regex(@"{{\s*range\s*(.+?)\s*}}(.*?){{\.}}(.*?){{end}}");
|
||||
var rangeRegex = new Regex(@"{{\s*range\s*(((?<index>\$.+?),)((\s*(?<element>.+?)\s*(:=)\s*)))?(?<variable>.+?)\s*}}(?<prefix>.*?){{\.}}(?<postfix>.*?){{end}}");
|
||||
var rangeRegexMatches = rangeRegex.Match(template);
|
||||
|
||||
while (rangeRegexMatches.Success)
|
||||
@@ -528,9 +528,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
var expanded = string.Empty;
|
||||
|
||||
var all = rangeRegexMatches.Groups[0].Value;
|
||||
var variable = rangeRegexMatches.Groups[1].Value;
|
||||
var prefix = rangeRegexMatches.Groups[2].Value;
|
||||
var postfix = rangeRegexMatches.Groups[3].Value;
|
||||
var index = rangeRegexMatches.Groups["index"].Value;
|
||||
var variable = rangeRegexMatches.Groups["variable"].Value;
|
||||
var prefix = rangeRegexMatches.Groups["prefix"].Value;
|
||||
var postfix = rangeRegexMatches.Groups["postfix"].Value;
|
||||
|
||||
var arrayIndex = 0;
|
||||
var indexReplace = "{{" + index + "}}";
|
||||
|
||||
foreach (var value in (ICollection<string>)variables[variable])
|
||||
{
|
||||
@@ -540,7 +544,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
newvalue = modifier(newvalue);
|
||||
}
|
||||
|
||||
expanded += prefix + newvalue + postfix;
|
||||
var indexValue = arrayIndex++;
|
||||
|
||||
if (index.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
expanded += prefix.Replace(indexReplace, indexValue.ToString()) + newvalue + postfix.Replace(indexReplace, indexValue.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
expanded += prefix + newvalue + postfix;
|
||||
}
|
||||
}
|
||||
|
||||
template = template.Replace(all, expanded);
|
||||
|
||||
@@ -60,7 +60,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
if (request.SearchPath.Response != null && request.SearchPath.Response.Type.Equals("json"))
|
||||
{
|
||||
if (request.SearchPath.Response != null && request.SearchPath.Response.NoResultsMessage != null && (request.SearchPath.Response.NoResultsMessage.Equals(results) || (request.SearchPath.Response.NoResultsMessage == string.Empty && results == string.Empty)))
|
||||
if (request.SearchPath.Response != null &&
|
||||
request.SearchPath.Response.NoResultsMessage != null &&
|
||||
((request.SearchPath.Response.NoResultsMessage != string.Empty && results.Contains(request.SearchPath.Response.NoResultsMessage)) || (request.SearchPath.Response.NoResultsMessage == string.Empty && results == string.Empty)))
|
||||
{
|
||||
return releases;
|
||||
}
|
||||
@@ -575,6 +577,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
release.TvdbId = (int)ParseUtil.CoerceLong(tvdbId);
|
||||
value = release.TvdbId.ToString();
|
||||
break;
|
||||
case "doubanid":
|
||||
var doubanIDRegEx = new Regex(@"(\d+)", RegexOptions.Compiled);
|
||||
var doubanIDMatch = doubanIDRegEx.Match(value);
|
||||
var doubanID = doubanIDMatch.Groups[1].Value;
|
||||
release.DoubanId = (int)ParseUtil.CoerceLong(doubanID);
|
||||
value = release.DoubanId.ToString();
|
||||
break;
|
||||
case "poster":
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.IMDBID"] = searchCriteria.FullImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId;
|
||||
variables[".Query.TVDBID"] = searchCriteria.TvdbId?.ToString() ?? null;
|
||||
variables[".Query.TMDBID"] = searchCriteria.TmdbId?.ToString() ?? null;
|
||||
variables[".Query.TVRageID"] = searchCriteria.RId?.ToString() ?? null;
|
||||
variables[".Query.TVMazeID"] = searchCriteria.TvMazeId?.ToString() ?? null;
|
||||
variables[".Query.TraktID"] = searchCriteria.TraktId?.ToString() ?? null;
|
||||
|
||||
@@ -34,28 +34,4 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public enum FileListCategories
|
||||
{
|
||||
[FieldOption]
|
||||
Movie_SD = 1,
|
||||
[FieldOption]
|
||||
Movie_DVD = 2,
|
||||
[FieldOption]
|
||||
Movie_DVDRO = 3,
|
||||
[FieldOption]
|
||||
Movie_HD = 4,
|
||||
[FieldOption]
|
||||
Movie_HDRO = 19,
|
||||
[FieldOption]
|
||||
Movie_BluRay = 20,
|
||||
[FieldOption]
|
||||
Movie_BluRay4K = 26,
|
||||
[FieldOption]
|
||||
Movie_3D = 25,
|
||||
[FieldOption]
|
||||
Movie_4K = 6,
|
||||
[FieldOption]
|
||||
Xxx = 7
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -14,7 +13,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
@@ -200,6 +198,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
if (Settings.UserAgent.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.HttpRequest.Headers.UserAgent = Settings.UserAgent;
|
||||
}
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -355,6 +358,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Cookie User-Agent", Type = FieldType.Textbox, HelpText = "User-Agent associated with cookie used from Browser")]
|
||||
public string UserAgent { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Search Freeleech torrents only")]
|
||||
public bool FreeLeechOnly { get; set; }
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
if (capabilities.TvSearchParams != null &&
|
||||
new[] { TvSearchParam.Q, TvSearchParam.TvdbId, TvSearchParam.RId }.Any(v => capabilities.TvSearchParams.Contains(v)) &&
|
||||
new[] { TvSearchParam.Q, TvSearchParam.TvdbId, TvSearchParam.TmdbId, TvSearchParam.RId }.Any(v => capabilities.TvSearchParams.Contains(v)) &&
|
||||
new[] { TvSearchParam.Season, TvSearchParam.Ep }.All(v => capabilities.TvSearchParams.Contains(v)))
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -125,6 +125,11 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
parameters.Add("tvdbid", searchCriteria.TvdbId.Value.ToString());
|
||||
}
|
||||
|
||||
if (searchCriteria.TmdbId.HasValue && capabilities.TvSearchTvdbAvailable)
|
||||
{
|
||||
parameters.Add("tmdbid", searchCriteria.TvdbId.Value.ToString());
|
||||
}
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() && capabilities.TvSearchImdbAvailable)
|
||||
{
|
||||
parameters.Add("imdbid", searchCriteria.ImdbId);
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
releaseInfo = base.ProcessItem(item, releaseInfo);
|
||||
releaseInfo.ImdbId = GetIntAttribute(item, "imdb");
|
||||
releaseInfo.TmdbId = GetIntAttribute(item, "tmdb");
|
||||
releaseInfo.TmdbId = GetIntAttribute(item, "tmdbid");
|
||||
releaseInfo.TvdbId = GetIntAttribute(item, "tvdbid");
|
||||
releaseInfo.TvRageId = GetIntAttribute(item, "rageid");
|
||||
releaseInfo.Grabs = GetIntAttribute(item, "grabs");
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class ShowRSS : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "ShowRSS";
|
||||
public override string[] IndexerUrls => new string[] { "https://showrss.info/" };
|
||||
public override string Language => "en-US";
|
||||
public override string Description => "showRSS is a service that allows you to keep track of your favorite TV shows";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public ShowRSS(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new ShowRSSRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new ShowRSSParser(Settings);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVHD);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class ShowRSSRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public NoAuthTorrentBaseSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public ShowRSSRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/other/all.rss", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class ShowRSSParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly NoAuthTorrentBaseSettings _settings;
|
||||
private string BrowseUrl => _settings.BaseUrl + "browse/";
|
||||
|
||||
public ShowRSSParser(NoAuthTorrentBaseSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(indexerResponse.Content);
|
||||
foreach (XmlNode node in xmlDoc.GetElementsByTagName("item"))
|
||||
{
|
||||
var title = node.SelectSingleNode(".//*[local-name()='raw_title']").InnerText;
|
||||
|
||||
// TODO: Make sure we don't return all sorts of trash
|
||||
//if (!query.MatchQueryStringAND(title))
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
var category = title.Contains("720p") || title.Contains("1080p") ?
|
||||
NewznabStandardCategory.TVHD :
|
||||
NewznabStandardCategory.TVSD;
|
||||
|
||||
var magnetUri = node.SelectSingleNode("link")?.InnerText;
|
||||
var publishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture);
|
||||
var infoHash = node.SelectSingleNode(".//*[local-name()='info_hash']").InnerText;
|
||||
var details = BrowseUrl + node.SelectSingleNode(".//*[local-name()='show_id']").InnerText;
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = title,
|
||||
InfoUrl = details,
|
||||
Categories = new List<IndexerCategory> { category },
|
||||
Guid = magnetUri,
|
||||
PublishDate = publishDate,
|
||||
InfoHash = infoHash,
|
||||
MagnetUrl = magnetUri,
|
||||
Size = 512,
|
||||
Seeders = 1,
|
||||
Peers = 2,
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
[FieldDefinition(4)]
|
||||
public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public virtual NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
@@ -117,7 +117,6 @@ namespace NzbDrone.Core.Instrumentation
|
||||
syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
|
||||
syslogTarget.MessageSend.Udp.Port = syslogPort;
|
||||
syslogTarget.MessageSend.Udp.Server = syslogServer;
|
||||
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
|
||||
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
|
||||
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public int ImdbId { get; set; }
|
||||
public int TmdbId { get; set; }
|
||||
public int TraktId { get; set; }
|
||||
public int DoubanId { get; set; }
|
||||
public int Year { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string BookTitle { get; set; }
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp.Xml" Version="0.16.0" />
|
||||
<PackageReference Include="AngleSharp.Xml" Version="0.17.0" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
||||
<PackageReference Include="MailKit" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.11" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="6.0.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.2" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="3.3.2" />
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="TinyTwitter" Version="1.1.2" />
|
||||
<PackageReference Include="Kveer.XmlRPC" Version="1.1.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.5" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<PackageReference Include="AngleSharp" Version="0.16.1" />
|
||||
<PackageReference Include="AngleSharp" Version="0.17.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Prowlarr.Common.csproj" />
|
||||
|
||||
@@ -146,16 +146,24 @@ namespace NzbDrone.Core.Update
|
||||
_logger.Info("Preparing client");
|
||||
_diskTransferService.TransferFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder, TransferMode.Move);
|
||||
|
||||
var updateClientExePath = _appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime);
|
||||
|
||||
if (!_diskProvider.FileExists(updateClientExePath))
|
||||
{
|
||||
_logger.Warn("Update client {0} does not exist, aborting update.", updateClientExePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set executable flag on update app
|
||||
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
|
||||
{
|
||||
_diskProvider.SetPermissions(_appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime), "0755");
|
||||
_diskProvider.SetFilePermissions(updateClientExePath, "755", null);
|
||||
}
|
||||
|
||||
_logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime));
|
||||
_logger.Info("Starting update client {0}", updateClientExePath);
|
||||
_logger.ProgressInfo("Prowlarr will restart shortly.");
|
||||
|
||||
_processProvider.Start(_appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime), GetUpdaterArgs(updateSandboxFolder));
|
||||
_processProvider.Start(updateClientExePath, GetUpdaterArgs(updateSandboxFolder));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -298,14 +306,6 @@ namespace NzbDrone.Core.Update
|
||||
// Check if we have to do an application update on startup
|
||||
try
|
||||
{
|
||||
// Don't do a prestartup update check unless BuiltIn update is enabled
|
||||
if (_configFileProvider.UpdateAutomatically ||
|
||||
_configFileProvider.UpdateMechanism != UpdateMechanism.BuiltIn ||
|
||||
_deploymentInfoProvider.IsExternalUpdateMechanism)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var updateMarker = Path.Combine(_appFolderInfo.AppDataFolder, "update_required");
|
||||
if (!_diskProvider.FileExists(updateMarker))
|
||||
{
|
||||
@@ -314,6 +314,15 @@ namespace NzbDrone.Core.Update
|
||||
|
||||
_logger.Debug("Post-install update check requested");
|
||||
|
||||
// Don't do a prestartup update check unless BuiltIn update is enabled
|
||||
if (!_configFileProvider.UpdateAutomatically ||
|
||||
_configFileProvider.UpdateMechanism != UpdateMechanism.BuiltIn ||
|
||||
_deploymentInfoProvider.IsExternalUpdateMechanism)
|
||||
{
|
||||
_logger.Debug("Built-in updater disabled, skipping post-install update check");
|
||||
return;
|
||||
}
|
||||
|
||||
var latestAvailable = _checkUpdateService.AvailableUpdate();
|
||||
if (latestAvailable == null)
|
||||
{
|
||||
|
||||
@@ -3,11 +3,11 @@ using NzbDrone.Common.Disk;
|
||||
|
||||
namespace NzbDrone.Core.Validation
|
||||
{
|
||||
public class FileChmodValidator : PropertyValidator
|
||||
public class FolderChmodValidator : PropertyValidator
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
||||
public FileChmodValidator(IDiskProvider diskProvider)
|
||||
public FolderChmodValidator(IDiskProvider diskProvider)
|
||||
: base("Must contain a valid Unix permissions octal")
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Validation
|
||||
return false;
|
||||
}
|
||||
|
||||
return _diskProvider.IsValidFilePermissionMask(context.PropertyValue.ToString());
|
||||
return _diskProvider.IsValidFolderPermissionMask(context.PropertyValue.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,16 @@ using DryIoc.Microsoft.DependencyInjection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Composition.Extensions;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Jobs;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
@@ -43,6 +46,7 @@ namespace NzbDrone.App.Test
|
||||
// dummy lifetime and broadcaster so tests resolve
|
||||
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
||||
container.RegisterInstance<IBroadcastSignalRMessage>(new Mock<IBroadcastSignalRMessage>().Object);
|
||||
container.RegisterInstance<IOptions<PostgresOptions>>(new Mock<IOptions<PostgresOptions>>().Object);
|
||||
|
||||
_container = container.GetServiceProvider();
|
||||
}
|
||||
@@ -53,6 +57,12 @@ namespace NzbDrone.App.Test
|
||||
_container.GetRequiredService<IEnumerable<IIndexer>>().Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_able_to_resolve_downloadclients()
|
||||
{
|
||||
_container.GetRequiredService<IEnumerable<IDownloadClient>>().Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void container_should_inject_itself()
|
||||
{
|
||||
|
||||
@@ -9,8 +9,12 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using DryIoc;
|
||||
using DryIoc.Microsoft.DependencyInjection;
|
||||
using FluentMigrator.Runner.Processors.Postgres;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
using NLog;
|
||||
@@ -23,6 +27,8 @@ using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Host;
|
||||
using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
{
|
||||
@@ -52,6 +58,7 @@ namespace NzbDrone.Host
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
var appMode = GetApplicationMode(startupContext);
|
||||
var config = GetConfiguration(startupContext);
|
||||
|
||||
switch (appMode)
|
||||
{
|
||||
@@ -80,12 +87,22 @@ namespace NzbDrone.Host
|
||||
// Utility mode
|
||||
default:
|
||||
{
|
||||
new Container(rules => rules.WithNzbDroneRules())
|
||||
.AutoAddServices(ASSEMBLIES)
|
||||
.AddNzbDroneLogger()
|
||||
.AddStartupContext(startupContext)
|
||||
.Resolve<UtilityModeRouter>()
|
||||
.Route(appMode);
|
||||
new HostBuilder()
|
||||
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
|
||||
.ConfigureContainer<IContainer>(c =>
|
||||
{
|
||||
c.AutoAddServices(Bootstrap.ASSEMBLIES)
|
||||
.AddNzbDroneLogger()
|
||||
.AddDatabase()
|
||||
.AddStartupContext(startupContext)
|
||||
.Resolve<UtilityModeRouter>()
|
||||
.Route(appMode);
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.Configure<PostgresOptions>(config.GetSection("Prowlarr:Postgres"));
|
||||
}).Build();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -135,6 +152,15 @@ namespace NzbDrone.Host
|
||||
.AddDatabase()
|
||||
.AddStartupContext(context);
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.Configure<PostgresOptions>(config.GetSection("Prowlarr:Postgres"));
|
||||
services.Configure<FormOptions>(x =>
|
||||
{
|
||||
//Double the default multipart body length from 128 MB to 256 MB
|
||||
x.MultipartBodyLengthLimit = 268435456;
|
||||
});
|
||||
})
|
||||
.ConfigureWebHost(builder =>
|
||||
{
|
||||
builder.UseConfiguration(config);
|
||||
@@ -205,6 +231,7 @@ namespace NzbDrone.Host
|
||||
return new ConfigurationBuilder()
|
||||
.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false)
|
||||
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.3.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.3.1" />
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NLog;
|
||||
using Npgsql;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using NzbDrone.Core.Indexers.FileList;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.Datastore;
|
||||
using Prowlarr.Http.ClientSchema;
|
||||
|
||||
namespace NzbDrone.Integration.Test
|
||||
@@ -19,6 +25,8 @@ namespace NzbDrone.Integration.Test
|
||||
|
||||
protected int Port { get; private set; }
|
||||
|
||||
protected PostgresOptions PostgresOptions { get; set; } = new ();
|
||||
|
||||
protected override string RootUrl => $"http://localhost:{Port}/";
|
||||
|
||||
protected override string ApiKey => _runner.ApiKey;
|
||||
@@ -27,7 +35,14 @@ namespace NzbDrone.Integration.Test
|
||||
{
|
||||
Port = Interlocked.Increment(ref StaticPort);
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), Port);
|
||||
PostgresOptions = PostgresDatabase.GetTestOptions();
|
||||
|
||||
if (PostgresOptions?.Host != null)
|
||||
{
|
||||
CreatePostgresDb(PostgresOptions);
|
||||
}
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), PostgresOptions, Port);
|
||||
_runner.Kill();
|
||||
|
||||
_runner.Start();
|
||||
@@ -56,6 +71,22 @@ namespace NzbDrone.Integration.Test
|
||||
protected override void StopTestTarget()
|
||||
{
|
||||
_runner.Kill();
|
||||
if (PostgresOptions?.Host != null)
|
||||
{
|
||||
DropPostgresDb(PostgresOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreatePostgresDb(PostgresOptions options)
|
||||
{
|
||||
PostgresDatabase.Create(options, MigrationType.Main);
|
||||
PostgresDatabase.Create(options, MigrationType.Log);
|
||||
}
|
||||
|
||||
private static void DropPostgresDb(PostgresOptions options)
|
||||
{
|
||||
PostgresDatabase.Drop(options, MigrationType.Main);
|
||||
PostgresDatabase.Drop(options, MigrationType.Log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.6" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
|
||||
@@ -18,11 +18,32 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||
[Platform(Exclude = "Win")]
|
||||
public class DiskProviderFixture : DiskProviderFixtureBase<DiskProvider>
|
||||
{
|
||||
private string _tempPath;
|
||||
|
||||
public DiskProviderFixture()
|
||||
{
|
||||
PosixOnly();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void MonoDiskProviderFixtureTearDown()
|
||||
{
|
||||
// Give ourselves back write permissions so we can delete it
|
||||
if (_tempPath != null)
|
||||
{
|
||||
if (Directory.Exists(_tempPath))
|
||||
{
|
||||
Syscall.chmod(_tempPath, FilePermissions.S_IRWXU);
|
||||
}
|
||||
else if (File.Exists(_tempPath))
|
||||
{
|
||||
Syscall.chmod(_tempPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR);
|
||||
}
|
||||
|
||||
_tempPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetWritePermissions(string path, bool writable)
|
||||
{
|
||||
if (Environment.UserName == "root")
|
||||
@@ -30,16 +51,41 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||
Assert.Inconclusive("Need non-root user to test write permissions.");
|
||||
}
|
||||
|
||||
SetWritePermissionsInternal(path, writable, false);
|
||||
}
|
||||
|
||||
protected void SetWritePermissionsInternal(string path, bool writable, bool setgid)
|
||||
{
|
||||
// Remove Write permissions, we're still owner so we can clean it up, but we'll have to do that explicitly.
|
||||
var entry = UnixFileSystemInfo.GetFileSystemEntry(path);
|
||||
Stat stat;
|
||||
Syscall.stat(path, out stat);
|
||||
FilePermissions mode = stat.st_mode;
|
||||
|
||||
if (writable)
|
||||
{
|
||||
entry.FileAccessPermissions |= FileAccessPermissions.UserWrite | FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite;
|
||||
mode |= FilePermissions.S_IWUSR | FilePermissions.S_IWGRP | FilePermissions.S_IWOTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.FileAccessPermissions &= ~(FileAccessPermissions.UserWrite | FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite);
|
||||
mode &= ~(FilePermissions.S_IWUSR | FilePermissions.S_IWGRP | FilePermissions.S_IWOTH);
|
||||
}
|
||||
|
||||
if (setgid)
|
||||
{
|
||||
mode |= FilePermissions.S_ISGID;
|
||||
}
|
||||
else
|
||||
{
|
||||
mode &= ~FilePermissions.S_ISGID;
|
||||
}
|
||||
|
||||
if (stat.st_mode != mode)
|
||||
{
|
||||
if (Syscall.chmod(path, mode) < 0)
|
||||
{
|
||||
var error = Stdlib.GetLastError();
|
||||
throw new LinuxPermissionsException("Error setting group: " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,24 +211,25 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||
var tempFile = GetTempFilePath();
|
||||
|
||||
File.WriteAllText(tempFile, "File1");
|
||||
SetWritePermissions(tempFile, false);
|
||||
SetWritePermissionsInternal(tempFile, false, false);
|
||||
_tempPath = tempFile;
|
||||
|
||||
// Verify test setup
|
||||
Syscall.stat(tempFile, out var fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0444");
|
||||
|
||||
Subject.SetPermissions(tempFile, "644");
|
||||
Subject.SetPermissions(tempFile, "755", null);
|
||||
Syscall.stat(tempFile, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0644");
|
||||
|
||||
Subject.SetPermissions(tempFile, "0644");
|
||||
Subject.SetPermissions(tempFile, "0755", null);
|
||||
Syscall.stat(tempFile, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0644");
|
||||
|
||||
if (OsInfo.Os != Os.Bsd)
|
||||
{
|
||||
// This is not allowed on BSD
|
||||
Subject.SetPermissions(tempFile, "1664");
|
||||
Subject.SetPermissions(tempFile, "1775", null);
|
||||
Syscall.stat(tempFile, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("1664");
|
||||
}
|
||||
@@ -194,62 +241,118 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||
var tempPath = GetTempFilePath();
|
||||
|
||||
Directory.CreateDirectory(tempPath);
|
||||
SetWritePermissions(tempPath, false);
|
||||
SetWritePermissionsInternal(tempPath, false, false);
|
||||
_tempPath = tempPath;
|
||||
|
||||
// Verify test setup
|
||||
Syscall.stat(tempPath, out var fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0555");
|
||||
|
||||
Subject.SetPermissions(tempPath, "644");
|
||||
Subject.SetPermissions(tempPath, "755", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0755");
|
||||
|
||||
Subject.SetPermissions(tempPath, "0644");
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0755");
|
||||
|
||||
Subject.SetPermissions(tempPath, "1664");
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("1775");
|
||||
|
||||
Subject.SetPermissions(tempPath, "775");
|
||||
Subject.SetPermissions(tempPath, "775", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0775");
|
||||
|
||||
Subject.SetPermissions(tempPath, "640");
|
||||
Subject.SetPermissions(tempPath, "750", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0750");
|
||||
|
||||
Subject.SetPermissions(tempPath, "0041");
|
||||
Subject.SetPermissions(tempPath, "051", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0051");
|
||||
|
||||
// reinstate sane permissions so fokder can be cleaned up
|
||||
Subject.SetPermissions(tempPath, "775");
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0775");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidFilePermissionMask_should_return_correct()
|
||||
public void should_preserve_setgid_on_set_folder_permissions()
|
||||
{
|
||||
// Files may not be executable
|
||||
Subject.IsValidFilePermissionMask("0777").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("0544").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("0454").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("0445").Should().BeFalse();
|
||||
var tempPath = GetTempFilePath();
|
||||
|
||||
Directory.CreateDirectory(tempPath);
|
||||
SetWritePermissionsInternal(tempPath, false, true);
|
||||
_tempPath = tempPath;
|
||||
|
||||
// Verify test setup
|
||||
Syscall.stat(tempPath, out var fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("2555");
|
||||
|
||||
Subject.SetPermissions(tempPath, "755", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("2755");
|
||||
|
||||
Subject.SetPermissions(tempPath, "775", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("2775");
|
||||
|
||||
Subject.SetPermissions(tempPath, "750", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("2750");
|
||||
|
||||
Subject.SetPermissions(tempPath, "051", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("2051");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_clear_setgid_on_set_folder_permissions()
|
||||
{
|
||||
var tempPath = GetTempFilePath();
|
||||
|
||||
Directory.CreateDirectory(tempPath);
|
||||
SetWritePermissionsInternal(tempPath, false, true);
|
||||
_tempPath = tempPath;
|
||||
|
||||
// Verify test setup
|
||||
Syscall.stat(tempPath, out var fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("2555");
|
||||
|
||||
Subject.SetPermissions(tempPath, "0755", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0755");
|
||||
|
||||
Subject.SetPermissions(tempPath, "0775", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0775");
|
||||
|
||||
Subject.SetPermissions(tempPath, "0750", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0750");
|
||||
|
||||
Subject.SetPermissions(tempPath, "0051", null);
|
||||
Syscall.stat(tempPath, out fileStat);
|
||||
NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0051");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidFolderPermissionMask_should_return_correct()
|
||||
{
|
||||
// No special bits should be set
|
||||
Subject.IsValidFilePermissionMask("1644").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("2644").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("4644").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("7644").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("1755").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("2755").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("4755").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("7755").Should().BeFalse();
|
||||
|
||||
// Files should be readable and writeable by owner
|
||||
Subject.IsValidFilePermissionMask("0400").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("0000").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("0200").Should().BeFalse();
|
||||
Subject.IsValidFilePermissionMask("0600").Should().BeTrue();
|
||||
// Folder should be readable and writeable by owner
|
||||
Subject.IsValidFolderPermissionMask("000").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("100").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("200").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("300").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("400").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("500").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("600").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("700").Should().BeTrue();
|
||||
|
||||
// Folder should be readable and writeable by owner
|
||||
Subject.IsValidFolderPermissionMask("0000").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0100").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0200").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0300").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0400").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0500").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0600").Should().BeFalse();
|
||||
Subject.IsValidFolderPermissionMask("0700").Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Test.DiskTests;
|
||||
using NzbDrone.Mono.Disk;
|
||||
@@ -8,9 +10,20 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||
[Platform(Exclude = "Win")]
|
||||
public class FreeSpaceFixture : FreeSpaceFixtureBase<DiskProvider>
|
||||
{
|
||||
public FreeSpaceFixture()
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
PosixOnly();
|
||||
Mocker.SetConstant<IProcMountProvider>(new ProcMountProvider(TestLogger));
|
||||
Mocker.GetMock<ISymbolicLinkResolver>()
|
||||
.Setup(v => v.GetCompleteRealPath(It.IsAny<string>()))
|
||||
.Returns<string>(s => s);
|
||||
}
|
||||
|
||||
[Ignore("Docker")]
|
||||
[Test]
|
||||
public void should_be_able_to_check_space_on_ramdrive()
|
||||
{
|
||||
Subject.GetAvailableSpace("/run/").Should().NotBe(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,9 @@ using NzbDrone.Test.Common;
|
||||
namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||
{
|
||||
[TestFixture]
|
||||
[Platform("Mono")]
|
||||
[Platform(Exclude = "Win")]
|
||||
public class SymbolicLinkResolverFixture : TestBase<SymbolicLinkResolver>
|
||||
{
|
||||
public SymbolicLinkResolverFixture()
|
||||
{
|
||||
MonoOnly();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_follow_nested_symlinks()
|
||||
{
|
||||
|
||||
@@ -61,15 +61,41 @@ namespace NzbDrone.Mono.Disk
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetPermissions(string path, string mask)
|
||||
public override void SetFilePermissions(string path, string mask, string group)
|
||||
{
|
||||
var permissions = NativeConvert.FromOctalPermissionString(mask);
|
||||
|
||||
SetPermissions(path, mask, group, permissions);
|
||||
}
|
||||
|
||||
public override void SetPermissions(string path, string mask, string group)
|
||||
{
|
||||
var permissions = NativeConvert.FromOctalPermissionString(mask);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
permissions = GetFilePermissions(permissions);
|
||||
}
|
||||
|
||||
SetPermissions(path, mask, group, permissions);
|
||||
}
|
||||
|
||||
protected void SetPermissions(string path, string mask, string group, FilePermissions permissions)
|
||||
{
|
||||
_logger.Debug("Setting permissions: {0} on {1}", mask, path);
|
||||
|
||||
var permissions = NativeConvert.FromOctalPermissionString(mask);
|
||||
|
||||
if (Directory.Exists(path))
|
||||
// Preserve non-access permissions
|
||||
if (Syscall.stat(path, out var curStat) < 0)
|
||||
{
|
||||
permissions = GetFolderPermissions(permissions);
|
||||
var error = Stdlib.GetLastError();
|
||||
|
||||
throw new LinuxPermissionsException("Error getting current permissions: " + error);
|
||||
}
|
||||
|
||||
// Preserve existing non-access permissions unless mask is 4 digits
|
||||
if (mask.Length < 4)
|
||||
{
|
||||
permissions |= curStat.st_mode & ~FilePermissions.ACCESSPERMS;
|
||||
}
|
||||
|
||||
if (Syscall.chmod(path, permissions) < 0)
|
||||
@@ -78,33 +104,39 @@ namespace NzbDrone.Mono.Disk
|
||||
|
||||
throw new LinuxPermissionsException("Error setting permissions: " + error);
|
||||
}
|
||||
|
||||
var groupId = GetGroupId(group);
|
||||
|
||||
if (Syscall.chown(path, unchecked((uint)-1), groupId) < 0)
|
||||
{
|
||||
var error = Stdlib.GetLastError();
|
||||
|
||||
throw new LinuxPermissionsException("Error setting group: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
private static FilePermissions GetFolderPermissions(FilePermissions permissions)
|
||||
private static FilePermissions GetFilePermissions(FilePermissions permissions)
|
||||
{
|
||||
permissions |= (FilePermissions)((int)(permissions & (FilePermissions.S_IRUSR | FilePermissions.S_IRGRP | FilePermissions.S_IROTH)) >> 2);
|
||||
permissions &= ~(FilePermissions.S_IXUSR | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH);
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public override bool IsValidFilePermissionMask(string mask)
|
||||
public override bool IsValidFolderPermissionMask(string mask)
|
||||
{
|
||||
try
|
||||
{
|
||||
var permissions = NativeConvert.FromOctalPermissionString(mask);
|
||||
|
||||
if ((permissions & (FilePermissions.S_ISUID | FilePermissions.S_ISGID | FilePermissions.S_ISVTX)) != 0)
|
||||
if ((permissions & ~FilePermissions.ACCESSPERMS) != 0)
|
||||
{
|
||||
// Only allow access permissions
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((permissions & (FilePermissions.S_IXUSR | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((permissions & (FilePermissions.S_IRUSR | FilePermissions.S_IWUSR)) != (FilePermissions.S_IRUSR | FilePermissions.S_IWUSR))
|
||||
if ((permissions & FilePermissions.S_IRWXU) != FilePermissions.S_IRWXU)
|
||||
{
|
||||
// We expect at least full owner permissions (700)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -281,9 +313,7 @@ namespace NzbDrone.Mono.Disk
|
||||
// Catch the exception and attempt to handle these edgecases
|
||||
|
||||
// Mono 6.x till 6.10 doesn't properly try use rename first.
|
||||
if (move &&
|
||||
((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() < new Version(6, 10)) ||
|
||||
(PlatformInfo.Platform == PlatformType.NetCore)))
|
||||
if (move && (PlatformInfo.Platform == PlatformType.NetCore))
|
||||
{
|
||||
if (Syscall.lstat(source, out var sourcestat) == 0 &&
|
||||
Syscall.lstat(destination, out var deststat) != 0 &&
|
||||
@@ -311,32 +341,7 @@ namespace NzbDrone.Mono.Disk
|
||||
var dstInfo = new FileInfo(destination);
|
||||
var exists = dstInfo.Exists && srcInfo.Exists;
|
||||
|
||||
if (PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 6) &&
|
||||
exists && dstInfo.Length == 0 && srcInfo.Length != 0)
|
||||
{
|
||||
// mono >=6.6 bug: zero length file since chmod happens at the start
|
||||
_logger.Debug("{3} failed to {2} file likely due to known {3} bug, attempting to {2} directly. '{0}' -> '{1}'", source, destination, move ? "move" : "copy", PlatformInfo.PlatformName);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Trace("Copying content from {0} to {1} ({2} bytes)", source, destination, srcInfo.Length);
|
||||
using (var srcStream = new FileStream(source, FileMode.Open, FileAccess.Read))
|
||||
using (var dstStream = new FileStream(destination, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
srcStream.CopyTo(dstStream);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If it fails again then bail
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else if (((PlatformInfo.Platform == PlatformType.Mono &&
|
||||
PlatformInfo.GetVersion() >= new Version(6, 0) &&
|
||||
PlatformInfo.GetVersion() < new Version(6, 6)) ||
|
||||
PlatformInfo.Platform == PlatformType.NetCore) &&
|
||||
exists && dstInfo.Length == srcInfo.Length)
|
||||
if (PlatformInfo.Platform == PlatformType.NetCore && exists && dstInfo.Length == srcInfo.Length)
|
||||
{
|
||||
// mono 6.0, mono 6.4 and netcore 3.1 bug: full length file since utime and chmod happens at the end
|
||||
_logger.Debug("{3} failed to {2} file likely due to known {3} bug, attempting to {2} directly. '{0}' -> '{1}'", source, destination, move ? "move" : "copy", PlatformInfo.PlatformName);
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace NzbDrone.Mono.Disk
|
||||
{ "apfs", DriveType.Fixed },
|
||||
{ "fuse.mergerfs", DriveType.Fixed },
|
||||
{ "fuse.glusterfs", DriveType.Network },
|
||||
{ "nullfs", DriveType.Fixed },
|
||||
{ "zfs", DriveType.Fixed }
|
||||
};
|
||||
|
||||
|
||||
69
src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs
Normal file
69
src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Npgsql;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Test.Common.Datastore
|
||||
{
|
||||
public static class PostgresDatabase
|
||||
{
|
||||
public static PostgresOptions GetTestOptions()
|
||||
{
|
||||
var options = PostgresOptions.GetOptions();
|
||||
|
||||
var uid = TestBase.GetUID();
|
||||
options.MainDb = uid + "_main";
|
||||
options.LogDb = uid + "_log";
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static void Create(PostgresOptions options, MigrationType migrationType)
|
||||
{
|
||||
var db = GetDatabaseName(options, migrationType);
|
||||
var connectionString = GetConnectionString(options);
|
||||
using var conn = new NpgsqlConnection(connectionString);
|
||||
conn.Open();
|
||||
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"CREATE DATABASE \"{db}\" WITH OWNER = {options.User} ENCODING = 'UTF8' CONNECTION LIMIT = -1;";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public static void Drop(PostgresOptions options, MigrationType migrationType)
|
||||
{
|
||||
var db = GetDatabaseName(options, migrationType);
|
||||
var connectionString = GetConnectionString(options);
|
||||
using var conn = new NpgsqlConnection(connectionString);
|
||||
conn.Open();
|
||||
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"DROP DATABASE \"{db}\" WITH (FORCE);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private static string GetConnectionString(PostgresOptions options)
|
||||
{
|
||||
var builder = new NpgsqlConnectionStringBuilder()
|
||||
{
|
||||
Host = options.Host,
|
||||
Port = options.Port,
|
||||
Username = options.User,
|
||||
Password = options.Password,
|
||||
Enlist = false
|
||||
};
|
||||
|
||||
return builder.ConnectionString;
|
||||
}
|
||||
|
||||
private static string GetDatabaseName(PostgresOptions options, MigrationType migrationType)
|
||||
{
|
||||
return migrationType switch
|
||||
{
|
||||
MigrationType.Main => options.MainDb,
|
||||
MigrationType.Log => options.LogDb,
|
||||
_ => throw new NotImplementedException("Unknown migration type")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs
Normal file
14
src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Test.Common.Datastore
|
||||
{
|
||||
public static class SqliteDatabase
|
||||
{
|
||||
public static string GetCachedDb(MigrationType type)
|
||||
{
|
||||
return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -9,7 +10,9 @@ using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Processes;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using RestSharp;
|
||||
|
||||
namespace NzbDrone.Test.Common
|
||||
@@ -22,13 +25,15 @@ namespace NzbDrone.Test.Common
|
||||
|
||||
public string AppData { get; private set; }
|
||||
public string ApiKey { get; private set; }
|
||||
public PostgresOptions PostgresOptions { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
|
||||
public NzbDroneRunner(Logger logger, int port = 9696)
|
||||
public NzbDroneRunner(Logger logger, PostgresOptions postgresOptions, int port = 9696)
|
||||
{
|
||||
_processProvider = new ProcessProvider(logger);
|
||||
_restClient = new RestClient($"http://localhost:{port}/api/v1");
|
||||
|
||||
PostgresOptions = postgresOptions;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
@@ -133,9 +138,23 @@ namespace NzbDrone.Test.Common
|
||||
|
||||
private void Start(string outputProwlarrConsoleExe)
|
||||
{
|
||||
StringDictionary envVars = new ();
|
||||
if (PostgresOptions?.Host != null)
|
||||
{
|
||||
envVars.Add("Prowlarr__Postgres__Host", PostgresOptions.Host);
|
||||
envVars.Add("Prowlarr__Postgres__Port", PostgresOptions.Port.ToString());
|
||||
envVars.Add("Prowlarr__Postgres__User", PostgresOptions.User);
|
||||
envVars.Add("Prowlarr__Postgres__Password", PostgresOptions.Password);
|
||||
envVars.Add("Prowlarr__Postgres__MainDb", PostgresOptions.MainDb);
|
||||
envVars.Add("Prowlarr__Postgres__LogDb", PostgresOptions.LogDb);
|
||||
|
||||
TestContext.Progress.WriteLine("Using env vars:\n{0}", envVars.ToJson());
|
||||
}
|
||||
|
||||
TestContext.Progress.WriteLine("Starting instance from {0} on port {1}", outputProwlarrConsoleExe, Port);
|
||||
|
||||
var args = "-nobrowser -nosingleinstancecheck -data=\"" + AppData + "\"";
|
||||
_nzbDroneProcess = _processProvider.Start(outputProwlarrConsoleExe, args, null, OnOutputDataReceived, OnOutputDataReceived);
|
||||
_nzbDroneProcess = _processProvider.Start(outputProwlarrConsoleExe, args, envVars, OnOutputDataReceived, OnOutputDataReceived);
|
||||
}
|
||||
|
||||
private void OnOutputDataReceived(string data)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="RestSharp" Version="106.15.0" />
|
||||
<PackageReference Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
||||
|
||||
@@ -166,14 +166,6 @@ namespace NzbDrone.Test.Common
|
||||
}
|
||||
}
|
||||
|
||||
protected void MonoOnly()
|
||||
{
|
||||
if (!PlatformInfo.IsMono)
|
||||
{
|
||||
throw new IgnoreException("mono specific test");
|
||||
}
|
||||
}
|
||||
|
||||
protected void NotBsd()
|
||||
{
|
||||
if (OsInfo.Os == Os.Bsd)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Prowlarr.Common.csproj" />
|
||||
|
||||
@@ -90,6 +90,12 @@ namespace NzbDrone.Update.UpdateEngine
|
||||
|
||||
Verify(installationFolder, processId);
|
||||
|
||||
if (installationFolder.EndsWith(@"\bin\Prowlarr") || installationFolder.EndsWith(@"/bin/Prowlarr"))
|
||||
{
|
||||
installationFolder = installationFolder.GetParentPath();
|
||||
_logger.Info("Fixed Installation Folder: {0}", installationFolder);
|
||||
}
|
||||
|
||||
var appType = _detectApplicationType.GetAppType();
|
||||
|
||||
_processProvider.FindProcessByName(ProcessProvider.PROWLARR_CONSOLE_PROCESS_NAME);
|
||||
@@ -125,7 +131,7 @@ namespace NzbDrone.Update.UpdateEngine
|
||||
// Set executable flag on app
|
||||
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
|
||||
{
|
||||
_diskProvider.SetPermissions(Path.Combine(installationFolder, "Prowlarr"), "0755");
|
||||
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "Prowlarr"), "755", null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -146,7 +152,7 @@ namespace NzbDrone.Update.UpdateEngine
|
||||
_terminateNzbDrone.Terminate(processId);
|
||||
|
||||
_logger.Info("Waiting for external auto-restart.");
|
||||
for (int i = 0; i < 5; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Test.DiskTests;
|
||||
using NzbDrone.Windows.Disk;
|
||||
|
||||
@@ -12,5 +14,30 @@ namespace NzbDrone.Windows.Test.DiskProviderTests
|
||||
{
|
||||
WindowsOnly();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_drive_doesnt_exist()
|
||||
{
|
||||
// Find a drive that doesn't exist.
|
||||
for (char driveletter = 'Z'; driveletter > 'D'; driveletter--)
|
||||
{
|
||||
if (new DriveInfo(driveletter.ToString()).IsReady)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.Throws<DirectoryNotFoundException>(() => Subject.GetAvailableSpace(driveletter + @":\NOT_A_REAL_PATH\DOES_NOT_EXIST"));
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Inconclusive("No drive available for testing.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_able_to_get_space_on_unc()
|
||||
{
|
||||
var result = Subject.GetAvailableSpace(@"\\localhost\c$\Windows");
|
||||
result.Should().BeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,11 @@ namespace NzbDrone.Windows.Disk
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetPermissions(string path, string mask)
|
||||
public override void SetFilePermissions(string path, string mask, string group)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetPermissions(string path, string mask, string group)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Core\Prowlarr.Core.csproj" />
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Prowlarr.Http.Authentication
|
||||
options.AccessDeniedPath = "/login?loginFailed=true";
|
||||
options.LoginPath = "/login";
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(7);
|
||||
options.SlidingExpiration = true;
|
||||
})
|
||||
.AddApiKey("API", options =>
|
||||
{
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using Prowlarr.Http.Extensions;
|
||||
@@ -15,17 +18,14 @@ namespace Prowlarr.Http.Authentication
|
||||
|
||||
public class AuthenticationService : IAuthenticationService
|
||||
{
|
||||
private const string AnonymousUser = "Anonymous";
|
||||
private static readonly Logger _authLogger = LogManager.GetLogger("Auth");
|
||||
private readonly IUserService _userService;
|
||||
|
||||
private static string API_KEY;
|
||||
private static AuthenticationType AUTH_METHOD;
|
||||
|
||||
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
API_KEY = configFileProvider.ApiKey;
|
||||
AUTH_METHOD = configFileProvider.AuthenticationMethod;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="ImpromptuInterface" Version="7.0.1" />
|
||||
<PackageReference Include="NLog" Version="4.7.14" />
|
||||
<PackageReference Include="ImpromptuInterface" Version="7.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Core\Prowlarr.Core.csproj" />
|
||||
|
||||
11
src/postgres.runsettings
Normal file
11
src/postgres.runsettings
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<RunConfiguration>
|
||||
<EnvironmentVariables>
|
||||
<Prowlarr__Postgres__Host>192.168.100.5</Prowlarr__Postgres__Host>
|
||||
<Prowlarr__Postgres__Port>5432</Prowlarr__Postgres__Port>
|
||||
<Prowlarr__Postgres__User>abc</Prowlarr__Postgres__User>
|
||||
<Prowlarr__Postgres__Password>abc</Prowlarr__Postgres__Password>
|
||||
</EnvironmentVariables>
|
||||
</RunConfiguration>
|
||||
</RunSettings>
|
||||
12
yarn.lock
12
yarn.lock
@@ -1150,15 +1150,15 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@microsoft/signalr@6.0.3":
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-6.0.3.tgz#9904efd48cd488e3c1c80930ff9fbb3c9f55895d"
|
||||
integrity sha512-wWGVC2xi8OxNjyir8iQWuyxWHy3Dkakk2Q3VreCE7pDzFAgZ4pId6abJlRPMVIQxkUvUGc8knMW5l3sv2bJ/yw==
|
||||
"@microsoft/signalr@6.0.6":
|
||||
version "6.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-6.0.6.tgz#6ba53623a64df64a80126694db4e37146647d3fe"
|
||||
integrity sha512-3dTLtgwEXUeE9R/3NZQslh1B2WbppeHVXdnpvSZc7Yz+tP5Yiw3KCVUHwKUVnYmd9/2v3DaI/pvCdihrss49zA==
|
||||
dependencies:
|
||||
abort-controller "^3.0.0"
|
||||
eventsource "^1.0.7"
|
||||
fetch-cookie "^0.11.0"
|
||||
node-fetch "^2.6.1"
|
||||
node-fetch "^2.6.7"
|
||||
ws "^7.4.5"
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
@@ -4774,7 +4774,7 @@ no-case@^3.0.4:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
|
||||
Reference in New Issue
Block a user