aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/EntryPoints
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/EntryPoints')
-rw-r--r--Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs124
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs343
-rw-r--r--Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs73
-rw-r--r--Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs77
-rw-r--r--Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs41
-rw-r--r--Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs203
-rw-r--r--Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs133
-rw-r--r--Emby.Server.Implementations/EntryPoints/UsageReporter.cs138
-rw-r--r--Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs165
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;
+ }
+ }
+}