diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations')
16 files changed, 1064 insertions, 167 deletions
diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityManager.cs b/MediaBrowser.Server.Implementations/Activity/ActivityManager.cs new file mode 100644 index 000000000..46eaa7f21 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Activity/ActivityManager.cs @@ -0,0 +1,40 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Activity +{ + public class ActivityManager : IActivityManager + { + public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; + + private readonly IActivityRepository _repo; + private readonly ILogger _logger; + + public ActivityManager(ILogger logger, IActivityRepository repo) + { + _logger = logger; + _repo = repo; + } + + public async Task Create(ActivityLogEntry entry) + { + entry.Id = Guid.NewGuid().ToString("N"); + entry.Date = DateTime.UtcNow; + + await _repo.Create(entry).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(EntryCreated, this, new GenericEventArgs<ActivityLogEntry>(entry), _logger); + } + + public QueryResult<ActivityLogEntry> GetActivityLogEntries(int? startIndex, int? limit) + { + return _repo.GetActivityLogEntries(startIndex, limit); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs new file mode 100644 index 000000000..787cbcc5b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs @@ -0,0 +1,293 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using MediaBrowser.Server.Implementations.Persistence; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Activity +{ + public class ActivityRepository : IActivityRepository, IDisposable + { + private IDbConnection _connection; + private readonly ILogger _logger; + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + private IDbCommand _saveActivityCommand; + + public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths) + { + _logger = logger; + _appPaths = appPaths; + } + + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db"); + + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY, Name TEXT, Overview TEXT, ShortOverview TEXT, Type TEXT, ItemId TEXT, UserId TEXT, DateCreated DATETIME, LogSeverity TEXT)", + "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)", + + //pragmas + "pragma temp_store = memory", + + "pragma shrink_memory" + }; + + _connection.RunQueries(queries, _logger); + + PrepareStatements(); + } + + private void PrepareStatements() + { + _saveActivityCommand = _connection.CreateCommand(); + _saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"; + + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Id"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Name"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Overview"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ShortOverview"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Type"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ItemId"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@UserId"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@DateCreated"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@LogSeverity"); + } + + private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries"; + + public Task Create(ActivityLogEntry entry) + { + return Update(entry); + } + + public async Task Update(ActivityLogEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException("entry"); + } + + await _writeLock.WaitAsync().ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id); + _saveActivityCommand.GetParameter(index++).Value = entry.Name; + _saveActivityCommand.GetParameter(index++).Value = entry.Overview; + _saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview; + _saveActivityCommand.GetParameter(index++).Value = entry.Type; + _saveActivityCommand.GetParameter(index++).Value = entry.ItemId; + _saveActivityCommand.GetParameter(index++).Value = entry.UserId; + _saveActivityCommand.GetParameter(index++).Value = entry.Date; + _saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString(); + + _saveActivityCommand.Transaction = transaction; + + _saveActivityCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + + public QueryResult<ActivityLogEntry> GetActivityLogEntries(int? startIndex, int? limit) + { + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseActivitySelectText; + + var whereClauses = new List<string>(); + + if (startIndex.HasValue && startIndex.Value > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries ORDER BY DateCreated DESC LIMIT {0})", + startIndex.Value.ToString(_usCulture))); + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateCreated DESC"; + + if (limit.HasValue) + { + cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from ActivityLogEntries"; + + var list = new List<ActivityLogEntry>(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(GetEntry(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult<ActivityLogEntry>() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + private ActivityLogEntry GetEntry(IDataReader reader) + { + var index = 0; + + var info = new ActivityLogEntry + { + Id = reader.GetGuid(index).ToString("N") + }; + + index++; + if (!reader.IsDBNull(index)) + { + info.Name = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.Overview = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.ShortOverview = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.Type = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.ItemId = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.UserId = reader.GetString(index); + } + + index++; + info.Date = reader.GetDateTime(index).ToUniversalTime(); + + index++; + if (!reader.IsDBNull(index)) + { + info.Severity = (LogSeverity)Enum.Parse(typeof(LogSeverity), reader.GetString(index), true); + } + + return info; + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// <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) + { + try + { + lock (_disposeLock) + { + if (_connection != null) + { + if (_connection.IsOpen()) + { + _connection.Close(); + } + + _connection.Dispose(); + _connection = null; + } + } + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing database", ex); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs new file mode 100644 index 000000000..7af2a1c84 --- /dev/null +++ b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs @@ -0,0 +1,543 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Implementations.Logging; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Updates; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MediaBrowser.Server.Implementations.EntryPoints +{ + public class ActivityLogEntryPoint : IServerEntryPoint + { + private readonly IInstallationManager _installationManager; + + //private readonly ILogManager _logManager; + private readonly ILogger _logger; + private readonly ISessionManager _sessionManager; + private readonly ITaskManager _taskManager; + private readonly IActivityManager _activityManager; + private readonly ILocalizationManager _localization; + + private readonly ILibraryManager _libraryManager; + private readonly ISubtitleManager _subManager; + private readonly IUserManager _userManager; + private readonly IServerConfigurationManager _config; + private readonly IServerApplicationHost _appHost; + + public ActivityLogEntryPoint(ISessionManager sessionManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) + { + //_logger = _logManager.GetLogger("ActivityLogEntryPoint"); + _sessionManager = sessionManager; + _taskManager = taskManager; + _activityManager = activityManager; + _localization = localization; + _installationManager = installationManager; + _libraryManager = libraryManager; + _subManager = subManager; + _userManager = userManager; + _config = config; + //_logManager = logManager; + _appHost = appHost; + } + + public void Run() + { + _taskManager.TaskExecuting += _taskManager_TaskExecuting; + _taskManager.TaskCompleted += _taskManager_TaskCompleted; + + _installationManager.PluginInstalled += _installationManager_PluginInstalled; + _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; + _installationManager.PluginUpdated += _installationManager_PluginUpdated; + + _libraryManager.ItemAdded += _libraryManager_ItemAdded; + _libraryManager.ItemRemoved += _libraryManager_ItemRemoved; + + _sessionManager.SessionStarted += _sessionManager_SessionStarted; + _sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed; + _sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded; + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + + _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + + _subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded; + _subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure; + + _userManager.UserCreated += _userManager_UserCreated; + _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; + _userManager.UserDeleted += _userManager_UserDeleted; + _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; + + _config.ConfigurationUpdated += _config_ConfigurationUpdated; + _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + + //_logManager.LoggerLoaded += _logManager_LoggerLoaded; + + _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; + } + + void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureForItem"), Notifications.Notifications.GetItemName(e.Item)), + Type = "SubtitleDownloadFailure", + ItemId = e.Item.Id.ToString("N"), + ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider), + Overview = LogHelper.GetLogMessage(e.Exception).ToString() + }); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + var item = e.MediaInfo; + + if (item == null) + { + //_logger.Warn("PlaybackStopped reported with null media info."); + return; + } + + if (e.Users.Count == 0) + { + return; + } + + var username = e.Users.First().Name; + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), username, item.Name), + Type = "PlaybackStopped", + ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), e.ClientName, e.DeviceName) + }); + } + + void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + { + var item = e.MediaInfo; + + if (item == null) + { + //_logger.Warn("PlaybackStart reported with null media info."); + return; + } + + if (e.Users.Count == 0) + { + return; + } + + var username = e.Users.First().Name; + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), username, item.Name), + Type = "PlaybackStart", + ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), e.ClientName, e.DeviceName) + }); + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + string name; + var session = e.SessionInfo; + + if (string.IsNullOrWhiteSpace(session.UserName)) + { + name = string.Format(_localization.GetLocalizedString("DeviceOfflineWithName"), session.DeviceName); + } + else + { + name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName); + } + + CreateLogEntry(new ActivityLogEntry + { + Name = name, + Type = "SessionEnded", + ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint) + }); + } + + void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationRequest> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), e.Argument.Username), + Type = "AuthenticationSucceeded" + }); + } + + void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username), + Type = "AuthenticationFailed" + }); + } + + void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = _localization.GetLocalizedString("MessageApplicationUpdated"), + Type = "ApplicationUpdated", + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr), + Overview = e.Argument.description + }); + } + + void _logManager_LoggerLoaded(object sender, EventArgs e) + { + } + + void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key), + Type = "NamedConfigurationUpdated" + }); + } + + void _config_ConfigurationUpdated(object sender, EventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"), + Type = "ServerConfigurationUpdated" + }); + } + + void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserConfigurationUpdatedWithName"), e.Argument.Name), + Type = "UserConfigurationUpdated" + }); + } + + void _userManager_UserDeleted(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name), + Type = "UserDeleted" + }); + } + + void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), + Type = "UserPasswordChanged" + }); + } + + void _userManager_UserCreated(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), + Type = "UserCreated" + }); + } + + void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)), + Type = "SubtitlesDownloaded", + ItemId = e.Item.Id.ToString("N"), + ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider) + }); + } + + void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + { + string name; + var session = e.SessionInfo; + + if (string.IsNullOrWhiteSpace(session.UserName)) + { + name = string.Format(_localization.GetLocalizedString("DeviceOnlineWithName"), session.DeviceName); + } + else + { + name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName); + } + + CreateLogEntry(new ActivityLogEntry + { + Name = name, + Type = "SessionStarted", + ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint) + }); + } + + void _libraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + { + if (e.Item is LiveTvProgram || e.Item is IChannelItem) + { + return; + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ItemRemovedWithName"), Notifications.Notifications.GetItemName(e.Item)), + Type = "ItemRemoved" + }); + } + + void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (e.Item is LiveTvProgram || e.Item is IChannelItem) + { + return; + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ItemAddedWithName"), Notifications.Notifications.GetItemName(e.Item)), + Type = "ItemAdded", + ItemId = e.Item.Id.ToString("N") + }); + } + + void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), + Type = "PluginUpdated", + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.Item2.versionStr), + Overview = e.Argument.Item2.description + }); + } + + void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), + Type = "PluginUninstalled" + }); + } + + void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), + Type = "PluginInstalled", + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr) + }); + } + + void _taskManager_TaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e) + { + var task = e.Argument; + + var activityTask = task.ScheduledTask as IScheduledTaskActivityLog; + if (activityTask != null && !activityTask.IsActivityLogged) + { + return; + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskStartedWithName"), task.Name), + Type = "ScheduledTaskStarted" + }); + } + + void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + { + var result = e.Result; + var task = e.Task; + + var activityTask = task.ScheduledTask as IScheduledTaskActivityLog; + if (activityTask != null && !activityTask.IsActivityLogged) + { + return; + } + + var time = result.EndTimeUtc - result.StartTimeUtc; + var runningTime = string.Format(_localization.GetLocalizedString("LabelRunningTimeValue"), ToUserFriendlyString(time)); + + if (result.Status == TaskCompletionStatus.Cancelled) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskCancelledWithName"), task.Name), + Type = "ScheduledTaskCancelled", + ShortOverview = runningTime + }); + } + else if (result.Status == TaskCompletionStatus.Completed) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskCompletedWithName"), task.Name), + Type = "ScheduledTaskCompleted", + ShortOverview = runningTime + }); + } + else if (result.Status == TaskCompletionStatus.Failed) + { + var vals = new List<string>(); + + if (!string.IsNullOrWhiteSpace(e.Result.ErrorMessage)) + { + vals.Add(e.Result.ErrorMessage); + } + if (!string.IsNullOrWhiteSpace(e.Result.LongErrorMessage)) + { + vals.Add(e.Result.LongErrorMessage); + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), + Type = "ScheduledTaskFailed", + Overview = string.Join(Environment.NewLine, vals.ToArray()), + ShortOverview = runningTime + }); + } + } + + private async void CreateLogEntry(ActivityLogEntry entry) + { + try + { + await _activityManager.Create(entry).ConfigureAwait(false); + } + catch + { + // Logged at lower levels + } + } + + public void Dispose() + { + _taskManager.TaskExecuting -= _taskManager_TaskExecuting; + _taskManager.TaskCompleted -= _taskManager_TaskCompleted; + + _installationManager.PluginInstalled -= _installationManager_PluginInstalled; + _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; + _installationManager.PluginUpdated -= _installationManager_PluginUpdated; + + _libraryManager.ItemAdded -= _libraryManager_ItemAdded; + _libraryManager.ItemRemoved -= _libraryManager_ItemRemoved; + + _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + _sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed; + _sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded; + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + + _subManager.SubtitlesDownloaded -= _subManager_SubtitlesDownloaded; + _subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure; + + _userManager.UserCreated -= _userManager_UserCreated; + _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; + _userManager.UserDeleted -= _userManager_UserDeleted; + _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; + + _config.ConfigurationUpdated -= _config_ConfigurationUpdated; + _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; + + //_logManager.LoggerLoaded -= _logManager_LoggerLoaded; + + _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + } + + /// <summary> + /// Constructs a user-friendly string for this TimeSpan instance. + /// </summary> + public static string ToUserFriendlyString(TimeSpan span) + { + const int DaysInYear = 365; + const int DaysInMonth = 30; + + // Get each non-zero value from TimeSpan component + List<string> values = new List<string>(); + + // Number of years + int days = span.Days; + if (days >= DaysInYear) + { + int years = (days / DaysInYear); + values.Add(CreateValueString(years, "year")); + days = (days % DaysInYear); + } + // Number of months + if (days >= DaysInMonth) + { + int months = (days / DaysInMonth); + values.Add(CreateValueString(months, "month")); + days = (days % DaysInMonth); + } + // Number of days + if (days >= 1) + values.Add(CreateValueString(days, "day")); + // Number of hours + if (span.Hours >= 1) + values.Add(CreateValueString(span.Hours, "hour")); + // Number of minutes + if (span.Minutes >= 1) + values.Add(CreateValueString(span.Minutes, "minute")); + // Number of seconds (include when 0 if no other components included) + if (span.Seconds >= 1 || values.Count == 0) + values.Add(CreateValueString(span.Seconds, "second")); + + // Combine values into string + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < values.Count; i++) + { + if (builder.Length > 0) + builder.Append((i == (values.Count - 1)) ? " and " : ", "); + builder.Append(values[i]); + } + // Return result + return builder.ToString(); + } + + /// <summary> + /// Constructs a string description of a time-span value. + /// </summary> + /// <param name="value">The value of this item</param> + /// <param name="description">The name of this item (singular form)</param> + private static string CreateValueString(int value, string description) + { + return String.Format("{0:#,##0} {1}", + value, (value == 1) ? description : String.Format("{0}s", description)); + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs index b10b64c3e..6a0bb780c 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs @@ -2,14 +2,12 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; @@ -37,7 +35,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications private readonly ITaskManager _taskManager; private readonly INotificationManager _notificationManager; - private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ISessionManager _sessionManager; private readonly IServerApplicationHost _appHost; @@ -45,14 +42,13 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications private Timer LibraryUpdateTimer { get; set; } private readonly object _libraryChangedSyncLock = new object(); - public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, IServerConfigurationManager config, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost) + public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost) { _installationManager = installationManager; _userManager = userManager; _logger = logger; _taskManager = taskManager; _notificationManager = notificationManager; - _config = config; _libraryManager = libraryManager; _sessionManager = sessionManager; _appHost = appHost; @@ -317,7 +313,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications } } - private string GetItemName(BaseItem item) + public static string GetItemName(BaseItem item) { var name = item.Name; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs index 42aadf62e..5f1db03c6 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs @@ -23,7 +23,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications public void Run() { _notificationsRepo.NotificationAdded += _notificationsRepo_NotificationAdded; - _notificationsRepo.NotificationUpdated += _notificationsRepo_NotificationUpdated; _notificationsRepo.NotificationsMarkedRead += _notificationsRepo_NotificationsMarkedRead; } @@ -40,13 +39,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications _serverManager.SendWebSocketMessage("NotificationsMarkedRead", msg); } - void _notificationsRepo_NotificationUpdated(object sender, NotificationUpdateEventArgs e) - { - var msg = e.Notification.UserId + "|" + e.Notification.Id; - - _serverManager.SendWebSocketMessage("NotificationUpdated", msg); - } - void _notificationsRepo_NotificationAdded(object sender, NotificationUpdateEventArgs e) { var msg = e.Notification.UserId + "|" + e.Notification.Id; @@ -57,7 +49,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications public void Dispose() { _notificationsRepo.NotificationAdded -= _notificationsRepo_NotificationAdded; - _notificationsRepo.NotificationUpdated -= _notificationsRepo_NotificationUpdated; } } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 305f2800f..b969d551a 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -3,11 +3,13 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Activity; using MediaBrowser.Model.Events; using System; using System.Threading; @@ -47,6 +49,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IDtoService _dtoService; private readonly ISessionManager _sessionManager; + private readonly IActivityManager _activityManager; /// <summary> /// Initializes a new instance of the <see cref="ServerEventNotifier" /> class. @@ -58,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// <param name="taskManager">The task manager.</param> /// <param name="dtoService">The dto service.</param> /// <param name="sessionManager">The session manager.</param> - public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager) + public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager, IActivityManager activityManager) { _serverManager = serverManager; _userManager = userManager; @@ -67,6 +70,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _taskManager = taskManager; _dtoService = dtoService; _sessionManager = sessionManager; + _activityManager = activityManager; } public void Run() @@ -84,6 +88,13 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; _taskManager.TaskCompleted += _taskManager_TaskCompleted; + + _activityManager.EntryCreated += _activityManager_EntryCreated; + } + + void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e) + { + _serverManager.SendWebSocketMessage("ActivityLogEntryCreated", e.Argument); } void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 1c4ccb141..8dfdfdaec 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -13,7 +13,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.FileOrganization { - public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask + public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask, IScheduledTaskActivityLog { private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; @@ -77,5 +77,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { get { return GetTvOptions().IsEnabled; } } + + public bool IsActivityLogged + { + get { return false; } + } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index b18d0df5e..cd871d8eb 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -110,7 +110,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { try { - var webSocketContext = ctx.AcceptWebSocket(null, null); + var webSocketContext = ctx.AcceptWebSocket(null); if (WebSocketHandler != null) { diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 43c9f3b9c..139f8629c 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -48,6 +48,7 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> /// <value>The user repository.</value> private IUserRepository UserRepository { get; set; } + public event EventHandler<GenericEventArgs<User>> UserPasswordChanged; private readonly IXmlSerializer _xmlSerializer; @@ -390,7 +391,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="user">The user.</param> /// <param name="newPassword">The new password.</param> /// <returns>Task.</returns> - public Task ChangePassword(User user, string newPassword) + public async Task ChangePassword(User user, string newPassword) { if (user == null) { @@ -399,7 +400,9 @@ namespace MediaBrowser.Server.Implementations.Library user.Password = string.IsNullOrEmpty(newPassword) ? string.Empty : GetSha1String(newPassword); - return UpdateUser(user); + await UpdateUser(user).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 605bbea0d..973089795 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -699,10 +699,10 @@ "HeaderProfileServerSettingsHelp": "These values control how Media Browser will present itself to the device.", "LabelMaxBitrate": "Max bitrate:", "LabelMaxBitrateHelp": "Specify a max bitrate in bandwidth constrained environments, or if the device imposes it's own limit.", - "LabelMaxStreamingBitrate": "Max streaming bitrate:", - "LabelMaxStreamingBitrateHelp": "Specify a max bitrate when streaming.", - "LabelMaxStaticBitrate": "Max sync bitrate:", - "LabelMaxStaticBitrateHelp": "Specify a max bitrate when syncing content at high quality.", + "LabelMaxStreamingBitrate": "Max streaming bitrate:", + "LabelMaxStreamingBitrateHelp": "Specify a max bitrate when streaming.", + "LabelMaxStaticBitrate": "Max sync bitrate:", + "LabelMaxStaticBitrateHelp": "Specify a max bitrate when syncing content at high quality.", "OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests", "OptionIgnoreTranscodeByteRangeRequestsHelp": "If enabled, these requests will be honored but will ignore the byte range header.", "LabelFriendlyName": "Friendly name", @@ -808,8 +808,8 @@ "TabNextUp": "Next Up", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the New button to start creating Collections.", - "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", - "MessageNoPlaylistItemsAvailable": "This playlist is currently empty.", + "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", + "MessageNoPlaylistItemsAvailable": "This playlist is currently empty.", "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client", "ButtonDismiss": "Dismiss", "MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.", @@ -918,52 +918,84 @@ "LabelContext": "Context:", "OptionContextStreaming": "Streaming", "OptionContextStatic": "Sync", - "ButtonAddToPlaylist": "Add to playlist", - "TabPlaylists": "Playlists", - "ButtonClose": "Close", - "LabelAllLanguages": "All languages", - "HeaderBrowseOnlineImages": "Browse Online Images", - "LabelSource": "Source:", - "OptionAll": "All", - "LabelImage": "Image:", - "ButtonUpload": "Upload", - "ButtonBrowseImages": "Browse Images", - "HeaderImages": "Images", - "HeaderBackdrops": "Backdrops", - "HeaderScreenshots": "Screenshots", - "HeaderAddUpdateImage": "Add/Update Image", - "LabelDropImageHere": "Drop image here", - "LabelJpgPngOnly": "JPG/PNG only", - "LabelImageType": "Image type:", - "OptionPrimary": "Primary", - "OptionArt": "Art", - "OptionBackdrop": "Backdrop", - "OptionBox": "Box", - "OptionBoxRear": "Box rear", - "OptionDisc": "Disc", - "OptionLogo": "Logo", - "OptionMenu": "Menu", - "OptionScreenshot": "Screenshot", - "OptionLocked": "Locked", - "OptionUnidentified": "Unidentified", - "OptionMissingParentalRating": "Missing parental rating", - "OptionStub": "Stub", - "HeaderEpisodes": "Episodes:", - "OptionSeason0": "Season 0", - "LabelReport": "Report:", - "OptionReportSongs": "Songs", - "OptionReportSeries": "Series", - "OptionReportSeasons": "Seasons", - "OptionReportTrailers": "Trailers", - "OptionReportMusicVideos": "Music videos", - "OptionReportMovies": "Movies", - "OptionReportHomeVideos": "Home videos", - "OptionReportGames": "Games", - "OptionReportEpisodes": "Episodes", - "OptionReportCollections": "Collections", - "OptionReportBooks": "Books", - "OptionReportArtists": "Artists", - "OptionReportAlbums": "Albums", + "ButtonAddToPlaylist": "Add to playlist", + "TabPlaylists": "Playlists", + "ButtonClose": "Close", + "LabelAllLanguages": "All languages", + "HeaderBrowseOnlineImages": "Browse Online Images", + "LabelSource": "Source:", + "OptionAll": "All", + "LabelImage": "Image:", + "ButtonUpload": "Upload", + "ButtonBrowseImages": "Browse Images", + "HeaderImages": "Images", + "HeaderBackdrops": "Backdrops", + "HeaderScreenshots": "Screenshots", + "HeaderAddUpdateImage": "Add/Update Image", + "LabelDropImageHere": "Drop image here", + "LabelJpgPngOnly": "JPG/PNG only", + "LabelImageType": "Image type:", + "OptionPrimary": "Primary", + "OptionArt": "Art", + "OptionBackdrop": "Backdrop", + "OptionBox": "Box", + "OptionBoxRear": "Box rear", + "OptionDisc": "Disc", + "OptionLogo": "Logo", + "OptionMenu": "Menu", + "OptionScreenshot": "Screenshot", + "OptionLocked": "Locked", + "OptionUnidentified": "Unidentified", + "OptionMissingParentalRating": "Missing parental rating", + "OptionStub": "Stub", + "HeaderEpisodes": "Episodes:", + "OptionSeason0": "Season 0", + "LabelReport": "Report:", + "OptionReportSongs": "Songs", + "OptionReportSeries": "Series", + "OptionReportSeasons": "Seasons", + "OptionReportTrailers": "Trailers", + "OptionReportMusicVideos": "Music videos", + "OptionReportMovies": "Movies", + "OptionReportHomeVideos": "Home videos", + "OptionReportGames": "Games", + "OptionReportEpisodes": "Episodes", + "OptionReportCollections": "Collections", + "OptionReportBooks": "Books", + "OptionReportArtists": "Artists", + "OptionReportAlbums": "Albums", "OptionReportAdultVideos": "Adult videos", - "ButtonMore": "More" + "ButtonMore": "More", + "HeaderActivity": "Activity", + "ScheduledTaskStartedWithName": "{0} started", + "ScheduledTaskCancelledWithName": "{0} was cancelled", + "ScheduledTaskCompletedWithName": "{0} completed", + "ScheduledTaskFailed": "Scheduled task completed", + "PluginInstalledWithName": "{0} was installed", + "PluginUpdatedWithName": "{0} was updated", + "PluginUninstalledWithName": "{0} was uninstalled", + "ScheduledTaskFailedWithName": "{0} failed", + "ItemAddedWithName": "{0} was added to the library", + "ItemRemovedWithName": "{0} was removed from the library", + "DeviceOnlineWithName": "{0} is connected", + "UserOnlineFromDevice": "{0} is online from {1}", + "DeviceOfflineWithName": "{0} has disconnected", + "UserOfflineFromDevice": "{0} has disconnected from {1}", + "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", + "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", + "LabelRunningTimeValue": "Running time: {0}", + "LabelIpAddressValue": "Ip address: {0}", + "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", + "UserCreatedWithName": "User {0} has been created", + "UserPasswordChangedWithName": "Password has been changed for user {0}", + "UserDeletedWithName": "User {0} has been deleted", + "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageApplicationUpdated": "Media Browser Server has been updated", + "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "UserStartedPlayingItemWithValues": "{0} has started playing {1}", + "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", + "AppDeviceValues": "App: {0}, Device: {1}", + "ProviderValue": "Provider: {0}" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9ee00dd42..58c8ef995 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -101,6 +101,8 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="Activity\ActivityManager.cs" /> + <Compile Include="Activity\ActivityRepository.cs" /> <Compile Include="Branding\BrandingConfigurationFactory.cs" /> <Compile Include="Channels\ChannelConfigurations.cs" /> <Compile Include="Channels\ChannelDownloadScheduledTask.cs" /> @@ -117,6 +119,7 @@ <Compile Include="Drawing\PlayedIndicatorDrawer.cs" /> <Compile Include="Drawing\UnplayedCountIndicator.cs" /> <Compile Include="Dto\DtoService.cs" /> + <Compile Include="EntryPoints\ActivityLogEntryPoint.cs" /> <Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" /> <Compile Include="EntryPoints\ExternalPortForwarding.cs" /> <Compile Include="EntryPoints\LibraryChangedNotifier.cs" /> @@ -492,4 +495,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index 2424a6652..d34d5a293 100644 --- a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -207,46 +207,6 @@ namespace MediaBrowser.Server.Implementations.Notifications } /// <summary> - /// Gets the notification. - /// </summary> - /// <param name="id">The id.</param> - /// <param name="userId">The user id.</param> - /// <returns>Notification.</returns> - /// <exception cref="System.ArgumentNullException"> - /// id - /// or - /// userId - /// </exception> - public Notification GetNotification(string id, string userId) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - if (string.IsNullOrEmpty(userId)) - { - throw new ArgumentNullException("userId"); - } - - using (var cmd = _connection.CreateCommand()) - { - cmd.CommandText = "select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId where Id=@Id And UserId = @UserId"; - - cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = new Guid(id); - cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId); - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) - { - if (reader.Read()) - { - return GetNotification(reader); - } - } - return null; - } - } - - /// <summary> /// Gets the level. /// </summary> /// <param name="reader">The reader.</param> @@ -290,32 +250,6 @@ namespace MediaBrowser.Server.Implementations.Notifications } /// <summary> - /// Updates the notification. - /// </summary> - /// <param name="notification">The notification.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task UpdateNotification(Notification notification, CancellationToken cancellationToken) - { - await ReplaceNotification(notification, cancellationToken).ConfigureAwait(false); - - if (NotificationUpdated != null) - { - try - { - NotificationUpdated(this, new NotificationUpdateEventArgs - { - Notification = notification - }); - } - catch (Exception ex) - { - _logger.ErrorException("Error in NotificationUpdated event handler", ex); - } - } - } - - /// <summary> /// Replaces the notification. /// </summary> /// <param name="notification">The notification.</param> diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index 3d0781e91..7a23d8e08 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -153,6 +153,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// <param name="result">The result.</param> private async void ProcessWebSocketMessageReceived(WebSocketMessageInfo result) { + //_logger.Debug("Websocket message received: {0}", result.MessageType); + var tasks = _webSocketListeners.Select(i => Task.Run(async () => { try diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 61c65cfd6..7db6d0191 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -14,11 +14,13 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; +using MediaBrowser.Server.Implementations.Security; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,7 +29,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.Security; namespace MediaBrowser.Server.Implementations.Session { @@ -76,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Session private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase); + public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed; + + public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationSucceeded; + /// <summary> /// Occurs when [playback start]. /// </summary> @@ -399,6 +404,11 @@ namespace MediaBrowser.Server.Implementations.Session Id = Guid.NewGuid().ToString("N") }; + sessionInfo.DeviceName = deviceName; + sessionInfo.UserId = userId; + sessionInfo.UserName = username; + sessionInfo.RemoteEndPoint = remoteEndPoint; + OnSessionStarted(sessionInfo); return sessionInfo; @@ -1191,44 +1201,37 @@ namespace MediaBrowser.Server.Implementations.Session /// <summary> /// Authenticates the new session. /// </summary> - /// <param name="username">The username.</param> - /// <param name="password">The password.</param> - /// <param name="clientType">Type of the client.</param> - /// <param name="appVersion">The application version.</param> - /// <param name="deviceId">The device identifier.</param> - /// <param name="deviceName">Name of the device.</param> - /// <param name="remoteEndPoint">The remote end point.</param> + /// <param name="request">The request.</param> /// <param name="isLocal">if set to <c>true</c> [is local].</param> /// <returns>Task{SessionInfo}.</returns> + /// <exception cref="AuthenticationException">Invalid user or password entered.</exception> /// <exception cref="System.UnauthorizedAccessException">Invalid user or password entered.</exception> /// <exception cref="UnauthorizedAccessException">Invalid user or password entered.</exception> - public async Task<AuthenticationResult> AuthenticateNewSession(string username, - string password, - string clientType, - string appVersion, - string deviceId, - string deviceName, - string remoteEndPoint, + public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request, bool isLocal) { - var result = (isLocal && string.Equals(clientType, "Dashboard", StringComparison.OrdinalIgnoreCase)) || - await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = (isLocal && string.Equals(request.App, "Dashboard", StringComparison.OrdinalIgnoreCase)) || + await _userManager.AuthenticateUser(request.Username, request.Password).ConfigureAwait(false); if (!result) { + EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger); + throw new AuthenticationException("Invalid user or password entered."); } var user = _userManager.Users - .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + .First(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); - var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false); + var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false); - var session = await LogSessionActivity(clientType, - appVersion, - deviceId, - deviceName, - remoteEndPoint, + EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationRequest>(request), _logger); + + var session = await LogSessionActivity(request.App, + request.AppVersion, + request.DeviceId, + request.DeviceName, + request.RemoteEndPoint, user) .ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index a50a6f02e..ed590a1f2 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Net; -using MediaBrowser.Controller; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index bb22e992b..65da74f9e 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Sync { - public class SyncRepository : ISyncRepository + public class SyncRepository : ISyncRepository, IDisposable { private IDbConnection _connection; private readonly ILogger _logger; @@ -422,8 +422,50 @@ namespace MediaBrowser.Server.Implementations.Sync } info.TargetId = reader.GetString(5); - + return info; } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// <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) + { + try + { + lock (_disposeLock) + { + if (_connection != null) + { + if (_connection.IsOpen()) + { + _connection.Close(); + } + + _connection.Dispose(); + _connection = null; + } + } + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing database", ex); + } + } + } } } |
