1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-10-29 23:22:39 +01:00

New: Group updates for the same movie for Kodi and Emby / Jellyfin

(cherry picked from commit 46c7de379c872f757847a311b21714e905466360)

Closes #10150
This commit is contained in:
Mark McDowall 2024-07-06 15:57:14 -07:00 committed by Bogdan
parent d5fb1c55c6
commit 017fa5ad80
7 changed files with 210 additions and 115 deletions

View File

@ -33,9 +33,10 @@ public void Setup()
Subject.Definition = new NotificationDefinition();
Subject.Definition.Settings = new XbmcSettings
{
UpdateLibrary = true
};
{
Host = "localhost",
UpdateLibrary = true
};
}
private void GivenOldFiles()
@ -48,16 +49,18 @@ private void GivenOldFiles()
.ToList();
Subject.Definition.Settings = new XbmcSettings
{
UpdateLibrary = true,
CleanLibrary = true
};
{
Host = "localhost",
UpdateLibrary = true,
CleanLibrary = true
};
}
[Test]
public void should_not_clean_if_no_movie_was_replaced()
{
Subject.OnDownload(_downloadMessage);
Subject.ProcessQueue();
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Never());
}
@ -67,6 +70,7 @@ public void should_clean_if_movie_was_replaced()
{
GivenOldFiles();
Subject.OnDownload(_downloadMessage);
Subject.ProcessQueue();
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Once());
}

View File

@ -45,7 +45,7 @@ public void should_update_using_movie_path()
.With(s => s.ImdbId = IMDB_ID)
.Build();
Subject.UpdateMovie(_settings, movie);
Subject.Update(_settings, movie);
Mocker.GetMock<IXbmcJsonApiProxy>()
.Verify(v => v.UpdateLibrary(_settings, It.IsAny<string>()), Times.Once());
@ -59,7 +59,7 @@ public void should_update_all_paths_when_movie_path_not_found()
.With(s => s.Title = "Not A Real Movie")
.Build();
Subject.UpdateMovie(_settings, fakeMovie);
Subject.Update(_settings, fakeMovie);
Mocker.GetMock<IXbmcJsonApiProxy>()
.Verify(v => v.UpdateLibrary(_settings, null), Times.Once());

View File

@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
@ -9,10 +12,18 @@ namespace NzbDrone.Core.Notifications.Emby
public class MediaBrowser : NotificationBase<MediaBrowserSettings>
{
private readonly IMediaBrowserService _mediaBrowserService;
private readonly MediaServerUpdateQueue<MediaBrowser, string> _updateQueue;
private readonly Logger _logger;
public MediaBrowser(IMediaBrowserService mediaBrowserService)
private static string Created = "Created";
private static string Deleted = "Deleted";
private static string Modified = "Modified";
public MediaBrowser(IMediaBrowserService mediaBrowserService, ICacheManager cacheManager, Logger logger)
{
_mediaBrowserService = mediaBrowserService;
_updateQueue = new MediaServerUpdateQueue<MediaBrowser, string>(cacheManager);
_logger = logger;
}
public override string Link => "https://emby.media/";
@ -33,18 +44,12 @@ public override void OnDownload(DownloadMessage message)
_mediaBrowserService.Notify(Settings, MOVIE_DOWNLOADED_TITLE_BRANDED, message.Message);
}
if (Settings.UpdateLibrary)
{
_mediaBrowserService.Update(Settings, message.Movie, "Created");
}
UpdateIfEnabled(message.Movie, Created);
}
public override void OnMovieRename(Movie movie, List<RenamedMovieFile> renamedFiles)
{
if (Settings.UpdateLibrary)
{
_mediaBrowserService.Update(Settings, movie, "Modified");
}
UpdateIfEnabled(movie, Modified);
}
public override void OnHealthIssue(HealthCheck.HealthCheck message)
@ -80,10 +85,7 @@ public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
_mediaBrowserService.Notify(Settings, MOVIE_DELETED_TITLE_BRANDED, deleteMessage.Message);
}
if (Settings.UpdateLibrary)
{
_mediaBrowserService.Update(Settings, deleteMessage.Movie, "Deleted");
}
UpdateIfEnabled(deleteMessage.Movie, Deleted);
}
}
@ -94,9 +96,34 @@ public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
_mediaBrowserService.Notify(Settings, MOVIE_FILE_DELETED_TITLE_BRANDED, deleteMessage.Message);
}
UpdateIfEnabled(deleteMessage.Movie, Deleted);
}
public override void ProcessQueue()
{
_updateQueue.ProcessQueue(Settings.Host, (items) =>
{
if (Settings.UpdateLibrary)
{
_logger.Debug("Performing library update for {0} movies", items.Count);
items.ForEach(item =>
{
// If there is only one update type for the movie use that, otherwise send null and let Emby decide
var updateType = item.Info.Count == 1 ? item.Info.First() : null;
_mediaBrowserService.Update(Settings, item.Movie, updateType);
});
}
});
}
private void UpdateIfEnabled(Movie movie, string updateType)
{
if (Settings.UpdateLibrary)
{
_mediaBrowserService.Update(Settings, deleteMessage.Movie, "Deleted");
_logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title);
_updateQueue.Add(Settings.Host, movie, updateType);
}
}

View File

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Notifications
{
public class MediaServerUpdateQueue<TQueueHost, TItemInfo>
where TQueueHost : class
{
private class UpdateQueue
{
public Dictionary<int, UpdateQueueItem<TItemInfo>> Pending { get; } = new ();
public bool Refreshing { get; set; }
}
private readonly ICached<UpdateQueue> _pendingMoviesCache;
public MediaServerUpdateQueue(ICacheManager cacheManager)
{
_pendingMoviesCache = cacheManager.GetRollingCache<UpdateQueue>(typeof(TQueueHost), "pendingMovies", TimeSpan.FromDays(1));
}
public void Add(string identifier, Movie movie, TItemInfo info)
{
var queue = _pendingMoviesCache.Get(identifier, () => new UpdateQueue());
lock (queue)
{
var item = queue.Pending.TryGetValue(movie.Id, out var value)
? value
: new UpdateQueueItem<TItemInfo>(movie);
item.Info.Add(info);
queue.Pending[movie.Id] = item;
}
}
public void ProcessQueue(string identifier, Action<List<UpdateQueueItem<TItemInfo>>> update)
{
var queue = _pendingMoviesCache.Find(identifier);
if (queue == null)
{
return;
}
lock (queue)
{
if (queue.Refreshing)
{
return;
}
queue.Refreshing = true;
}
try
{
while (true)
{
List<UpdateQueueItem<TItemInfo>> items;
lock (queue)
{
if (queue.Pending.Empty())
{
queue.Refreshing = false;
return;
}
items = queue.Pending.Values.ToList();
queue.Pending.Clear();
}
update(items);
}
}
catch
{
lock (queue)
{
queue.Refreshing = false;
}
throw;
}
}
}
public class UpdateQueueItem<TItemInfo>
{
public Movie Movie { get; set; }
public HashSet<TItemInfo> Info { get; set; }
public UpdateQueueItem(Movie movie)
{
Movie = movie;
Info = new HashSet<TItemInfo>();
}
}
}

View File

@ -18,23 +18,15 @@ public class PlexServer : NotificationBase<PlexServerSettings>
{
private readonly IPlexServerService _plexServerService;
private readonly IPlexTvService _plexTvService;
private readonly MediaServerUpdateQueue<PlexServer, bool> _updateQueue;
private readonly Logger _logger;
private class PlexUpdateQueue
{
public Dictionary<int, Movie> Pending { get; } = new Dictionary<int, Movie>();
public bool Refreshing { get; set; }
}
private readonly ICached<PlexUpdateQueue> _pendingMoviesCache;
public PlexServer(IPlexServerService plexServerService, IPlexTvService plexTvService, ICacheManager cacheManager, Logger logger)
{
_plexServerService = plexServerService;
_plexTvService = plexTvService;
_updateQueue = new MediaServerUpdateQueue<PlexServer, bool>(cacheManager);
_logger = logger;
_pendingMoviesCache = cacheManager.GetRollingCache<PlexUpdateQueue>(GetType(), "pendingSeries", TimeSpan.FromDays(1));
}
public override string Link => "https://www.plex.tv/";
@ -70,66 +62,20 @@ private void UpdateIfEnabled(Movie movie)
if (Settings.UpdateLibrary)
{
_logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title);
var queue = _pendingMoviesCache.Get(Settings.Host, () => new PlexUpdateQueue());
lock (queue)
{
queue.Pending[movie.Id] = movie;
}
_updateQueue.Add(Settings.Host, movie, false);
}
}
public override void ProcessQueue()
{
var queue = _pendingMoviesCache.Find(Settings.Host);
if (queue == null)
_updateQueue.ProcessQueue(Settings.Host, (items) =>
{
return;
}
lock (queue)
{
if (queue.Refreshing)
if (Settings.UpdateLibrary)
{
return;
_logger.Debug("Performing library update for {0} movies", items.Count);
_plexServerService.UpdateLibrary(items.Select(i => i.Movie), Settings);
}
queue.Refreshing = true;
}
try
{
while (true)
{
List<Movie> refreshingMovies;
lock (queue)
{
if (queue.Pending.Empty())
{
queue.Refreshing = false;
return;
}
refreshingMovies = queue.Pending.Values.ToList();
queue.Pending.Clear();
}
if (Settings.UpdateLibrary)
{
_logger.Debug("Performing library update for {0} movies", refreshingMovies.Count);
_plexServerService.UpdateLibrary(refreshingMovies, Settings);
}
}
}
catch
{
lock (queue)
{
queue.Refreshing = false;
}
throw;
}
});
}
public override ValidationResult Test()
@ -203,13 +149,6 @@ public override object RequestAction(string action, IDictionary<string, string>
{
var result = new List<FieldSelectStringOption>();
// result.Add(new FieldSelectStringOption
// {
// Value = s.Name,
// Name = s.Name,
// IsDisabled = true
// });
s.Connections.ForEach(c =>
{
var isSecure = c.Protocol == "https";

View File

@ -3,6 +3,7 @@
using System.Net.Sockets;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
@ -12,11 +13,13 @@ namespace NzbDrone.Core.Notifications.Xbmc
public class Xbmc : NotificationBase<XbmcSettings>
{
private readonly IXbmcService _xbmcService;
private readonly MediaServerUpdateQueue<Xbmc, bool> _updateQueue;
private readonly Logger _logger;
public Xbmc(IXbmcService xbmcService, Logger logger)
public Xbmc(IXbmcService xbmcService, ICacheManager cacheManager, Logger logger)
{
_xbmcService = xbmcService;
_updateQueue = new MediaServerUpdateQueue<Xbmc, bool>(cacheManager);
_logger = logger;
}
@ -34,12 +37,12 @@ public override void OnDownload(DownloadMessage message)
const string header = "Radarr - Downloaded";
Notify(Settings, header, message.Message);
UpdateAndCleanMovie(message.Movie, message.OldMovieFiles.Any());
UpdateAndClean(message.Movie, message.OldMovieFiles.Any());
}
public override void OnMovieRename(Movie movie, List<RenamedMovieFile> renamedFiles)
{
UpdateAndCleanMovie(movie);
UpdateAndClean(movie);
}
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
@ -47,7 +50,7 @@ public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
const string header = "Radarr - Deleted";
Notify(Settings, header, deleteMessage.Message);
UpdateAndCleanMovie(deleteMessage.Movie, true);
UpdateAndClean(deleteMessage.Movie, true);
}
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
@ -57,7 +60,7 @@ public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
const string header = "Radarr - Deleted";
Notify(Settings, header, deleteMessage.Message);
UpdateAndCleanMovie(deleteMessage.Movie, true);
UpdateAndClean(deleteMessage.Movie, true);
}
}
@ -83,6 +86,35 @@ public override void OnManualInteractionRequired(ManualInteractionRequiredMessag
public override string Name => "Kodi";
public override void ProcessQueue()
{
_updateQueue.ProcessQueue(Settings.Host, (items) =>
{
_logger.Debug("Performing library update for {0} movies", items.Count);
items.ForEach(item =>
{
try
{
if (Settings.UpdateLibrary)
{
_xbmcService.Update(Settings, item.Movie);
}
if (item.Info.Contains(true) && Settings.CleanLibrary)
{
_xbmcService.Clean(Settings);
}
}
catch (SocketException ex)
{
var logMessage = string.Format("Unable to connect to Kodi Host: {0}:{1}", Settings.Host, Settings.Port);
_logger.Debug(ex, logMessage);
}
});
});
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
@ -108,24 +140,12 @@ private void Notify(XbmcSettings settings, string header, string message)
}
}
private void UpdateAndCleanMovie(Movie movie, bool clean = true)
private void UpdateAndClean(Movie movie, bool clean = true)
{
try
if (Settings.UpdateLibrary || Settings.CleanLibrary)
{
if (Settings.UpdateLibrary)
{
_xbmcService.UpdateMovie(Settings, movie);
}
if (clean && Settings.CleanLibrary)
{
_xbmcService.Clean(Settings);
}
}
catch (SocketException ex)
{
var logMessage = string.Format("Unable to connect to Kodi Host: {0}:{1}", Settings.Host, Settings.Port);
_logger.Debug(ex, logMessage);
_logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title);
_updateQueue.Add(Settings.Host, movie, clean);
}
}
}

View File

@ -11,7 +11,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
public interface IXbmcService
{
void Notify(XbmcSettings settings, string title, string message);
void UpdateMovie(XbmcSettings settings, Movie movie);
void Update(XbmcSettings settings, Movie movie);
void Clean(XbmcSettings settings);
ValidationFailure Test(XbmcSettings settings, string message);
}
@ -34,7 +34,7 @@ public void Notify(XbmcSettings settings, string title, string message)
_proxy.Notify(settings, title, message);
}
public void UpdateMovie(XbmcSettings settings, Movie movie)
public void Update(XbmcSettings settings, Movie movie)
{
if (CheckIfVideoPlayerOpen(settings))
{