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

(cherry picked from commit 46c7de379c872f757847a311b21714e905466360)

Closes #10150
pull/10580/head
Mark McDowall 6 months ago committed by Bogdan
parent d5fb1c55c6
commit 017fa5ad80

@ -34,6 +34,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
Subject.Definition = new NotificationDefinition(); Subject.Definition = new NotificationDefinition();
Subject.Definition.Settings = new XbmcSettings Subject.Definition.Settings = new XbmcSettings
{ {
Host = "localhost",
UpdateLibrary = true UpdateLibrary = true
}; };
} }
@ -49,6 +50,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
Subject.Definition.Settings = new XbmcSettings Subject.Definition.Settings = new XbmcSettings
{ {
Host = "localhost",
UpdateLibrary = true, UpdateLibrary = true,
CleanLibrary = true CleanLibrary = true
}; };
@ -58,6 +60,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
public void should_not_clean_if_no_movie_was_replaced() public void should_not_clean_if_no_movie_was_replaced()
{ {
Subject.OnDownload(_downloadMessage); Subject.OnDownload(_downloadMessage);
Subject.ProcessQueue();
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Never()); Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Never());
} }
@ -67,6 +70,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
{ {
GivenOldFiles(); GivenOldFiles();
Subject.OnDownload(_downloadMessage); Subject.OnDownload(_downloadMessage);
Subject.ProcessQueue();
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Once()); Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Once());
} }

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

@ -1,5 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results; using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
@ -9,10 +12,18 @@ namespace NzbDrone.Core.Notifications.Emby
public class MediaBrowser : NotificationBase<MediaBrowserSettings> public class MediaBrowser : NotificationBase<MediaBrowserSettings>
{ {
private readonly IMediaBrowserService _mediaBrowserService; 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; _mediaBrowserService = mediaBrowserService;
_updateQueue = new MediaServerUpdateQueue<MediaBrowser, string>(cacheManager);
_logger = logger;
} }
public override string Link => "https://emby.media/"; public override string Link => "https://emby.media/";
@ -33,18 +44,12 @@ namespace NzbDrone.Core.Notifications.Emby
_mediaBrowserService.Notify(Settings, MOVIE_DOWNLOADED_TITLE_BRANDED, message.Message); _mediaBrowserService.Notify(Settings, MOVIE_DOWNLOADED_TITLE_BRANDED, message.Message);
} }
if (Settings.UpdateLibrary) UpdateIfEnabled(message.Movie, Created);
{
_mediaBrowserService.Update(Settings, message.Movie, "Created");
}
} }
public override void OnMovieRename(Movie movie, List<RenamedMovieFile> renamedFiles) public override void OnMovieRename(Movie movie, List<RenamedMovieFile> renamedFiles)
{ {
if (Settings.UpdateLibrary) UpdateIfEnabled(movie, Modified);
{
_mediaBrowserService.Update(Settings, movie, "Modified");
}
} }
public override void OnHealthIssue(HealthCheck.HealthCheck message) public override void OnHealthIssue(HealthCheck.HealthCheck message)
@ -80,10 +85,7 @@ namespace NzbDrone.Core.Notifications.Emby
_mediaBrowserService.Notify(Settings, MOVIE_DELETED_TITLE_BRANDED, deleteMessage.Message); _mediaBrowserService.Notify(Settings, MOVIE_DELETED_TITLE_BRANDED, deleteMessage.Message);
} }
if (Settings.UpdateLibrary) UpdateIfEnabled(deleteMessage.Movie, Deleted);
{
_mediaBrowserService.Update(Settings, deleteMessage.Movie, "Deleted");
}
} }
} }
@ -94,9 +96,34 @@ namespace NzbDrone.Core.Notifications.Emby
_mediaBrowserService.Notify(Settings, MOVIE_FILE_DELETED_TITLE_BRANDED, deleteMessage.Message); _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) 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);
} }
} }

@ -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>();
}
}
}

@ -18,23 +18,15 @@ namespace NzbDrone.Core.Notifications.Plex.Server
{ {
private readonly IPlexServerService _plexServerService; private readonly IPlexServerService _plexServerService;
private readonly IPlexTvService _plexTvService; private readonly IPlexTvService _plexTvService;
private readonly MediaServerUpdateQueue<PlexServer, bool> _updateQueue;
private readonly Logger _logger; 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) public PlexServer(IPlexServerService plexServerService, IPlexTvService plexTvService, ICacheManager cacheManager, Logger logger)
{ {
_plexServerService = plexServerService; _plexServerService = plexServerService;
_plexTvService = plexTvService; _plexTvService = plexTvService;
_updateQueue = new MediaServerUpdateQueue<PlexServer, bool>(cacheManager);
_logger = logger; _logger = logger;
_pendingMoviesCache = cacheManager.GetRollingCache<PlexUpdateQueue>(GetType(), "pendingSeries", TimeSpan.FromDays(1));
} }
public override string Link => "https://www.plex.tv/"; public override string Link => "https://www.plex.tv/";
@ -70,66 +62,20 @@ namespace NzbDrone.Core.Notifications.Plex.Server
if (Settings.UpdateLibrary) if (Settings.UpdateLibrary)
{ {
_logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title); _logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title);
var queue = _pendingMoviesCache.Get(Settings.Host, () => new PlexUpdateQueue()); _updateQueue.Add(Settings.Host, movie, false);
lock (queue)
{
queue.Pending[movie.Id] = movie;
}
} }
} }
public override void ProcessQueue() public override void ProcessQueue()
{ {
var queue = _pendingMoviesCache.Find(Settings.Host); _updateQueue.ProcessQueue(Settings.Host, (items) =>
if (queue == null)
{
return;
}
lock (queue)
{
if (queue.Refreshing)
{
return;
}
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) if (Settings.UpdateLibrary)
{ {
_logger.Debug("Performing library update for {0} movies", refreshingMovies.Count); _logger.Debug("Performing library update for {0} movies", items.Count);
_plexServerService.UpdateLibrary(refreshingMovies, Settings); _plexServerService.UpdateLibrary(items.Select(i => i.Movie), Settings);
}
}
}
catch
{
lock (queue)
{
queue.Refreshing = false;
}
throw;
} }
});
} }
public override ValidationResult Test() public override ValidationResult Test()
@ -203,13 +149,6 @@ namespace NzbDrone.Core.Notifications.Plex.Server
{ {
var result = new List<FieldSelectStringOption>(); var result = new List<FieldSelectStringOption>();
// result.Add(new FieldSelectStringOption
// {
// Value = s.Name,
// Name = s.Name,
// IsDisabled = true
// });
s.Connections.ForEach(c => s.Connections.ForEach(c =>
{ {
var isSecure = c.Protocol == "https"; var isSecure = c.Protocol == "https";

@ -3,6 +3,7 @@ using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
@ -12,11 +13,13 @@ namespace NzbDrone.Core.Notifications.Xbmc
public class Xbmc : NotificationBase<XbmcSettings> public class Xbmc : NotificationBase<XbmcSettings>
{ {
private readonly IXbmcService _xbmcService; private readonly IXbmcService _xbmcService;
private readonly MediaServerUpdateQueue<Xbmc, bool> _updateQueue;
private readonly Logger _logger; private readonly Logger _logger;
public Xbmc(IXbmcService xbmcService, Logger logger) public Xbmc(IXbmcService xbmcService, ICacheManager cacheManager, Logger logger)
{ {
_xbmcService = xbmcService; _xbmcService = xbmcService;
_updateQueue = new MediaServerUpdateQueue<Xbmc, bool>(cacheManager);
_logger = logger; _logger = logger;
} }
@ -34,12 +37,12 @@ namespace NzbDrone.Core.Notifications.Xbmc
const string header = "Radarr - Downloaded"; const string header = "Radarr - Downloaded";
Notify(Settings, header, message.Message); 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) public override void OnMovieRename(Movie movie, List<RenamedMovieFile> renamedFiles)
{ {
UpdateAndCleanMovie(movie); UpdateAndClean(movie);
} }
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage) public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
@ -47,7 +50,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
const string header = "Radarr - Deleted"; const string header = "Radarr - Deleted";
Notify(Settings, header, deleteMessage.Message); Notify(Settings, header, deleteMessage.Message);
UpdateAndCleanMovie(deleteMessage.Movie, true); UpdateAndClean(deleteMessage.Movie, true);
} }
public override void OnMovieDelete(MovieDeleteMessage deleteMessage) public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
@ -57,7 +60,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
const string header = "Radarr - Deleted"; const string header = "Radarr - Deleted";
Notify(Settings, header, deleteMessage.Message); Notify(Settings, header, deleteMessage.Message);
UpdateAndCleanMovie(deleteMessage.Movie, true); UpdateAndClean(deleteMessage.Movie, true);
} }
} }
@ -83,6 +86,35 @@ namespace NzbDrone.Core.Notifications.Xbmc
public override string Name => "Kodi"; 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() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();
@ -108,24 +140,12 @@ namespace NzbDrone.Core.Notifications.Xbmc
} }
} }
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) _logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title);
{ _updateQueue.Add(Settings.Host, movie, clean);
_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);
} }
} }
} }

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

Loading…
Cancel
Save