From b76a1abda578b8ff64bad2997b036b0fc43e264f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Nov 2016 03:14:14 -0400 Subject: move classes to portable server lib --- .../Notifications/Notifications.cs | 547 +++++++++++++++++++++ .../Notifications/WebSocketNotifier.cs | 54 ++ 2 files changed, 601 insertions(+) create mode 100644 Emby.Server.Implementations/Notifications/Notifications.cs create mode 100644 Emby.Server.Implementations/Notifications/WebSocketNotifier.cs (limited to 'Emby.Server.Implementations/Notifications') diff --git a/Emby.Server.Implementations/Notifications/Notifications.cs b/Emby.Server.Implementations/Notifications/Notifications.cs new file mode 100644 index 0000000000..2d441c18cc --- /dev/null +++ b/Emby.Server.Implementations/Notifications/Notifications.cs @@ -0,0 +1,547 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Devices; +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.Entities; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Updates; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.Notifications +{ + /// + /// Creates notifications for various system events + /// + public class Notifications : IServerEntryPoint + { + private readonly IInstallationManager _installationManager; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + + private readonly ITaskManager _taskManager; + private readonly INotificationManager _notificationManager; + + private readonly ILibraryManager _libraryManager; + private readonly ISessionManager _sessionManager; + private readonly IServerApplicationHost _appHost; + private readonly ITimerFactory _timerFactory; + + private ITimer LibraryUpdateTimer { get; set; } + private readonly object _libraryChangedSyncLock = new object(); + + private readonly IConfigurationManager _config; + private readonly IDeviceManager _deviceManager; + + public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager, ITimerFactory timerFactory) + { + _installationManager = installationManager; + _userManager = userManager; + _logger = logger; + _taskManager = taskManager; + _notificationManager = notificationManager; + _libraryManager = libraryManager; + _sessionManager = sessionManager; + _appHost = appHost; + _config = config; + _deviceManager = deviceManager; + _timerFactory = timerFactory; + } + + public void Run() + { + _installationManager.PluginInstalled += _installationManager_PluginInstalled; + _installationManager.PluginUpdated += _installationManager_PluginUpdated; + _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; + _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; + + _taskManager.TaskCompleted += _taskManager_TaskCompleted; + + _userManager.UserCreated += _userManager_UserCreated; + _libraryManager.ItemAdded += _libraryManager_ItemAdded; + _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; + _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; + _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; + _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; + + _userManager.UserLockedOut += _userManager_UserLockedOut; + } + + async void _userManager_UserLockedOut(object sender, GenericEventArgs e) + { + var type = NotificationType.UserLockedOut.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type + }; + + notification.Variables["UserName"] = e.Argument.Name; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs e) + { + var type = NotificationType.CameraImageUploaded.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type + }; + + notification.Variables["DeviceName"] = e.Argument.Device.Name; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _appHost_ApplicationUpdated(object sender, GenericEventArgs e) + { + var type = NotificationType.ApplicationUpdateInstalled.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type, + Url = e.Argument.infoUrl + }; + + notification.Variables["Version"] = e.Argument.versionStr; + notification.Variables["ReleaseNotes"] = e.Argument.description; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _installationManager_PluginUpdated(object sender, GenericEventArgs> e) + { + var type = NotificationType.PluginUpdateInstalled.ToString(); + + var installationInfo = e.Argument.Item1; + + var notification = new NotificationRequest + { + Description = e.Argument.Item2.description, + NotificationType = type + }; + + notification.Variables["Name"] = installationInfo.Name; + notification.Variables["Version"] = installationInfo.Version.ToString(); + notification.Variables["ReleaseNotes"] = e.Argument.Item2.description; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _installationManager_PluginInstalled(object sender, GenericEventArgs e) + { + var type = NotificationType.PluginInstalled.ToString(); + + var installationInfo = e.Argument; + + var notification = new NotificationRequest + { + Description = installationInfo.description, + NotificationType = type + }; + + notification.Variables["Name"] = installationInfo.name; + notification.Variables["Version"] = installationInfo.versionStr; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) + { + // This notification is for users who can't auto-update (aka running as service) + if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate) + { + return; + } + + var type = NotificationType.ApplicationUpdateAvailable.ToString(); + + var notification = new NotificationRequest + { + Description = "Please see emby.media for details.", + NotificationType = type + }; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _appHost_HasPendingRestartChanged(object sender, EventArgs e) + { + if (!_appHost.HasPendingRestart) + { + return; + } + + var type = NotificationType.ServerRestartRequired.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type + }; + + await SendNotification(notification).ConfigureAwait(false); + } + + private NotificationOptions GetOptions() + { + return _config.GetConfiguration("notifications"); + } + + void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + { + var item = e.MediaInfo; + + if (item == null) + { + _logger.Warn("PlaybackStart reported with null media info."); + return; + } + + var video = e.Item as Video; + if (video != null && video.IsThemeMedia) + { + return; + } + + var type = GetPlaybackNotificationType(item.MediaType); + + SendPlaybackNotification(type, e); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + var item = e.MediaInfo; + + if (item == null) + { + _logger.Warn("PlaybackStopped reported with null media info."); + return; + } + + var video = e.Item as Video; + if (video != null && video.IsThemeMedia) + { + return; + } + + var type = GetPlaybackStoppedNotificationType(item.MediaType); + + SendPlaybackNotification(type, e); + } + + private async void SendPlaybackNotification(string type, PlaybackProgressEventArgs e) + { + var user = e.Users.FirstOrDefault(); + + if (user != null && !GetOptions().IsEnabledToMonitorUser(type, user.Id.ToString("N"))) + { + return; + } + + var item = e.MediaInfo; + + if ( item.IsThemeMedia) + { + // Don't report theme song or local trailer playback + return; + } + + var notification = new NotificationRequest + { + NotificationType = type + }; + + if (e.Item != null) + { + notification.Variables["ItemName"] = GetItemName(e.Item); + } + else + { + notification.Variables["ItemName"] = item.Name; + } + + notification.Variables["UserName"] = user == null ? "Unknown user" : user.Name; + notification.Variables["AppName"] = e.ClientName; + notification.Variables["DeviceName"] = e.DeviceName; + + await SendNotification(notification).ConfigureAwait(false); + } + + private string GetPlaybackNotificationType(string mediaType) + { + if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.AudioPlayback.ToString(); + } + if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.GamePlayback.ToString(); + } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.VideoPlayback.ToString(); + } + + return null; + } + + private string GetPlaybackStoppedNotificationType(string mediaType) + { + if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.AudioPlaybackStopped.ToString(); + } + if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.GamePlaybackStopped.ToString(); + } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.VideoPlaybackStopped.ToString(); + } + + return null; + } + + private readonly List _itemsAdded = new List(); + void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (!FilterItem(e.Item)) + { + return; + } + + lock (_libraryChangedSyncLock) + { + if (LibraryUpdateTimer == null) + { + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000, + Timeout.Infinite); + } + else + { + LibraryUpdateTimer.Change(5000, Timeout.Infinite); + } + + _itemsAdded.Add(e.Item); + } + } + + private bool FilterItem(BaseItem item) + { + if (item.IsFolder) + { + return false; + } + + if (item.LocationType == LocationType.Virtual) + { + return false; + } + + if (item is IItemByName) + { + return false; + } + + return item.SourceType == SourceType.Library; + } + + private async void LibraryUpdateTimerCallback(object state) + { + List items; + + lock (_libraryChangedSyncLock) + { + items = _itemsAdded.ToList(); + _itemsAdded.Clear(); + DisposeLibraryUpdateTimer(); + } + + items = items.Take(10).ToList(); + + foreach (var item in items) + { + var notification = new NotificationRequest + { + NotificationType = NotificationType.NewLibraryContent.ToString() + }; + + notification.Variables["Name"] = GetItemName(item); + + await SendNotification(notification).ConfigureAwait(false); + } + } + + public static string GetItemName(BaseItem item) + { + var name = item.Name; + var episode = item as Episode; + if (episode != null) + { + if (episode.IndexNumber.HasValue) + { + name = string.Format("Ep{0} - {1}", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); + } + if (episode.ParentIndexNumber.HasValue) + { + name = string.Format("S{0}, {1}", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); + } + } + + var hasSeries = item as IHasSeries; + + if (hasSeries != null) + { + name = hasSeries.SeriesName + " - " + name; + } + + var hasArtist = item as IHasArtist; + if (hasArtist != null) + { + var artists = hasArtist.AllArtists; + + if (artists.Count > 0) + { + name = hasArtist.AllArtists[0] + " - " + name; + } + } + + return name; + } + + async void _userManager_UserCreated(object sender, GenericEventArgs e) + { + var notification = new NotificationRequest + { + UserIds = new List { e.Argument.Id.ToString("N") }, + Name = "Welcome to Emby!", + Description = "Check back here for more notifications." + }; + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + { + var result = e.Result; + + if (result.Status == TaskCompletionStatus.Failed) + { + var type = NotificationType.TaskFailed.ToString(); + + var notification = new NotificationRequest + { + Description = result.ErrorMessage, + Level = NotificationLevel.Error, + NotificationType = type + }; + + notification.Variables["Name"] = result.Name; + notification.Variables["ErrorMessage"] = result.ErrorMessage; + + await SendNotification(notification).ConfigureAwait(false); + } + } + + async void _installationManager_PluginUninstalled(object sender, GenericEventArgs e) + { + var type = NotificationType.PluginUninstalled.ToString(); + + var plugin = e.Argument; + + var notification = new NotificationRequest + { + NotificationType = type + }; + + notification.Variables["Name"] = plugin.Name; + notification.Variables["Version"] = plugin.Version.ToString(); + + await SendNotification(notification).ConfigureAwait(false); + } + + async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) + { + var installationInfo = e.InstallationInfo; + + var type = NotificationType.InstallationFailed.ToString(); + + var notification = new NotificationRequest + { + Level = NotificationLevel.Error, + Description = e.Exception.Message, + NotificationType = type + }; + + notification.Variables["Name"] = installationInfo.Name; + notification.Variables["Version"] = installationInfo.Version; + + await SendNotification(notification).ConfigureAwait(false); + } + + private async Task SendNotification(NotificationRequest notification) + { + try + { + await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending notification", ex); + } + } + + public void Dispose() + { + DisposeLibraryUpdateTimer(); + + _installationManager.PluginInstalled -= _installationManager_PluginInstalled; + _installationManager.PluginUpdated -= _installationManager_PluginUpdated; + _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; + _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; + + _taskManager.TaskCompleted -= _taskManager_TaskCompleted; + + _userManager.UserCreated -= _userManager_UserCreated; + _libraryManager.ItemAdded -= _libraryManager_ItemAdded; + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + + _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; + _appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged; + _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + + _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; + _userManager.UserLockedOut -= _userManager_UserLockedOut; + } + + private void DisposeLibraryUpdateTimer() + { + if (LibraryUpdateTimer != null) + { + LibraryUpdateTimer.Dispose(); + LibraryUpdateTimer = null; + } + } + } +} diff --git a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs new file mode 100644 index 0000000000..8b3367217c --- /dev/null +++ b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs @@ -0,0 +1,54 @@ +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Plugins; +using System.Linq; + +namespace Emby.Server.Implementations.Notifications +{ + /// + /// Notifies clients anytime a notification is added or udpated + /// + public class WebSocketNotifier : IServerEntryPoint + { + private readonly INotificationsRepository _notificationsRepo; + + private readonly IServerManager _serverManager; + + public WebSocketNotifier(INotificationsRepository notificationsRepo, IServerManager serverManager) + { + _notificationsRepo = notificationsRepo; + _serverManager = serverManager; + } + + public void Run() + { + _notificationsRepo.NotificationAdded += _notificationsRepo_NotificationAdded; + + _notificationsRepo.NotificationsMarkedRead += _notificationsRepo_NotificationsMarkedRead; + } + + void _notificationsRepo_NotificationsMarkedRead(object sender, NotificationReadEventArgs e) + { + var list = e.IdList.ToList(); + + list.Add(e.UserId); + list.Add(e.IsRead.ToString().ToLower()); + + var msg = string.Join("|", list.ToArray()); + + _serverManager.SendWebSocketMessage("NotificationsMarkedRead", msg); + } + + void _notificationsRepo_NotificationAdded(object sender, NotificationUpdateEventArgs e) + { + var msg = e.Notification.UserId + "|" + e.Notification.Id; + + _serverManager.SendWebSocketMessage("NotificationAdded", msg); + } + + public void Dispose() + { + _notificationsRepo.NotificationAdded -= _notificationsRepo_NotificationAdded; + } + } +} -- cgit v1.2.3 From 0d5d91b4c4575271d16fe507a328fbcbb1510acd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Nov 2016 03:35:00 -0400 Subject: move notification classes --- .../Emby.Server.Implementations.csproj | 5 + .../Notifications/CoreNotificationTypes.cs | 198 ++++++++++++++ .../IConfigurableNotificationService.cs | 8 + .../Notifications/InternalNotificationService.cs | 61 +++++ .../NotificationConfigurationFactory.cs | 21 ++ .../Notifications/NotificationManager.cs | 296 +++++++++++++++++++++ .../MediaBrowser.Server.Implementations.csproj | 5 - .../Notifications/CoreNotificationTypes.cs | 198 -------------- .../IConfigurableNotificationService.cs | 8 - .../Notifications/InternalNotificationService.cs | 61 ----- .../NotificationConfigurationFactory.cs | 21 -- .../Notifications/NotificationManager.cs | 296 --------------------- .../ApplicationHost.cs | 1 + 13 files changed, 590 insertions(+), 589 deletions(-) create mode 100644 Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs create mode 100644 Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs create mode 100644 Emby.Server.Implementations/Notifications/InternalNotificationService.cs create mode 100644 Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs create mode 100644 Emby.Server.Implementations/Notifications/NotificationManager.cs delete mode 100644 MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs delete mode 100644 MediaBrowser.Server.Implementations/Notifications/IConfigurableNotificationService.cs delete mode 100644 MediaBrowser.Server.Implementations/Notifications/InternalNotificationService.cs delete mode 100644 MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs delete mode 100644 MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs (limited to 'Emby.Server.Implementations/Notifications') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 567c9b99ed..18e3576799 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -115,6 +115,11 @@ + + + + + diff --git a/Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs b/Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs new file mode 100644 index 0000000000..f9fb98f85e --- /dev/null +++ b/Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs @@ -0,0 +1,198 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.Notifications; +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Globalization; + +namespace Emby.Server.Implementations.Notifications +{ + public class CoreNotificationTypes : INotificationTypeFactory + { + private readonly ILocalizationManager _localization; + private readonly IServerApplicationHost _appHost; + + public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost) + { + _localization = localization; + _appHost = appHost; + } + + public IEnumerable GetNotificationTypes() + { + var knownTypes = new List + { + new NotificationTypeInfo + { + Type = NotificationType.ApplicationUpdateInstalled.ToString(), + DefaultDescription = "{ReleaseNotes}", + DefaultTitle = "A new version of Emby Server has been installed.", + Variables = new List{"Version"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.InstallationFailed.ToString(), + DefaultTitle = "{Name} installation failed.", + Variables = new List{"Name", "Version"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.PluginInstalled.ToString(), + DefaultTitle = "{Name} was installed.", + Variables = new List{"Name", "Version"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.PluginError.ToString(), + DefaultTitle = "{Name} has encountered an error.", + DefaultDescription = "{ErrorMessage}", + Variables = new List{"Name", "ErrorMessage"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.PluginUninstalled.ToString(), + DefaultTitle = "{Name} was uninstalled.", + Variables = new List{"Name", "Version"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.PluginUpdateInstalled.ToString(), + DefaultTitle = "{Name} was updated.", + DefaultDescription = "{ReleaseNotes}", + Variables = new List{"Name", "ReleaseNotes", "Version"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.ServerRestartRequired.ToString(), + DefaultTitle = "Please restart Emby Server to finish updating." + }, + + new NotificationTypeInfo + { + Type = NotificationType.TaskFailed.ToString(), + DefaultTitle = "{Name} failed.", + DefaultDescription = "{ErrorMessage}", + Variables = new List{"Name", "ErrorMessage"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.NewLibraryContent.ToString(), + DefaultTitle = "{Name} has been added to your media library.", + Variables = new List{"Name"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.AudioPlayback.ToString(), + DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", + Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.GamePlayback.ToString(), + DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", + Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.VideoPlayback.ToString(), + DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", + Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.AudioPlaybackStopped.ToString(), + DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", + Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.GamePlaybackStopped.ToString(), + DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", + Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.VideoPlaybackStopped.ToString(), + DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", + Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.CameraImageUploaded.ToString(), + DefaultTitle = "A new camera image has been uploaded from {DeviceName}.", + Variables = new List{"DeviceName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.UserLockedOut.ToString(), + DefaultTitle = "{UserName} has been locked out.", + Variables = new List{"UserName"} + } + }; + + if (!_appHost.CanSelfUpdate) + { + knownTypes.Add(new NotificationTypeInfo + { + Type = NotificationType.ApplicationUpdateAvailable.ToString(), + DefaultTitle = "A new version of Emby Server is available for download." + }); + } + + foreach (var type in knownTypes) + { + Update(type); + } + + var systemName = _localization.GetLocalizedString("CategorySystem"); + + return knownTypes.OrderByDescending(i => string.Equals(i.Category, systemName, StringComparison.OrdinalIgnoreCase)) + .ThenBy(i => i.Category) + .ThenBy(i => i.Name); + } + + private void Update(NotificationTypeInfo note) + { + note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type; + + note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1; + + if (note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1) + { + note.Category = _localization.GetLocalizedString("CategoryUser"); + } + else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1) + { + note.Category = _localization.GetLocalizedString("CategoryPlugin"); + } + else if (note.Type.IndexOf("CameraImageUploaded", StringComparison.OrdinalIgnoreCase) != -1) + { + note.Category = _localization.GetLocalizedString("CategorySync"); + } + else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1) + { + note.Category = _localization.GetLocalizedString("CategoryUser"); + } + else + { + note.Category = _localization.GetLocalizedString("CategorySystem"); + } + } + } +} diff --git a/Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs b/Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs new file mode 100644 index 0000000000..d74667c486 --- /dev/null +++ b/Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs @@ -0,0 +1,8 @@ +namespace Emby.Server.Implementations.Notifications +{ + public interface IConfigurableNotificationService + { + bool IsHidden { get; } + bool IsEnabled(string notificationType); + } +} diff --git a/Emby.Server.Implementations/Notifications/InternalNotificationService.cs b/Emby.Server.Implementations/Notifications/InternalNotificationService.cs new file mode 100644 index 0000000000..61c564f188 --- /dev/null +++ b/Emby.Server.Implementations/Notifications/InternalNotificationService.cs @@ -0,0 +1,61 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.Notifications; +using System.Threading; +using System.Threading.Tasks; +using System; + +namespace Emby.Server.Implementations.Notifications +{ + public class InternalNotificationService : INotificationService, IConfigurableNotificationService + { + private readonly INotificationsRepository _repo; + + public InternalNotificationService(INotificationsRepository repo) + { + _repo = repo; + } + + public string Name + { + get { return "Dashboard Notifications"; } + } + + public Task SendNotification(UserNotification request, CancellationToken cancellationToken) + { + return _repo.AddNotification(new Notification + { + Date = request.Date, + Description = request.Description, + Level = request.Level, + Name = request.Name, + Url = request.Url, + UserId = request.User.Id.ToString("N") + + }, cancellationToken); + } + + public bool IsEnabledForUser(User user) + { + return user.Policy.IsAdministrator; + } + + public bool IsHidden + { + get { return true; } + } + + public bool IsEnabled(string notificationType) + { + if (notificationType.IndexOf("playback", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + if (notificationType.IndexOf("newlibrarycontent", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + return true; + } + } +} diff --git a/Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs b/Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs new file mode 100644 index 0000000000..a7c5b12337 --- /dev/null +++ b/Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs @@ -0,0 +1,21 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Notifications; +using System.Collections.Generic; + +namespace Emby.Server.Implementations.Notifications +{ + public class NotificationConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new List + { + new ConfigurationStore + { + Key = "notifications", + ConfigurationType = typeof (NotificationOptions) + } + }; + } + } +} diff --git a/Emby.Server.Implementations/Notifications/NotificationManager.cs b/Emby.Server.Implementations/Notifications/NotificationManager.cs new file mode 100644 index 0000000000..db79804978 --- /dev/null +++ b/Emby.Server.Implementations/Notifications/NotificationManager.cs @@ -0,0 +1,296 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Notifications; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Extensions; + +namespace Emby.Server.Implementations.Notifications +{ + public class NotificationManager : INotificationManager + { + private readonly ILogger _logger; + private readonly IUserManager _userManager; + private readonly IServerConfigurationManager _config; + + private INotificationService[] _services; + private INotificationTypeFactory[] _typeFactories; + + public NotificationManager(ILogManager logManager, IUserManager userManager, IServerConfigurationManager config) + { + _userManager = userManager; + _config = config; + _logger = logManager.GetLogger(GetType().Name); + } + + private NotificationOptions GetConfiguration() + { + return _config.GetConfiguration("notifications"); + } + + public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken) + { + var notificationType = request.NotificationType; + + var options = string.IsNullOrWhiteSpace(notificationType) ? + null : + GetConfiguration().GetOptions(notificationType); + + var users = GetUserIds(request, options) + .Select(i => _userManager.GetUserById(i)); + + var title = GetTitle(request, options); + var description = GetDescription(request, options); + + var tasks = _services.Where(i => IsEnabled(i, notificationType)) + .Select(i => SendNotification(request, i, users, title, description, cancellationToken)); + + return Task.WhenAll(tasks); + } + + private Task SendNotification(NotificationRequest request, + INotificationService service, + IEnumerable users, + string title, + string description, + CancellationToken cancellationToken) + { + users = users.Where(i => IsEnabledForUser(service, i)) + .ToList(); + + var tasks = users.Select(i => SendNotification(request, service, title, description, i, cancellationToken)); + + return Task.WhenAll(tasks); + + } + + private IEnumerable GetUserIds(NotificationRequest request, NotificationOption options) + { + if (request.SendToUserMode.HasValue) + { + switch (request.SendToUserMode.Value) + { + case SendToUserType.Admins: + return _userManager.Users.Where(i => i.Policy.IsAdministrator) + .Select(i => i.Id.ToString("N")); + case SendToUserType.All: + return _userManager.Users.Select(i => i.Id.ToString("N")); + case SendToUserType.Custom: + return request.UserIds; + default: + throw new ArgumentException("Unrecognized SendToUserMode: " + request.SendToUserMode.Value); + } + } + + if (options != null && !string.IsNullOrWhiteSpace(request.NotificationType)) + { + var config = GetConfiguration(); + + return _userManager.Users + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) + .Select(i => i.Id.ToString("N")); + } + + return request.UserIds; + } + + private async Task SendNotification(NotificationRequest request, + INotificationService service, + string title, + string description, + User user, + CancellationToken cancellationToken) + { + var notification = new UserNotification + { + Date = request.Date, + Description = description, + Level = request.Level, + Name = title, + Url = request.Url, + User = user + }; + + _logger.Debug("Sending notification via {0} to user {1}", service.Name, user.Name); + + try + { + await service.SendNotification(notification, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending notification to {0}", ex, service.Name); + } + } + + private string GetTitle(NotificationRequest request, NotificationOption options) + { + var title = request.Name; + + // If empty, grab from options + if (string.IsNullOrEmpty(title)) + { + if (!string.IsNullOrEmpty(request.NotificationType)) + { + if (options != null) + { + title = options.Title; + } + } + } + + // If still empty, grab default + if (string.IsNullOrEmpty(title)) + { + if (!string.IsNullOrEmpty(request.NotificationType)) + { + var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase)); + + if (info != null) + { + title = info.DefaultTitle; + } + } + } + + title = title ?? string.Empty; + + foreach (var pair in request.Variables) + { + var token = "{" + pair.Key + "}"; + + title = title.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase); + } + + return title; + } + + private string GetDescription(NotificationRequest request, NotificationOption options) + { + var text = request.Description; + + // If empty, grab from options + if (string.IsNullOrEmpty(text)) + { + if (!string.IsNullOrEmpty(request.NotificationType)) + { + if (options != null) + { + text = options.Description; + } + } + } + + // If still empty, grab default + if (string.IsNullOrEmpty(text)) + { + if (!string.IsNullOrEmpty(request.NotificationType)) + { + var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase)); + + if (info != null) + { + text = info.DefaultDescription; + } + } + } + + text = text ?? string.Empty; + + foreach (var pair in request.Variables) + { + var token = "{" + pair.Key + "}"; + + text = text.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase); + } + + return text; + } + + private bool IsEnabledForUser(INotificationService service, User user) + { + try + { + return service.IsEnabledForUser(user); + } + catch (Exception ex) + { + _logger.ErrorException("Error in IsEnabledForUser", ex); + return false; + } + } + + private bool IsEnabled(INotificationService service, string notificationType) + { + if (string.IsNullOrEmpty(notificationType)) + { + return true; + } + + var configurable = service as IConfigurableNotificationService; + + if (configurable != null) + { + return configurable.IsEnabled(notificationType); + } + + return GetConfiguration().IsServiceEnabled(service.Name, notificationType); + } + + public void AddParts(IEnumerable services, IEnumerable notificationTypeFactories) + { + _services = services.ToArray(); + _typeFactories = notificationTypeFactories.ToArray(); + } + + public IEnumerable GetNotificationTypes() + { + var list = _typeFactories.Select(i => + { + try + { + return i.GetNotificationTypes().ToList(); + } + catch (Exception ex) + { + _logger.ErrorException("Error in GetNotificationTypes", ex); + return new List(); + } + + }).SelectMany(i => i).ToList(); + + var config = GetConfiguration(); + + foreach (var i in list) + { + i.Enabled = config.IsEnabled(i.Type); + } + + return list; + } + + public IEnumerable GetNotificationServices() + { + return _services.Where(i => + { + var configurable = i as IConfigurableNotificationService; + + return configurable == null || !configurable.IsHidden; + + }).Select(i => new NotificationServiceInfo + { + Name = i.Name, + Id = i.Name.GetMD5().ToString("N") + + }).OrderBy(i => i.Name); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 3fb3ca8833..95285d6047 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -212,7 +212,6 @@ - @@ -230,10 +229,6 @@ - - - - diff --git a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs deleted file mode 100644 index 0f2629f587..0000000000 --- a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs +++ /dev/null @@ -1,198 +0,0 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Notifications; -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.Globalization; - -namespace MediaBrowser.Server.Implementations.Notifications -{ - public class CoreNotificationTypes : INotificationTypeFactory - { - private readonly ILocalizationManager _localization; - private readonly IServerApplicationHost _appHost; - - public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost) - { - _localization = localization; - _appHost = appHost; - } - - public IEnumerable GetNotificationTypes() - { - var knownTypes = new List - { - new NotificationTypeInfo - { - Type = NotificationType.ApplicationUpdateInstalled.ToString(), - DefaultDescription = "{ReleaseNotes}", - DefaultTitle = "A new version of Emby Server has been installed.", - Variables = new List{"Version"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.InstallationFailed.ToString(), - DefaultTitle = "{Name} installation failed.", - Variables = new List{"Name", "Version"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.PluginInstalled.ToString(), - DefaultTitle = "{Name} was installed.", - Variables = new List{"Name", "Version"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.PluginError.ToString(), - DefaultTitle = "{Name} has encountered an error.", - DefaultDescription = "{ErrorMessage}", - Variables = new List{"Name", "ErrorMessage"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.PluginUninstalled.ToString(), - DefaultTitle = "{Name} was uninstalled.", - Variables = new List{"Name", "Version"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.PluginUpdateInstalled.ToString(), - DefaultTitle = "{Name} was updated.", - DefaultDescription = "{ReleaseNotes}", - Variables = new List{"Name", "ReleaseNotes", "Version"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.ServerRestartRequired.ToString(), - DefaultTitle = "Please restart Emby Server to finish updating." - }, - - new NotificationTypeInfo - { - Type = NotificationType.TaskFailed.ToString(), - DefaultTitle = "{Name} failed.", - DefaultDescription = "{ErrorMessage}", - Variables = new List{"Name", "ErrorMessage"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.NewLibraryContent.ToString(), - DefaultTitle = "{Name} has been added to your media library.", - Variables = new List{"Name"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.AudioPlayback.ToString(), - DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", - Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.GamePlayback.ToString(), - DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", - Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.VideoPlayback.ToString(), - DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", - Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.AudioPlaybackStopped.ToString(), - DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", - Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.GamePlaybackStopped.ToString(), - DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", - Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.VideoPlaybackStopped.ToString(), - DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", - Variables = new List{"UserName", "ItemName", "DeviceName", "AppName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.CameraImageUploaded.ToString(), - DefaultTitle = "A new camera image has been uploaded from {DeviceName}.", - Variables = new List{"DeviceName"} - }, - - new NotificationTypeInfo - { - Type = NotificationType.UserLockedOut.ToString(), - DefaultTitle = "{UserName} has been locked out.", - Variables = new List{"UserName"} - } - }; - - if (!_appHost.CanSelfUpdate) - { - knownTypes.Add(new NotificationTypeInfo - { - Type = NotificationType.ApplicationUpdateAvailable.ToString(), - DefaultTitle = "A new version of Emby Server is available for download." - }); - } - - foreach (var type in knownTypes) - { - Update(type); - } - - var systemName = _localization.GetLocalizedString("CategorySystem"); - - return knownTypes.OrderByDescending(i => string.Equals(i.Category, systemName, StringComparison.OrdinalIgnoreCase)) - .ThenBy(i => i.Category) - .ThenBy(i => i.Name); - } - - private void Update(NotificationTypeInfo note) - { - note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type; - - note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1; - - if (note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1) - { - note.Category = _localization.GetLocalizedString("CategoryUser"); - } - else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1) - { - note.Category = _localization.GetLocalizedString("CategoryPlugin"); - } - else if (note.Type.IndexOf("CameraImageUploaded", StringComparison.OrdinalIgnoreCase) != -1) - { - note.Category = _localization.GetLocalizedString("CategorySync"); - } - else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1) - { - note.Category = _localization.GetLocalizedString("CategoryUser"); - } - else - { - note.Category = _localization.GetLocalizedString("CategorySystem"); - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Notifications/IConfigurableNotificationService.cs b/MediaBrowser.Server.Implementations/Notifications/IConfigurableNotificationService.cs deleted file mode 100644 index cdfd0f640f..0000000000 --- a/MediaBrowser.Server.Implementations/Notifications/IConfigurableNotificationService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MediaBrowser.Server.Implementations.Notifications -{ - public interface IConfigurableNotificationService - { - bool IsHidden { get; } - bool IsEnabled(string notificationType); - } -} diff --git a/MediaBrowser.Server.Implementations/Notifications/InternalNotificationService.cs b/MediaBrowser.Server.Implementations/Notifications/InternalNotificationService.cs deleted file mode 100644 index 4a625f0fb0..0000000000 --- a/MediaBrowser.Server.Implementations/Notifications/InternalNotificationService.cs +++ /dev/null @@ -1,61 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Notifications; -using System.Threading; -using System.Threading.Tasks; -using System; - -namespace MediaBrowser.Server.Implementations.Notifications -{ - public class InternalNotificationService : INotificationService, IConfigurableNotificationService - { - private readonly INotificationsRepository _repo; - - public InternalNotificationService(INotificationsRepository repo) - { - _repo = repo; - } - - public string Name - { - get { return "Dashboard Notifications"; } - } - - public Task SendNotification(UserNotification request, CancellationToken cancellationToken) - { - return _repo.AddNotification(new Notification - { - Date = request.Date, - Description = request.Description, - Level = request.Level, - Name = request.Name, - Url = request.Url, - UserId = request.User.Id.ToString("N") - - }, cancellationToken); - } - - public bool IsEnabledForUser(User user) - { - return user.Policy.IsAdministrator; - } - - public bool IsHidden - { - get { return true; } - } - - public bool IsEnabled(string notificationType) - { - if (notificationType.IndexOf("playback", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - if (notificationType.IndexOf("newlibrarycontent", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - return true; - } - } -} diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs deleted file mode 100644 index a336eba0ed..0000000000 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Notifications; -using System.Collections.Generic; - -namespace MediaBrowser.Server.Implementations.Notifications -{ - public class NotificationConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - Key = "notifications", - ConfigurationType = typeof (NotificationOptions) - } - }; - } - } -} diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs deleted file mode 100644 index f19ff8a5f2..0000000000 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ /dev/null @@ -1,296 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Notifications; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Extensions; - -namespace MediaBrowser.Server.Implementations.Notifications -{ - public class NotificationManager : INotificationManager - { - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly IServerConfigurationManager _config; - - private INotificationService[] _services; - private INotificationTypeFactory[] _typeFactories; - - public NotificationManager(ILogManager logManager, IUserManager userManager, IServerConfigurationManager config) - { - _userManager = userManager; - _config = config; - _logger = logManager.GetLogger(GetType().Name); - } - - private NotificationOptions GetConfiguration() - { - return _config.GetConfiguration("notifications"); - } - - public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken) - { - var notificationType = request.NotificationType; - - var options = string.IsNullOrWhiteSpace(notificationType) ? - null : - GetConfiguration().GetOptions(notificationType); - - var users = GetUserIds(request, options) - .Select(i => _userManager.GetUserById(i)); - - var title = GetTitle(request, options); - var description = GetDescription(request, options); - - var tasks = _services.Where(i => IsEnabled(i, notificationType)) - .Select(i => SendNotification(request, i, users, title, description, cancellationToken)); - - return Task.WhenAll(tasks); - } - - private Task SendNotification(NotificationRequest request, - INotificationService service, - IEnumerable users, - string title, - string description, - CancellationToken cancellationToken) - { - users = users.Where(i => IsEnabledForUser(service, i)) - .ToList(); - - var tasks = users.Select(i => SendNotification(request, service, title, description, i, cancellationToken)); - - return Task.WhenAll(tasks); - - } - - private IEnumerable GetUserIds(NotificationRequest request, NotificationOption options) - { - if (request.SendToUserMode.HasValue) - { - switch (request.SendToUserMode.Value) - { - case SendToUserType.Admins: - return _userManager.Users.Where(i => i.Policy.IsAdministrator) - .Select(i => i.Id.ToString("N")); - case SendToUserType.All: - return _userManager.Users.Select(i => i.Id.ToString("N")); - case SendToUserType.Custom: - return request.UserIds; - default: - throw new ArgumentException("Unrecognized SendToUserMode: " + request.SendToUserMode.Value); - } - } - - if (options != null && !string.IsNullOrWhiteSpace(request.NotificationType)) - { - var config = GetConfiguration(); - - return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) - .Select(i => i.Id.ToString("N")); - } - - return request.UserIds; - } - - private async Task SendNotification(NotificationRequest request, - INotificationService service, - string title, - string description, - User user, - CancellationToken cancellationToken) - { - var notification = new UserNotification - { - Date = request.Date, - Description = description, - Level = request.Level, - Name = title, - Url = request.Url, - User = user - }; - - _logger.Debug("Sending notification via {0} to user {1}", service.Name, user.Name); - - try - { - await service.SendNotification(notification, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending notification to {0}", ex, service.Name); - } - } - - private string GetTitle(NotificationRequest request, NotificationOption options) - { - var title = request.Name; - - // If empty, grab from options - if (string.IsNullOrEmpty(title)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - if (options != null) - { - title = options.Title; - } - } - } - - // If still empty, grab default - if (string.IsNullOrEmpty(title)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase)); - - if (info != null) - { - title = info.DefaultTitle; - } - } - } - - title = title ?? string.Empty; - - foreach (var pair in request.Variables) - { - var token = "{" + pair.Key + "}"; - - title = title.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase); - } - - return title; - } - - private string GetDescription(NotificationRequest request, NotificationOption options) - { - var text = request.Description; - - // If empty, grab from options - if (string.IsNullOrEmpty(text)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - if (options != null) - { - text = options.Description; - } - } - } - - // If still empty, grab default - if (string.IsNullOrEmpty(text)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase)); - - if (info != null) - { - text = info.DefaultDescription; - } - } - } - - text = text ?? string.Empty; - - foreach (var pair in request.Variables) - { - var token = "{" + pair.Key + "}"; - - text = text.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase); - } - - return text; - } - - private bool IsEnabledForUser(INotificationService service, User user) - { - try - { - return service.IsEnabledForUser(user); - } - catch (Exception ex) - { - _logger.ErrorException("Error in IsEnabledForUser", ex); - return false; - } - } - - private bool IsEnabled(INotificationService service, string notificationType) - { - if (string.IsNullOrEmpty(notificationType)) - { - return true; - } - - var configurable = service as IConfigurableNotificationService; - - if (configurable != null) - { - return configurable.IsEnabled(notificationType); - } - - return GetConfiguration().IsServiceEnabled(service.Name, notificationType); - } - - public void AddParts(IEnumerable services, IEnumerable notificationTypeFactories) - { - _services = services.ToArray(); - _typeFactories = notificationTypeFactories.ToArray(); - } - - public IEnumerable GetNotificationTypes() - { - var list = _typeFactories.Select(i => - { - try - { - return i.GetNotificationTypes().ToList(); - } - catch (Exception ex) - { - _logger.ErrorException("Error in GetNotificationTypes", ex); - return new List(); - } - - }).SelectMany(i => i).ToList(); - - var config = GetConfiguration(); - - foreach (var i in list) - { - i.Enabled = config.IsEnabled(i.Type); - } - - return list; - } - - public IEnumerable GetNotificationServices() - { - return _services.Where(i => - { - var configurable = i as IConfigurableNotificationService; - - return configurable == null || !configurable.IsHidden; - - }).Select(i => new NotificationServiceInfo - { - Name = i.Name, - Id = i.Name.GetMD5().ToString("N") - - }).OrderBy(i => i.Name); - } - } -} diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 7c8945ff2e..294e780cca 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -107,6 +107,7 @@ using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.Library; +using Emby.Server.Implementations.Notifications; using Emby.Server.Implementations.Persistence; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.TV; -- cgit v1.2.3 From fa714425dd91fed2ca691cd45f73f7ea5a579dff Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 18 Nov 2016 03:39:20 -0500 Subject: begin to rework repositories --- Emby.Server.Core/Activity/ActivityRepository.cs | 252 ----------- Emby.Server.Core/ApplicationHost.cs | 46 +- Emby.Server.Core/Devices/DeviceRepository.cs | 208 --------- Emby.Server.Core/Migrations/DbMigration.cs | 3 +- Emby.Server.Core/Migrations/IVersionMigration.cs | 9 - .../Migrations/UpdateLevelMigration.cs | 1 + .../Notifications/SqliteNotificationsRepository.cs | 470 --------------------- Emby.Server.Core/Social/SharingRepository.cs | 158 ------- .../Activity/ActivityRepository.cs | 197 +++++++++ .../Data/BaseSqliteRepository.cs | 123 ++++++ .../Data/CleanDatabaseScheduledTask.cs | 361 ++++++++++++++++ .../Data/SqliteExtensions.cs | 105 +++++ .../Devices/DeviceRepository.cs | 208 +++++++++ .../Emby.Server.Implementations.csproj | 17 +- .../Library/Validators/PeopleValidator.cs | 6 +- .../Migrations/IVersionMigration.cs | 9 + .../Notifications/SqliteNotificationsRepository.cs | 309 ++++++++++++++ .../Persistence/CleanDatabaseScheduledTask.cs | 361 ---------------- .../Social/SharingRepository.cs | 114 +++++ Emby.Server.Implementations/packages.config | 2 + MediaBrowser.Model/Logging/LogHelper.cs | 4 +- .../MediaBrowser.Server.Mono.csproj | 8 + MediaBrowser.Server.Mono/Program.cs | 2 + MediaBrowser.Server.Mono/packages.config | 2 + MediaBrowser.ServerApplication/MainStartup.cs | 2 + .../MediaBrowser.ServerApplication.csproj | 25 +- MediaBrowser.ServerApplication/packages.config | 3 +- src/Emby.Server/Program.cs | 1 + src/Emby.Server/project.json | 3 +- 29 files changed, 1511 insertions(+), 1498 deletions(-) delete mode 100644 Emby.Server.Core/Activity/ActivityRepository.cs delete mode 100644 Emby.Server.Core/Devices/DeviceRepository.cs delete mode 100644 Emby.Server.Core/Migrations/IVersionMigration.cs delete mode 100644 Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs delete mode 100644 Emby.Server.Core/Social/SharingRepository.cs create mode 100644 Emby.Server.Implementations/Activity/ActivityRepository.cs create mode 100644 Emby.Server.Implementations/Data/BaseSqliteRepository.cs create mode 100644 Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs create mode 100644 Emby.Server.Implementations/Data/SqliteExtensions.cs create mode 100644 Emby.Server.Implementations/Devices/DeviceRepository.cs create mode 100644 Emby.Server.Implementations/Migrations/IVersionMigration.cs create mode 100644 Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs delete mode 100644 Emby.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs create mode 100644 Emby.Server.Implementations/Social/SharingRepository.cs (limited to 'Emby.Server.Implementations/Notifications') diff --git a/Emby.Server.Core/Activity/ActivityRepository.cs b/Emby.Server.Core/Activity/ActivityRepository.cs deleted file mode 100644 index c11dcf7238..0000000000 --- a/Emby.Server.Core/Activity/ActivityRepository.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.IO; -using System.Threading.Tasks; -using Emby.Server.Core.Data; -using MediaBrowser.Controller; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; - -namespace Emby.Server.Core.Activity -{ - public class ActivityRepository : BaseSqliteRepository, IActivityRepository - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public ActivityRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector) - : base(logManager, connector) - { - DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); - } - - public async Task Initialize() - { - using (var connection = await CreateConnection().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)" - }; - - connection.RunQueries(queries, Logger); - } - } - - 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"); - } - - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - using (var 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"); - - 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(); - } - } - } - } - } - - public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) - { - using (var connection = CreateConnection(true).Result) - { - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = BaseActivitySelectText; - - var whereClauses = new List(); - - if (minDate.HasValue) - { - whereClauses.Add("DateCreated>=@DateCreated"); - cmd.Parameters.Add(cmd, "@DateCreated", DbType.Date).Value = minDate.Value; - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - if (startIndex.HasValue && startIndex.Value > 0) - { - var pagingWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", - pagingWhereText, - startIndex.Value.ToString(_usCulture))); - } - - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - cmd.CommandText += whereText; - - cmd.CommandText += " ORDER BY DateCreated DESC"; - - if (limit.HasValue) - { - cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture); - } - - cmd.CommandText += "; select count (Id) from ActivityLogEntries" + whereTextWithoutPaging; - - var list = new List(); - 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() - { - 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; - } - } -} diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index d3d292ca58..7e67c8a083 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -83,23 +83,20 @@ using Emby.Dlna.Main; using Emby.Dlna.MediaReceiverRegistrar; using Emby.Dlna.Ssdp; using Emby.Server.Core; -using Emby.Server.Core.Activity; +using Emby.Server.Implementations.Activity; using Emby.Server.Core.Configuration; using Emby.Server.Core.Data; -using Emby.Server.Core.Devices; +using Emby.Server.Implementations.Devices; using Emby.Server.Core.FFMpeg; using Emby.Server.Core.IO; using Emby.Server.Core.Localization; using Emby.Server.Core.Migrations; -using Emby.Server.Core.Notifications; using Emby.Server.Core.Security; -using Emby.Server.Core.Social; +using Emby.Server.Implementations.Social; using Emby.Server.Core.Sync; -using Emby.Server.Implementations.Activity; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Connect; -using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FileOrganization; @@ -110,7 +107,7 @@ using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.MediaEncoder; using Emby.Server.Implementations.Notifications; -using Emby.Server.Implementations.Persistence; +using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.ServerManager; @@ -136,6 +133,7 @@ using ServiceStack; using SocketHttpListener.Primitives; using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions; using Emby.Drawing; +using Emby.Server.Implementations.Migrations; namespace Emby.Server.Core { @@ -282,12 +280,12 @@ namespace Emby.Server.Core INetworkManager networkManager, Action certificateGenerator, Func defaultUsernameFactory) - : base(applicationPaths, - logManager, - fileSystem, - environmentInfo, - systemEvents, - memoryStreamFactory, + : base(applicationPaths, + logManager, + fileSystem, + environmentInfo, + systemEvents, + memoryStreamFactory, networkManager) { StartupOptions = options; @@ -683,11 +681,11 @@ namespace Emby.Server.Core EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager); RegisterSingleInstance(EncodingManager); - var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, GetDbConnector()); - await sharingRepo.Initialize().ConfigureAwait(false); + var sharingRepo = new SharingRepository(LogManager.GetLogger("SharingRepository"), ApplicationPaths); + sharingRepo.Initialize(); RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); - var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false); + var activityLogRepo = GetActivityLogRepository(); RegisterSingleInstance(activityLogRepo); RegisterSingleInstance(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager)); @@ -708,7 +706,7 @@ namespace Emby.Server.Core ((UserDataManager)UserDataManager).Repository = userDataRepo; await itemRepo.Initialize(userDataRepo).ConfigureAwait(false); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; - await ConfigureNotificationsRepository().ConfigureAwait(false); + ConfigureNotificationsRepository(); progress.Report(100); SetStaticProperties(); @@ -792,7 +790,7 @@ namespace Emby.Server.Core () => SubtitleEncoder, () => MediaSourceManager, HttpClient, - ZipClient, + ZipClient, MemoryStreamFactory, ProcessFactory, (Environment.ProcessorCount > 2 ? 14000 : 40000), @@ -837,11 +835,11 @@ namespace Emby.Server.Core return repo; } - private async Task GetActivityLogRepository() + private IActivityRepository GetActivityLogRepository() { - var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths, GetDbConnector()); + var repo = new ActivityRepository(LogManager.GetLogger("ActivityRepository"), ServerConfigurationManager.ApplicationPaths); - await repo.Initialize().ConfigureAwait(false); + repo.Initialize(); return repo; } @@ -858,11 +856,11 @@ namespace Emby.Server.Core /// /// Configures the repositories. /// - private async Task ConfigureNotificationsRepository() + private void ConfigureNotificationsRepository() { - var repo = new SqliteNotificationsRepository(LogManager, ApplicationPaths, GetDbConnector()); + var repo = new SqliteNotificationsRepository(LogManager.GetLogger("SqliteNotificationsRepository"), ServerConfigurationManager.ApplicationPaths); - await repo.Initialize().ConfigureAwait(false); + repo.Initialize(); NotificationsRepository = repo; diff --git a/Emby.Server.Core/Devices/DeviceRepository.cs b/Emby.Server.Core/Devices/DeviceRepository.cs deleted file mode 100644 index 63aa7b67a0..0000000000 --- a/Emby.Server.Core/Devices/DeviceRepository.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Session; - -namespace Emby.Server.Core.Devices -{ - public class DeviceRepository : IDeviceRepository - { - private readonly object _syncLock = new object(); - - private readonly IApplicationPaths _appPaths; - private readonly IJsonSerializer _json; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - - private Dictionary _devices; - - public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem) - { - _appPaths = appPaths; - _json = json; - _logger = logger; - _fileSystem = fileSystem; - } - - private string GetDevicesPath() - { - return Path.Combine(_appPaths.DataPath, "devices"); - } - - private string GetDevicePath(string id) - { - return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); - } - - public Task SaveDevice(DeviceInfo device) - { - var path = Path.Combine(GetDevicePath(device.Id), "device.json"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_syncLock) - { - _json.SerializeToFile(device, path); - _devices[device.Id] = device; - } - return Task.FromResult(true); - } - - public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) - { - var device = GetDevice(reportedId); - - if (device == null) - { - throw new ArgumentException("No device has been registed with id " + reportedId); - } - - device.Capabilities = capabilities; - SaveDevice(device); - - return Task.FromResult(true); - } - - public ClientCapabilities GetCapabilities(string reportedId) - { - var device = GetDevice(reportedId); - - return device == null ? null : device.Capabilities; - } - - public DeviceInfo GetDevice(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - return GetDevices() - .FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); - } - - public IEnumerable GetDevices() - { - lock (_syncLock) - { - if (_devices == null) - { - _devices = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var devices = LoadDevices().ToList(); - foreach (var device in devices) - { - _devices[device.Id] = device; - } - } - return _devices.Values.ToList(); - } - } - - private IEnumerable LoadDevices() - { - var path = GetDevicesPath(); - - try - { - return _fileSystem - .GetFilePaths(path, true) - .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) - .ToList() - .Select(i => - { - try - { - return _json.DeserializeFromFile(i); - } - catch (Exception ex) - { - _logger.ErrorException("Error reading {0}", ex, i); - return null; - } - }) - .Where(i => i != null); - } - catch (IOException) - { - return new List(); - } - } - - public Task DeleteDevice(string id) - { - var path = GetDevicePath(id); - - lock (_syncLock) - { - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (DirectoryNotFoundException) - { - } - - _devices = null; - } - - return Task.FromResult(true); - } - - public ContentUploadHistory GetCameraUploadHistory(string deviceId) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - - lock (_syncLock) - { - try - { - return _json.DeserializeFromFile(path); - } - catch (IOException) - { - return new ContentUploadHistory - { - DeviceId = deviceId - }; - } - } - } - - public void AddCameraUpload(string deviceId, LocalFileInfo file) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_syncLock) - { - ContentUploadHistory history; - - try - { - history = _json.DeserializeFromFile(path); - } - catch (IOException) - { - history = new ContentUploadHistory - { - DeviceId = deviceId - }; - } - - history.DeviceId = deviceId; - history.FilesUploaded.Add(file); - - _json.SerializeToFile(history, path); - } - } - } -} diff --git a/Emby.Server.Core/Migrations/DbMigration.cs b/Emby.Server.Core/Migrations/DbMigration.cs index 17e0860938..5d652770f8 100644 --- a/Emby.Server.Core/Migrations/DbMigration.cs +++ b/Emby.Server.Core/Migrations/DbMigration.cs @@ -1,8 +1,9 @@ using System.Threading.Tasks; -using Emby.Server.Implementations.Persistence; +using Emby.Server.Implementations.Data; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Tasks; using Emby.Server.Core.Data; +using Emby.Server.Implementations.Migrations; namespace Emby.Server.Core.Migrations { diff --git a/Emby.Server.Core/Migrations/IVersionMigration.cs b/Emby.Server.Core/Migrations/IVersionMigration.cs deleted file mode 100644 index 0190e289a4..0000000000 --- a/Emby.Server.Core/Migrations/IVersionMigration.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Emby.Server.Core.Migrations -{ - public interface IVersionMigration - { - Task Run(); - } -} diff --git a/Emby.Server.Core/Migrations/UpdateLevelMigration.cs b/Emby.Server.Core/Migrations/UpdateLevelMigration.cs index bbedd7b63c..c79dbabea5 100644 --- a/Emby.Server.Core/Migrations/UpdateLevelMigration.cs +++ b/Emby.Server.Core/Migrations/UpdateLevelMigration.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; +using Emby.Server.Implementations.Migrations; namespace Emby.Server.Core.Migrations { diff --git a/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs deleted file mode 100644 index cd4ac28dcd..0000000000 --- a/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs +++ /dev/null @@ -1,470 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Core.Data; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Notifications; - -namespace Emby.Server.Core.Notifications -{ - public class SqliteNotificationsRepository : BaseSqliteRepository, INotificationsRepository - { - public SqliteNotificationsRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector dbConnector) : base(logManager, dbConnector) - { - DbFilePath = Path.Combine(appPaths.DataPath, "notifications.db"); - } - - public event EventHandler NotificationAdded; - public event EventHandler NotificationsMarkedRead; - ////public event EventHandler NotificationUpdated; - - public async Task Initialize() - { - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - string[] queries = { - - "create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT NULL, Url TEXT NULL, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT NULL, PRIMARY KEY (Id, UserId))", - "create index if not exists idx_Notifications1 on Notifications(Id)", - "create index if not exists idx_Notifications2 on Notifications(UserId)" - }; - - connection.RunQueries(queries, Logger); - } - } - - /// - /// Gets the notifications. - /// - /// The query. - /// NotificationResult. - public NotificationResult GetNotifications(NotificationQuery query) - { - var result = new NotificationResult(); - - using (var connection = CreateConnection(true).Result) - { - using (var cmd = connection.CreateCommand()) - { - var clauses = new List(); - - if (query.IsRead.HasValue) - { - clauses.Add("IsRead=@IsRead"); - cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = query.IsRead.Value; - } - - clauses.Add("UserId=@UserId"); - cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(query.UserId); - - var whereClause = " where " + string.Join(" And ", clauses.ToArray()); - - cmd.CommandText = string.Format("select count(Id) from Notifications{0};select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause); - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) - { - if (reader.Read()) - { - result.TotalRecordCount = reader.GetInt32(0); - } - - if (reader.NextResult()) - { - var notifications = GetNotifications(reader); - - if (query.StartIndex.HasValue) - { - notifications = notifications.Skip(query.StartIndex.Value); - } - - if (query.Limit.HasValue) - { - notifications = notifications.Take(query.Limit.Value); - } - - result.Notifications = notifications.ToArray(); - } - } - - return result; - } - } - } - - public NotificationsSummary GetNotificationsSummary(string userId) - { - var result = new NotificationsSummary(); - - using (var connection = CreateConnection(true).Result) - { - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "select Level from Notifications where UserId=@UserId and IsRead=@IsRead"; - - cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId); - cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = false; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) - { - var levels = new List(); - - while (reader.Read()) - { - levels.Add(GetLevel(reader, 0)); - } - - result.UnreadCount = levels.Count; - - if (levels.Count > 0) - { - result.MaxUnreadNotificationLevel = levels.Max(); - } - } - - return result; - } - } - } - - /// - /// Gets the notifications. - /// - /// The reader. - /// IEnumerable{Notification}. - private IEnumerable GetNotifications(IDataReader reader) - { - var list = new List(); - - while (reader.Read()) - { - list.Add(GetNotification(reader)); - } - - return list; - } - - private Notification GetNotification(IDataReader reader) - { - var notification = new Notification - { - Id = reader.GetGuid(0).ToString("N"), - UserId = reader.GetGuid(1).ToString("N"), - Date = reader.GetDateTime(2).ToUniversalTime(), - Name = reader.GetString(3) - }; - - if (!reader.IsDBNull(4)) - { - notification.Description = reader.GetString(4); - } - - if (!reader.IsDBNull(5)) - { - notification.Url = reader.GetString(5); - } - - notification.Level = GetLevel(reader, 6); - notification.IsRead = reader.GetBoolean(7); - - return notification; - } - - /// - /// Gets the level. - /// - /// The reader. - /// The index. - /// NotificationLevel. - private NotificationLevel GetLevel(IDataReader reader, int index) - { - NotificationLevel level; - - var val = reader.GetString(index); - - Enum.TryParse(val, true, out level); - - return level; - } - - /// - /// Adds the notification. - /// - /// The notification. - /// The cancellation token. - /// Task. - public async Task AddNotification(Notification notification, CancellationToken cancellationToken) - { - await ReplaceNotification(notification, cancellationToken).ConfigureAwait(false); - - if (NotificationAdded != null) - { - try - { - NotificationAdded(this, new NotificationUpdateEventArgs - { - Notification = notification - }); - } - catch (Exception ex) - { - Logger.ErrorException("Error in NotificationAdded event handler", ex); - } - } - } - - /// - /// Replaces the notification. - /// - /// The notification. - /// The cancellation token. - /// Task. - private async Task ReplaceNotification(Notification notification, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(notification.Id)) - { - notification.Id = Guid.NewGuid().ToString("N"); - } - if (string.IsNullOrEmpty(notification.UserId)) - { - throw new ArgumentException("The notification must have a user id"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - using (var replaceNotificationCommand = connection.CreateCommand()) - { - replaceNotificationCommand.CommandText = "replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (@Id, @UserId, @Date, @Name, @Description, @Url, @Level, @IsRead, @Category, @RelatedId)"; - - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Id"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@UserId"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Date"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Name"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Description"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Url"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Level"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@IsRead"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Category"); - replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@RelatedId"); - - IDbTransaction transaction = null; - - try - { - transaction = connection.BeginTransaction(); - - replaceNotificationCommand.GetParameter("@Id").Value = new Guid(notification.Id); - replaceNotificationCommand.GetParameter("@UserId").Value = new Guid(notification.UserId); - replaceNotificationCommand.GetParameter("@Date").Value = notification.Date.ToUniversalTime(); - replaceNotificationCommand.GetParameter("@Name").Value = notification.Name; - replaceNotificationCommand.GetParameter("@Description").Value = notification.Description; - replaceNotificationCommand.GetParameter("@Url").Value = notification.Url; - replaceNotificationCommand.GetParameter("@Level").Value = notification.Level.ToString(); - replaceNotificationCommand.GetParameter("@IsRead").Value = notification.IsRead; - replaceNotificationCommand.GetParameter("@Category").Value = string.Empty; - replaceNotificationCommand.GetParameter("@RelatedId").Value = string.Empty; - - replaceNotificationCommand.Transaction = transaction; - - replaceNotificationCommand.ExecuteNonQuery(); - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save notification:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - } - } - } - } - - /// - /// Marks the read. - /// - /// The notification id list. - /// The user id. - /// if set to true [is read]. - /// The cancellation token. - /// Task. - public async Task MarkRead(IEnumerable notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) - { - var list = notificationIdList.ToList(); - var idArray = list.Select(i => new Guid(i)).ToArray(); - - await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false); - - if (NotificationsMarkedRead != null) - { - try - { - NotificationsMarkedRead(this, new NotificationReadEventArgs - { - IdList = list.ToArray(), - IsRead = isRead, - UserId = userId - }); - } - catch (Exception ex) - { - Logger.ErrorException("Error in NotificationsMarkedRead event handler", ex); - } - } - } - - public async Task MarkAllRead(string userId, bool isRead, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - using (var markAllReadCommand = connection.CreateCommand()) - { - markAllReadCommand.CommandText = "update Notifications set IsRead=@IsRead where UserId=@UserId"; - - markAllReadCommand.Parameters.Add(markAllReadCommand, "@UserId"); - markAllReadCommand.Parameters.Add(markAllReadCommand, "@IsRead"); - - IDbTransaction transaction = null; - - try - { - cancellationToken.ThrowIfCancellationRequested(); - - transaction = connection.BeginTransaction(); - - markAllReadCommand.GetParameter(0).Value = new Guid(userId); - markAllReadCommand.GetParameter(1).Value = isRead; - - markAllReadCommand.ExecuteNonQuery(); - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save notification:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - } - } - } - } - - private async Task MarkReadInternal(IEnumerable notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - using (var markReadCommand = connection.CreateCommand()) - { - markReadCommand.CommandText = "update Notifications set IsRead=@IsRead where Id=@Id and UserId=@UserId"; - - markReadCommand.Parameters.Add(markReadCommand, "@UserId"); - markReadCommand.Parameters.Add(markReadCommand, "@IsRead"); - markReadCommand.Parameters.Add(markReadCommand, "@Id"); - - IDbTransaction transaction = null; - - try - { - cancellationToken.ThrowIfCancellationRequested(); - - transaction = connection.BeginTransaction(); - - markReadCommand.GetParameter(0).Value = new Guid(userId); - markReadCommand.GetParameter(1).Value = isRead; - - foreach (var id in notificationIdList) - { - markReadCommand.GetParameter(2).Value = id; - - markReadCommand.Transaction = transaction; - - markReadCommand.ExecuteNonQuery(); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save notification:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - } - } - } - } - } -} diff --git a/Emby.Server.Core/Social/SharingRepository.cs b/Emby.Server.Core/Social/SharingRepository.cs deleted file mode 100644 index 5503b7ab3b..0000000000 --- a/Emby.Server.Core/Social/SharingRepository.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Data; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Core.Data; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Social; - -namespace Emby.Server.Core.Social -{ - public class SharingRepository : BaseSqliteRepository, ISharingRepository - { - public SharingRepository(ILogManager logManager, IApplicationPaths appPaths, IDbConnector dbConnector) - : base(logManager, dbConnector) - { - DbFilePath = Path.Combine(appPaths.DataPath, "shares.db"); - } - - /// - /// Opens the connection to the database - /// - /// Task. - public async Task Initialize() - { - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - string[] queries = { - - "create table if not exists Shares (Id GUID, ItemId TEXT, UserId TEXT, ExpirationDate DateTime, PRIMARY KEY (Id))", - "create index if not exists idx_Shares on Shares(Id)", - - "pragma shrink_memory" - }; - - connection.RunQueries(queries, Logger); - } - } - - public async Task CreateShare(SocialShareInfo info) - { - if (info == null) - { - throw new ArgumentNullException("info"); - } - if (string.IsNullOrWhiteSpace(info.Id)) - { - throw new ArgumentNullException("info.Id"); - } - - var cancellationToken = CancellationToken.None; - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - using (var saveShareCommand = connection.CreateCommand()) - { - saveShareCommand.CommandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (@Id, @ItemId, @UserId, @ExpirationDate)"; - - saveShareCommand.Parameters.Add(saveShareCommand, "@Id"); - saveShareCommand.Parameters.Add(saveShareCommand, "@ItemId"); - saveShareCommand.Parameters.Add(saveShareCommand, "@UserId"); - saveShareCommand.Parameters.Add(saveShareCommand, "@ExpirationDate"); - - IDbTransaction transaction = null; - - try - { - transaction = connection.BeginTransaction(); - - saveShareCommand.GetParameter(0).Value = new Guid(info.Id); - saveShareCommand.GetParameter(1).Value = info.ItemId; - saveShareCommand.GetParameter(2).Value = info.UserId; - saveShareCommand.GetParameter(3).Value = info.ExpirationDate; - - saveShareCommand.Transaction = transaction; - - saveShareCommand.ExecuteNonQuery(); - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save share:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - } - } - } - } - - public SocialShareInfo GetShareInfo(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - using (var connection = CreateConnection(true).Result) - { - var cmd = connection.CreateCommand(); - cmd.CommandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = @id"; - - cmd.Parameters.Add(cmd, "@id", DbType.Guid).Value = new Guid(id); - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) - { - if (reader.Read()) - { - return GetSocialShareInfo(reader); - } - } - - return null; - } - } - - private SocialShareInfo GetSocialShareInfo(IDataReader reader) - { - var info = new SocialShareInfo(); - - info.Id = reader.GetGuid(0).ToString("N"); - info.ItemId = reader.GetString(1); - info.UserId = reader.GetString(2); - info.ExpirationDate = reader.GetDateTime(3).ToUniversalTime(); - - return info; - } - - public async Task DeleteShare(string id) - { - - } - } -} diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs new file mode 100644 index 0000000000..ea9e537c93 --- /dev/null +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Activity +{ + public class ActivityRepository : BaseSqliteRepository, IActivityRepository + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths) + : base(logger) + { + DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); + } + + public void Initialize() + { + using (var connection = CreateConnection()) + { + 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)" + }; + + connection.RunQueries(queries); + } + } + + 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"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + var commandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + db.Execute(commandText, + entry.Id.ToGuidParamValue(), + entry.Name, + entry.Overview, + entry.ShortOverview, + entry.Type, + entry.ItemId, + entry.UserId, + entry.Date.ToDateTimeParamValue(), + entry.Severity.ToString()); + }); + } + } + } + + public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) + { + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var commandText = BaseActivitySelectText; + + var whereClauses = new List(); + var paramList = new List(); + + if (minDate.HasValue) + { + whereClauses.Add("DateCreated>=@DateCreated"); + paramList.Add(minDate.Value.ToDateTimeParamValue()); + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + if (startIndex.HasValue && startIndex.Value > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", + pagingWhereText, + startIndex.Value.ToString(_usCulture))); + } + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + commandText += whereText; + + commandText += " ORDER BY DateCreated DESC"; + + if (limit.HasValue) + { + commandText += " LIMIT " + limit.Value.ToString(_usCulture); + } + + var totalRecordCount = connection.Query("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging, paramList.ToArray()).SelectScalarInt().First(); + + var list = new List(); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + list.Add(GetEntry(row)); + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = totalRecordCount + }; + } + } + } + + private ActivityLogEntry GetEntry(IReadOnlyList reader) + { + var index = 0; + + var info = new ActivityLogEntry + { + Id = reader[index].ReadGuid().ToString("N") + }; + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Name = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Overview = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.ShortOverview = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Type = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.ItemId = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.UserId = reader[index].ToString(); + } + + index++; + info.Date = reader[index].ReadDateTime(); + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Severity = (LogSeverity)Enum.Parse(typeof(LogSeverity), reader[index].ToString(), true); + } + + return info; + } + } +} diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs new file mode 100644 index 0000000000..c7ac630a05 --- /dev/null +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + public abstract class BaseSqliteRepository : IDisposable + { + protected string DbFilePath { get; set; } + protected SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); + protected ILogger Logger { get; private set; } + + protected BaseSqliteRepository(ILogger logger) + { + Logger = logger; + } + + protected virtual bool EnableConnectionPooling + { + get { return true; } + } + + protected virtual SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false) + { + SQLite3.EnableSharedCache = false; + + ConnectionFlags connectionFlags; + + if (isReadOnly) + { + connectionFlags = ConnectionFlags.ReadOnly; + //connectionFlags = ConnectionFlags.Create; + //connectionFlags |= ConnectionFlags.ReadWrite; + } + else + { + connectionFlags = ConnectionFlags.Create; + connectionFlags |= ConnectionFlags.ReadWrite; + } + + if (EnableConnectionPooling) + { + connectionFlags |= ConnectionFlags.SharedCached; + } + else + { + connectionFlags |= ConnectionFlags.PrivateCache; + } + + connectionFlags |= ConnectionFlags.NoMutex; + + var db = SQLite3.Open(DbFilePath, connectionFlags, null); + + var queries = new[] + { + "PRAGMA page_size=4096", + "PRAGMA journal_mode=WAL", + "PRAGMA temp_store=memory", + "PRAGMA synchronous=Normal", + //"PRAGMA cache size=-10000" + }; + + //foreach (var query in queries) + //{ + // db.Execute(query); + //} + + db.ExecuteAll(string.Join(";", queries)); + + return db; + } + + private bool _disposed; + protected void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name + " has been disposed and cannot be accessed."); + } + } + + public void Dispose() + { + _disposed = true; + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + try + { + lock (_disposeLock) + { + WriteLock.Wait(); + + CloseConnection(); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error disposing database", ex); + } + } + } + + protected virtual void CloseConnection() + { + + } + } +} diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs new file mode 100644 index 0000000000..dd32e2cbd9 --- /dev/null +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -0,0 +1,361 @@ +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Emby.Server.Implementations.ScheduledTasks; + +namespace Emby.Server.Implementations.Data +{ + public class CleanDatabaseScheduledTask : IScheduledTask + { + private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepo; + private readonly ILogger _logger; + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IHttpServer _httpServer; + private readonly ILocalizationManager _localization; + private readonly ITaskManager _taskManager; + + public const int MigrationVersion = 23; + public static bool EnableUnavailableMessage = false; + const int LatestSchemaVersion = 109; + + public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager) + { + _libraryManager = libraryManager; + _itemRepo = itemRepo; + _logger = logger; + _config = config; + _fileSystem = fileSystem; + _httpServer = httpServer; + _localization = localization; + _taskManager = taskManager; + } + + public string Name + { + get { return "Clean Database"; } + } + + public string Description + { + get { return "Deletes obsolete content from the database."; } + } + + public string Category + { + get { return "Library"; } + } + + public async Task Execute(CancellationToken cancellationToken, IProgress progress) + { + OnProgress(0); + + // Ensure these objects are lazy loaded. + // Without this there is a deadlock that will need to be investigated + var rootChildren = _libraryManager.RootFolder.Children.ToList(); + rootChildren = _libraryManager.GetUserRootFolder().Children.ToList(); + + var innerProgress = new ActionableProgress(); + innerProgress.RegisterAction(p => + { + double newPercentCommplete = .4 * p; + OnProgress(newPercentCommplete); + + progress.Report(newPercentCommplete); + }); + + await UpdateToLatestSchema(cancellationToken, innerProgress).ConfigureAwait(false); + + innerProgress = new ActionableProgress(); + innerProgress.RegisterAction(p => + { + double newPercentCommplete = 40 + .05 * p; + OnProgress(newPercentCommplete); + progress.Report(newPercentCommplete); + }); + await CleanDeadItems(cancellationToken, innerProgress).ConfigureAwait(false); + progress.Report(45); + + innerProgress = new ActionableProgress(); + innerProgress.RegisterAction(p => + { + double newPercentCommplete = 45 + .55 * p; + OnProgress(newPercentCommplete); + progress.Report(newPercentCommplete); + }); + await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false); + progress.Report(100); + + await _itemRepo.UpdateInheritedValues(cancellationToken).ConfigureAwait(false); + + if (_config.Configuration.MigrationVersion < MigrationVersion) + { + _config.Configuration.MigrationVersion = MigrationVersion; + _config.SaveConfiguration(); + } + + if (_config.Configuration.SchemaVersion < LatestSchemaVersion) + { + _config.Configuration.SchemaVersion = LatestSchemaVersion; + _config.SaveConfiguration(); + } + + if (EnableUnavailableMessage) + { + EnableUnavailableMessage = false; + _httpServer.GlobalResponse = null; + _taskManager.QueueScheduledTask(); + } + + _taskManager.SuspendTriggers = false; + } + + private void OnProgress(double newPercentCommplete) + { + if (EnableUnavailableMessage) + { + var html = "Emby"; + var text = _localization.GetLocalizedString("DbUpgradeMessage"); + html += string.Format(text, newPercentCommplete.ToString("N2", CultureInfo.InvariantCulture)); + + html += ""; + html += ""; + + _httpServer.GlobalResponse = html; + } + } + + private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) + { + var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery + { + IsCurrentSchema = false, + ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name } + }); + + var numComplete = 0; + var numItems = itemIds.Count; + + _logger.Debug("Upgrading schema for {0} items", numItems); + + var list = new List(); + + foreach (var itemId in itemIds) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (itemId != Guid.Empty) + { + // Somehow some invalid data got into the db. It probably predates the boundary checking + var item = _libraryManager.GetItemById(itemId); + + if (item != null) + { + list.Add(item); + } + } + + if (list.Count >= 1000) + { + try + { + await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error saving item", ex); + } + + list.Clear(); + } + + numComplete++; + double percent = numComplete; + percent /= numItems; + progress.Report(percent * 100); + } + + if (list.Count > 0) + { + try + { + await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error saving item", ex); + } + } + + progress.Report(100); + } + + private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress progress) + { + var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery + { + HasDeadParentId = true + }); + + var numComplete = 0; + var numItems = itemIds.Count; + + _logger.Debug("Cleaning {0} items with dead parent links", numItems); + + foreach (var itemId in itemIds) + { + cancellationToken.ThrowIfCancellationRequested(); + + var item = _libraryManager.GetItemById(itemId); + + if (item != null) + { + _logger.Info("Cleaning item {0} type: {1} path: {2}", item.Name, item.GetType().Name, item.Path ?? string.Empty); + + await item.Delete(new DeleteOptions + { + DeleteFileLocation = false + + }).ConfigureAwait(false); + } + + numComplete++; + double percent = numComplete; + percent /= numItems; + progress.Report(percent * 100); + } + + progress.Report(100); + } + + private async Task CleanDeletedItems(CancellationToken cancellationToken, IProgress progress) + { + var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery + { + LocationTypes = new[] { LocationType.FileSystem }, + //Limit = limit, + + // These have their own cleanup routines + ExcludeItemTypes = new[] + { + typeof(Person).Name, + typeof(Genre).Name, + typeof(MusicGenre).Name, + typeof(GameGenre).Name, + typeof(Studio).Name, + typeof(Year).Name, + typeof(Channel).Name, + typeof(AggregateFolder).Name, + typeof(CollectionFolder).Name + } + }); + + var numComplete = 0; + var numItems = result.Items.Length; + + foreach (var item in result.Items) + { + cancellationToken.ThrowIfCancellationRequested(); + + var path = item.Item2; + + try + { + if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path)) + { + continue; + } + + var libraryItem = _libraryManager.GetItemById(item.Item1); + + if (libraryItem.IsTopParent) + { + continue; + } + + var hasDualAccess = libraryItem as IHasDualAccess; + if (hasDualAccess != null && hasDualAccess.IsAccessedByName) + { + continue; + } + + var libraryItemPath = libraryItem.Path; + if (!string.Equals(libraryItemPath, path, StringComparison.OrdinalIgnoreCase)) + { + _logger.Error("CleanDeletedItems aborting delete for item {0}-{1} because paths don't match. {2}---{3}", libraryItem.Id, libraryItem.Name, libraryItem.Path ?? string.Empty, path ?? string.Empty); + continue; + } + + if (Folder.IsPathOffline(path)) + { + await libraryItem.UpdateIsOffline(true).ConfigureAwait(false); + continue; + } + + _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty); + + await libraryItem.OnFileDeleted().ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error in CleanDeletedItems. File {0}", ex, path); + } + + numComplete++; + double percent = numComplete; + percent /= numItems; + progress.Report(percent * 100); + } + } + + /// + /// Creates the triggers that define when the task will run + /// + /// IEnumerable{BaseTaskTrigger}. + public IEnumerable GetDefaultTriggers() + { + return new[] { + + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + }; + } + + public string Key + { + get { return "CleanDatabase"; } + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs new file mode 100644 index 0000000000..62615c669a --- /dev/null +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -0,0 +1,105 @@ +using System; +using System.Globalization; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + public static class SqliteExtensions + { + public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries) + { + if (queries == null) + { + throw new ArgumentNullException("queries"); + } + + connection.RunInTransaction(conn => + { + //foreach (var query in queries) + //{ + // conn.Execute(query); + //} + conn.ExecuteAll(string.Join(";", queries)); + }); + } + + public static byte[] ToGuidParamValue(this string str) + { + return new Guid(str).ToByteArray(); + } + + public static Guid ReadGuid(this IResultSetValue result) + { + return new Guid(result.ToBlob()); + } + + public static string ToDateTimeParamValue(this DateTime dateValue) + { + var kind = DateTimeKind.Utc; + + return (dateValue.Kind == DateTimeKind.Unspecified) + ? DateTime.SpecifyKind(dateValue, kind).ToString( + GetDateTimeKindFormat(kind), + CultureInfo.InvariantCulture) + : dateValue.ToString( + GetDateTimeKindFormat(dateValue.Kind), + CultureInfo.InvariantCulture); + } + + private static string GetDateTimeKindFormat( + DateTimeKind kind) + { + return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal; + } + + /// + /// An array of ISO-8601 DateTime formats that we support parsing. + /// + private static string[] _datetimeFormats = new string[] { + "THHmmssK", + "THHmmK", + "HH:mm:ss.FFFFFFFK", + "HH:mm:ssK", + "HH:mmK", + "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */ + "yyyy-MM-dd HH:mm:ssK", + "yyyy-MM-dd HH:mmK", + "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", + "yyyy-MM-ddTHH:mmK", + "yyyy-MM-ddTHH:mm:ssK", + "yyyyMMddHHmmssK", + "yyyyMMddHHmmK", + "yyyyMMddTHHmmssFFFFFFFK", + "THHmmss", + "THHmm", + "HH:mm:ss.FFFFFFF", + "HH:mm:ss", + "HH:mm", + "yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */ + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm", + "yyyy-MM-ddTHH:mm:ss.FFFFFFF", + "yyyy-MM-ddTHH:mm", + "yyyy-MM-ddTHH:mm:ss", + "yyyyMMddHHmmss", + "yyyyMMddHHmm", + "yyyyMMddTHHmmssFFFFFFF", + "yyyy-MM-dd", + "yyyyMMdd", + "yy-MM-dd" + }; + + private static string _datetimeFormatUtc = _datetimeFormats[5]; + private static string _datetimeFormatLocal = _datetimeFormats[19]; + + public static DateTime ReadDateTime(this IResultSetValue result) + { + var dateText = result.ToString(); + + return DateTime.ParseExact( + dateText, _datetimeFormats, + DateTimeFormatInfo.InvariantInfo, + DateTimeStyles.None).ToUniversalTime(); + } + } +} diff --git a/Emby.Server.Implementations/Devices/DeviceRepository.cs b/Emby.Server.Implementations/Devices/DeviceRepository.cs new file mode 100644 index 0000000000..f739765b34 --- /dev/null +++ b/Emby.Server.Implementations/Devices/DeviceRepository.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Session; + +namespace Emby.Server.Implementations.Devices +{ + public class DeviceRepository : IDeviceRepository + { + private readonly object _syncLock = new object(); + + private readonly IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + private Dictionary _devices; + + public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem) + { + _appPaths = appPaths; + _json = json; + _logger = logger; + _fileSystem = fileSystem; + } + + private string GetDevicesPath() + { + return Path.Combine(_appPaths.DataPath, "devices"); + } + + private string GetDevicePath(string id) + { + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); + } + + public Task SaveDevice(DeviceInfo device) + { + var path = Path.Combine(GetDevicePath(device.Id), "device.json"); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_syncLock) + { + _json.SerializeToFile(device, path); + _devices[device.Id] = device; + } + return Task.FromResult(true); + } + + public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) + { + var device = GetDevice(reportedId); + + if (device == null) + { + throw new ArgumentException("No device has been registed with id " + reportedId); + } + + device.Capabilities = capabilities; + SaveDevice(device); + + return Task.FromResult(true); + } + + public ClientCapabilities GetCapabilities(string reportedId) + { + var device = GetDevice(reportedId); + + return device == null ? null : device.Capabilities; + } + + public DeviceInfo GetDevice(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + return GetDevices() + .FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); + } + + public IEnumerable GetDevices() + { + lock (_syncLock) + { + if (_devices == null) + { + _devices = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var devices = LoadDevices().ToList(); + foreach (var device in devices) + { + _devices[device.Id] = device; + } + } + return _devices.Values.ToList(); + } + } + + private IEnumerable LoadDevices() + { + var path = GetDevicesPath(); + + try + { + return _fileSystem + .GetFilePaths(path, true) + .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) + .ToList() + .Select(i => + { + try + { + return _json.DeserializeFromFile(i); + } + catch (Exception ex) + { + _logger.ErrorException("Error reading {0}", ex, i); + return null; + } + }) + .Where(i => i != null); + } + catch (IOException) + { + return new List(); + } + } + + public Task DeleteDevice(string id) + { + var path = GetDevicePath(id); + + lock (_syncLock) + { + try + { + _fileSystem.DeleteDirectory(path, true); + } + catch (IOException) + { + } + + _devices = null; + } + + return Task.FromResult(true); + } + + public ContentUploadHistory GetCameraUploadHistory(string deviceId) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + + lock (_syncLock) + { + try + { + return _json.DeserializeFromFile(path); + } + catch (IOException) + { + return new ContentUploadHistory + { + DeviceId = deviceId + }; + } + } + } + + public void AddCameraUpload(string deviceId, LocalFileInfo file) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_syncLock) + { + ContentUploadHistory history; + + try + { + history = _json.DeserializeFromFile(path); + } + catch (IOException) + { + history = new ContentUploadHistory + { + DeviceId = deviceId + }; + } + + history.DeviceId = deviceId; + history.FilesUploaded.Add(file); + + _json.SerializeToFile(history, path); + } + } + } +} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 13d184b59c..6843ad9d73 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,6 +34,7 @@ + @@ -51,6 +52,7 @@ + @@ -165,6 +167,7 @@ + @@ -173,8 +176,11 @@ + - + + + @@ -197,6 +203,7 @@ + @@ -291,6 +298,14 @@ ..\packages\MediaBrowser.Naming.1.0.2\lib\portable-net45+win8\MediaBrowser.Naming.dll True + + ..\packages\SQLitePCL.pretty.1.1.0\lib\portable-net45+netcore45+wpa81+wp8\SQLitePCL.pretty.dll + True + + + ..\packages\SQLitePCLRaw.core.1.1.0\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + True + ..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll True diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index c32af8eb1e..6cd24058ae 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -127,7 +127,11 @@ namespace Emby.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person.Key); - var options = new MetadataRefreshOptions(_fileSystem); + var options = new MetadataRefreshOptions(_fileSystem) + { + ImageRefreshMode = ImageRefreshMode.ValidationOnly, + MetadataRefreshMode = MetadataRefreshMode.ValidationOnly + }; await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/Migrations/IVersionMigration.cs b/Emby.Server.Implementations/Migrations/IVersionMigration.cs new file mode 100644 index 0000000000..7804912e3b --- /dev/null +++ b/Emby.Server.Implementations/Migrations/IVersionMigration.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Emby.Server.Implementations.Migrations +{ + public interface IVersionMigration + { + Task Run(); + } +} diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs new file mode 100644 index 0000000000..fcf45b0ad6 --- /dev/null +++ b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Notifications; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Notifications +{ + public class SqliteNotificationsRepository : BaseSqliteRepository, INotificationsRepository + { + public SqliteNotificationsRepository(ILogger logger, IServerApplicationPaths appPaths) : base(logger) + { + DbFilePath = Path.Combine(appPaths.DataPath, "notifications.db"); + } + + public event EventHandler NotificationAdded; + public event EventHandler NotificationsMarkedRead; + ////public event EventHandler NotificationUpdated; + + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + "create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT NULL, Url TEXT NULL, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT NULL, PRIMARY KEY (Id, UserId))", + "create index if not exists idx_Notifications1 on Notifications(Id)", + "create index if not exists idx_Notifications2 on Notifications(UserId)" + }; + + connection.RunQueries(queries); + } + } + + /// + /// Gets the notifications. + /// + /// The query. + /// NotificationResult. + public NotificationResult GetNotifications(NotificationQuery query) + { + var result = new NotificationResult(); + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var clauses = new List(); + var paramList = new List(); + + if (query.IsRead.HasValue) + { + clauses.Add("IsRead=?"); + paramList.Add(query.IsRead.Value); + } + + clauses.Add("UserId=?"); + paramList.Add(query.UserId.ToGuidParamValue()); + + var whereClause = " where " + string.Join(" And ", clauses.ToArray()); + + result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray()).SelectScalarInt().First(); + + var commandText = string.Format("select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause); + + if (query.Limit.HasValue || query.StartIndex.HasValue) + { + var offset = query.StartIndex ?? 0; + + if (query.Limit.HasValue || offset > 0) + { + commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + } + + if (offset > 0) + { + commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + } + } + + var resultList = new List(); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + resultList.Add(GetNotification(row)); + } + + result.Notifications = resultList.ToArray(); + } + } + + return result; + } + + public NotificationsSummary GetNotificationsSummary(string userId) + { + var result = new NotificationsSummary(); + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select Level from Notifications where UserId=? and IsRead=?", userId.ToGuidParamValue(), false)) + { + var levels = new List(); + + levels.Add(GetLevel(row, 0)); + + result.UnreadCount = levels.Count; + + if (levels.Count > 0) + { + result.MaxUnreadNotificationLevel = levels.Max(); + } + } + + return result; + } + } + } + + private Notification GetNotification(IReadOnlyList reader) + { + var notification = new Notification + { + Id = reader[0].ReadGuid().ToString("N"), + UserId = reader[1].ReadGuid().ToString("N"), + Date = reader[2].ReadDateTime(), + Name = reader[3].ToString() + }; + + if (reader[4].SQLiteType != SQLiteType.Null) + { + notification.Description = reader[4].ToString(); + } + + if (reader[5].SQLiteType != SQLiteType.Null) + { + notification.Url = reader[5].ToString(); + } + + notification.Level = GetLevel(reader, 6); + notification.IsRead = reader[7].ToBool(); + + return notification; + } + + /// + /// Gets the level. + /// + /// The reader. + /// The index. + /// NotificationLevel. + private NotificationLevel GetLevel(IReadOnlyList reader, int index) + { + NotificationLevel level; + + var val = reader[index].ToString(); + + Enum.TryParse(val, true, out level); + + return level; + } + + /// + /// Adds the notification. + /// + /// The notification. + /// The cancellation token. + /// Task. + public async Task AddNotification(Notification notification, CancellationToken cancellationToken) + { + await ReplaceNotification(notification, cancellationToken).ConfigureAwait(false); + + if (NotificationAdded != null) + { + try + { + NotificationAdded(this, new NotificationUpdateEventArgs + { + Notification = notification + }); + } + catch (Exception ex) + { + Logger.ErrorException("Error in NotificationAdded event handler", ex); + } + } + } + + /// + /// Replaces the notification. + /// + /// The notification. + /// The cancellation token. + /// Task. + private async Task ReplaceNotification(Notification notification, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(notification.Id)) + { + notification.Id = Guid.NewGuid().ToString("N"); + } + if (string.IsNullOrEmpty(notification.UserId)) + { + throw new ArgumentException("The notification must have a user id"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(conn => + { + conn.Execute("replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + notification.Id.ToGuidParamValue(), + notification.UserId.ToGuidParamValue(), + notification.Date.ToDateTimeParamValue(), + notification.Name, + notification.Description, + notification.Url, + notification.Level.ToString(), + notification.IsRead, + string.Empty, + string.Empty); + }); + } + } + } + + /// + /// Marks the read. + /// + /// The notification id list. + /// The user id. + /// if set to true [is read]. + /// The cancellation token. + /// Task. + public async Task MarkRead(IEnumerable notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) + { + var list = notificationIdList.ToList(); + var idArray = list.Select(i => new Guid(i)).ToArray(); + + await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false); + + if (NotificationsMarkedRead != null) + { + try + { + NotificationsMarkedRead(this, new NotificationReadEventArgs + { + IdList = list.ToArray(), + IsRead = isRead, + UserId = userId + }); + } + catch (Exception ex) + { + Logger.ErrorException("Error in NotificationsMarkedRead event handler", ex); + } + } + } + + public async Task MarkAllRead(string userId, bool isRead, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(conn => + { + conn.Execute("update Notifications set IsRead=? where UserId=?", userId.ToGuidParamValue(), isRead); + }); + } + } + } + + private async Task MarkReadInternal(IEnumerable notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(conn => + { + var userIdParam = userId.ToGuidParamValue(); + + foreach (var id in notificationIdList) + { + conn.Execute("update Notifications set IsRead=? where UserId=? and Id=?", userIdParam, isRead, id); + } + }); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs deleted file mode 100644 index 88f4f1f815..0000000000 --- a/Emby.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ /dev/null @@ -1,361 +0,0 @@ -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Tasks; -using Emby.Server.Implementations.ScheduledTasks; - -namespace Emby.Server.Implementations.Persistence -{ - public class CleanDatabaseScheduledTask : IScheduledTask - { - private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; - private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IHttpServer _httpServer; - private readonly ILocalizationManager _localization; - private readonly ITaskManager _taskManager; - - public const int MigrationVersion = 23; - public static bool EnableUnavailableMessage = false; - const int LatestSchemaVersion = 109; - - public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager) - { - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _logger = logger; - _config = config; - _fileSystem = fileSystem; - _httpServer = httpServer; - _localization = localization; - _taskManager = taskManager; - } - - public string Name - { - get { return "Clean Database"; } - } - - public string Description - { - get { return "Deletes obsolete content from the database."; } - } - - public string Category - { - get { return "Library"; } - } - - public async Task Execute(CancellationToken cancellationToken, IProgress progress) - { - OnProgress(0); - - // Ensure these objects are lazy loaded. - // Without this there is a deadlock that will need to be investigated - var rootChildren = _libraryManager.RootFolder.Children.ToList(); - rootChildren = _libraryManager.GetUserRootFolder().Children.ToList(); - - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => - { - double newPercentCommplete = .4 * p; - OnProgress(newPercentCommplete); - - progress.Report(newPercentCommplete); - }); - - await UpdateToLatestSchema(cancellationToken, innerProgress).ConfigureAwait(false); - - innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => - { - double newPercentCommplete = 40 + .05 * p; - OnProgress(newPercentCommplete); - progress.Report(newPercentCommplete); - }); - await CleanDeadItems(cancellationToken, innerProgress).ConfigureAwait(false); - progress.Report(45); - - innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => - { - double newPercentCommplete = 45 + .55 * p; - OnProgress(newPercentCommplete); - progress.Report(newPercentCommplete); - }); - await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false); - progress.Report(100); - - await _itemRepo.UpdateInheritedValues(cancellationToken).ConfigureAwait(false); - - if (_config.Configuration.MigrationVersion < MigrationVersion) - { - _config.Configuration.MigrationVersion = MigrationVersion; - _config.SaveConfiguration(); - } - - if (_config.Configuration.SchemaVersion < LatestSchemaVersion) - { - _config.Configuration.SchemaVersion = LatestSchemaVersion; - _config.SaveConfiguration(); - } - - if (EnableUnavailableMessage) - { - EnableUnavailableMessage = false; - _httpServer.GlobalResponse = null; - _taskManager.QueueScheduledTask(); - } - - _taskManager.SuspendTriggers = false; - } - - private void OnProgress(double newPercentCommplete) - { - if (EnableUnavailableMessage) - { - var html = "Emby"; - var text = _localization.GetLocalizedString("DbUpgradeMessage"); - html += string.Format(text, newPercentCommplete.ToString("N2", CultureInfo.InvariantCulture)); - - html += ""; - html += ""; - - _httpServer.GlobalResponse = html; - } - } - - private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) - { - var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - IsCurrentSchema = false, - ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name } - }); - - var numComplete = 0; - var numItems = itemIds.Count; - - _logger.Debug("Upgrading schema for {0} items", numItems); - - var list = new List(); - - foreach (var itemId in itemIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (itemId != Guid.Empty) - { - // Somehow some invalid data got into the db. It probably predates the boundary checking - var item = _libraryManager.GetItemById(itemId); - - if (item != null) - { - list.Add(item); - } - } - - if (list.Count >= 1000) - { - try - { - await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error saving item", ex); - } - - list.Clear(); - } - - numComplete++; - double percent = numComplete; - percent /= numItems; - progress.Report(percent * 100); - } - - if (list.Count > 0) - { - try - { - await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error saving item", ex); - } - } - - progress.Report(100); - } - - private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress progress) - { - var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - HasDeadParentId = true - }); - - var numComplete = 0; - var numItems = itemIds.Count; - - _logger.Debug("Cleaning {0} items with dead parent links", numItems); - - foreach (var itemId in itemIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var item = _libraryManager.GetItemById(itemId); - - if (item != null) - { - _logger.Info("Cleaning item {0} type: {1} path: {2}", item.Name, item.GetType().Name, item.Path ?? string.Empty); - - await item.Delete(new DeleteOptions - { - DeleteFileLocation = false - - }).ConfigureAwait(false); - } - - numComplete++; - double percent = numComplete; - percent /= numItems; - progress.Report(percent * 100); - } - - progress.Report(100); - } - - private async Task CleanDeletedItems(CancellationToken cancellationToken, IProgress progress) - { - var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery - { - LocationTypes = new[] { LocationType.FileSystem }, - //Limit = limit, - - // These have their own cleanup routines - ExcludeItemTypes = new[] - { - typeof(Person).Name, - typeof(Genre).Name, - typeof(MusicGenre).Name, - typeof(GameGenre).Name, - typeof(Studio).Name, - typeof(Year).Name, - typeof(Channel).Name, - typeof(AggregateFolder).Name, - typeof(CollectionFolder).Name - } - }); - - var numComplete = 0; - var numItems = result.Items.Length; - - foreach (var item in result.Items) - { - cancellationToken.ThrowIfCancellationRequested(); - - var path = item.Item2; - - try - { - if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path)) - { - continue; - } - - var libraryItem = _libraryManager.GetItemById(item.Item1); - - if (libraryItem.IsTopParent) - { - continue; - } - - var hasDualAccess = libraryItem as IHasDualAccess; - if (hasDualAccess != null && hasDualAccess.IsAccessedByName) - { - continue; - } - - var libraryItemPath = libraryItem.Path; - if (!string.Equals(libraryItemPath, path, StringComparison.OrdinalIgnoreCase)) - { - _logger.Error("CleanDeletedItems aborting delete for item {0}-{1} because paths don't match. {2}---{3}", libraryItem.Id, libraryItem.Name, libraryItem.Path ?? string.Empty, path ?? string.Empty); - continue; - } - - if (Folder.IsPathOffline(path)) - { - await libraryItem.UpdateIsOffline(true).ConfigureAwait(false); - continue; - } - - _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty); - - await libraryItem.OnFileDeleted().ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error in CleanDeletedItems. File {0}", ex, path); - } - - numComplete++; - double percent = numComplete; - percent /= numItems; - progress.Report(percent * 100); - } - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} - }; - } - - public string Key - { - get { return "CleanDatabase"; } - } - } -} \ No newline at end of file diff --git a/Emby.Server.Implementations/Social/SharingRepository.cs b/Emby.Server.Implementations/Social/SharingRepository.cs new file mode 100644 index 0000000000..e09b7f5b99 --- /dev/null +++ b/Emby.Server.Implementations/Social/SharingRepository.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Social; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Social +{ + public class SharingRepository : BaseSqliteRepository, ISharingRepository + { + public SharingRepository(ILogger logger, IApplicationPaths appPaths) + : base(logger) + { + DbFilePath = Path.Combine(appPaths.DataPath, "shares.db"); + } + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists Shares (Id GUID, ItemId TEXT, UserId TEXT, ExpirationDate DateTime, PRIMARY KEY (Id))", + "create index if not exists idx_Shares on Shares(Id)", + + "pragma shrink_memory" + }; + + connection.RunQueries(queries); + } + } + + public async Task CreateShare(SocialShareInfo info) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + if (string.IsNullOrWhiteSpace(info.Id)) + { + throw new ArgumentNullException("info.Id"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + var commandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (?, ?, ?, ?)"; + + db.Execute(commandText, + info.Id.ToGuidParamValue(), + info.ItemId, + info.UserId, + info.ExpirationDate.ToDateTimeParamValue()); + }); + } + } + } + + public SocialShareInfo GetShareInfo(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var commandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = ?"; + + var paramList = new List(); + paramList.Add(id.ToGuidParamValue()); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + return GetSocialShareInfo(row); + } + } + } + + return null; + } + + private SocialShareInfo GetSocialShareInfo(IReadOnlyList reader) + { + var info = new SocialShareInfo(); + + info.Id = reader[0].ReadGuid().ToString("N"); + info.ItemId = reader[1].ToString(); + info.UserId = reader[2].ToString(); + info.ExpirationDate = reader[3].ReadDateTime(); + + return info; + } + + public async Task DeleteShare(string id) + { + + } + } +} diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 71fdbffc2d..519e4a0ad9 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -2,5 +2,7 @@ + + \ No newline at end of file diff --git a/MediaBrowser.Model/Logging/LogHelper.cs b/MediaBrowser.Model/Logging/LogHelper.cs index 5b8090d30e..cf1c021862 100644 --- a/MediaBrowser.Model/Logging/LogHelper.cs +++ b/MediaBrowser.Model/Logging/LogHelper.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Model.Logging var messageText = new StringBuilder(); - messageText.AppendLine(exception.Message); + messageText.AppendLine(exception.ToString()); messageText.AppendLine(exception.GetType().FullName); @@ -71,7 +71,7 @@ namespace MediaBrowser.Model.Logging private static void AppendInnerException(StringBuilder messageText, Exception e) { messageText.AppendLine("InnerException: " + e.GetType().FullName); - messageText.AppendLine(e.Message); + messageText.AppendLine(e.ToString()); LogExceptionData(messageText, e); diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 270c43e131..b6d3c5ce63 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -84,6 +84,14 @@ ..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll True + + ..\packages\SQLitePCLRaw.core.1.1.0\lib\net45\SQLitePCLRaw.core.dll + True + + + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.0\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + True + ..\ThirdParty\MediaBrowser.IsoMounting.Linux\MediaBrowser.IsoMounting.Linux.dll diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index f490a829a2..9cc13a59e4 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -34,6 +34,8 @@ namespace MediaBrowser.Server.Mono public static void Main(string[] args) { + SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3()); + var applicationPath = Assembly.GetEntryAssembly().Location; var options = new StartupOptions(); diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index 5727f4c185..028ee1e458 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/MediaBrowser.Server.Mono/packages.config @@ -5,4 +5,6 @@ + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index f5e7681333..328041bc3d 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -90,6 +90,8 @@ namespace MediaBrowser.ServerApplication var success = SetDllDirectory(architecturePath); + SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3()); + var appPaths = CreateApplicationPaths(ApplicationPath, IsRunningAsService); var logManager = new NlogManager(appPaths.LogDirectoryPath, "server"); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 6984ff2bed..e5a632d69b 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -90,13 +90,21 @@ ..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll True + + ..\packages\SQLitePCLRaw.core.1.1.0\lib\net45\SQLitePCLRaw.core.dll + True + + + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.0\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + True + - ..\packages\System.Data.SQLite.Core.1.0.103\lib\net46\System.Data.SQLite.dll - True + False + ..\ThirdParty\System.Data.SQLite.ManagedOnly\1.0.94.0\System.Data.SQLite.dll @@ -623,6 +631,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1063,6 +1074,9 @@ PreserveNewest + + PreserveNewest + @@ -1163,13 +1177,6 @@ - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - -