diff options
Diffstat (limited to 'Emby.Server.Implementations/EntryPoints')
9 files changed, 1297 insertions, 0 deletions
diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs new file mode 100644 index 000000000..38708648f --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -0,0 +1,124 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.EntryPoints +{ + public class AutomaticRestartEntryPoint : IServerEntryPoint + { + private readonly IServerApplicationHost _appHost; + private readonly ILogger _logger; + private readonly ITaskManager _iTaskManager; + private readonly ISessionManager _sessionManager; + private readonly IServerConfigurationManager _config; + private readonly ILiveTvManager _liveTvManager; + private readonly ITimerFactory _timerFactory; + + private ITimer _timer; + + public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager, ITimerFactory timerFactory) + { + _appHost = appHost; + _logger = logger; + _iTaskManager = iTaskManager; + _sessionManager = sessionManager; + _config = config; + _liveTvManager = liveTvManager; + _timerFactory = timerFactory; + } + + public void Run() + { + if (_appHost.CanSelfRestart) + { + _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; + } + } + + void _appHost_HasPendingRestartChanged(object sender, EventArgs e) + { + DisposeTimer(); + + if (_appHost.HasPendingRestart) + { + _timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); + } + } + + private async void TimerCallback(object state) + { + if (_config.Configuration.EnableAutomaticRestart) + { + var isIdle = await IsIdle().ConfigureAwait(false); + + if (isIdle) + { + DisposeTimer(); + + try + { + _appHost.Restart(); + } + catch (Exception ex) + { + _logger.ErrorException("Error restarting server", ex); + } + } + } + } + + private async Task<bool> IsIdle() + { + if (_iTaskManager.ScheduledTasks.Any(i => i.State != TaskState.Idle)) + { + return false; + } + + if (_liveTvManager.Services.Count == 1) + { + try + { + var timers = await _liveTvManager.GetTimers(new TimerQuery(), CancellationToken.None).ConfigureAwait(false); + if (timers.Items.Any(i => i.Status == RecordingStatus.InProgress)) + { + return false; + } + } + catch (Exception ex) + { + _logger.ErrorException("Error getting timers", ex); + } + } + + var now = DateTime.UtcNow; + + return !_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 30); + } + + public void Dispose() + { + _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; + + DisposeTimer(); + } + + private void DisposeTimer() + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs new file mode 100644 index 000000000..91142f928 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -0,0 +1,343 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.EntryPoints +{ + public class LibraryChangedNotifier : IServerEntryPoint + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + private readonly ITimerFactory _timerFactory; + + /// <summary> + /// The _library changed sync lock + /// </summary> + private readonly object _libraryChangedSyncLock = new object(); + + private readonly List<Folder> _foldersAddedTo = new List<Folder>(); + private readonly List<Folder> _foldersRemovedFrom = new List<Folder>(); + + private readonly List<BaseItem> _itemsAdded = new List<BaseItem>(); + private readonly List<BaseItem> _itemsRemoved = new List<BaseItem>(); + private readonly List<BaseItem> _itemsUpdated = new List<BaseItem>(); + + /// <summary> + /// Gets or sets the library update timer. + /// </summary> + /// <value>The library update timer.</value> + private ITimer LibraryUpdateTimer { get; set; } + + /// <summary> + /// The library update duration + /// </summary> + private const int LibraryUpdateDuration = 5000; + + public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, ITimerFactory timerFactory) + { + _libraryManager = libraryManager; + _sessionManager = sessionManager; + _userManager = userManager; + _logger = logger; + _timerFactory = timerFactory; + } + + public void Run() + { + _libraryManager.ItemAdded += libraryManager_ItemAdded; + _libraryManager.ItemUpdated += libraryManager_ItemUpdated; + _libraryManager.ItemRemoved += libraryManager_ItemRemoved; + + } + + /// <summary> + /// Handles the ItemAdded event of the libraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + void libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (!FilterItem(e.Item)) + { + return; + } + + lock (_libraryChangedSyncLock) + { + if (LibraryUpdateTimer == null) + { + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + Timeout.Infinite); + } + else + { + LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); + } + + if (e.Item.Parent != null) + { + _foldersAddedTo.Add(e.Item.Parent); + } + + _itemsAdded.Add(e.Item); + } + } + + /// <summary> + /// Handles the ItemUpdated event of the libraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + void libraryManager_ItemUpdated(object sender, ItemChangeEventArgs e) + { + if (!FilterItem(e.Item)) + { + return; + } + + lock (_libraryChangedSyncLock) + { + if (LibraryUpdateTimer == null) + { + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + Timeout.Infinite); + } + else + { + LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); + } + + _itemsUpdated.Add(e.Item); + } + } + + /// <summary> + /// Handles the ItemRemoved event of the libraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + void libraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + { + if (!FilterItem(e.Item)) + { + return; + } + + lock (_libraryChangedSyncLock) + { + if (LibraryUpdateTimer == null) + { + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + Timeout.Infinite); + } + else + { + LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); + } + + if (e.Item.Parent != null) + { + _foldersRemovedFrom.Add(e.Item.Parent); + } + + _itemsRemoved.Add(e.Item); + } + } + + /// <summary> + /// Libraries the update timer callback. + /// </summary> + /// <param name="state">The state.</param> + private void LibraryUpdateTimerCallback(object state) + { + lock (_libraryChangedSyncLock) + { + // Remove dupes in case some were saved multiple times + var foldersAddedTo = _foldersAddedTo.DistinctBy(i => i.Id).ToList(); + + var foldersRemovedFrom = _foldersRemovedFrom.DistinctBy(i => i.Id).ToList(); + + var itemsUpdated = _itemsUpdated + .Where(i => !_itemsAdded.Contains(i)) + .DistinctBy(i => i.Id) + .ToList(); + + SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None); + + if (LibraryUpdateTimer != null) + { + LibraryUpdateTimer.Dispose(); + LibraryUpdateTimer = null; + } + + _itemsAdded.Clear(); + _itemsRemoved.Clear(); + _itemsUpdated.Clear(); + _foldersAddedTo.Clear(); + _foldersRemovedFrom.Clear(); + } + } + + /// <summary> + /// Sends the change notifications. + /// </summary> + /// <param name="itemsAdded">The items added.</param> + /// <param name="itemsUpdated">The items updated.</param> + /// <param name="itemsRemoved">The items removed.</param> + /// <param name="foldersAddedTo">The folders added to.</param> + /// <param name="foldersRemovedFrom">The folders removed from.</param> + /// <param name="cancellationToken">The cancellation token.</param> + private async void SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken) + { + foreach (var user in _userManager.Users.ToList()) + { + var id = user.Id; + var userSessions = _sessionManager.Sessions + .Where(u => u.UserId.HasValue && u.UserId.Value == id && u.SessionController != null && u.IsActive) + .ToList(); + + if (userSessions.Count > 0) + { + LibraryUpdateInfo info; + + try + { + info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, + foldersRemovedFrom, id); + } + catch (Exception ex) + { + _logger.ErrorException("Error in GetLibraryUpdateInfo", ex); + return; + } + + foreach (var userSession in userSessions) + { + try + { + await userSession.SessionController.SendLibraryUpdateInfo(info, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending LibraryChanged message", ex); + } + } + } + + } + } + + /// <summary> + /// Gets the library update info. + /// </summary> + /// <param name="itemsAdded">The items added.</param> + /// <param name="itemsUpdated">The items updated.</param> + /// <param name="itemsRemoved">The items removed.</param> + /// <param name="foldersAddedTo">The folders added to.</param> + /// <param name="foldersRemovedFrom">The folders removed from.</param> + /// <param name="userId">The user id.</param> + /// <returns>LibraryUpdateInfo.</returns> + private LibraryUpdateInfo GetLibraryUpdateInfo(IEnumerable<BaseItem> itemsAdded, IEnumerable<BaseItem> itemsUpdated, IEnumerable<BaseItem> itemsRemoved, IEnumerable<Folder> foldersAddedTo, IEnumerable<Folder> foldersRemovedFrom, Guid userId) + { + var user = _userManager.GetUserById(userId); + + return new LibraryUpdateInfo + { + ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + + ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + + ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + + FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList() + }; + } + + private bool FilterItem(BaseItem item) + { + if (!item.IsFolder && item.LocationType == LocationType.Virtual) + { + return false; + } + + if (item is IItemByName && !(item is MusicArtist)) + { + return false; + } + + return item.SourceType == SourceType.Library; + } + + /// <summary> + /// Translates the physical item to user library. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + /// <param name="includeIfNotFound">if set to <c>true</c> [include if not found].</param> + /// <returns>IEnumerable{``0}.</returns> + private IEnumerable<T> TranslatePhysicalItemToUserLibrary<T>(T item, User user, bool includeIfNotFound = false) + where T : BaseItem + { + // If the physical root changed, return the user root + if (item is AggregateFolder) + { + return new[] { user.RootFolder as T }; + } + + // Return it only if it's in the user's library + if (includeIfNotFound || item.IsVisibleStandalone(user)) + { + return new[] { item }; + } + + return new T[] { }; + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + Dispose(true); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + if (LibraryUpdateTimer != null) + { + LibraryUpdateTimer.Dispose(); + LibraryUpdateTimer = null; + } + + _libraryManager.ItemAdded -= libraryManager_ItemAdded; + _libraryManager.ItemUpdated -= libraryManager_ItemUpdated; + _libraryManager.ItemRemoved -= libraryManager_ItemRemoved; + } + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs new file mode 100644 index 000000000..0203b5192 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs @@ -0,0 +1,73 @@ +using MediaBrowser.Common.Security; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; +using System; +using System.Threading.Tasks; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.EntryPoints +{ + /// <summary> + /// Class LoadRegistrations + /// </summary> + public class LoadRegistrations : IServerEntryPoint + { + /// <summary> + /// The _security manager + /// </summary> + private readonly ISecurityManager _securityManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + private ITimer _timer; + private readonly ITimerFactory _timerFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="LoadRegistrations" /> class. + /// </summary> + /// <param name="securityManager">The security manager.</param> + /// <param name="logManager">The log manager.</param> + public LoadRegistrations(ISecurityManager securityManager, ILogManager logManager, ITimerFactory timerFactory) + { + _securityManager = securityManager; + _timerFactory = timerFactory; + + _logger = logManager.GetLogger("Registration Loader"); + } + + /// <summary> + /// Runs this instance. + /// </summary> + public void Run() + { + _timer = _timerFactory.Create(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12)); + } + + private async Task LoadAllRegistrations() + { + try + { + await _securityManager.LoadAllRegistrationInfo().ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error loading registration info", ex); + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs new file mode 100644 index 000000000..b674fc39b --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Threading; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; + +namespace Emby.Server.Implementations.EntryPoints +{ + public class RecordingNotifier : IServerEntryPoint + { + private readonly ILiveTvManager _liveTvManager; + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + + public RecordingNotifier(ISessionManager sessionManager, IUserManager userManager, ILogger logger, ILiveTvManager liveTvManager) + { + _sessionManager = sessionManager; + _userManager = userManager; + _logger = logger; + _liveTvManager = liveTvManager; + } + + public void Run() + { + _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled; + _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled; + _liveTvManager.TimerCreated += _liveTvManager_TimerCreated; + _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated; + } + + private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + { + SendMessage("SeriesTimerCreated", e.Argument); + } + + private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + { + SendMessage("TimerCreated", e.Argument); + } + + private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + { + SendMessage("SeriesTimerCancelled", e.Argument); + } + + private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + { + SendMessage("TimerCancelled", e.Argument); + } + + private async void SendMessage(string name, TimerEventInfo info) + { + var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id.ToString("N")).ToList(); + + try + { + await _sessionManager.SendMessageToUserSessions<TimerEventInfo>(users, name, info, CancellationToken.None); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); + } + } + + public void Dispose() + { + _liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled; + _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; + _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; + _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs new file mode 100644 index 000000000..77de849a1 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using System.Threading; + +namespace Emby.Server.Implementations.EntryPoints +{ + /// <summary> + /// Class RefreshUsersMetadata + /// </summary> + public class RefreshUsersMetadata : IServerEntryPoint + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + public RefreshUsersMetadata(IUserManager userManager) + { + _userManager = userManager; + } + + /// <summary> + /// Runs this instance. + /// </summary> + public async void Run() + { + await _userManager.RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs new file mode 100644 index 000000000..4d640bc95 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -0,0 +1,203 @@ +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Threading; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.EntryPoints +{ + /// <summary> + /// Class WebSocketEvents + /// </summary> + public class ServerEventNotifier : IServerEntryPoint + { + /// <summary> + /// The _server manager + /// </summary> + private readonly IServerManager _serverManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _installation manager + /// </summary> + private readonly IInstallationManager _installationManager; + + /// <summary> + /// The _kernel + /// </summary> + private readonly IServerApplicationHost _appHost; + + /// <summary> + /// The _task manager + /// </summary> + private readonly ITaskManager _taskManager; + + private readonly ISessionManager _sessionManager; + private readonly ISyncManager _syncManager; + + public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, ISessionManager sessionManager, ISyncManager syncManager) + { + _serverManager = serverManager; + _userManager = userManager; + _installationManager = installationManager; + _appHost = appHost; + _taskManager = taskManager; + _sessionManager = sessionManager; + _syncManager = syncManager; + } + + public void Run() + { + _userManager.UserDeleted += userManager_UserDeleted; + _userManager.UserUpdated += userManager_UserUpdated; + _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; + + _appHost.HasPendingRestartChanged += kernel_HasPendingRestartChanged; + + _installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling += _installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled += _installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted += _installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; + + _taskManager.TaskCompleted += _taskManager_TaskCompleted; + _syncManager.SyncJobCreated += _syncManager_SyncJobCreated; + _syncManager.SyncJobCancelled += _syncManager_SyncJobCancelled; + } + + void _syncManager_SyncJobCancelled(object sender, GenericEventArgs<SyncJob> e) + { + _sessionManager.SendMessageToUserDeviceSessions(e.Argument.TargetId, "SyncJobCancelled", e.Argument, CancellationToken.None); + } + + void _syncManager_SyncJobCreated(object sender, GenericEventArgs<SyncJobCreationResult> e) + { + _sessionManager.SendMessageToUserDeviceSessions(e.Argument.Job.TargetId, "SyncJobCreated", e.Argument, CancellationToken.None); + } + + void _installationManager_PackageInstalling(object sender, InstallationEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstalling", e.InstallationInfo); + } + + void _installationManager_PackageInstallationCancelled(object sender, InstallationEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationCancelled", e.InstallationInfo); + } + + void _installationManager_PackageInstallationCompleted(object sender, InstallationEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationCompleted", e.InstallationInfo); + } + + void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationFailed", e.InstallationInfo); + } + + void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + { + _serverManager.SendWebSocketMessage("ScheduledTaskEnded", e.Result); + } + + /// <summary> + /// Installations the manager_ plugin uninstalled. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The e.</param> + void InstallationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e) + { + _serverManager.SendWebSocketMessage("PluginUninstalled", e.Argument.GetPluginInfo()); + } + + /// <summary> + /// Handles the HasPendingRestartChanged event of the kernel control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> + void kernel_HasPendingRestartChanged(object sender, EventArgs e) + { + _sessionManager.SendRestartRequiredNotification(CancellationToken.None); + } + + /// <summary> + /// Users the manager_ user updated. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The e.</param> + void userManager_UserUpdated(object sender, GenericEventArgs<User> e) + { + var dto = _userManager.GetUserDto(e.Argument); + + SendMessageToUserSession(e.Argument, "UserUpdated", dto); + } + + /// <summary> + /// Users the manager_ user deleted. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The e.</param> + void userManager_UserDeleted(object sender, GenericEventArgs<User> e) + { + SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N")); + } + + void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e) + { + var dto = _userManager.GetUserDto(e.Argument); + + SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto); + } + + private async void SendMessageToUserSession<T>(User user, string name, T data) + { + await _sessionManager.SendMessageToUserSessions(new List<string> { user.Id.ToString("N") }, name, data, CancellationToken.None); + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + Dispose(true); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + _userManager.UserDeleted -= userManager_UserDeleted; + _userManager.UserUpdated -= userManager_UserUpdated; + _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; + + _installationManager.PluginUninstalled -= InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling -= _installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled -= _installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted -= _installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; + + _appHost.HasPendingRestartChanged -= kernel_HasPendingRestartChanged; + _syncManager.SyncJobCreated -= _syncManager_SyncJobCreated; + _syncManager.SyncJobCancelled -= _syncManager_SyncJobCancelled; + } + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs new file mode 100644 index 000000000..1b897ca29 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -0,0 +1,133 @@ +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; + +namespace Emby.Server.Implementations.EntryPoints +{ + /// <summary> + /// Class UsageEntryPoint + /// </summary> + public class UsageEntryPoint : IServerEntryPoint + { + private readonly IApplicationHost _applicationHost; + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly IServerConfigurationManager _config; + + private readonly ConcurrentDictionary<Guid, ClientInfo> _apps = new ConcurrentDictionary<Guid, ClientInfo>(); + + public UsageEntryPoint(ILogger logger, IApplicationHost applicationHost, IHttpClient httpClient, ISessionManager sessionManager, IUserManager userManager, IServerConfigurationManager config) + { + _logger = logger; + _applicationHost = applicationHost; + _httpClient = httpClient; + _sessionManager = sessionManager; + _userManager = userManager; + _config = config; + + _sessionManager.SessionStarted += _sessionManager_SessionStarted; + } + + void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + { + var session = e.SessionInfo; + + if (!string.IsNullOrEmpty(session.Client) && + !string.IsNullOrEmpty(session.DeviceName) && + !string.IsNullOrEmpty(session.DeviceId) && + !string.IsNullOrEmpty(session.ApplicationVersion)) + { + var keys = new List<string> + { + session.Client, + session.DeviceName, + session.DeviceId, + session.ApplicationVersion + }; + + var key = string.Join("_", keys.ToArray()).GetMD5(); + + _apps.GetOrAdd(key, guid => GetNewClientInfo(session)); + } + } + + private async void ReportNewSession(ClientInfo client) + { + if (!_config.Configuration.EnableAnonymousUsageReporting) + { + return; + } + + try + { + await new UsageReporter(_applicationHost, _httpClient, _userManager, _logger) + .ReportAppUsage(client, CancellationToken.None) + .ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending anonymous usage statistics.", ex); + } + } + + private ClientInfo GetNewClientInfo(SessionInfo session) + { + var info = new ClientInfo + { + AppName = session.Client, + AppVersion = session.ApplicationVersion, + DeviceName = session.DeviceName, + DeviceId = session.DeviceId + }; + + ReportNewSession(info); + + return info; + } + + public async void Run() + { + await Task.Delay(5000).ConfigureAwait(false); + OnTimerFired(); + } + + /// <summary> + /// Called when [timer fired]. + /// </summary> + private async void OnTimerFired() + { + if (!_config.Configuration.EnableAnonymousUsageReporting) + { + return; + } + + try + { + await new UsageReporter(_applicationHost, _httpClient, _userManager, _logger) + .ReportServerUsage(CancellationToken.None) + .ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending anonymous usage statistics.", ex); + } + } + + public void Dispose() + { + _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs new file mode 100644 index 000000000..be848acb7 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs @@ -0,0 +1,138 @@ +using MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Connect; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; + +namespace Emby.Server.Implementations.EntryPoints +{ + public class UsageReporter + { + private readonly IApplicationHost _applicationHost; + private readonly IHttpClient _httpClient; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + private const string MbAdminUrl = "https://www.mb3admin.com/admin/"; + + public UsageReporter(IApplicationHost applicationHost, IHttpClient httpClient, IUserManager userManager, ILogger logger) + { + _applicationHost = applicationHost; + _httpClient = httpClient; + _userManager = userManager; + _logger = logger; + } + + public async Task ReportServerUsage(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var data = new Dictionary<string, string> + { + { "feature", _applicationHost.Name }, + { "mac", _applicationHost.SystemId }, + { "serverid", _applicationHost.SystemId }, + { "deviceid", _applicationHost.SystemId }, + { "ver", _applicationHost.ApplicationVersion.ToString() }, + { "platform", _applicationHost.OperatingSystemDisplayName }, + { "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()} + }; + + var users = _userManager.Users.ToList(); + + data["localusers"] = users.Count(i => !i.ConnectLinkType.HasValue).ToString(CultureInfo.InvariantCulture); + data["guests"] = users.Count(i => i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest).ToString(CultureInfo.InvariantCulture); + data["linkedusers"] = users.Count(i => i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.LinkedUser).ToString(CultureInfo.InvariantCulture); + + data["plugins"] = string.Join(",", _applicationHost.Plugins.Select(i => i.Id).ToArray()); + + var logErrors = false; +#if DEBUG + logErrors = true; +#endif + var options = new HttpRequestOptions + { + Url = MbAdminUrl + "service/registration/ping", + CancellationToken = cancellationToken, + + // Seeing block length errors + EnableHttpCompression = false, + + LogRequest = false, + LogErrors = logErrors, + BufferContent = false + }; + + options.SetPostData(data); + + using (var response = await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)) + { + + } + } + + public async Task ReportAppUsage(ClientInfo app, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(app.DeviceId)) + { + throw new ArgumentException("Client info must have a device Id"); + } + + _logger.Info("App Activity: app: {0}, version: {1}, deviceId: {2}, deviceName: {3}", + app.AppName ?? "Unknown App", + app.AppVersion ?? "Unknown", + app.DeviceId, + app.DeviceName ?? "Unknown"); + + cancellationToken.ThrowIfCancellationRequested(); + + var data = new Dictionary<string, string> + { + { "feature", app.AppName ?? "Unknown App" }, + { "serverid", _applicationHost.SystemId }, + { "deviceid", app.DeviceId }, + { "mac", app.DeviceId }, + { "ver", app.AppVersion ?? "Unknown" }, + { "platform", app.DeviceName }, + }; + + var logErrors = false; + +#if DEBUG + logErrors = true; +#endif + var options = new HttpRequestOptions + { + Url = MbAdminUrl + "service/registration/ping", + CancellationToken = cancellationToken, + + // Seeing block length errors + EnableHttpCompression = false, + + LogRequest = false, + LogErrors = logErrors, + BufferContent = false + }; + + options.SetPostData(data); + + using (var response = await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)) + { + + } + } + } + + public class ClientInfo + { + public string AppName { get; set; } + public string AppVersion { get; set; } + public string DeviceName { get; set; } + public string DeviceId { get; set; } + } +} diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs new file mode 100644 index 000000000..b93410180 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -0,0 +1,165 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.EntryPoints +{ + class UserDataChangeNotifier : IServerEntryPoint + { + private readonly ISessionManager _sessionManager; + private readonly ILogger _logger; + private readonly IUserDataManager _userDataManager; + private readonly IUserManager _userManager; + + private readonly object _syncLock = new object(); + private ITimer UpdateTimer { get; set; } + private readonly ITimerFactory _timerFactory; + private const int UpdateDuration = 500; + + private readonly Dictionary<Guid, List<IHasUserData>> _changedItems = new Dictionary<Guid, List<IHasUserData>>(); + + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager, ITimerFactory timerFactory) + { + _userDataManager = userDataManager; + _sessionManager = sessionManager; + _logger = logger; + _userManager = userManager; + _timerFactory = timerFactory; + } + + public void Run() + { + _userDataManager.UserDataSaved += _userDataManager_UserDataSaved; + } + + void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) + { + if (e.SaveReason == UserDataSaveReason.PlaybackProgress) + { + return; + } + + lock (_syncLock) + { + if (UpdateTimer == null) + { + UpdateTimer = _timerFactory.Create(UpdateTimerCallback, null, UpdateDuration, + Timeout.Infinite); + } + else + { + UpdateTimer.Change(UpdateDuration, Timeout.Infinite); + } + + List<IHasUserData> keys; + + if (!_changedItems.TryGetValue(e.UserId, out keys)) + { + keys = new List<IHasUserData>(); + _changedItems[e.UserId] = keys; + } + + keys.Add(e.Item); + + var baseItem = e.Item as BaseItem; + + // Go up one level for indicators + if (baseItem != null) + { + var parent = baseItem.GetParent(); + + if (parent != null) + { + keys.Add(parent); + } + } + } + } + + private void UpdateTimerCallback(object state) + { + lock (_syncLock) + { + // Remove dupes in case some were saved multiple times + var changes = _changedItems.ToList(); + _changedItems.Clear(); + + var task = SendNotifications(changes, CancellationToken.None); + + if (UpdateTimer != null) + { + UpdateTimer.Dispose(); + UpdateTimer = null; + } + } + } + + private async Task SendNotifications(IEnumerable<KeyValuePair<Guid, List<IHasUserData>>> changes, CancellationToken cancellationToken) + { + foreach (var pair in changes) + { + var userId = pair.Key; + var userSessions = _sessionManager.Sessions + .Where(u => u.ContainsUser(userId) && u.SessionController != null && u.IsActive) + .ToList(); + + if (userSessions.Count > 0) + { + var user = _userManager.GetUserById(userId); + + var dtoList = pair.Value + .DistinctBy(i => i.Id) + .Select(i => + { + var dto = _userDataManager.GetUserDataDto(i, user).Result; + dto.ItemId = i.Id.ToString("N"); + return dto; + }) + .ToList(); + + var info = new UserDataChangeInfo + { + UserId = userId.ToString("N"), + + UserDataList = dtoList + }; + + foreach (var userSession in userSessions) + { + try + { + await userSession.SessionController.SendUserDataChangeInfo(info, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending UserDataChanged message", ex); + } + } + } + + } + } + + public void Dispose() + { + if (UpdateTimer != null) + { + UpdateTimer.Dispose(); + UpdateTimer = null; + } + + _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + } + } +} |
