diff options
31 files changed, 167 insertions, 766 deletions
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 63fd8ce5a..a5b8e2b3c 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -811,7 +811,7 @@ namespace Emby.Dlna.PlayTo } /// <inheritdoc /> - public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken) + public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) { if (_disposed) { @@ -823,17 +823,17 @@ namespace Emby.Dlna.PlayTo return Task.CompletedTask; } - if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.Play) { return SendPlayCommand(data as PlayRequest, cancellationToken); } - if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.PlayState) { return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); } - if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.GeneralCommand) { return SendGeneralCommand(data as GeneralCommand, cancellationToken); } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index c9d21d963..ff64e217a 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -105,7 +106,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - _sessionManager.SendMessageToAdminSessions("RefreshProgress", dict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); } catch { @@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - _sessionManager.SendMessageToAdminSessions("RefreshProgress", collectionFolderDict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None); } catch { @@ -345,7 +346,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, "LibraryChanged", info, cancellationToken).ConfigureAwait(false); + await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 44d2580d6..824bb85f4 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -46,25 +47,25 @@ namespace Emby.Server.Implementations.EntryPoints private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.SeriesTimerCreated, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.SeriesTimerCancelled, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.TimerCancelled, e.Argument).ConfigureAwait(false); } - private async Task SendMessage(string name, TimerEventInfo info) + private async Task SendMessage(SessionMessageType name, TimerEventInfo info) { var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList(); diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 1da717e75..1989e9ed2 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.EntryPoints private Task SendNotifications(Guid userId, List<BaseItem> changedItems, CancellationToken cancellationToken) { - return _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, "UserDataChanged", () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); + return _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.UserDataChanged, () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); } private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List<BaseItem> changedItems) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 7eae4e764..fed2addf8 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Json; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) + if (info.MessageType == SessionMessageType.KeepAlive) { await SendKeepAliveResponse().ConfigureAwait(false); } @@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer new WebSocketMessage<string> { MessageId = Guid.NewGuid(), - MessageType = "KeepAlive" + MessageType = SessionMessageType.KeepAlive }, CancellationToken.None); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index fa4caeb18..b1ab20da2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1064,10 +1064,10 @@ namespace Emby.Server.Implementations.Session AssertCanControl(session, controllingSession); } - return SendMessageToSession(session, "GeneralCommand", command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.GeneralCommand, command, cancellationToken); } - private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken) + private static async Task SendMessageToSession<T>(SessionInfo session, SessionMessageType name, T data, CancellationToken cancellationToken) { var controllers = session.SessionControllers; var messageId = Guid.NewGuid(); @@ -1078,7 +1078,7 @@ namespace Emby.Server.Implementations.Session } } - private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, string name, T data, CancellationToken cancellationToken) + private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, SessionMessageType name, T data, CancellationToken cancellationToken) { IEnumerable<Task> GetTasks() { @@ -1178,7 +1178,7 @@ namespace Emby.Server.Implementations.Session } } - await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.Play, command, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> @@ -1186,7 +1186,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> @@ -1194,7 +1194,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); } private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user) @@ -1297,7 +1297,7 @@ namespace Emby.Server.Implementations.Session } } - return SendMessageToSession(session, "Playstate", command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.PlayState, command, cancellationToken); } private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) @@ -1322,7 +1322,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken); } /// <summary> @@ -1334,7 +1334,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); } /// <summary> @@ -1348,7 +1348,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Beginning SendServerRestartNotification"); - return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken); } /// <summary> @@ -1874,7 +1874,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken) + public Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1887,7 +1887,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken) + public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken) { CheckDisposed(); @@ -1902,7 +1902,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken) + public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1911,7 +1911,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken) + public Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 15c2af220..a5f847953 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -8,6 +8,7 @@ using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -316,7 +317,7 @@ namespace Emby.Server.Implementations.Session return webSocket.SendAsync( new WebSocketMessage<int> { - MessageType = "ForceKeepAlive", + MessageType = SessionMessageType.ForceKeepAlive, Data = WebSocketLostTimeout }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 94604ca1e..b986ffa1c 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public Task SendMessage<T>( - string name, + SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 849b3b709..77d55828d 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners @@ -29,11 +30,14 @@ namespace Jellyfin.Api.WebSocketListeners _activityManager.EntryCreated += OnEntryCreated; } - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "ActivityLogEntry"; + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.ActivityLogEntry; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.ActivityLogEntryStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.ActivityLogEntryStop; /// <summary> /// Gets the data to send. diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index 8a966c137..80314b923 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -33,11 +34,14 @@ namespace Jellyfin.Api.WebSocketListeners _taskManager.TaskCompleted += OnTaskCompleted; } - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "ScheduledTasksInfo"; + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.ScheduledTasksInfo; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.ScheduledTasksInfoStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.ScheduledTasksInfoStop; /// <summary> /// Gets the data to send. diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs index 1fb5dc412..1cf43a005 100644 --- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners @@ -34,7 +35,13 @@ namespace Jellyfin.Api.WebSocketListeners } /// <inheritdoc /> - protected override string Name => "Sessions"; + protected override SessionMessageType Type => SessionMessageType.Sessions; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.SessionsStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.SessionsStop; /// <summary> /// Gets the data to send. diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs index 80ed56cd8..0993c6df7 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Tasks; namespace Jellyfin.Server.Implementations.Events.Consumers.System @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System /// <inheritdoc /> public async Task OnEvent(TaskCompletionEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("ScheduledTaskEnded", eventArgs.Result, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.ScheduledTaskEnded, eventArgs.Result, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs index 1c600683a..1d790da6b 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationCancelled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCancelled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs index ea0c878d4..a1faf18fc 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(InstallationFailedEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationFailed", eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationFailed, eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs index 3dda5a04c..bd1a71404 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginInstalledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationCompleted", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCompleted, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs index f691d11a7..b513ac64a 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginInstallingEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstalling", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstalling, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs index 709692f6b..1fd7b9adf 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginUninstalledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PluginUninstalled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageUninstalled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs index 10367a939..303e88621 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events.Users; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Users { @@ -30,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users { await _sessionManager.SendMessageToUserSessions( new List<Guid> { eventArgs.Argument.Id }, - "UserDeleted", + SessionMessageType.UserDeleted, eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture), CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs index 6081dd044..a14911b94 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Events.Users; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Users { @@ -33,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users { await _sessionManager.SendMessageToUserSessions( new List<Guid> { e.Argument.Id }, - "UserUpdated", + SessionMessageType.UserUpdated, _userManager.GetUserDto(e.Argument), CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index fb1ee3b2b..f6c76e4d9 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -125,8 +125,8 @@ namespace Jellyfin.Server.Middleware switch (ex) { case ArgumentException _: return StatusCodes.Status400BadRequest; - case AuthenticationException _: - case SecurityException _: return StatusCodes.Status401Unauthorized; + case AuthenticationException _: return StatusCodes.Status401Unauthorized; + case SecurityException _: return StatusCodes.Status403Forbidden; case DirectoryNotFoundException _: case FileNotFoundException _: case ResourceNotFoundException _: return StatusCodes.Status404NotFound; diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 916dea58b..28227603b 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -8,6 +8,7 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Net @@ -28,10 +29,22 @@ namespace MediaBrowser.Controller.Net new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>(); /// <summary> - /// Gets the name. + /// Gets the type used for the messages sent to the client. /// </summary> - /// <value>The name.</value> - protected abstract string Name { get; } + /// <value>The type.</value> + protected abstract SessionMessageType Type { get; } + + /// <summary> + /// Gets the message type received from the client to start sending messages. + /// </summary> + /// <value>The type.</value> + protected abstract SessionMessageType StartType { get; } + + /// <summary> + /// Gets the message type received from the client to stop sending messages. + /// </summary> + /// <value>The type.</value> + protected abstract SessionMessageType StopType { get; } /// <summary> /// Gets the data to send. @@ -66,12 +79,12 @@ namespace MediaBrowser.Controller.Net throw new ArgumentNullException(nameof(message)); } - if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase)) + if (message.MessageType == StartType) { Start(message); } - if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase)) + if (message.MessageType == StopType) { Stop(message); } @@ -159,7 +172,7 @@ namespace MediaBrowser.Controller.Net new WebSocketMessage<TReturnDataType> { MessageId = Guid.NewGuid(), - MessageType = Name, + MessageType = Type, Data = data }, cancellationToken).ConfigureAwait(false); @@ -176,7 +189,7 @@ namespace MediaBrowser.Controller.Net } catch (Exception ex) { - Logger.LogError(ex, "Error sending web socket message {Name}", Name); + Logger.LogError(ex, "Error sending web socket message {Name}", Type); DisposeConnection(tuple); } } diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 22d6e2a04..bc4ccd44c 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.Session { @@ -23,6 +24,6 @@ namespace MediaBrowser.Controller.Session /// <summary> /// Sends the message. /// </summary> - Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken); + Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 228b2331d..04c3004ee 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -188,16 +188,16 @@ namespace MediaBrowser.Controller.Session /// <param name="data">The data.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken); + Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken); /// <summary> /// Sends the message to user sessions. /// </summary> /// <typeparam name="T"></typeparam> /// <returns>Task.</returns> - Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken); + Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken); - Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken); + Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken); /// <summary> /// Sends the message to user device sessions. @@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Session /// <param name="data">The data.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken); + Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken); /// <summary> /// Sends the restart required message. diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 890469d36..54ef49ea6 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -25,8 +25,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableInternetProviders { get; set; } - public bool ImportMissingEpisodes { get; set; } - public bool EnableAutomaticSeriesGrouping { get; set; } public bool EnableEmbeddedTitles { get; set; } diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs index 660eebeda..bffbbe612 100644 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ b/MediaBrowser.Model/Net/WebSocketMessage.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using MediaBrowser.Model.Session; namespace MediaBrowser.Model.Net { @@ -15,7 +16,7 @@ namespace MediaBrowser.Model.Net /// Gets or sets the type of the message. /// </summary> /// <value>The type of the message.</value> - public string MessageType { get; set; } + public SessionMessageType MessageType { get; set; } public Guid MessageId { get; set; } diff --git a/MediaBrowser.Model/Session/SessionMessageType.cs b/MediaBrowser.Model/Session/SessionMessageType.cs new file mode 100644 index 000000000..23c41026d --- /dev/null +++ b/MediaBrowser.Model/Session/SessionMessageType.cs @@ -0,0 +1,50 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Model.Session +{ + /// <summary> + /// The different kinds of messages that are used in the WebSocket api. + /// </summary> + public enum SessionMessageType + { + // Server -> Client + ForceKeepAlive, + GeneralCommand, + UserDataChanged, + Sessions, + Play, + SyncPlayCommand, + SyncPlayGroupUpdate, + PlayState, + RestartRequired, + ServerShuttingDown, + ServerRestarting, + LibraryChanged, + UserDeleted, + UserUpdated, + SeriesTimerCreated, + TimerCreated, + SeriesTimerCancelled, + TimerCancelled, + RefreshProgress, + ScheduledTaskEnded, + PackageInstallationCancelled, + PackageInstallationFailed, + PackageInstallationCompleted, + PackageInstalling, + PackageUninstalled, + ActivityLogEntry, + ScheduledTasksInfo, + + // Client -> Server + ActivityLogEntryStart, + ActivityLogEntryStop, + SessionsStart, + SessionsStop, + ScheduledTasksInfoStart, + ScheduledTasksInfoStop, + + // Shared + KeepAlive, + } +} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index b6fb4267f..a0c7d4ad0 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -158,6 +158,14 @@ namespace MediaBrowser.Providers.Manager var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); + if (response.StatusCode != HttpStatusCode.OK) + { + throw new HttpException("Invalid image received.") + { + StatusCode = response.StatusCode + }; + } + var contentType = response.Content.Headers.ContentType.MediaType; // Workaround for tvheadend channel icons diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs index f22d484ab..5e9a4a225 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs @@ -80,32 +80,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken)); } - public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language, - CancellationToken cancellationToken) - { - // Traverse all episode pages and join them together - var episodes = new List<EpisodeRecord>(); - var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken) - .ConfigureAwait(false); - episodes.AddRange(episodePage.Data); - if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue) - { - return episodes; - } - - int next = episodePage.Links.Next.Value; - int last = episodePage.Links.Last.Value; - - for (var page = next; page <= last; ++page) - { - episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken) - .ConfigureAwait(false); - episodes.AddRange(episodePage.Data); - } - - return episodes; - } - public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync( string imdbId, string language, diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs deleted file mode 100644 index 905cbefd3..000000000 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ /dev/null @@ -1,229 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.TV -{ - public class DummySeasonProvider - { - private readonly ILogger _logger; - private readonly ILocalizationManager _localization; - private readonly ILibraryManager _libraryManager; - private readonly IFileSystem _fileSystem; - - public DummySeasonProvider( - ILogger logger, - ILocalizationManager localization, - ILibraryManager libraryManager, - IFileSystem fileSystem) - { - _logger = logger; - _localization = localization; - _libraryManager = libraryManager; - _fileSystem = fileSystem; - } - - public async Task<bool> Run(Series series, CancellationToken cancellationToken) - { - var seasonsRemoved = RemoveObsoleteSeasons(series); - - var hasNewSeasons = await AddDummySeasonFolders(series, cancellationToken).ConfigureAwait(false); - - if (hasNewSeasons) - { - // var directoryService = new DirectoryService(_fileSystem); - - // await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false); - - // await series.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(directoryService)) - // .ConfigureAwait(false); - } - - return seasonsRemoved || hasNewSeasons; - } - - private async Task<bool> AddDummySeasonFolders(Series series, CancellationToken cancellationToken) - { - var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode) - .Cast<Episode>() - .Where(i => !i.IsInSeasonFolder) - .ToList(); - - var hasChanges = false; - - List<Season> seasons = null; - - // Loop through the unique season numbers - foreach (var seasonNumber in episodesInSeriesFolder.Select(i => i.ParentIndexNumber ?? -1) - .Where(i => i >= 0) - .Distinct() - .ToList()) - { - if (seasons == null) - { - seasons = series.Children.OfType<Season>().ToList(); - } - - var existingSeason = seasons - .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); - - if (existingSeason == null) - { - await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false); - hasChanges = true; - seasons = null; - } - else if (existingSeason.IsVirtualItem) - { - existingSeason.IsVirtualItem = false; - await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); - seasons = null; - } - } - - // Unknown season - create a dummy season to put these under - if (episodesInSeriesFolder.Any(i => !i.ParentIndexNumber.HasValue)) - { - if (seasons == null) - { - seasons = series.Children.OfType<Season>().ToList(); - } - - var existingSeason = seasons - .FirstOrDefault(i => !i.IndexNumber.HasValue); - - if (existingSeason == null) - { - await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false); - - hasChanges = true; - seasons = null; - } - else if (existingSeason.IsVirtualItem) - { - existingSeason.IsVirtualItem = false; - await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); - seasons = null; - } - } - - return hasChanges; - } - - /// <summary> - /// Adds the season. - /// </summary> - public async Task<Season> AddSeason( - Series series, - int? seasonNumber, - bool isVirtualItem, - CancellationToken cancellationToken) - { - string seasonName; - if (seasonNumber == null) - { - seasonName = _localization.GetLocalizedString("NameSeasonUnknown"); - } - else if (seasonNumber == 0) - { - seasonName = _libraryManager.GetLibraryOptions(series).SeasonZeroDisplayName; - } - else - { - seasonName = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("NameSeasonNumber"), - seasonNumber.Value); - } - - _logger.LogInformation("Creating Season {0} entry for {1}", seasonName, series.Name); - - var season = new Season - { - Name = seasonName, - IndexNumber = seasonNumber, - Id = _libraryManager.GetNewItemId( - series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName, - typeof(Season)), - IsVirtualItem = isVirtualItem, - SeriesId = series.Id, - SeriesName = series.Name - }; - - season.SetParent(series); - - series.AddChild(season, cancellationToken); - - await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - - return season; - } - - private bool RemoveObsoleteSeasons(Series series) - { - var existingSeasons = series.Children.OfType<Season>().ToList(); - - var physicalSeasons = existingSeasons - .Where(i => i.LocationType != LocationType.Virtual) - .ToList(); - - var virtualSeasons = existingSeasons - .Where(i => i.LocationType == LocationType.Virtual) - .ToList(); - - var seasonsToRemove = virtualSeasons - .Where(i => - { - if (i.IndexNumber.HasValue) - { - var seasonNumber = i.IndexNumber.Value; - - // If there's a physical season with the same number, delete it - if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value == seasonNumber))) - { - return true; - } - } - - // If there are no episodes with this season number, delete it - if (!i.GetEpisodes().Any()) - { - return true; - } - - return false; - }) - .ToList(); - - var hasChanges = false; - - foreach (var seasonToRemove in seasonsToRemove) - { - _logger.LogInformation("Removing virtual season {0} {1}", series.Name, seasonToRemove.IndexNumber); - - _libraryManager.DeleteItem( - seasonToRemove, - new DeleteOptions - { - DeleteFileLocation = true - }, - false); - - hasChanges = true; - } - - return hasChanges; - } - } -} diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs deleted file mode 100644 index c833b1227..000000000 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ /dev/null @@ -1,404 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Plugins.TheTvdb; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.TV -{ - public class MissingEpisodeProvider - { - private const double UnairedEpisodeThresholdDays = 2; - - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly ILocalizationManager _localization; - private readonly IFileSystem _fileSystem; - private readonly TvdbClientManager _tvdbClientManager; - - public MissingEpisodeProvider( - ILogger logger, - IServerConfigurationManager config, - ILibraryManager libraryManager, - ILocalizationManager localization, - IFileSystem fileSystem, - TvdbClientManager tvdbClientManager) - { - _logger = logger; - _config = config; - _libraryManager = libraryManager; - _localization = localization; - _fileSystem = fileSystem; - _tvdbClientManager = tvdbClientManager; - } - - public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken) - { - var tvdbIdString = series.GetProviderId(MetadataProvider.Tvdb); - if (string.IsNullOrEmpty(tvdbIdString)) - { - return false; - } - - var episodes = await _tvdbClientManager.GetAllEpisodesAsync( - int.Parse(tvdbIdString, CultureInfo.InvariantCulture), - series.GetPreferredMetadataLanguage(), - cancellationToken).ConfigureAwait(false); - - var episodeLookup = episodes - .Select(i => - { - if (!DateTime.TryParse(i.FirstAired, out var firstAired)) - { - firstAired = default; - } - - var seasonNumber = i.AiredSeason.GetValueOrDefault(-1); - var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1); - return (seasonNumber, episodeNumber, firstAired); - }) - .Where(i => i.seasonNumber != -1 && i.episodeNumber != -1) - .OrderBy(i => i.seasonNumber) - .ThenBy(i => i.episodeNumber) - .ToList(); - - var allRecursiveChildren = series.GetRecursiveChildren(); - - var hasBadData = HasInvalidContent(allRecursiveChildren); - - // Be conservative here to avoid creating missing episodes for ones they already have - var addMissingEpisodes = !hasBadData && _libraryManager.GetLibraryOptions(series).ImportMissingEpisodes; - - var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(allRecursiveChildren, episodeLookup); - - if (anySeasonsRemoved) - { - // refresh this - allRecursiveChildren = series.GetRecursiveChildren(); - } - - var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(allRecursiveChildren, episodeLookup, addMissingEpisodes); - - if (anyEpisodesRemoved) - { - // refresh this - allRecursiveChildren = series.GetRecursiveChildren(); - } - - var hasNewEpisodes = false; - - if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name)) - { - hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, episodeLookup, cancellationToken) - .ConfigureAwait(false); - } - - if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved) - { - return true; - } - - return false; - } - - /// <summary> - /// Returns true if a series has any seasons or episodes without season or episode numbers - /// If this data is missing no virtual items will be added in order to prevent possible duplicates. - /// </summary> - private bool HasInvalidContent(IList<BaseItem> allItems) - { - return allItems.OfType<Season>().Any(i => !i.IndexNumber.HasValue) || - allItems.OfType<Episode>().Any(i => - { - if (!i.ParentIndexNumber.HasValue) - { - return true; - } - - // You could have episodes under season 0 with no number - return false; - }); - } - - private async Task<bool> AddMissingEpisodes( - Series series, - IEnumerable<BaseItem> allItems, - bool addMissingEpisodes, - IReadOnlyCollection<(int seasonNumber, int episodenumber, DateTime firstAired)> episodeLookup, - CancellationToken cancellationToken) - { - var existingEpisodes = allItems.OfType<Episode>().ToList(); - - var seasonCounts = episodeLookup.GroupBy(e => e.seasonNumber).ToDictionary(g => g.Key, g => g.Count()); - - var hasChanges = false; - - foreach (var tuple in episodeLookup) - { - if (tuple.seasonNumber <= 0 || tuple.episodenumber <= 0) - { - // Ignore episode/season zeros - continue; - } - - var existingEpisode = GetExistingEpisode(existingEpisodes, seasonCounts, tuple); - - if (existingEpisode != null) - { - continue; - } - - var airDate = tuple.firstAired; - - var now = DateTime.UtcNow.AddDays(-UnairedEpisodeThresholdDays); - - if ((airDate < now && addMissingEpisodes) || airDate > now) - { - // tvdb has a lot of nearly blank episodes - _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.seasonNumber, tuple.episodenumber); - await AddEpisode(series, tuple.seasonNumber, tuple.episodenumber, cancellationToken).ConfigureAwait(false); - - hasChanges = true; - } - } - - return hasChanges; - } - - /// <summary> - /// Removes the virtual entry after a corresponding physical version has been added. - /// </summary> - private bool RemoveObsoleteOrMissingEpisodes( - IEnumerable<BaseItem> allRecursiveChildren, - IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup, - bool allowMissingEpisodes) - { - var existingEpisodes = allRecursiveChildren.OfType<Episode>(); - - var physicalEpisodes = new List<Episode>(); - var virtualEpisodes = new List<Episode>(); - foreach (var episode in existingEpisodes) - { - if (episode.LocationType == LocationType.Virtual) - { - virtualEpisodes.Add(episode); - } - else - { - physicalEpisodes.Add(episode); - } - } - - var episodesToRemove = virtualEpisodes - .Where(i => - { - if (!i.IndexNumber.HasValue || !i.ParentIndexNumber.HasValue) - { - return true; - } - - var seasonNumber = i.ParentIndexNumber.Value; - var episodeNumber = i.IndexNumber.Value; - - // If there's a physical episode with the same season and episode number, delete it - if (physicalEpisodes.Any(p => - p.ParentIndexNumber.HasValue && p.ParentIndexNumber.Value == seasonNumber && - p.ContainsEpisodeNumber(episodeNumber))) - { - return true; - } - - // If the episode no longer exists in the remote lookup, delete it - if (!episodeLookup.Any(e => e.seasonNumber == seasonNumber && e.episodeNumber == episodeNumber)) - { - return true; - } - - // If it's missing, but not unaired, remove it - return !allowMissingEpisodes && i.IsMissingEpisode && - (!i.PremiereDate.HasValue || - i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) < - DateTime.Now.Date); - }); - - var hasChanges = false; - - foreach (var episodeToRemove in episodesToRemove) - { - _libraryManager.DeleteItem( - episodeToRemove, - new DeleteOptions - { - DeleteFileLocation = true - }, - false); - - hasChanges = true; - } - - return hasChanges; - } - - /// <summary> - /// Removes the obsolete or missing seasons. - /// </summary> - /// <param name="allRecursiveChildren">All recursive children.</param> - /// <param name="episodeLookup">The episode lookup.</param> - /// <returns><see cref="bool" />.</returns> - private bool RemoveObsoleteOrMissingSeasons( - IList<BaseItem> allRecursiveChildren, - IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup) - { - var existingSeasons = allRecursiveChildren.OfType<Season>().ToList(); - - var physicalSeasons = new List<Season>(); - var virtualSeasons = new List<Season>(); - foreach (var season in existingSeasons) - { - if (season.LocationType == LocationType.Virtual) - { - virtualSeasons.Add(season); - } - else - { - physicalSeasons.Add(season); - } - } - - var allEpisodes = allRecursiveChildren.OfType<Episode>().ToList(); - - var seasonsToRemove = virtualSeasons - .Where(i => - { - if (i.IndexNumber.HasValue) - { - var seasonNumber = i.IndexNumber.Value; - - // If there's a physical season with the same number, delete it - if (physicalSeasons.Any(p => p.IndexNumber.HasValue && p.IndexNumber.Value == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal))) - { - return true; - } - - // If the season no longer exists in the remote lookup, delete it, but only if an existing episode doesn't require it - return episodeLookup.All(e => e.seasonNumber != seasonNumber) && allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder); - } - - // Season does not have a number - // Remove if there are no episodes directly in series without a season number - return allEpisodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder); - }); - - var hasChanges = false; - - foreach (var seasonToRemove in seasonsToRemove) - { - _libraryManager.DeleteItem( - seasonToRemove, - new DeleteOptions - { - DeleteFileLocation = true - }, - false); - - hasChanges = true; - } - - return hasChanges; - } - - /// <summary> - /// Adds the episode. - /// </summary> - /// <param name="series">The series.</param> - /// <param name="seasonNumber">The season number.</param> - /// <param name="episodeNumber">The episode number.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task AddEpisode(Series series, int seasonNumber, int episodeNumber, CancellationToken cancellationToken) - { - var season = series.Children.OfType<Season>() - .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); - - if (season == null) - { - var provider = new DummySeasonProvider(_logger, _localization, _libraryManager, _fileSystem); - season = await provider.AddSeason(series, seasonNumber, true, cancellationToken).ConfigureAwait(false); - } - - var name = "Episode " + episodeNumber.ToString(CultureInfo.InvariantCulture); - - var episode = new Episode - { - Name = name, - IndexNumber = episodeNumber, - ParentIndexNumber = seasonNumber, - Id = _libraryManager.GetNewItemId( - series.Id + seasonNumber.ToString(CultureInfo.InvariantCulture) + name, - typeof(Episode)), - IsVirtualItem = true, - SeasonId = season?.Id ?? Guid.Empty, - SeriesId = series.Id - }; - - season.AddChild(episode, cancellationToken); - - await episode.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - } - - /// <summary> - /// Gets the existing episode. - /// </summary> - /// <param name="existingEpisodes">The existing episodes.</param> - /// <param name="seasonCounts"></param> - /// <param name="episodeTuple"></param> - /// <returns>Episode.</returns> - private Episode GetExistingEpisode( - IEnumerable<Episode> existingEpisodes, - IReadOnlyDictionary<int, int> seasonCounts, - (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple) - { - var seasonNumber = episodeTuple.seasonNumber; - var episodeNumber = episodeTuple.episodeNumber; - - while (true) - { - var episode = GetExistingEpisode(existingEpisodes, seasonNumber, episodeNumber); - if (episode != null) - { - return episode; - } - - seasonNumber--; - - if (seasonCounts.ContainsKey(seasonNumber)) - { - episodeNumber += seasonCounts[seasonNumber]; - } - else - { - break; - } - } - - return null; - } - - private Episode GetExistingEpisode(IEnumerable<Episode> existingEpisodes, int season, int episode) - => existingEpisodes.FirstOrDefault(i => i.ParentIndexNumber == season && i.ContainsEpisodeNumber(episode)); - } -} diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index a2c0e62c1..c8fc568a2 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -1,65 +1,26 @@ #pragma warning disable CS1591 -using System; -using System.Threading; -using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; -using MediaBrowser.Providers.Plugins.TheTvdb; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.TV { public class SeriesMetadataService : MetadataService<Series, SeriesInfo> { - private readonly ILocalizationManager _localization; - private readonly TvdbClientManager _tvdbClientManager; - public SeriesMetadataService( IServerConfigurationManager serverConfigurationManager, ILogger<SeriesMetadataService> logger, IProviderManager providerManager, IFileSystem fileSystem, - ILibraryManager libraryManager, - ILocalizationManager localization, - TvdbClientManager tvdbClientManager) + ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) { - _localization = localization; - _tvdbClientManager = tvdbClientManager; - } - - /// <inheritdoc /> - protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) - { - await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false); - - var seasonProvider = new DummySeasonProvider(Logger, _localization, LibraryManager, FileSystem); - await seasonProvider.Run(item, cancellationToken).ConfigureAwait(false); - - // TODO why does it not register this itself omg - var provider = new MissingEpisodeProvider( - Logger, - ServerConfigurationManager, - LibraryManager, - _localization, - FileSystem, - _tvdbClientManager); - - try - { - await provider.Run(item, true, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error in DummySeasonProvider for {ItemPath}", item.Path); - } } /// <inheritdoc /> |
