1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-31 18:36:15 -04:00

Compare commits

...

11 Commits

18 changed files with 384 additions and 38 deletions

View File

@@ -48,11 +48,11 @@
<Private>True</Private>
</Reference>
<Reference Include="Org.Mentalis, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\Org.Mentalis.dll</HintPath>
<HintPath>..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\Org.Mentalis.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SocksWebProxy, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\SocksWebProxy.dll</HintPath>
<Reference Include="SocksWebProxy, Version=1.3.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\SocksWebProxy.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNet4.SocksProxy" version="1.1.0.0" targetFramework="net40" />
<package id="DotNet4.SocksProxy" version="1.3.2.0" targetFramework="net40" />
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="4.3.4" targetFramework="net40" />

View File

@@ -111,23 +111,32 @@ namespace NzbDrone.Core.Extras.Files
public void HandleAsync(EpisodeFileDeletedEvent message)
{
var episodeFile = message.EpisodeFile;
var series = _seriesService.GetSeries(message.EpisodeFile.SeriesId);
foreach (var extra in _repository.GetFilesByEpisodeFile(episodeFile.Id))
if (message.Reason == DeleteMediaFileReason.NoLinkedEpisodes)
{
var path = Path.Combine(series.Path, extra.RelativePath);
_logger.Debug("Removing episode file from DB as part of cleanup routine, not deleting extra files from disk.");
}
if (_diskProvider.FileExists(path))
else
{
var series = _seriesService.GetSeries(message.EpisodeFile.SeriesId);
foreach (var extra in _repository.GetFilesByEpisodeFile(episodeFile.Id))
{
if (PermanentlyDelete)
{
_diskProvider.DeleteFile(path);
}
var path = Path.Combine(series.Path, extra.RelativePath);
else
if (_diskProvider.FileExists(path))
{
// Send extra files to the recycling bin so they can be recovered if necessary
_recycleBinProvider.DeleteFile(path);
if (PermanentlyDelete)
{
_diskProvider.DeleteFile(path);
}
else
{
// Send extra files to the recycling bin so they can be recovered if necessary
_recycleBinProvider.DeleteFile(path);
}
}
}
}

View File

@@ -236,7 +236,7 @@ namespace NzbDrone.Core.History
{
if (message.Reason == DeleteMediaFileReason.NoLinkedEpisodes)
{
_logger.Debug("Removing episode file from DB as part of cleanup routine.");
_logger.Debug("Removing episode file from DB as part of cleanup routine, not creating history event.");
return;
}

View File

@@ -5,7 +5,7 @@ namespace NzbDrone.Core.Notifications.Plex.Models
{
public class PlexPreferences
{
[JsonProperty("_children")]
[JsonProperty("Setting")]
public List<PlexPreference> Preferences { get; set; }
}
@@ -15,4 +15,10 @@ namespace NzbDrone.Core.Notifications.Plex.Models
public string Type { get; set; }
public string Value { get; set; }
}
public class PlexPreferencesLegacy
{
[JsonProperty("_children")]
public List<PlexPreference> Preferences { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace NzbDrone.Core.Notifications.Plex.Models
{
public class PlexResponse<T>
{
public T MediaContainer { get; set; }
}
}

View File

@@ -3,11 +3,10 @@ using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Plex.Models
{
public class PlexSectionDetails
public class PlexSectionLocation
{
public int Id { get; set; }
public string Path { get; set; }
public string Language { get; set; }
}
public class PlexSection
@@ -18,13 +17,31 @@ namespace NzbDrone.Core.Notifications.Plex.Models
public string Type { get; set; }
public string Language { get; set; }
[JsonProperty("_children")]
public List<PlexSectionDetails> Sections { get; set; }
[JsonProperty("Location")]
public List<PlexSectionLocation> Locations { get; set; }
}
public class PlexMediaContainer
public class PlexSectionsContainer
{
[JsonProperty("Directory")]
public List<PlexSection> Sections { get; set; }
}
public class PlexSectionLegacy
{
[JsonProperty("key")]
public int Id { get; set; }
public string Type { get; set; }
public string Language { get; set; }
[JsonProperty("_children")]
public List<PlexSectionLocation> Locations { get; set; }
}
public class PlexMediaContainerLegacy
{
[JsonProperty("_children")]
public List<PlexSection> Directories { get; set; }
public List<PlexSectionLegacy> Sections { get; set; }
}
}

View File

@@ -12,6 +12,12 @@ namespace NzbDrone.Core.Notifications.Plex.Models
}
public class PlexSectionResponse
{
[JsonProperty("Metadata")]
public List<PlexSectionItem> Items { get; set; }
}
public class PlexSectionResponseLegacy
{
[JsonProperty("_children")]
public List<PlexSectionItem> Items { get; set; }

View File

@@ -44,8 +44,24 @@ namespace NzbDrone.Core.Notifications.Plex
_logger.Trace("Sections response: {0}", response.Content);
CheckForError(response, settings);
return Json.Deserialize<PlexMediaContainer>(response.Content)
.Directories
if (response.Content.Contains("_children"))
{
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
.Sections
.Where(d => d.Type == "show")
.Select(s => new PlexSection
{
Id = s.Id,
Language = s.Language,
Locations = s.Locations,
Type = s.Type
})
.ToList();
}
return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response.Content)
.MediaContainer
.Sections
.Where(d => d.Type == "show")
.ToList();
}
@@ -81,7 +97,15 @@ namespace NzbDrone.Core.Notifications.Plex
_logger.Trace("Version response: {0}", response.Content);
CheckForError(response, settings);
return Json.Deserialize<PlexIdentity>(response.Content).Version;
if (response.Content.Contains("_children"))
{
return Json.Deserialize<PlexIdentity>(response.Content)
.Version;
}
return Json.Deserialize<PlexResponse<PlexIdentity>>(response.Content)
.MediaContainer
.Version;
}
public List<PlexPreference> Preferences(PlexServerSettings settings)
@@ -93,7 +117,15 @@ namespace NzbDrone.Core.Notifications.Plex
_logger.Trace("Preferences response: {0}", response.Content);
CheckForError(response, settings);
return Json.Deserialize<PlexPreferences>(response.Content).Preferences;
if (response.Content.Contains("_children"))
{
return Json.Deserialize<PlexPreferencesLegacy>(response.Content)
.Preferences;
}
return Json.Deserialize<PlexResponse<PlexPreferences>>(response.Content)
.MediaContainer
.Preferences;
}
public int? GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings)
@@ -107,8 +139,20 @@ namespace NzbDrone.Core.Notifications.Plex
_logger.Trace("Sections response: {0}", response.Content);
CheckForError(response, settings);
var items = Json.Deserialize<PlexSectionResponse>(response.Content)
.Items;
List<PlexSectionItem> items;
if (response.Content.Contains("_children"))
{
items = Json.Deserialize<PlexSectionResponseLegacy>(response.Content)
.Items;
}
else
{
items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(response.Content)
.MediaContainer
.Items;
}
if (items == null || items.Empty())
{
@@ -203,7 +247,15 @@ namespace NzbDrone.Core.Notifications.Plex
throw new PlexAuthenticationException("Unauthorized - Username or password is incorrect");
}
var error = Json.Deserialize<PlexError>(response.Content);
if (response.Content.IsNullOrWhiteSpace())
{
_logger.Trace("No response body returned, no error detected");
return;
}
var error = response.Content.Contains("_children") ?
Json.Deserialize<PlexError>(response.Content) :
Json.Deserialize<PlexResponse<PlexError>>(response.Content).MediaContainer;
if (error != null && !error.Error.IsNullOrWhiteSpace())
{

View File

@@ -19,12 +19,14 @@ namespace NzbDrone.Core.Notifications.Plex
public class PlexServerService : IPlexServerService
{
private readonly ICached<Version> _versionCache;
private readonly ICached<bool> _partialUpdateCache;
private readonly IPlexServerProxy _plexServerProxy;
private readonly Logger _logger;
public PlexServerService(ICacheManager cacheManager, IPlexServerProxy plexServerProxy, Logger logger)
{
_versionCache = cacheManager.GetCache<Version>(GetType(), "versionCache");
_partialUpdateCache = cacheManager.GetCache<bool>(GetType(), "partialUpdateCache");
_plexServerProxy = plexServerProxy;
_logger = logger;
@@ -35,9 +37,12 @@ namespace NzbDrone.Core.Notifications.Plex
try
{
_logger.Debug("Sending Update Request to Plex Server");
var version = _versionCache.Get(settings.Host, () => GetVersion(settings), TimeSpan.FromHours(2));
ValidateVersion(version);
var sections = GetSections(settings);
var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings), TimeSpan.FromHours(2));
var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings, version), TimeSpan.FromHours(2));
if (partialUpdates)
{
@@ -64,13 +69,10 @@ namespace NzbDrone.Core.Notifications.Plex
return _plexServerProxy.GetTvSections(settings).ToList();
}
private bool PartialUpdatesAllowed(PlexServerSettings settings)
private bool PartialUpdatesAllowed(PlexServerSettings settings, Version version)
{
try
{
var rawVersion = GetVersion(settings);
var version = new Version(Regex.Match(rawVersion, @"^(\d+[.-]){4}").Value.Trim('.', '-'));
if (version >= new Version(0, 9, 12, 0))
{
var preferences = GetPreferences(settings);
@@ -92,13 +94,28 @@ namespace NzbDrone.Core.Notifications.Plex
return false;
}
private string GetVersion(PlexServerSettings settings)
private void ValidateVersion(Version version)
{
if (version >= new Version(1, 3, 0) && version < new Version(1, 3, 1))
{
throw new PlexVersionException("Found version {0}, upgrade to PMS 1.3.1 to fix library updating and then restart Sonarr", version);
}
}
private Version GetVersion(PlexServerSettings settings)
{
_logger.Debug("Getting version from Plex host: {0}", settings.Host);
return _plexServerProxy.Version(settings);
var rawVersion = _plexServerProxy.Version(settings);
var version = new Version(Regex.Match(rawVersion, @"^(\d+[.-]){4}").Value.Trim('.', '-'));
return version;
}
private List<PlexPreference> GetPreferences(PlexServerSettings settings)
{
_logger.Debug("Getting preferences from Plex host: {0}", settings.Host);

View File

@@ -0,0 +1,15 @@
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexVersionException : NzbDroneException
{
public PlexVersionException(string message) : base(message)
{
}
public PlexVersionException(string message, params object[] args) : base(message, args)
{
}
}
}

View File

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

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Notifications.Telegram
{
public class Telegram : NotificationBase<TelegramSettings>
{
private readonly ITelegramProxy _proxy;
public Telegram(ITelegramProxy proxy)
{
_proxy = proxy;
}
public override string Link
{
get { return "https://telegram.org/"; }
}
public override void OnGrab(GrabMessage grabMessage)
{
const string title = "Episode Grabbed";
_proxy.SendNotification(title, grabMessage.Message, Settings);
}
public override void OnDownload(DownloadMessage message)
{
const string title = "Episode Downloaded";
_proxy.SendNotification(title, message.Message, Settings);
}
public override void OnRename(Series series)
{
}
public override string Name
{
get
{
return "Telegram";
}
}
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Telegram
{
public class TelegramError
{
public bool Ok { get; set; }
[JsonProperty(PropertyName = "error_code")]
public int ErrorCode { get; set; }
public string Description { get; set; }
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using RestSharp;
using NzbDrone.Core.Rest;
namespace NzbDrone.Core.Notifications.Telegram
{
public interface ITelegramProxy
{
void SendNotification(string title, string message, TelegramSettings settings);
ValidationFailure Test(TelegramSettings settings);
}
public class TelegramProxy : ITelegramProxy
{
private readonly Logger _logger;
private const string URL = "https://api.telegram.org";
public TelegramProxy(Logger logger)
{
_logger = logger;
}
public void SendNotification(string title, string message, TelegramSettings settings)
{
//Format text to add the title before and bold using markdown
var text = $"*{title}*\n{message}";
var client = RestClientFactory.BuildClient(URL);
var request = new RestRequest("bot{token}/sendmessage", Method.POST);
request.AddUrlSegment("token", settings.BotToken);
request.AddParameter("chat_id", settings.ChatId);
request.AddParameter("parse_mode", "Markdown");
request.AddParameter("text", text);
client.ExecuteAndValidate(request);
}
public ValidationFailure Test(TelegramSettings settings)
{
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
SendNotification(title, body, settings);
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message: " + ex.Message);
var restException = ex as RestException;
if (restException != null && restException.Response.StatusCode == HttpStatusCode.BadRequest)
{
var error = Json.Deserialize<TelegramError>(restException.Response.Content);
var property = error.Description.ContainsIgnoreCase("chat not found") ? "ChatId" : "BotToken";
return new ValidationFailure(property, error.Description);
}
return new ValidationFailure("BotToken", "Unable to send test message");
}
return null;
}
}
}

View File

@@ -0,0 +1,40 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Telegram
{
public class TelegramSettingsValidator : AbstractValidator<TelegramSettings>
{
public TelegramSettingsValidator()
{
RuleFor(c => c.BotToken).NotEmpty();
RuleFor(c => c.ChatId).NotEmpty();
}
}
public class TelegramSettings : IProviderConfig
{
private static readonly TelegramSettingsValidator Validator = new TelegramSettingsValidator();
[FieldDefinition(0, Label = "Bot Token", HelpLink = "https://core.telegram.org/bots")]
public string BotToken { get; set; }
[FieldDefinition(1, Label = "Chat ID", HelpLink = "http://stackoverflow.com/a/37396871/882971", HelpText = "You must start a conversation with the bot or add it to your group to receive messages")]
public string ChatId { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrWhiteSpace(ChatId) && !string.IsNullOrWhiteSpace(BotToken);
}
}
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -822,12 +822,14 @@
<Compile Include="Notifications\Boxcar\BoxcarSettings.cs" />
<Compile Include="Notifications\GrabMessage.cs" />
<Compile Include="Notifications\Plex\Models\PlexIdentity.cs" />
<Compile Include="Notifications\Plex\Models\PlexResponse.cs" />
<Compile Include="Notifications\Plex\Models\PlexPreferences.cs" />
<Compile Include="Notifications\Plex\Models\PlexSectionItem.cs" />
<Compile Include="Notifications\Plex\Models\PlexSection.cs" />
<Compile Include="Notifications\Plex\PlexAuthenticationException.cs" />
<Compile Include="Notifications\CustomScript\CustomScript.cs" />
<Compile Include="Notifications\CustomScript\CustomScriptSettings.cs" />
<Compile Include="Notifications\Plex\PlexVersionException.cs" />
<Compile Include="Notifications\Plex\PlexHomeTheater.cs" />
<Compile Include="Notifications\Plex\PlexHomeTheaterSettings.cs" />
<Compile Include="Notifications\Plex\PlexClientService.cs" />
@@ -841,6 +843,10 @@
<Compile Include="Notifications\Synology\SynologyIndexer.cs" />
<Compile Include="Notifications\Synology\SynologyIndexerProxy.cs" />
<Compile Include="Notifications\Synology\SynologyIndexerSettings.cs" />
<Compile Include="Notifications\Telegram\InvalidResponseException.cs" />
<Compile Include="Notifications\Telegram\Telegram.cs" />
<Compile Include="Notifications\Telegram\TelegramService.cs" />
<Compile Include="Notifications\Telegram\TelegramSettings.cs" />
<Compile Include="Notifications\Twitter\OAuthToken.cs" />
<Compile Include="Notifications\Twitter\TwitterException.cs" />
<Compile Include="Notifications\Webhook\WebhookEpisode.cs" />
@@ -1172,6 +1178,7 @@
<Link>libsqlite3.0.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="Notifications\Telegram\TelegramError.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -470,6 +470,10 @@ namespace NzbDrone.Core.Organizer
}
break;
case "MPEG-2 Video":
videoCodec = "MPEG2";
break;
default:
videoCodec = episodeFile.MediaInfo.VideoCodec;
break;