diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index d268a6ba84..550c16b4c4 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -62,7 +62,6 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Controller.Resolvers;
@@ -393,7 +392,7 @@ namespace Emby.Server.Implementations
/// Runs the startup tasks.
///
/// .
- public async Task RunStartupTasksAsync()
+ public Task RunStartupTasksAsync()
{
Logger.LogInformation("Running startup tasks");
@@ -405,38 +404,10 @@ namespace Emby.Server.Implementations
Resolve().SetFFmpegPath();
Logger.LogInformation("ServerId: {ServerId}", SystemId);
-
- var entryPoints = GetExports();
-
- var stopWatch = new Stopwatch();
- stopWatch.Start();
-
- await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false);
- Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
-
Logger.LogInformation("Core startup complete");
CoreStartupHasCompleted = true;
- stopWatch.Restart();
-
- await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
- Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
- stopWatch.Stop();
- }
-
- private IEnumerable StartEntryPoints(IEnumerable entryPoints, bool isBeforeStartup)
- {
- foreach (var entryPoint in entryPoints)
- {
- if (isBeforeStartup != (entryPoint is IRunBeforeStartup))
- {
- continue;
- }
-
- Logger.LogDebug("Starting entry point {Type}", entryPoint.GetType());
-
- yield return entryPoint.RunAsync();
- }
+ return Task.CompletedTask;
}
///
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 83e7b230df..4c668379c8 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -13,19 +13,19 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints;
///
-/// A that notifies users when libraries are updated.
+/// A responsible for notifying users when libraries are updated.
///
-public sealed class LibraryChangedNotifier : IServerEntryPoint
+public sealed class LibraryChangedNotifier : IHostedService, IDisposable
{
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager;
@@ -70,7 +70,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
}
///
- public Task RunAsync()
+ public Task StartAsync(CancellationToken cancellationToken)
{
_libraryManager.ItemAdded += OnLibraryItemAdded;
_libraryManager.ItemUpdated += OnLibraryItemUpdated;
@@ -83,6 +83,20 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
return Task.CompletedTask;
}
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _libraryManager.ItemAdded -= OnLibraryItemAdded;
+ _libraryManager.ItemUpdated -= OnLibraryItemUpdated;
+ _libraryManager.ItemRemoved -= OnLibraryItemRemoved;
+
+ _providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
+ _providerManager.RefreshStarted -= OnProviderRefreshStarted;
+ _providerManager.RefreshProgress -= OnProviderRefreshProgress;
+
+ return Task.CompletedTask;
+ }
+
private void OnProviderRefreshProgress(object? sender, GenericEventArgs> e)
{
var item = e.Argument.Item1;
@@ -137,9 +151,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
}
private void OnProviderRefreshStarted(object? sender, GenericEventArgs e)
- {
- OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 0)));
- }
+ => OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 0)));
private void OnProviderRefreshCompleted(object? sender, GenericEventArgs e)
{
@@ -342,7 +354,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
return item.SourceType == SourceType.Library;
}
- private IEnumerable GetTopParentIds(List items, List allUserRootChildren)
+ private static IEnumerable GetTopParentIds(List items, List allUserRootChildren)
{
var list = new List();
@@ -363,7 +375,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
return list.Distinct(StringComparer.Ordinal);
}
- private IEnumerable TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false)
+ private T[] TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false)
where T : BaseItem
{
// If the physical root changed, return the user root
@@ -384,18 +396,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
///
public void Dispose()
{
- _libraryManager.ItemAdded -= OnLibraryItemAdded;
- _libraryManager.ItemUpdated -= OnLibraryItemUpdated;
- _libraryManager.ItemRemoved -= OnLibraryItemRemoved;
-
- _providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
- _providerManager.RefreshStarted -= OnProviderRefreshStarted;
- _providerManager.RefreshProgress -= OnProviderRefreshProgress;
-
- if (_libraryUpdateTimer is not null)
- {
- _libraryUpdateTimer.Dispose();
- _libraryUpdateTimer = null;
- }
+ _libraryUpdateTimer?.Dispose();
+ _libraryUpdateTimer = null;
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index d32759017d..957ad9c01b 100644
--- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -8,14 +6,17 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
+using Microsoft.Extensions.Hosting;
namespace Emby.Server.Implementations.EntryPoints
{
- public sealed class UserDataChangeNotifier : IServerEntryPoint
+ ///
+ /// responsible for notifying users when associated item data is updated.
+ ///
+ public sealed class UserDataChangeNotifier : IHostedService, IDisposable
{
private const int UpdateDuration = 500;
@@ -23,25 +24,43 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IUserDataManager _userDataManager;
private readonly IUserManager _userManager;
- private readonly Dictionary> _changedItems = new Dictionary>();
+ private readonly Dictionary> _changedItems = new();
+ private readonly object _syncLock = new();
- private readonly object _syncLock = new object();
private Timer? _updateTimer;
- public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ public UserDataChangeNotifier(
+ IUserDataManager userDataManager,
+ ISessionManager sessionManager,
+ IUserManager userManager)
{
_userDataManager = userDataManager;
_sessionManager = sessionManager;
_userManager = userManager;
}
- public Task RunAsync()
+ ///
+ public Task StartAsync(CancellationToken cancellationToken)
{
_userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
return Task.CompletedTask;
}
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
+
+ return Task.CompletedTask;
+ }
+
private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e)
{
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
@@ -103,55 +122,40 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- await SendNotifications(changes, CancellationToken.None).ConfigureAwait(false);
- }
-
- private async Task SendNotifications(List>> changes, CancellationToken cancellationToken)
- {
- foreach ((var key, var value) in changes)
+ foreach (var (userId, changedItems) in changes)
{
- await SendNotifications(key, value, cancellationToken).ConfigureAwait(false);
+ await _sessionManager.SendMessageToUserSessions(
+ [userId],
+ SessionMessageType.UserDataChanged,
+ () => GetUserDataChangeInfo(userId, changedItems),
+ default).ConfigureAwait(false);
}
}
- private Task SendNotifications(Guid userId, List changedItems, CancellationToken cancellationToken)
- {
- return _sessionManager.SendMessageToUserSessions(new List { userId }, SessionMessageType.UserDataChanged, () => GetUserDataChangeInfo(userId, changedItems), cancellationToken);
- }
-
private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List changedItems)
{
var user = _userManager.GetUserById(userId);
- var dtoList = changedItems
- .DistinctBy(x => x.Id)
- .Select(i =>
- {
- var dto = _userDataManager.GetUserDataDto(i, user);
- dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture);
- return dto;
- })
- .ToArray();
-
- var userIdString = userId.ToString("N", CultureInfo.InvariantCulture);
-
return new UserDataChangeInfo
{
- UserId = userIdString,
-
- UserDataList = dtoList
+ UserId = userId.ToString("N", CultureInfo.InvariantCulture),
+ UserDataList = changedItems
+ .DistinctBy(x => x.Id)
+ .Select(i =>
+ {
+ var dto = _userDataManager.GetUserDataDto(i, user);
+ dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture);
+ return dto;
+ })
+ .ToArray()
};
}
+ ///
public void Dispose()
{
- if (_updateTimer is not null)
- {
- _updateTimer.Dispose();
- _updateTimer = null;
- }
-
- _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
+ _updateTimer?.Dispose();
+ _updateTimer = null;
}
}
}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index dde38906f3..31617d1a5f 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -11,11 +9,13 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO
{
- public class LibraryMonitor : ILibraryMonitor
+ ///
+ public sealed class LibraryMonitor : ILibraryMonitor, IDisposable
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
@@ -25,19 +25,19 @@ namespace Emby.Server.Implementations.IO
///
/// The file system watchers.
///
- private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary _fileSystemWatchers = new(StringComparer.OrdinalIgnoreCase);
///
/// The affected paths.
///
- private readonly List _activeRefreshers = new List();
+ private readonly List _activeRefreshers = [];
///
/// A dynamic list of paths that should be ignored. Added to during our own file system modifications.
///
- private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary _tempIgnoredPaths = new(StringComparer.OrdinalIgnoreCase);
- private bool _disposed = false;
+ private bool _disposed;
///
/// Initializes a new instance of the class.
@@ -46,34 +46,31 @@ namespace Emby.Server.Implementations.IO
/// The library manager.
/// The configuration manager.
/// The filesystem.
+ /// The .
public LibraryMonitor(
ILogger logger,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
- IFileSystem fileSystem)
+ IFileSystem fileSystem,
+ IHostApplicationLifetime appLifetime)
{
_libraryManager = libraryManager;
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
- }
- ///
- /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
- ///
- /// The path.
- private void TemporarilyIgnore(string path)
- {
- _tempIgnoredPaths[path] = path;
+ appLifetime.ApplicationStarted.Register(Start);
}
+ ///
public void ReportFileSystemChangeBeginning(string path)
{
ArgumentException.ThrowIfNullOrEmpty(path);
- TemporarilyIgnore(path);
+ _tempIgnoredPaths[path] = path;
}
+ ///
public async void ReportFileSystemChangeComplete(string path, bool refreshPath)
{
ArgumentException.ThrowIfNullOrEmpty(path);
@@ -107,14 +104,10 @@ namespace Emby.Server.Implementations.IO
var options = _libraryManager.GetLibraryOptions(item);
- if (options is not null)
- {
- return options.EnableRealtimeMonitor;
- }
-
- return false;
+ return options is not null && options.EnableRealtimeMonitor;
}
+ ///
public void Start()
{
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
@@ -306,20 +299,11 @@ namespace Emby.Server.Implementations.IO
{
if (removeFromList)
{
- RemoveWatcherFromList(watcher);
+ _fileSystemWatchers.TryRemove(watcher.Path, out _);
}
}
}
- ///
- /// Removes the watcher from list.
- ///
- /// The watcher.
- private void RemoveWatcherFromList(FileSystemWatcher watcher)
- {
- _fileSystemWatchers.TryRemove(watcher.Path, out _);
- }
-
///
/// Handles the Error event of the watcher control.
///
@@ -352,6 +336,7 @@ namespace Emby.Server.Implementations.IO
}
}
+ ///
public void ReportFileSystemChanged(string path)
{
ArgumentException.ThrowIfNullOrEmpty(path);
@@ -479,31 +464,15 @@ namespace Emby.Server.Implementations.IO
}
}
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
+ ///
public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
- if (disposing)
- {
- Stop();
- }
-
+ Stop();
_disposed = true;
}
}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitorStartup.cs b/Emby.Server.Implementations/IO/LibraryMonitorStartup.cs
deleted file mode 100644
index c51cf05459..0000000000
--- a/Emby.Server.Implementations/IO/LibraryMonitorStartup.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
-
-namespace Emby.Server.Implementations.IO
-{
- ///
- /// which is responsible for starting the library monitor.
- ///
- public sealed class LibraryMonitorStartup : IServerEntryPoint
- {
- private readonly ILibraryMonitor _monitor;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library monitor.
- public LibraryMonitorStartup(ILibraryMonitor monitor)
- {
- _monitor = monitor;
- }
-
- ///
- public Task RunAsync()
- {
- _monitor.Start();
- return Task.CompletedTask;
- }
-
- ///
- public void Dispose()
- {
- }
- }
-}
diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs
deleted file mode 100644
index a471ea1d50..0000000000
--- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using Jellyfin.Data.Events;
-using Jellyfin.Data.Queries;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
-
-namespace Jellyfin.Server.Implementations.Users
-{
- public sealed class DeviceAccessEntryPoint : IServerEntryPoint
- {
- private readonly IUserManager _userManager;
- private readonly IDeviceManager _deviceManager;
- private readonly ISessionManager _sessionManager;
-
- public DeviceAccessEntryPoint(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager)
- {
- _userManager = userManager;
- _deviceManager = deviceManager;
- _sessionManager = sessionManager;
- }
-
- public Task RunAsync()
- {
- _userManager.OnUserUpdated += OnUserUpdated;
-
- return Task.CompletedTask;
- }
-
- public void Dispose()
- {
- }
-
- private async void OnUserUpdated(object? sender, GenericEventArgs e)
- {
- var user = e.Argument;
- if (!user.HasPermission(PermissionKind.EnableAllDevices))
- {
- await UpdateDeviceAccess(user).ConfigureAwait(false);
- }
- }
-
- private async Task UpdateDeviceAccess(User user)
- {
- var existing = (await _deviceManager.GetDevices(new DeviceQuery
- {
- UserId = user.Id
- }).ConfigureAwait(false)).Items;
-
- foreach (var device in existing)
- {
- if (!string.IsNullOrEmpty(device.DeviceId) && !_deviceManager.CanAccessDevice(user, device.DeviceId))
- {
- await _sessionManager.Logout(device).ConfigureAwait(false);
- }
- }
- }
- }
-}
diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs
new file mode 100644
index 0000000000..e40b541a35
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs
@@ -0,0 +1,76 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using Jellyfin.Data.Events;
+using Jellyfin.Data.Queries;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Session;
+using Microsoft.Extensions.Hosting;
+
+namespace Jellyfin.Server.Implementations.Users;
+
+///
+/// responsible for managing user device permissions.
+///
+public sealed class DeviceAccessHost : IHostedService
+{
+ private readonly IUserManager _userManager;
+ private readonly IDeviceManager _deviceManager;
+ private readonly ISessionManager _sessionManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ public DeviceAccessHost(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager)
+ {
+ _userManager = userManager;
+ _deviceManager = deviceManager;
+ _sessionManager = sessionManager;
+ }
+
+ ///
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _userManager.OnUserUpdated += OnUserUpdated;
+
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _userManager.OnUserUpdated -= OnUserUpdated;
+
+ return Task.CompletedTask;
+ }
+
+ private async void OnUserUpdated(object? sender, GenericEventArgs e)
+ {
+ var user = e.Argument;
+ if (!user.HasPermission(PermissionKind.EnableAllDevices))
+ {
+ await UpdateDeviceAccess(user).ConfigureAwait(false);
+ }
+ }
+
+ private async Task UpdateDeviceAccess(User user)
+ {
+ var existing = (await _deviceManager.GetDevices(new DeviceQuery
+ {
+ UserId = user.Id
+ }).ConfigureAwait(false)).Items;
+
+ foreach (var device in existing)
+ {
+ if (!string.IsNullOrEmpty(device.DeviceId) && !_deviceManager.CanAccessDevice(user, device.DeviceId))
+ {
+ await _sessionManager.Logout(device).ConfigureAwait(false);
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 7d5f22545d..558ad5b7bd 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -4,7 +4,10 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
+using Emby.Server.Implementations.EntryPoints;
using Jellyfin.Api.Middleware;
+using Jellyfin.LiveTv;
+using Jellyfin.LiveTv.EmbyTV;
using Jellyfin.LiveTv.Extensions;
using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking;
@@ -17,6 +20,7 @@ using Jellyfin.Server.Infrastructure;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
+using MediaBrowser.XbmcMetadata;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
@@ -124,7 +128,13 @@ namespace Jellyfin.Server
services.AddHlsPlaylistGenerator();
services.AddLiveTvServices();
+ services.AddHostedService();
services.AddHostedService();
+ services.AddHostedService();
+ services.AddHostedService();
+ services.AddHostedService();
+ services.AddHostedService();
+ services.AddHostedService();
}
///
diff --git a/MediaBrowser.Controller/Library/ILibraryMonitor.cs b/MediaBrowser.Controller/Library/ILibraryMonitor.cs
index de74aa5a11..6d2f5b8738 100644
--- a/MediaBrowser.Controller/Library/ILibraryMonitor.cs
+++ b/MediaBrowser.Controller/Library/ILibraryMonitor.cs
@@ -1,10 +1,9 @@
-#pragma warning disable CS1591
-
-using System;
-
namespace MediaBrowser.Controller.Library
{
- public interface ILibraryMonitor : IDisposable
+ ///
+ /// Service responsible for monitoring library filesystems for changes.
+ ///
+ public interface ILibraryMonitor
{
///
/// Starts this instance.
diff --git a/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs b/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs
deleted file mode 100644
index 2b831103a5..0000000000
--- a/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace MediaBrowser.Controller.Plugins
-{
- ///
- /// Indicates that a should be invoked as a pre-startup task.
- ///
- public interface IRunBeforeStartup
- {
- }
-}
diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
deleted file mode 100644
index 6024661e15..0000000000
--- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Plugins
-{
- ///
- /// Represents an entry point for a module in the application. This interface is scanned for automatically and
- /// provides a hook to initialize the module at application start.
- /// The entry point can additionally be flagged as a pre-startup task by implementing the
- /// interface.
- ///
- public interface IServerEntryPoint : IDisposable
- {
- ///
- /// Run the initialization for this module. This method is invoked at application start.
- ///
- /// A representing the asynchronous operation.
- Task RunAsync();
- }
-}
diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs
deleted file mode 100644
index a6216ef305..0000000000
--- a/MediaBrowser.XbmcMetadata/EntryPoint.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.XbmcMetadata.Configuration;
-using MediaBrowser.XbmcMetadata.Savers;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.XbmcMetadata
-{
- public sealed class EntryPoint : IServerEntryPoint
- {
- private readonly IUserDataManager _userDataManager;
- private readonly ILogger _logger;
- private readonly IProviderManager _providerManager;
- private readonly IConfigurationManager _config;
-
- public EntryPoint(
- IUserDataManager userDataManager,
- ILogger logger,
- IProviderManager providerManager,
- IConfigurationManager config)
- {
- _userDataManager = userDataManager;
- _logger = logger;
- _providerManager = providerManager;
- _config = config;
- }
-
- ///
- public Task RunAsync()
- {
- _userDataManager.UserDataSaved += OnUserDataSaved;
-
- return Task.CompletedTask;
- }
-
- private void OnUserDataSaved(object? sender, UserDataSaveEventArgs e)
- {
- if (e.SaveReason == UserDataSaveReason.PlaybackFinished || e.SaveReason == UserDataSaveReason.TogglePlayed || e.SaveReason == UserDataSaveReason.UpdateUserRating)
- {
- if (!string.IsNullOrWhiteSpace(_config.GetNfoConfiguration().UserId))
- {
- _ = SaveMetadataForItemAsync(e.Item, ItemUpdateType.MetadataDownload);
- }
- }
- }
-
- ///
- public void Dispose()
- {
- _userDataManager.UserDataSaved -= OnUserDataSaved;
- }
-
- private async Task SaveMetadataForItemAsync(BaseItem item, ItemUpdateType updateReason)
- {
- if (!item.IsFileProtocol || !item.SupportsLocalMetadata)
- {
- return;
- }
-
- try
- {
- await _providerManager.SaveMetadataAsync(item, updateReason, new[] { BaseNfoSaver.SaverName }).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error saving metadata for {Path}", item.Path ?? item.Name);
- }
- }
- }
-}
diff --git a/MediaBrowser.XbmcMetadata/NfoUserDataSaver.cs b/MediaBrowser.XbmcMetadata/NfoUserDataSaver.cs
new file mode 100644
index 0000000000..b2882194dd
--- /dev/null
+++ b/MediaBrowser.XbmcMetadata/NfoUserDataSaver.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.XbmcMetadata.Configuration;
+using MediaBrowser.XbmcMetadata.Savers;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.XbmcMetadata;
+
+///
+/// responsible for updating NFO files' user data.
+///
+public sealed class NfoUserDataSaver : IHostedService
+{
+ private readonly ILogger _logger;
+ private readonly IConfigurationManager _config;
+ private readonly IUserDataManager _userDataManager;
+ private readonly IProviderManager _providerManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ public NfoUserDataSaver(
+ ILogger logger,
+ IConfigurationManager config,
+ IUserDataManager userDataManager,
+ IProviderManager providerManager)
+ {
+ _logger = logger;
+ _config = config;
+ _userDataManager = userDataManager;
+ _providerManager = providerManager;
+ }
+
+ ///
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _userDataManager.UserDataSaved += OnUserDataSaved;
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _userDataManager.UserDataSaved -= OnUserDataSaved;
+ return Task.CompletedTask;
+ }
+
+ private async void OnUserDataSaved(object? sender, UserDataSaveEventArgs e)
+ {
+ if (e.SaveReason is not (UserDataSaveReason.PlaybackFinished
+ or UserDataSaveReason.TogglePlayed or UserDataSaveReason.UpdateUserRating))
+ {
+ return;
+ }
+
+ if (string.IsNullOrWhiteSpace(_config.GetNfoConfiguration().UserId))
+ {
+ return;
+ }
+
+ var item = e.Item;
+ if (!item.IsFileProtocol || !item.SupportsLocalMetadata)
+ {
+ return;
+ }
+
+ try
+ {
+ await _providerManager.SaveMetadataAsync(item, ItemUpdateType.MetadataDownload, [BaseNfoSaver.SaverName])
+ .ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error saving metadata for {Path}", item.Path ?? item.Name);
+ }
+ }
+}
diff --git a/src/Jellyfin.LiveTv/EmbyTV/EntryPoint.cs b/src/Jellyfin.LiveTv/EmbyTV/EntryPoint.cs
deleted file mode 100644
index e750c05ac8..0000000000
--- a/src/Jellyfin.LiveTv/EmbyTV/EntryPoint.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Plugins;
-
-namespace Jellyfin.LiveTv.EmbyTV
-{
- public sealed class EntryPoint : IServerEntryPoint
- {
- ///
- public Task RunAsync()
- {
- return EmbyTV.Current.Start();
- }
-
- ///
- public void Dispose()
- {
- }
- }
-}
diff --git a/src/Jellyfin.LiveTv/EmbyTV/LiveTvHost.cs b/src/Jellyfin.LiveTv/EmbyTV/LiveTvHost.cs
new file mode 100644
index 0000000000..dc15d53ffa
--- /dev/null
+++ b/src/Jellyfin.LiveTv/EmbyTV/LiveTvHost.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.LiveTv;
+using Microsoft.Extensions.Hosting;
+
+namespace Jellyfin.LiveTv.EmbyTV;
+
+///
+/// responsible for initializing Live TV.
+///
+public sealed class LiveTvHost : IHostedService
+{
+ private readonly EmbyTV _service;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The available s.
+ public LiveTvHost(IEnumerable services)
+ {
+ _service = services.OfType().First();
+ }
+
+ ///
+ public Task StartAsync(CancellationToken cancellationToken) => _service.Start();
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+}
diff --git a/src/Jellyfin.LiveTv/RecordingNotifier.cs b/src/Jellyfin.LiveTv/RecordingNotifier.cs
index 2923948ebf..226d525e71 100644
--- a/src/Jellyfin.LiveTv/RecordingNotifier.cs
+++ b/src/Jellyfin.LiveTv/RecordingNotifier.cs
@@ -1,7 +1,3 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
using System;
using System.Linq;
using System.Threading;
@@ -10,34 +6,44 @@ using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Session;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Jellyfin.LiveTv
{
- public sealed class RecordingNotifier : IServerEntryPoint
+ ///
+ /// responsible for notifying users when a LiveTV recording is completed.
+ ///
+ public sealed class RecordingNotifier : IHostedService
{
- private readonly ILiveTvManager _liveTvManager;
+ private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
- private readonly ILogger _logger;
+ private readonly ILiveTvManager _liveTvManager;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
public RecordingNotifier(
+ ILogger logger,
ISessionManager sessionManager,
IUserManager userManager,
- ILogger logger,
ILiveTvManager liveTvManager)
{
+ _logger = logger;
_sessionManager = sessionManager;
_userManager = userManager;
- _logger = logger;
_liveTvManager = liveTvManager;
}
///
- public Task RunAsync()
+ public Task StartAsync(CancellationToken cancellationToken)
{
_liveTvManager.TimerCancelled += OnLiveTvManagerTimerCancelled;
_liveTvManager.SeriesTimerCancelled += OnLiveTvManagerSeriesTimerCancelled;
@@ -47,29 +53,35 @@ namespace Jellyfin.LiveTv
return Task.CompletedTask;
}
- private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs e)
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
{
- await SendMessage(SessionMessageType.SeriesTimerCreated, e.Argument).ConfigureAwait(false);
- }
+ _liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled;
+ _liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled;
+ _liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated;
+ _liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated;
- private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs e)
- {
- await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false);
+ return Task.CompletedTask;
}
- private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs e)
- {
- await SendMessage(SessionMessageType.SeriesTimerCancelled, e.Argument).ConfigureAwait(false);
- }
+ private async void OnLiveTvManagerSeriesTimerCreated(object? sender, GenericEventArgs e)
+ => await SendMessage(SessionMessageType.SeriesTimerCreated, e.Argument).ConfigureAwait(false);
- private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs e)
- {
- await SendMessage(SessionMessageType.TimerCancelled, e.Argument).ConfigureAwait(false);
- }
+ private async void OnLiveTvManagerTimerCreated(object? sender, GenericEventArgs e)
+ => await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false);
+
+ private async void OnLiveTvManagerSeriesTimerCancelled(object? sender, GenericEventArgs e)
+ => await SendMessage(SessionMessageType.SeriesTimerCancelled, e.Argument).ConfigureAwait(false);
+
+ private async void OnLiveTvManagerTimerCancelled(object? sender, GenericEventArgs e)
+ => await SendMessage(SessionMessageType.TimerCancelled, e.Argument).ConfigureAwait(false);
private async Task SendMessage(SessionMessageType name, TimerEventInfo info)
{
- var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
+ var users = _userManager.Users
+ .Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess))
+ .Select(i => i.Id)
+ .ToList();
try
{
@@ -80,14 +92,5 @@ namespace Jellyfin.LiveTv
_logger.LogError(ex, "Error sending message");
}
}
-
- ///
- public void Dispose()
- {
- _liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled;
- _liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled;
- _liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated;
- _liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated;
- }
}
}
diff --git a/src/Jellyfin.Networking/ExternalPortForwarding.cs b/src/Jellyfin.Networking/PortForwardingHost.cs
similarity index 76%
rename from src/Jellyfin.Networking/ExternalPortForwarding.cs
rename to src/Jellyfin.Networking/PortForwardingHost.cs
index df9e43ca90..d01343624e 100644
--- a/src/Jellyfin.Networking/ExternalPortForwarding.cs
+++ b/src/Jellyfin.Networking/PortForwardingHost.cs
@@ -1,7 +1,3 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -12,36 +8,34 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Plugins;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Mono.Nat;
namespace Jellyfin.Networking;
///
-/// Server entrypoint handling external port forwarding.
+/// responsible for UPnP port forwarding.
///
-public sealed class ExternalPortForwarding : IServerEntryPoint
+public sealed class PortForwardingHost : IHostedService, IDisposable
{
private readonly IServerApplicationHost _appHost;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
+ private readonly ConcurrentDictionary _createdRules = new();
- private readonly ConcurrentDictionary _createdRules = new ConcurrentDictionary();
-
- private Timer _timer;
- private string _configIdentifier;
-
+ private Timer? _timer;
+ private string? _configIdentifier;
private bool _disposed;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The logger.
/// The application host.
/// The configuration manager.
- public ExternalPortForwarding(
- ILogger logger,
+ public PortForwardingHost(
+ ILogger logger,
IServerApplicationHost appHost,
IServerConfigurationManager config)
{
@@ -66,7 +60,7 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
.ToString();
}
- private void OnConfigurationUpdated(object sender, EventArgs e)
+ private void OnConfigurationUpdated(object? sender, EventArgs e)
{
var oldConfigIdentifier = _configIdentifier;
_configIdentifier = GetConfigIdentifier();
@@ -79,7 +73,7 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
}
///
- public Task RunAsync()
+ public Task StartAsync(CancellationToken cancellationToken)
{
Start();
@@ -88,6 +82,14 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
return Task.CompletedTask;
}
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ Stop();
+
+ return Task.CompletedTask;
+ }
+
private void Start()
{
var config = _config.GetNetworkConfiguration();
@@ -101,7 +103,8 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
- _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
+ _timer?.Dispose();
+ _timer = new Timer(_ => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
}
private void Stop()
@@ -112,13 +115,23 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
_timer?.Dispose();
+ _timer = null;
}
- private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+ private async void OnNatUtilityDeviceFound(object? sender, DeviceEventArgs e)
{
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
try
{
- await CreateRules(e.Device).ConfigureAwait(false);
+ // On some systems the device discovered event seems to fire repeatedly
+ // This check will help ensure we're not trying to port map the same device over and over
+ if (!_createdRules.TryAdd(e.Device.DeviceEndpoint, 0))
+ {
+ return;
+ }
+
+ await Task.WhenAll(CreatePortMaps(e.Device)).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -126,20 +139,6 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
}
}
- private Task CreateRules(INatDevice device)
- {
- ObjectDisposedException.ThrowIf(_disposed, this);
-
- // On some systems the device discovered event seems to fire repeatedly
- // This check will help ensure we're not trying to port map the same device over and over
- if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
- {
- return Task.CompletedTask;
- }
-
- return Task.WhenAll(CreatePortMaps(device));
- }
-
private IEnumerable CreatePortMaps(INatDevice device)
{
var config = _config.GetNetworkConfiguration();
@@ -185,8 +184,6 @@ public sealed class ExternalPortForwarding : IServerEntryPoint
_config.ConfigurationUpdated -= OnConfigurationUpdated;
- Stop();
-
_timer?.Dispose();
_timer = null;