New: Synology Media Indexer support in Connect.
This commit is contained in:
parent
93c6047cd5
commit
15eeb19cd5
|
@ -0,0 +1,102 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Notifications;
|
||||||
|
using NzbDrone.Core.Notifications.Synology;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.NotificationTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SynologyIndexerFixture : CoreTest<SynologyIndexer>
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
private DownloadMessage _upgrade;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
_series = new Series()
|
||||||
|
{
|
||||||
|
Path = @"C:\Test\".AsOsAgnostic()
|
||||||
|
};
|
||||||
|
|
||||||
|
_upgrade = new DownloadMessage()
|
||||||
|
{
|
||||||
|
Series = _series,
|
||||||
|
|
||||||
|
EpisodeFile = new EpisodeFile
|
||||||
|
{
|
||||||
|
RelativePath = "file1.S01E01E02.mkv"
|
||||||
|
},
|
||||||
|
|
||||||
|
OldFiles = new List<EpisodeFile>
|
||||||
|
{
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
RelativePath = "file1.S01E01.mkv"
|
||||||
|
},
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
RelativePath = "file1.S01E02.mkv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Subject.Definition = new NotificationDefinition
|
||||||
|
{
|
||||||
|
Settings = new SynologyIndexerSettings
|
||||||
|
{
|
||||||
|
UpdateLibrary = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_library_if_disabled()
|
||||||
|
{
|
||||||
|
(Subject.Definition.Settings as SynologyIndexerSettings).UpdateLibrary = false;
|
||||||
|
|
||||||
|
Subject.AfterRename(_series);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISynologyIndexerProxy>()
|
||||||
|
.Verify(v => v.UpdateFolder(_series.Path), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_remove_old_episodes_on_upgrade()
|
||||||
|
{
|
||||||
|
Subject.OnDownload(_upgrade);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISynologyIndexerProxy>()
|
||||||
|
.Verify(v => v.DeleteFile(@"C:\Test\file1.S01E01.mkv".AsOsAgnostic()), Times.Once());
|
||||||
|
|
||||||
|
Mocker.GetMock<ISynologyIndexerProxy>()
|
||||||
|
.Verify(v => v.DeleteFile(@"C:\Test\file1.S01E02.mkv".AsOsAgnostic()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_add_new_episode_on_upgrade()
|
||||||
|
{
|
||||||
|
Subject.OnDownload(_upgrade);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISynologyIndexerProxy>()
|
||||||
|
.Verify(v => v.AddFile(@"C:\Test\file1.S01E01E02.mkv".AsOsAgnostic()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_update_entire_series_folder_on_rename()
|
||||||
|
{
|
||||||
|
Subject.AfterRename(_series);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISynologyIndexerProxy>()
|
||||||
|
.Verify(v => v.UpdateFolder(@"C:\Test\".AsOsAgnostic()), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -241,6 +241,7 @@
|
||||||
<Compile Include="MetadataSourceTests\TvdbDataProxyFixture.cs" />
|
<Compile Include="MetadataSourceTests\TvdbDataProxyFixture.cs" />
|
||||||
<Compile Include="MetadataSourceTests\SearchSeriesComparerFixture.cs" />
|
<Compile Include="MetadataSourceTests\SearchSeriesComparerFixture.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
|
||||||
|
<Compile Include="NotificationTests\SynologyIndexerFixture.cs" />
|
||||||
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
|
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
|
||||||
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
|
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
|
||||||
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
|
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Synology
|
||||||
|
{
|
||||||
|
public class SynologyException : NzbDroneException
|
||||||
|
{
|
||||||
|
public SynologyException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynologyException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Synology
|
||||||
|
{
|
||||||
|
public class SynologyIndexer : NotificationBase<SynologyIndexerSettings>
|
||||||
|
{
|
||||||
|
private readonly ISynologyIndexerProxy _indexerProxy;
|
||||||
|
|
||||||
|
public SynologyIndexer(ISynologyIndexerProxy indexerProxy)
|
||||||
|
{
|
||||||
|
_indexerProxy = indexerProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Link
|
||||||
|
{
|
||||||
|
get { return "http://www.synology.com"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGrab(string message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDownload(DownloadMessage message)
|
||||||
|
{
|
||||||
|
if (Settings.UpdateLibrary)
|
||||||
|
{
|
||||||
|
foreach (var oldFile in message.OldFiles)
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(message.Series.Path, oldFile.RelativePath);
|
||||||
|
|
||||||
|
_indexerProxy.DeleteFile(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(message.Series.Path, message.EpisodeFile.RelativePath);
|
||||||
|
|
||||||
|
_indexerProxy.AddFile(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AfterRename(Series series)
|
||||||
|
{
|
||||||
|
if (Settings.UpdateLibrary)
|
||||||
|
{
|
||||||
|
_indexerProxy.UpdateFolder(series.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(TestConnection());
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual ValidationFailure TestConnection()
|
||||||
|
{
|
||||||
|
if (!OsInfo.IsLinux)
|
||||||
|
{
|
||||||
|
return new ValidationFailure(null, "Must be a Synology");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_indexerProxy.Test())
|
||||||
|
{
|
||||||
|
return new ValidationFailure(null, "Not a Synology or synoindex not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Processes;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Synology
|
||||||
|
{
|
||||||
|
public interface ISynologyIndexerProxy
|
||||||
|
{
|
||||||
|
bool Test();
|
||||||
|
void AddFile(string filepath);
|
||||||
|
void DeleteFile(string filepath);
|
||||||
|
void AddFolder(string folderpath);
|
||||||
|
void DeleteFolder(string folderpath);
|
||||||
|
void UpdateFolder(string folderpath);
|
||||||
|
void UpdateLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SynologyIndexerProxy : ISynologyIndexerProxy
|
||||||
|
{
|
||||||
|
private const string SynoIndexPath = "/usr/syno/bin/synoindex";
|
||||||
|
|
||||||
|
private readonly IProcessProvider _processProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public SynologyIndexerProxy(IProcessProvider processProvider, Logger logger)
|
||||||
|
{
|
||||||
|
_processProvider = processProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Test()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ExecuteCommand("--help", false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.WarnException("synoindex not available", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFile(string filePath)
|
||||||
|
{
|
||||||
|
ExecuteCommand("-a " + Escape(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFile(string filePath)
|
||||||
|
{
|
||||||
|
ExecuteCommand("-d " + Escape(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFolder(string folderPath)
|
||||||
|
{
|
||||||
|
ExecuteCommand("-A " + Escape(folderPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFolder(string folderPath)
|
||||||
|
{
|
||||||
|
ExecuteCommand("-D " + Escape(folderPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateFolder(string folderPath)
|
||||||
|
{
|
||||||
|
ExecuteCommand("-R " + Escape(folderPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLibrary()
|
||||||
|
{
|
||||||
|
ExecuteCommand("-R video");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteCommand(string args, bool throwOnStdOut = true)
|
||||||
|
{
|
||||||
|
var output = _processProvider.StartAndCapture(SynoIndexPath, args);
|
||||||
|
|
||||||
|
if (output.Standard.Count != 0 && throwOnStdOut)
|
||||||
|
{
|
||||||
|
throw new SynologyException("synoindex returned an error: {0}", string.Join("\n", output.Standard));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.Error.Count != 0)
|
||||||
|
{
|
||||||
|
throw new SynologyException("synoindex returned an error: {0}", string.Join("\n", output.Error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Escape(string arg)
|
||||||
|
{
|
||||||
|
return string.Format("\"{0}\"", arg.Replace("\"", "\\\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Synology
|
||||||
|
{
|
||||||
|
public class SynologyIndexerSettingsValidator : AbstractValidator<SynologyIndexerSettings>
|
||||||
|
{
|
||||||
|
public SynologyIndexerSettingsValidator()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SynologyIndexerSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly SynologyIndexerSettingsValidator Validator = new SynologyIndexerSettingsValidator();
|
||||||
|
|
||||||
|
public SynologyIndexerSettings()
|
||||||
|
{
|
||||||
|
UpdateLibrary = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Update Library", Type = FieldType.Checkbox, HelpText = "Call synoindex on localhost to update a library file")]
|
||||||
|
public Boolean UpdateLibrary { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -665,6 +665,10 @@
|
||||||
<Compile Include="Metadata\MetadataType.cs" />
|
<Compile Include="Metadata\MetadataType.cs" />
|
||||||
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
||||||
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
||||||
|
<Compile Include="Notifications\Synology\SynologyException.cs" />
|
||||||
|
<Compile Include="Notifications\Synology\SynologyIndexer.cs" />
|
||||||
|
<Compile Include="Notifications\Synology\SynologyIndexerProxy.cs" />
|
||||||
|
<Compile Include="Notifications\Synology\SynologyIndexerSettings.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
||||||
|
|
Loading…
Reference in New Issue