瀏覽代碼

rework notifications infrastructure

Luke Pulverenti 11 年之前
父節點
當前提交
547291f048
共有 34 個文件被更改,包括 451 次插入319 次删除
  1. 4 2
      MediaBrowser.Api/Images/ImageService.cs
  2. 3 3
      MediaBrowser.Api/Images/RemoteImageService.cs
  3. 1 1
      MediaBrowser.Api/ItemLookupService.cs
  4. 1 1
      MediaBrowser.Api/ItemRefreshService.cs
  5. 1 1
      MediaBrowser.Api/ItemUpdateService.cs
  6. 6 6
      MediaBrowser.Api/Library/LibraryService.cs
  7. 5 3
      MediaBrowser.Api/Music/InstantMixService.cs
  8. 18 27
      MediaBrowser.Api/NotificationsService.cs
  9. 2 2
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  10. 1 1
      MediaBrowser.Api/SimilarItemsHelper.cs
  11. 2 2
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  12. 2 4
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  13. 9 7
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  14. 2 2
      MediaBrowser.Api/VideosService.cs
  15. 0 9
      MediaBrowser.Controller/Dto/IDtoService.cs
  16. 5 0
      MediaBrowser.Controller/Library/ILibraryManager.cs
  17. 2 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  18. 49 0
      MediaBrowser.Controller/Notifications/INotificationManager.cs
  19. 5 14
      MediaBrowser.Controller/Notifications/INotificationsRepository.cs
  20. 2 2
      MediaBrowser.Controller/Notifications/NotificationUpdateEventArgs.cs
  21. 21 0
      MediaBrowser.Controller/Notifications/UserNotification.cs
  22. 10 39
      MediaBrowser.Dlna/Server/ContentDirectory.cs
  23. 1 8
      MediaBrowser.Model/ApiClient/IApiClient.cs
  24. 24 7
      MediaBrowser.Model/Notifications/Notification.cs
  25. 0 38
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  26. 84 88
      MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifier.cs
  27. 15 17
      MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs
  28. 2 2
      MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs
  29. 1 1
      MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
  30. 3 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  31. 42 0
      MediaBrowser.Server.Implementations/Notifications/InternalNotificationService.cs
  32. 94 0
      MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs
  33. 26 31
      MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
  34. 8 0
      MediaBrowser.ServerApplication/ApplicationHost.cs

+ 4 - 2
MediaBrowser.Api/Images/ImageService.cs

@@ -358,7 +358,7 @@ namespace MediaBrowser.Api.Images
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetItemImageInfos request)
         public object Get(GetItemImageInfos request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var result = GetItemImageInfos(item);
             var result = GetItemImageInfos(item);
 
 
@@ -493,7 +493,9 @@ namespace MediaBrowser.Api.Images
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetItemImage request)
         public object Get(GetItemImage request)
         {
         {
-            var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.RootFolder : _dtoService.GetItemByDtoId(request.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? 
+                _libraryManager.RootFolder :
+                _libraryManager.GetItemById(request.Id);
 
 
             return GetImage(request, item);
             return GetImage(request, item);
         }
         }

+ 3 - 3
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -175,7 +175,7 @@ namespace MediaBrowser.Api.Images
 
 
         public object Get(GetRemoteImageProviders request)
         public object Get(GetRemoteImageProviders request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var result = GetImageProviders(item);
             var result = GetImageProviders(item);
 
 
@@ -201,7 +201,7 @@ namespace MediaBrowser.Api.Images
 
 
         public object Get(GetRemoteImages request)
         public object Get(GetRemoteImages request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var result = GetRemoteImageResult(item, request);
             var result = GetRemoteImageResult(item, request);
 
 
@@ -269,7 +269,7 @@ namespace MediaBrowser.Api.Images
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         public void Post(DownloadRemoteImage request)
         public void Post(DownloadRemoteImage request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var task = DownloadRemoteImage(item, request);
             var task = DownloadRemoteImage(item, request);
 
 

+ 1 - 1
MediaBrowser.Api/ItemLookupService.cs

@@ -124,7 +124,7 @@ namespace MediaBrowser.Api
 
 
         public object Get(GetExternalIdInfos request)
         public object Get(GetExternalIdInfos request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var infos = _providerManager.GetExternalIdInfos(item).ToList();
             var infos = _providerManager.GetExternalIdInfos(item).ToList();
 
 

+ 1 - 1
MediaBrowser.Api/ItemRefreshService.cs

@@ -250,7 +250,7 @@ namespace MediaBrowser.Api
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task RefreshItem(RefreshItem request)
         private async Task RefreshItem(RefreshItem request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var options = GetRefreshOptions(request);
             var options = GetRefreshOptions(request);
             
             

+ 1 - 1
MediaBrowser.Api/ItemUpdateService.cs

@@ -91,7 +91,7 @@ namespace MediaBrowser.Api
 
 
         private async Task UpdateItem(UpdateItem request)
         private async Task UpdateItem(UpdateItem request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.ItemId);
+            var item = _libraryManager.GetItemById(request.ItemId);
 
 
             var newLockData = request.LockData ?? false;
             var newLockData = request.LockData ?? false;
             var dontFetchMetaChanged = item.IsLocked != newLockData;
             var dontFetchMetaChanged = item.IsLocked != newLockData;

+ 6 - 6
MediaBrowser.Api/Library/LibraryService.cs

@@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Library
 
 
         public object Get(GetFile request)
         public object Get(GetFile request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
             var locationType = item.LocationType;
             var locationType = item.LocationType;
             if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
             if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
             {
             {
@@ -350,7 +350,7 @@ namespace MediaBrowser.Api.Library
         /// <returns>Task{BaseItemDto[]}.</returns>
         /// <returns>Task{BaseItemDto[]}.</returns>
         public List<BaseItemDto> GetAncestors(GetAncestors request)
         public List<BaseItemDto> GetAncestors(GetAncestors request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var baseItemDtos = new List<BaseItemDto>();
             var baseItemDtos = new List<BaseItemDto>();
 
 
@@ -504,7 +504,7 @@ namespace MediaBrowser.Api.Library
 
 
         private Task DeleteItem(DeleteItem request)
         private Task DeleteItem(DeleteItem request)
         {
         {
-            var item = _dtoService.GetItemByDtoId(request.Id);
+            var item = _libraryManager.GetItemById(request.Id);
 
 
             var session = GetSession(_sessionManager);
             var session = GetSession(_sessionManager);
 
 
@@ -593,7 +593,7 @@ namespace MediaBrowser.Api.Library
                            ? (request.UserId.HasValue
                            ? (request.UserId.HasValue
                                   ? user.RootFolder
                                   ? user.RootFolder
                                   : (Folder)_libraryManager.RootFolder)
                                   : (Folder)_libraryManager.RootFolder)
-                           : _dtoService.GetItemByDtoId(request.Id, request.UserId);
+                           : _libraryManager.GetItemById(request.Id);
 
 
             var originalItem = item;
             var originalItem = item;
 
 
@@ -661,7 +661,7 @@ namespace MediaBrowser.Api.Library
                            ? (request.UserId.HasValue
                            ? (request.UserId.HasValue
                                   ? user.RootFolder
                                   ? user.RootFolder
                                   : (Folder)_libraryManager.RootFolder)
                                   : (Folder)_libraryManager.RootFolder)
-                           : _dtoService.GetItemByDtoId(request.Id, request.UserId);
+                           : _libraryManager.GetItemById(request.Id);
 
 
             var originalItem = item;
             var originalItem = item;
 
 
@@ -771,7 +771,7 @@ namespace MediaBrowser.Api.Library
                            ? (userId.HasValue
                            ? (userId.HasValue
                                   ? user.RootFolder
                                   ? user.RootFolder
                                   : (Folder)_libraryManager.RootFolder)
                                   : (Folder)_libraryManager.RootFolder)
-                           : _dtoService.GetItemByDtoId(id, userId);
+                           : _libraryManager.GetItemById(id);
 
 
             // Get everything
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields))
             var fields = Enum.GetNames(typeof(ItemFields))

+ 5 - 3
MediaBrowser.Api/Music/InstantMixService.cs

@@ -38,18 +38,20 @@ namespace MediaBrowser.Api.Music
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
 
 
         private readonly IDtoService _dtoService;
         private readonly IDtoService _dtoService;
+        private readonly ILibraryManager _libraryManager;
         private readonly IMusicManager _musicManager;
         private readonly IMusicManager _musicManager;
 
 
-        public InstantMixService(IUserManager userManager, IDtoService dtoService, IMusicManager musicManager)
+        public InstantMixService(IUserManager userManager, IDtoService dtoService, IMusicManager musicManager, ILibraryManager libraryManager)
         {
         {
             _userManager = userManager;
             _userManager = userManager;
             _dtoService = dtoService;
             _dtoService = dtoService;
             _musicManager = musicManager;
             _musicManager = musicManager;
+            _libraryManager = libraryManager;
         }
         }
 
 
         public object Get(GetInstantMixFromSong request)
         public object Get(GetInstantMixFromSong request)
         {
         {
-            var item = (Audio)_dtoService.GetItemByDtoId(request.Id);
+            var item = (Audio)_libraryManager.GetItemById(request.Id);
 
 
             var user = _userManager.GetUserById(request.UserId.Value);
             var user = _userManager.GetUserById(request.UserId.Value);
 
 
@@ -60,7 +62,7 @@ namespace MediaBrowser.Api.Music
 
 
         public object Get(GetInstantMixFromAlbum request)
         public object Get(GetInstantMixFromAlbum request)
         {
         {
-            var album = (MusicAlbum)_dtoService.GetItemByDtoId(request.Id);
+            var album = (MusicAlbum)_libraryManager.GetItemById(request.Id);
 
 
             var user = _userManager.GetUserById(request.UserId.Value);
             var user = _userManager.GetUserById(request.UserId.Value);
 
 

+ 18 - 27
MediaBrowser.Api/NotificationsService.cs

@@ -2,7 +2,7 @@
 using MediaBrowser.Model.Notifications;
 using MediaBrowser.Model.Notifications;
 using ServiceStack;
 using ServiceStack;
 using System;
 using System;
-using System.Linq;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -28,17 +28,17 @@ namespace MediaBrowser.Api
     public class GetNotificationsSummary : IReturn<NotificationsSummary>
     public class GetNotificationsSummary : IReturn<NotificationsSummary>
     {
     {
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
     }
     }
 
 
     [Route("/Notifications/{UserId}", "POST", Summary = "Adds a notifications")]
     [Route("/Notifications/{UserId}", "POST", Summary = "Adds a notifications")]
-    public class AddUserNotification : IReturn<Notification>
+    public class AddUserNotification : IReturnVoid
     {
     {
         [ApiMember(Name = "Id", Description = "The Id of the new notification. If unspecified one will be provided.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "Id", Description = "The Id of the new notification. If unspecified one will be provided.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public Guid? Id { get; set; }
+        public string Id { get; set; }
 
 
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
 
         [ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string Name { get; set; }
         public string Name { get; set; }
@@ -49,12 +49,6 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string Url { get; set; }
         public string Url { get; set; }
 
 
-        [ApiMember(Name = "Category", Description = "The notification's category", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Category { get; set; }
-
-        [ApiMember(Name = "RelatedId", Description = "The notification's related id (item)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string RelatedId { get; set; }
-
         [ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public NotificationLevel Level { get; set; }
         public NotificationLevel Level { get; set; }
     }
     }
@@ -63,7 +57,7 @@ namespace MediaBrowser.Api
     public class MarkRead : IReturnVoid
     public class MarkRead : IReturnVoid
     {
     {
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
 
         [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         public string Ids { get; set; }
         public string Ids { get; set; }
@@ -73,7 +67,7 @@ namespace MediaBrowser.Api
     public class MarkUnread : IReturnVoid
     public class MarkUnread : IReturnVoid
     {
     {
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
 
         [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         public string Ids { get; set; }
         public string Ids { get; set; }
@@ -82,17 +76,19 @@ namespace MediaBrowser.Api
     public class NotificationsService : BaseApiService
     public class NotificationsService : BaseApiService
     {
     {
         private readonly INotificationsRepository _notificationsRepo;
         private readonly INotificationsRepository _notificationsRepo;
+        private readonly INotificationManager _notificationManager;
 
 
-        public NotificationsService(INotificationsRepository notificationsRepo)
+        public NotificationsService(INotificationsRepository notificationsRepo, INotificationManager notificationManager)
         {
         {
             _notificationsRepo = notificationsRepo;
             _notificationsRepo = notificationsRepo;
+            _notificationManager = notificationManager;
         }
         }
 
 
-        public object Post(AddUserNotification request)
+        public void Post(AddUserNotification request)
         {
         {
             var task = AddNotification(request);
             var task = AddNotification(request);
 
 
-            return ToOptimizedResult(task.Result);
+            Task.WaitAll(task);
         }
         }
 
 
         public object Get(GetNotificationsSummary request)
         public object Get(GetNotificationsSummary request)
@@ -102,24 +98,19 @@ namespace MediaBrowser.Api
             return result;
             return result;
         }
         }
 
 
-        private async Task<Notification> AddNotification(AddUserNotification request)
+        private async Task AddNotification(AddUserNotification request)
         {
         {
-            var notification = new Notification
+            var notification = new NotificationRequest
             {
             {
-                Id = request.Id ?? Guid.NewGuid(),
                 Date = DateTime.UtcNow,
                 Date = DateTime.UtcNow,
                 Description = request.Description,
                 Description = request.Description,
                 Level = request.Level,
                 Level = request.Level,
                 Name = request.Name,
                 Name = request.Name,
                 Url = request.Url,
                 Url = request.Url,
-                UserId = request.UserId,
-                Category = request.Category,
-                RelatedId = request.RelatedId
+                UserIds = new List<string> { request.UserId }
             };
             };
 
 
-            await _notificationsRepo.AddNotification(notification, CancellationToken.None).ConfigureAwait(false);
-
-            return notification;
+            await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
         }
         }
 
 
         public void Post(MarkRead request)
         public void Post(MarkRead request)
@@ -136,9 +127,9 @@ namespace MediaBrowser.Api
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
 
 
-        private Task MarkRead(string idList, Guid userId, bool read)
+        private Task MarkRead(string idList, string userId, bool read)
         {
         {
-            var ids = idList.Split(',').Select(i => new Guid(i));
+            var ids = idList.Split(',');
 
 
             return _notificationsRepo.MarkRead(ids, userId, read, CancellationToken.None);
             return _notificationsRepo.MarkRead(ids, userId, read, CancellationToken.None);
         }
         }

+ 2 - 2
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1336,8 +1336,8 @@ namespace MediaBrowser.Api.Playback
             }
             }
 
 
             var item = string.IsNullOrEmpty(request.MediaSourceId) ?
             var item = string.IsNullOrEmpty(request.MediaSourceId) ?
-                DtoService.GetItemByDtoId(request.Id) :
-                DtoService.GetItemByDtoId(request.MediaSourceId);
+                LibraryManager.GetItemById(request.Id) :
+                LibraryManager.GetItemById(request.MediaSourceId);
 
 
             if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
             if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
             {
             {

+ 1 - 1
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -73,7 +73,7 @@ namespace MediaBrowser.Api
 
 
             var item = string.IsNullOrEmpty(request.Id) ?
             var item = string.IsNullOrEmpty(request.Id) ?
                 (request.UserId.HasValue ? user.RootFolder :
                 (request.UserId.HasValue ? user.RootFolder :
-                libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId);
+                libraryManager.RootFolder) : libraryManager.GetItemById(request.Id);
 
 
             var fields = request.GetItemFields().ToList();
             var fields = request.GetItemFields().ToList();
 
 

+ 2 - 2
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -61,14 +61,14 @@ namespace MediaBrowser.Api.UserLibrary
             if (request.UserId.HasValue)
             if (request.UserId.HasValue)
             {
             {
                 user = UserManager.GetUserById(request.UserId.Value);
                 user = UserManager.GetUserById(request.UserId.Value);
-                item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoService.GetItemByDtoId(request.ParentId, user.Id);
+                item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId);
 
 
                 libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList();
                 libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList();
 
 
             }
             }
             else
             else
             {
             {
-                item = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : DtoService.GetItemByDtoId(request.ParentId);
+                item = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
 
 
                 libraryItems = LibraryManager.RootFolder.RecursiveChildren.ToList();
                 libraryItems = LibraryManager.RootFolder.RecursiveChildren.ToList();
             }
             }

+ 2 - 4
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -373,11 +373,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <exception cref="System.InvalidOperationException"></exception>
         /// <exception cref="System.InvalidOperationException"></exception>
         private IEnumerable<BaseItem> GetItemsToSerialize(GetItems request, User user)
         private IEnumerable<BaseItem> GetItemsToSerialize(GetItems request, User user)
         {
         {
-            var userId = user == null ? (Guid?)null : user.Id;
-
             var item = string.IsNullOrEmpty(request.ParentId) ?
             var item = string.IsNullOrEmpty(request.ParentId) ?
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
-                _dtoService.GetItemByDtoId(request.ParentId, userId);
+                _libraryManager.GetItemById(request.ParentId);
 
 
             // Default list type = children
             // Default list type = children
             IEnumerable<BaseItem> items;
             IEnumerable<BaseItem> items;
@@ -386,7 +384,7 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 var idList = request.Ids.Split(',').ToList();
                 var idList = request.Ids.Split(',').ToList();
 
 
-                items = idList.Select(i => _dtoService.GetItemByDtoId(i, userId));
+                items = idList.Select(i => _libraryManager.GetItemById(i));
             }
             }
 
 
             else if (request.Recursive)
             else if (request.Recursive)

+ 9 - 7
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -463,7 +463,9 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _dtoService.GetItemByDtoId(request.Id, user.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? 
+                user.RootFolder : 
+                _libraryManager.GetItemById(request.Id);
 
 
             // Get everything
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
@@ -529,7 +531,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _dtoService.GetItemByDtoId(request.Id, user.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
 
 
             // Get everything
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
@@ -559,7 +561,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _dtoService.GetItemByDtoId(request.Id, user.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
 
 
             // Get everything
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
@@ -597,7 +599,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _dtoService.GetItemByDtoId(request.Id, user.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
 
 
             var items = _libraryManager.GetIntros(item, user);
             var items = _libraryManager.GetIntros(item, user);
 
 
@@ -651,7 +653,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
-            var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _dtoService.GetItemByDtoId(itemId, user.Id);
+            var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
 
 
             var key = item.GetUserDataKey();
             var key = item.GetUserDataKey();
 
 
@@ -701,7 +703,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
-            var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _dtoService.GetItemByDtoId(itemId, user.Id);
+            var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
 
 
             var key = item.GetUserDataKey();
             var key = item.GetUserDataKey();
 
 
@@ -871,7 +873,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task<UserItemDataDto> UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed)
         private async Task<UserItemDataDto> UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed)
         {
         {
-            var item = _dtoService.GetItemByDtoId(itemId, user.Id);
+            var item = _libraryManager.GetItemById(itemId);
 
 
             if (wasPlayed)
             if (wasPlayed)
             {
             {

+ 2 - 2
MediaBrowser.Api/VideosService.cs

@@ -73,7 +73,7 @@ namespace MediaBrowser.Api
                            ? (request.UserId.HasValue
                            ? (request.UserId.HasValue
                                   ? user.RootFolder
                                   ? user.RootFolder
                                   : _libraryManager.RootFolder)
                                   : _libraryManager.RootFolder)
-                           : _dtoService.GetItemByDtoId(request.Id, request.UserId);
+                           : _libraryManager.GetItemById(request.Id);
 
 
             // Get everything
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields))
             var fields = Enum.GetNames(typeof(ItemFields))
@@ -104,7 +104,7 @@ namespace MediaBrowser.Api
 
 
         private async Task RemoveAlternateVersions(DeleteAlternateSources request)
         private async Task RemoveAlternateVersions(DeleteAlternateSources request)
         {
         {
-            var video = (Video)_dtoService.GetItemByDtoId(request.Id);
+            var video = (Video)_libraryManager.GetItemById(request.Id);
 
 
             foreach (var link in video.GetLinkedAlternateVersions())
             foreach (var link in video.GetLinkedAlternateVersions())
             {
             {

+ 0 - 9
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
-using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
 namespace MediaBrowser.Controller.Dto
 namespace MediaBrowser.Controller.Dto
@@ -33,14 +32,6 @@ namespace MediaBrowser.Controller.Dto
         /// <returns>UserItemDataDto.</returns>
         /// <returns>UserItemDataDto.</returns>
         UserItemDataDto GetUserItemDataDto(UserItemData data);
         UserItemDataDto GetUserItemDataDto(UserItemData data);
 
 
-        /// <summary>
-        /// Gets the item by dto id.
-        /// </summary>
-        /// <param name="id">The id.</param>
-        /// <param name="userId">The user id.</param>
-        /// <returns>BaseItem.</returns>
-        BaseItem GetItemByDtoId(string id, Guid? userId = null);
-
         /// <summary>
         /// <summary>
         /// Attaches the primary image aspect ratio.
         /// Attaches the primary image aspect ratio.
         /// </summary>
         /// </summary>

+ 5 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -342,5 +342,10 @@ namespace MediaBrowser.Controller.Library
                 DeleteFileLocation = true
                 DeleteFileLocation = true
             });
             });
         }
         }
+
+        public static BaseItem GetItemById(this ILibraryManager manager, string id)
+        {
+            return manager.GetItemById(new Guid(id));
+        }
     }
     }
 }
 }

+ 2 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -167,8 +167,10 @@
     <Compile Include="Net\IHttpServer.cs" />
     <Compile Include="Net\IHttpServer.cs" />
     <Compile Include="Net\IRestfulService.cs" />
     <Compile Include="Net\IRestfulService.cs" />
     <Compile Include="News\INewsService.cs" />
     <Compile Include="News\INewsService.cs" />
+    <Compile Include="Notifications\INotificationManager.cs" />
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
+    <Compile Include="Notifications\UserNotification.cs" />
     <Compile Include="Persistence\IFileOrganizationRepository.cs" />
     <Compile Include="Persistence\IFileOrganizationRepository.cs" />
     <Compile Include="Persistence\MediaStreamQuery.cs" />
     <Compile Include="Persistence\MediaStreamQuery.cs" />
     <Compile Include="Providers\DirectoryService.cs" />
     <Compile Include="Providers\DirectoryService.cs" />

+ 49 - 0
MediaBrowser.Controller/Notifications/INotificationManager.cs

@@ -0,0 +1,49 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Notifications;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Notifications
+{
+    public interface INotificationManager
+    {
+        /// <summary>
+        /// Sends the notification.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendNotification(NotificationRequest request, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Adds the parts.
+        /// </summary>
+        /// <param name="services">The services.</param>
+        void AddParts(IEnumerable<INotificationService> services);
+    }
+
+    public interface INotificationService
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+
+        /// <summary>
+        /// Sends the notification.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendNotification(UserNotification request, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Determines whether [is enabled for user] [the specified user identifier].
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <returns><c>true</c> if [is enabled for user] [the specified user identifier]; otherwise, <c>false</c>.</returns>
+        bool IsEnabledForUser(User user);
+    }
+}

+ 5 - 14
MediaBrowser.Controller/Notifications/INotificationsRepository.cs

@@ -1,8 +1,7 @@
-using System.Threading;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Notifications;
+using MediaBrowser.Model.Notifications;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.Notifications
 namespace MediaBrowser.Controller.Notifications
@@ -44,7 +43,7 @@ namespace MediaBrowser.Controller.Notifications
         /// <param name="id">The id.</param>
         /// <param name="id">The id.</param>
         /// <param name="userId">The user id.</param>
         /// <param name="userId">The user id.</param>
         /// <returns>Notification.</returns>
         /// <returns>Notification.</returns>
-        Notification GetNotification(Guid id, Guid userId);
+        Notification GetNotification(string id, string userId);
 
 
         /// <summary>
         /// <summary>
         /// Adds the notification.
         /// Adds the notification.
@@ -54,14 +53,6 @@ namespace MediaBrowser.Controller.Notifications
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task AddNotification(Notification notification, CancellationToken cancellationToken);
         Task AddNotification(Notification notification, CancellationToken cancellationToken);
 
 
-        /// <summary>
-        /// Updates the notification.
-        /// </summary>
-        /// <param name="notification">The notification.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task UpdateNotification(Notification notification, CancellationToken cancellationToken);
-
         /// <summary>
         /// <summary>
         /// Marks the read.
         /// Marks the read.
         /// </summary>
         /// </summary>
@@ -70,13 +61,13 @@ namespace MediaBrowser.Controller.Notifications
         /// <param name="isRead">if set to <c>true</c> [is read].</param>
         /// <param name="isRead">if set to <c>true</c> [is read].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task MarkRead(IEnumerable<Guid> notificationIdList, Guid userId, bool isRead, CancellationToken cancellationToken);
+        Task MarkRead(IEnumerable<string> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Gets the notifications summary.
         /// Gets the notifications summary.
         /// </summary>
         /// </summary>
         /// <param name="userId">The user id.</param>
         /// <param name="userId">The user id.</param>
         /// <returns>NotificationsSummary.</returns>
         /// <returns>NotificationsSummary.</returns>
-        NotificationsSummary GetNotificationsSummary(Guid userId);
+        NotificationsSummary GetNotificationsSummary(string userId);
     }
     }
 }
 }

+ 2 - 2
MediaBrowser.Controller/Notifications/NotificationUpdateEventArgs.cs

@@ -10,8 +10,8 @@ namespace MediaBrowser.Controller.Notifications
 
 
     public class NotificationReadEventArgs : EventArgs
     public class NotificationReadEventArgs : EventArgs
     {
     {
-        public Guid[] IdList { get; set; }
-        public Guid UserId { get; set; }
+        public string[] IdList { get; set; }
+        public string UserId { get; set; }
         public bool IsRead { get; set; }
         public bool IsRead { get; set; }
     }
     }
 }
 }

+ 21 - 0
MediaBrowser.Controller/Notifications/UserNotification.cs

@@ -0,0 +1,21 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Notifications;
+using System;
+
+namespace MediaBrowser.Controller.Notifications
+{
+    public class UserNotification
+    {
+        public string Name { get; set; }
+
+        public string Description { get; set; }
+
+        public string Url { get; set; }
+
+        public NotificationLevel Level { get; set; }
+
+        public DateTime Date { get; set; }
+
+        public User User { get; set; }
+    }
+}

+ 10 - 39
MediaBrowser.Dlna/Server/ContentDirectory.cs

@@ -8,9 +8,7 @@ using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Linq;
 using System.Linq;
-using System.Threading;
 
 
 namespace MediaBrowser.Dlna.Server
 namespace MediaBrowser.Dlna.Server
 {
 {
@@ -27,9 +25,6 @@ namespace MediaBrowser.Dlna.Server
 
 
         private readonly IEventManager _eventManager;
         private readonly IEventManager _eventManager;
 
 
-        private int _systemUpdateId;
-        private Timer _systemUpdateTimer;
-
         public ContentDirectory(IDlnaManager dlna,
         public ContentDirectory(IDlnaManager dlna,
             IUserDataManager userDataManager,
             IUserDataManager userDataManager,
             IImageProcessor imageProcessor,
             IImageProcessor imageProcessor,
@@ -49,9 +44,16 @@ namespace MediaBrowser.Dlna.Server
             _userManager = userManager;
             _userManager = userManager;
             _eventManager = eventManager;
             _eventManager = eventManager;
             _logger = logManager.GetLogger("DlnaContentDirectory");
             _logger = logManager.GetLogger("DlnaContentDirectory");
+        }
+
+        private int SystemUpdateId
+        {
+            get
+            {
+                var now = DateTime.UtcNow;
 
 
-            _systemUpdateTimer = new Timer(SystemUdpateTimerCallback, null, Timeout.Infinite,
-                Convert.ToInt64(TimeSpan.FromMinutes(60).TotalMilliseconds));
+                return now.Year + now.DayOfYear + now.Hour;
+            }
         }
         }
 
 
         public string GetContentDirectoryXml(IDictionary<string, string> headers)
         public string GetContentDirectoryXml(IDictionary<string, string> headers)
@@ -80,7 +82,7 @@ namespace MediaBrowser.Dlna.Server
                 _imageProcessor,
                 _imageProcessor,
                 _userDataManager,
                 _userDataManager,
                 user,
                 user,
-                _systemUpdateId)
+                SystemUpdateId)
                 .ProcessControlRequest(request);
                 .ProcessControlRequest(request);
         }
         }
 
 
@@ -110,40 +112,9 @@ namespace MediaBrowser.Dlna.Server
             return _userManager.Users.First();
             return _userManager.Users.First();
         }
         }
 
 
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private async void SystemUdpateTimerCallback(object state)
-        {
-            var values = new Dictionary<string, string>();
-
-            _systemUpdateId++;
-            values["SystemUpdateID"] = _systemUpdateId.ToString(_usCulture);
-
-            try
-            {
-                await _eventManager.TriggerEvent("upnp:event", values).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error sending system update notification", ex);
-            }
-        }
-
-        private readonly object _disposeLock = new object();
         public void Dispose()
         public void Dispose()
         {
         {
-            lock (_disposeLock)
-            {
-                DisposeUpdateTimer();
-            }
-        }
 
 
-        private void DisposeUpdateTimer()
-        {
-            if (_systemUpdateTimer != null)
-            {
-                _systemUpdateTimer.Dispose();
-                _systemUpdateTimer = null;
-            }
         }
         }
     }
     }
 }
 }

+ 1 - 8
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -140,19 +140,12 @@ namespace MediaBrowser.Model.ApiClient
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task MarkNotificationsRead(string userId, IEnumerable<Guid> notificationIdList, bool isRead);
         Task MarkNotificationsRead(string userId, IEnumerable<Guid> notificationIdList, bool isRead);
 
 
-        /// <summary>
-        /// Updates the notification.
-        /// </summary>
-        /// <param name="notification">The notification.</param>
-        /// <returns>Task.</returns>
-        Task UpdateNotification(Notification notification);
-
         /// <summary>
         /// <summary>
         /// Adds the notification.
         /// Adds the notification.
         /// </summary>
         /// </summary>
         /// <param name="notification">The notification.</param>
         /// <param name="notification">The notification.</param>
         /// <returns>Task{Notification}.</returns>
         /// <returns>Task{Notification}.</returns>
-        Task<Notification> AddNotification(Notification notification);
+        Task SendNotification(NotificationRequest notification);
 
 
         /// <summary>
         /// <summary>
         /// Gets the notifications summary.
         /// Gets the notifications summary.

+ 24 - 7
MediaBrowser.Model/Notifications/Notification.cs

@@ -1,12 +1,13 @@
 using System;
 using System;
+using System.Collections.Generic;
 
 
 namespace MediaBrowser.Model.Notifications
 namespace MediaBrowser.Model.Notifications
 {
 {
     public class Notification
     public class Notification
     {
     {
-        public Guid Id { get; set; }
+        public string Id { get; set; }
 
 
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
 
         public DateTime Date { get; set; }
         public DateTime Date { get; set; }
 
 
@@ -17,16 +18,32 @@ namespace MediaBrowser.Model.Notifications
         public string Description { get; set; }
         public string Description { get; set; }
 
 
         public string Url { get; set; }
         public string Url { get; set; }
-
-        public string Category { get; set; }
-
-        public string RelatedId { get; set; }
         
         
         public NotificationLevel Level { get; set; }
         public NotificationLevel Level { get; set; }
 
 
         public Notification()
         public Notification()
         {
         {
-            Id = Guid.NewGuid();
+            Date = DateTime.UtcNow;
+        }
+    }
+
+    public class NotificationRequest
+    {
+        public string Name { get; set; }
+
+        public string Description { get; set; }
+
+        public string Url { get; set; }
+
+        public NotificationLevel Level { get; set; }
+
+        public List<string> UserIds { get; set; }
+
+        public DateTime Date { get; set; }
+        
+        public NotificationRequest()
+        {
+            UserIds = new List<string>();
             Date = DateTime.UtcNow;
             Date = DateTime.UtcNow;
         }
         }
     }
     }

+ 0 - 38
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -556,44 +556,6 @@ namespace MediaBrowser.Server.Implementations.Dto
             return dto;
             return dto;
         }
         }
 
 
-
-        /// <summary>
-        /// Gets a BaseItem based upon it's client-side item id
-        /// </summary>
-        /// <param name="id">The id.</param>
-        /// <param name="userId">The user id.</param>
-        /// <returns>BaseItem.</returns>
-        public BaseItem GetItemByDtoId(string id, Guid? userId = null)
-        {
-            if (string.IsNullOrEmpty(id))
-            {
-                throw new ArgumentNullException("id");
-            }
-
-            BaseItem item = null;
-
-            if (userId.HasValue)
-            {
-                item = _libraryManager.GetItemById(new Guid(id));
-            }
-
-            // If we still don't find it, look within individual user views
-            if (item == null && !userId.HasValue)
-            {
-                foreach (var user in _userManager.Users)
-                {
-                    item = GetItemByDtoId(id, user.Id);
-
-                    if (item != null)
-                    {
-                        break;
-                    }
-                }
-            }
-
-            return item;
-        }
-
         /// <summary>
         /// <summary>
         /// Sets simple property values on a DTOBaseItem
         /// Sets simple property values on a DTOBaseItem
         /// </summary>
         /// </summary>

+ 84 - 88
MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifier.cs

@@ -20,20 +20,20 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
     /// </summary>
     /// </summary>
     public class Notifications : IServerEntryPoint
     public class Notifications : IServerEntryPoint
     {
     {
-        private readonly INotificationsRepository _notificationsRepo;
         private readonly IInstallationManager _installationManager;
         private readonly IInstallationManager _installationManager;
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly ILogger _logger;
         private readonly ILogger _logger;
 
 
         private readonly ITaskManager _taskManager;
         private readonly ITaskManager _taskManager;
+        private readonly INotificationManager _notificationManager;
 
 
-        public Notifications(IInstallationManager installationManager, INotificationsRepository notificationsRepo, IUserManager userManager, ILogger logger, ITaskManager taskManager)
+        public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager)
         {
         {
             _installationManager = installationManager;
             _installationManager = installationManager;
-            _notificationsRepo = notificationsRepo;
             _userManager = userManager;
             _userManager = userManager;
             _logger = logger;
             _logger = logger;
             _taskManager = taskManager;
             _taskManager = taskManager;
+            _notificationManager = notificationManager;
         }
         }
 
 
         public void Run()
         public void Run()
@@ -49,21 +49,25 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
 
 
         async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
         async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
         {
         {
-            var notification = new Notification
+            var userIds = _userManager
+              .Users
+              .Select(i => i.Id.ToString("N"))
+              .ToList();
+
+            var notification = new NotificationRequest
             {
             {
-                UserId = e.Argument.Id,
-                Category = "UserCreated",
+                UserIds = userIds,
                 Name = "Welcome to Media Browser!",
                 Name = "Welcome to Media Browser!",
                 Description = "Check back here for more notifications."
                 Description = "Check back here for more notifications."
             };
             };
 
 
             try
             try
             {
             {
-                await _notificationsRepo.AddNotification(notification, CancellationToken.None).ConfigureAwait(false);
+                await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                _logger.ErrorException("Error adding notification", ex);
+                _logger.ErrorException("Error sending notification", ex);
             }
             }
         }
         }
 
 
@@ -73,29 +77,27 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
 
 
             if (result.Status == TaskCompletionStatus.Failed)
             if (result.Status == TaskCompletionStatus.Failed)
             {
             {
-                foreach (var user in _userManager
-                    .Users
-                    .Where(i => i.Configuration.IsAdministrator)
-                    .ToList())
+                var userIds = _userManager
+                  .Users
+                  .Where(i => i.Configuration.IsAdministrator)
+                  .Select(i => i.Id.ToString("N"))
+                  .ToList();
+
+                var notification = new NotificationRequest
+                {
+                    UserIds = userIds,
+                    Name = result.Name + " failed",
+                    Description = result.ErrorMessage,
+                    Level = NotificationLevel.Error
+                };
+
+                try
                 {
                 {
-                    var notification = new Notification
-                    {
-                        UserId = user.Id,
-                        Category = "ScheduledTaskFailed",
-                        Name = result.Name + " failed",
-                        RelatedId = result.Name,
-                        Description = result.ErrorMessage,
-                        Level = NotificationLevel.Error
-                    };
-
-                    try
-                    {
-                        await _notificationsRepo.AddNotification(notification, CancellationToken.None).ConfigureAwait(false);
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.ErrorException("Error adding notification", ex);
-                    }
+                    await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error sending notification", ex);
                 }
                 }
             }
             }
         }
         }
@@ -104,27 +106,25 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
         {
         {
             var plugin = e.Argument;
             var plugin = e.Argument;
 
 
-            foreach (var user in _userManager
-                .Users
-                .Where(i => i.Configuration.IsAdministrator)
-                .ToList())
+            var userIds = _userManager
+              .Users
+              .Where(i => i.Configuration.IsAdministrator)
+              .Select(i => i.Id.ToString("N"))
+              .ToList();
+
+            var notification = new NotificationRequest
             {
             {
-                var notification = new Notification
-                {
-                    UserId = user.Id,
-                    Category = "PluginUninstalled",
-                    Name = plugin.Name + " has been uninstalled",
-                    RelatedId = plugin.Id.ToString()
-                };
+                UserIds = userIds,
+                Name = plugin.Name + " has been uninstalled"
+            };
 
 
-                try
-                {
-                    await _notificationsRepo.AddNotification(notification, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error adding notification", ex);
-                }
+            try
+            {
+                await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error sending notification", ex);
             }
             }
         }
         }
 
 
@@ -132,28 +132,26 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
         {
         {
             var installationInfo = e.InstallationInfo;
             var installationInfo = e.InstallationInfo;
 
 
-            foreach (var user in _userManager
-                .Users
-                .Where(i => i.Configuration.IsAdministrator)
-                .ToList())
+            var userIds = _userManager
+              .Users
+              .Where(i => i.Configuration.IsAdministrator)
+              .Select(i => i.Id.ToString("N"))
+              .ToList();
+
+            var notification = new NotificationRequest
             {
             {
-                var notification = new Notification
-                {
-                    UserId = user.Id,
-                    Category = "PackageInstallationCompleted",
-                    Name = installationInfo.Name + " " + installationInfo.Version + " was installed",
-                    RelatedId = installationInfo.Name,
-                    Description = e.PackageVersionInfo.description
-                };
+                UserIds = userIds,
+                Name = installationInfo.Name + " " + installationInfo.Version + " was installed",
+                Description = e.PackageVersionInfo.description
+            };
 
 
-                try
-                {
-                    await _notificationsRepo.AddNotification(notification, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error adding notification", ex);
-                }
+            try
+            {
+                await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error sending notification", ex);
             }
             }
         }
         }
 
 
@@ -161,29 +159,27 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
         {
         {
             var installationInfo = e.InstallationInfo;
             var installationInfo = e.InstallationInfo;
 
 
-            foreach (var user in _userManager
+            var userIds = _userManager
                 .Users
                 .Users
                 .Where(i => i.Configuration.IsAdministrator)
                 .Where(i => i.Configuration.IsAdministrator)
-                .ToList())
+                .Select(i => i.Id.ToString("N"))
+                .ToList();
+
+            var notification = new NotificationRequest
             {
             {
-                var notification = new Notification
-                {
-                    UserId = user.Id,
-                    Category = "PackageInstallationFailed",
-                    Level = NotificationLevel.Error,
-                    Name = installationInfo.Name + " " + installationInfo.Version + " installation failed",
-                    RelatedId = installationInfo.Name,
-                    Description = e.Exception.Message
-                };
+                UserIds = userIds,
+                Level = NotificationLevel.Error,
+                Name = installationInfo.Name + " " + installationInfo.Version + " installation failed",
+                Description = e.Exception.Message
+            };
 
 
-                try
-                {
-                    await _notificationsRepo.AddNotification(notification, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error adding notification", ex);
-                }
+            try
+            {
+                await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error sending notification", ex);
             }
             }
         }
         }
 
 

+ 15 - 17
MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Plugins;
@@ -26,22 +25,23 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
         private readonly IApplicationPaths _appPaths;
         private readonly IApplicationPaths _appPaths;
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IJsonSerializer _json;
         private readonly IJsonSerializer _json;
-        private readonly INotificationsRepository _notificationsRepo;
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
         private readonly TimeSpan _frequency = TimeSpan.FromHours(6);
         private readonly TimeSpan _frequency = TimeSpan.FromHours(6);
         private readonly TimeSpan _maxAge = TimeSpan.FromDays(31);
         private readonly TimeSpan _maxAge = TimeSpan.FromDays(31);
 
 
-        public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, INotificationsRepository notificationsRepo, IUserManager userManager, IFileSystem fileSystem)
+        private readonly INotificationManager _notificationManager;
+
+        public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, IUserManager userManager, IFileSystem fileSystem, INotificationManager notificationManager)
         {
         {
             _appPaths = appPaths;
             _appPaths = appPaths;
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
             _json = json;
             _json = json;
-            _notificationsRepo = notificationsRepo;
             _userManager = userManager;
             _userManager = userManager;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
+            _notificationManager = notificationManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -107,21 +107,19 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
                 .Where(i => string.Equals(i.active, "1") && i.date.ToUniversalTime() > lastRunTime && (DateTime.UtcNow - i.date.ToUniversalTime()) <= _maxAge)
                 .Where(i => string.Equals(i.active, "1") && i.date.ToUniversalTime() > lastRunTime && (DateTime.UtcNow - i.date.ToUniversalTime()) <= _maxAge)
                 .ToList();
                 .ToList();
 
 
-            foreach (var user in _userManager.Users.ToList())
+            var userIds = _userManager.Users.Select(i => i.Id.ToString("N")).ToList();
+
+            foreach (var notification in notificationList)
             {
             {
-                foreach (var notification in notificationList)
+                await _notificationManager.SendNotification(new NotificationRequest
                 {
                 {
-                    await _notificationsRepo.AddNotification(new Notification
-                    {
-                        Category = notification.category,
-                        Date = notification.date,
-                        Name = notification.name,
-                        Description = notification.description,
-                        Url = notification.url,
-                        UserId = user.Id
-
-                    }, CancellationToken.None).ConfigureAwait(false);
-                }
+                    Date = notification.date,
+                    Name = notification.name,
+                    Description = notification.description,
+                    Url = notification.url,
+                    UserIds = userIds
+
+                }, CancellationToken.None).ConfigureAwait(false);
             }
             }
         }
         }
 
 

+ 2 - 2
MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs

@@ -30,9 +30,9 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
 
 
         void _notificationsRepo_NotificationsMarkedRead(object sender, NotificationReadEventArgs e)
         void _notificationsRepo_NotificationsMarkedRead(object sender, NotificationReadEventArgs e)
         {
         {
-            var list = e.IdList.Select(i => i.ToString("N")).ToList();
+            var list = e.IdList.ToList();
 
 
-            list.Add(e.UserId.ToString("N"));
+            list.Add(e.UserId);
             list.Add(e.IsRead.ToString().ToLower());
             list.Add(e.IsRead.ToString().ToLower());
 
 
             var msg = string.Join("|", list.ToArray());
             var msg = string.Join("|", list.ToArray());

+ 1 - 1
MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs

@@ -117,7 +117,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
         /// </summary>
         /// </summary>
         /// <param name="list">The list.</param>
         /// <param name="list">The list.</param>
         /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns>
-        public static bool ContainsMusic(IEnumerable<FileSystemInfo> list)
+        private static bool ContainsMusic(IEnumerable<FileSystemInfo> list)
         {
         {
             // If list contains at least 2 audio files or at least one and no video files consider it to contain music
             // If list contains at least 2 audio files or at least one and no video files consider it to contain music
             var foundAudio = 0;
             var foundAudio = 0;

+ 3 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -185,11 +185,13 @@
     <Compile Include="MediaEncoder\EncodingManager.cs" />
     <Compile Include="MediaEncoder\EncodingManager.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />
     <Compile Include="News\NewsService.cs" />
     <Compile Include="News\NewsService.cs" />
+    <Compile Include="Notifications\InternalNotificationService.cs" />
+    <Compile Include="Notifications\NotificationManager.cs" />
     <Compile Include="Persistence\SqliteChapterRepository.cs" />
     <Compile Include="Persistence\SqliteChapterRepository.cs" />
     <Compile Include="Persistence\SqliteExtensions.cs" />
     <Compile Include="Persistence\SqliteExtensions.cs" />
     <Compile Include="Persistence\SqliteFileOrganizationRepository.cs" />
     <Compile Include="Persistence\SqliteFileOrganizationRepository.cs" />
     <Compile Include="Persistence\SqliteMediaStreamsRepository.cs" />
     <Compile Include="Persistence\SqliteMediaStreamsRepository.cs" />
-    <Compile Include="Persistence\SqliteNotificationsRepository.cs" />
+    <Compile Include="Notifications\SqliteNotificationsRepository.cs" />
     <Compile Include="Persistence\SqliteProviderInfoRepository.cs" />
     <Compile Include="Persistence\SqliteProviderInfoRepository.cs" />
     <Compile Include="Persistence\SqliteShrinkMemoryTimer.cs" />
     <Compile Include="Persistence\SqliteShrinkMemoryTimer.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />

+ 42 - 0
MediaBrowser.Server.Implementations/Notifications/InternalNotificationService.cs

@@ -0,0 +1,42 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Notifications;
+using MediaBrowser.Model.Notifications;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Notifications
+{
+    public class InternalNotificationService : INotificationService
+    {
+        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 true;
+        }
+    }
+}

+ 94 - 0
MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs

@@ -0,0 +1,94 @@
+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;
+
+namespace MediaBrowser.Server.Implementations.Notifications
+{
+    public class NotificationManager : INotificationManager
+    {
+        private readonly ILogger _logger;
+        private readonly IUserManager _userManager;
+        private INotificationService[] _services;
+
+        public NotificationManager(ILogManager logManager, IUserManager userManager)
+        {
+            _userManager = userManager;
+            _logger = logManager.GetLogger(GetType().Name);
+        }
+
+        public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
+        {
+            var users = request.UserIds.Select(i => _userManager.GetUserById(new Guid(i)));
+
+            var tasks = _services.Select(i => SendNotification(request, i, users, cancellationToken));
+
+            return Task.WhenAll(tasks);
+        }
+
+        public Task SendNotification(NotificationRequest request,
+            INotificationService service,
+            IEnumerable<User> users,
+            CancellationToken cancellationToken)
+        {
+            users = users.Where(i => IsEnabledForUser(service, i))
+                .ToList();
+
+            var tasks = users.Select(i => SendNotification(request, service, i, cancellationToken));
+
+            return Task.WhenAll(tasks);
+
+        }
+
+        public async Task SendNotification(NotificationRequest request,
+            INotificationService service,
+            User user,
+            CancellationToken cancellationToken)
+        {
+            var notification = new UserNotification
+            {
+                Date = request.Date,
+                Description = request.Description,
+                Level = request.Level,
+                Name = request.Name,
+                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 bool IsEnabledForUser(INotificationService service, User user)
+        {
+            try
+            {
+                return service.IsEnabledForUser(user);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error in IsEnabledForUser", ex);
+                return false;
+            }
+        }
+
+        public void AddParts(IEnumerable<INotificationService> services)
+        {
+            _services = services.ToArray();
+        }
+    }
+}

+ 26 - 31
MediaBrowser.Server.Implementations/Persistence/SqliteNotificationsRepository.cs → MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs

@@ -1,16 +1,17 @@
-using System.IO;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Notifications;
 using MediaBrowser.Model.Notifications;
+using MediaBrowser.Server.Implementations.Persistence;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Data;
 using System.Data;
+using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
-namespace MediaBrowser.Server.Implementations.Persistence
+namespace MediaBrowser.Server.Implementations.Notifications
 {
 {
     public class SqliteNotificationsRepository : INotificationsRepository
     public class SqliteNotificationsRepository : INotificationsRepository
     {
     {
@@ -134,7 +135,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
         }
         }
 
 
-        public NotificationsSummary GetNotificationsSummary(Guid userId)
+        public NotificationsSummary GetNotificationsSummary(string userId)
         {
         {
             var result = new NotificationsSummary();
             var result = new NotificationsSummary();
 
 
@@ -142,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
             {
                 cmd.CommandText = "select Level from Notifications where UserId=@UserId and IsRead=@IsRead";
                 cmd.CommandText = "select Level from Notifications where UserId=@UserId and IsRead=@IsRead";
 
 
-                cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = userId;
+                cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId);
                 cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = false;
                 cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = false;
 
 
                 using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
                 using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
@@ -183,8 +184,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
         {
         {
             var notification = new Notification
             var notification = new Notification
             {
             {
-                Id = reader.GetGuid(0),
-                UserId = reader.GetGuid(1),
+                Id = reader.GetGuid(0).ToString("N"),
+                UserId = reader.GetGuid(1).ToString("N"),
                 Date = reader.GetDateTime(2).ToUniversalTime(),
                 Date = reader.GetDateTime(2).ToUniversalTime(),
                 Name = reader.GetString(3)
                 Name = reader.GetString(3)
             };
             };
@@ -202,13 +203,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
             notification.Level = GetLevel(reader, 6);
             notification.Level = GetLevel(reader, 6);
             notification.IsRead = reader.GetBoolean(7);
             notification.IsRead = reader.GetBoolean(7);
 
 
-            notification.Category = reader.GetString(8);
-
-            if (!reader.IsDBNull(9))
-            {
-                notification.RelatedId = reader.GetString(9);
-            }
-
             return notification;
             return notification;
         }
         }
 
 
@@ -223,13 +217,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
         /// or
         /// or
         /// userId
         /// userId
         /// </exception>
         /// </exception>
-        public Notification GetNotification(Guid id, Guid userId)
+        public Notification GetNotification(string id, string userId)
         {
         {
-            if (id == Guid.Empty)
+            if (string.IsNullOrEmpty(id))
             {
             {
                 throw new ArgumentNullException("id");
                 throw new ArgumentNullException("id");
             }
             }
-            if (userId == Guid.Empty)
+            if (string.IsNullOrEmpty(userId))
             {
             {
                 throw new ArgumentNullException("userId");
                 throw new ArgumentNullException("userId");
             }
             }
@@ -238,8 +232,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
             {
                 cmd.CommandText = "select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId where Id=@Id And UserId = @UserId";
                 cmd.CommandText = "select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId where Id=@Id And UserId = @UserId";
 
 
-                cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = id;
-                cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = userId;
+                cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = new Guid(id);
+                cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId);
 
 
                 using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
                 using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
                 {
                 {
@@ -329,11 +323,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task ReplaceNotification(Notification notification, CancellationToken cancellationToken)
         private async Task ReplaceNotification(Notification notification, CancellationToken cancellationToken)
         {
         {
-            if (notification.Id == Guid.Empty)
+            if (string.IsNullOrEmpty(notification.Id))
             {
             {
-                throw new ArgumentException("The notification must have an id");
+                notification.Id = Guid.NewGuid().ToString("N");
             }
             }
-            if (notification.UserId == Guid.Empty)
+            if (string.IsNullOrEmpty(notification.UserId))
             {
             {
                 throw new ArgumentException("The notification must have a user id");
                 throw new ArgumentException("The notification must have a user id");
             }
             }
@@ -348,16 +342,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
             {
                 transaction = _connection.BeginTransaction();
                 transaction = _connection.BeginTransaction();
 
 
-                _replaceNotificationCommand.GetParameter(0).Value = notification.Id;
-                _replaceNotificationCommand.GetParameter(1).Value = notification.UserId;
+                _replaceNotificationCommand.GetParameter(0).Value = new Guid(notification.Id);
+                _replaceNotificationCommand.GetParameter(1).Value = new Guid(notification.UserId);
                 _replaceNotificationCommand.GetParameter(2).Value = notification.Date.ToUniversalTime();
                 _replaceNotificationCommand.GetParameter(2).Value = notification.Date.ToUniversalTime();
                 _replaceNotificationCommand.GetParameter(3).Value = notification.Name;
                 _replaceNotificationCommand.GetParameter(3).Value = notification.Name;
                 _replaceNotificationCommand.GetParameter(4).Value = notification.Description;
                 _replaceNotificationCommand.GetParameter(4).Value = notification.Description;
                 _replaceNotificationCommand.GetParameter(5).Value = notification.Url;
                 _replaceNotificationCommand.GetParameter(5).Value = notification.Url;
                 _replaceNotificationCommand.GetParameter(6).Value = notification.Level.ToString();
                 _replaceNotificationCommand.GetParameter(6).Value = notification.Level.ToString();
                 _replaceNotificationCommand.GetParameter(7).Value = notification.IsRead;
                 _replaceNotificationCommand.GetParameter(7).Value = notification.IsRead;
-                _replaceNotificationCommand.GetParameter(8).Value = notification.Category;
-                _replaceNotificationCommand.GetParameter(9).Value = notification.RelatedId;
+                _replaceNotificationCommand.GetParameter(8).Value = string.Empty;
+                _replaceNotificationCommand.GetParameter(9).Value = string.Empty;
 
 
                 _replaceNotificationCommand.Transaction = transaction;
                 _replaceNotificationCommand.Transaction = transaction;
 
 
@@ -404,9 +398,10 @@ namespace MediaBrowser.Server.Implementations.Persistence
         /// <param name="isRead">if set to <c>true</c> [is read].</param>
         /// <param name="isRead">if set to <c>true</c> [is read].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public async Task MarkRead(IEnumerable<Guid> notificationIdList, Guid userId, bool isRead, CancellationToken cancellationToken)
+        public async Task MarkRead(IEnumerable<string> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken)
         {
         {
-            var idArray = notificationIdList.ToArray();
+            var list = notificationIdList.ToList();
+            var idArray = list.Select(i => new Guid(i)).ToArray();
 
 
             await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false);
             await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false);
 
 
@@ -416,7 +411,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 {
                 {
                     NotificationsMarkedRead(this, new NotificationReadEventArgs
                     NotificationsMarkedRead(this, new NotificationReadEventArgs
                     {
                     {
-                        IdList = idArray.ToArray(),
+                        IdList = list.ToArray(),
                         IsRead = isRead,
                         IsRead = isRead,
                         UserId = userId
                         UserId = userId
                     });
                     });
@@ -428,7 +423,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
         }
         }
 
 
-        private async Task MarkReadInternal(IEnumerable<Guid> notificationIdList, Guid userId, bool isRead, CancellationToken cancellationToken)
+        private async Task MarkReadInternal(IEnumerable<Guid> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken)
         {
         {
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
@@ -442,7 +437,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
                 transaction = _connection.BeginTransaction();
                 transaction = _connection.BeginTransaction();
 
 
-                _markReadCommand.GetParameter(0).Value = userId;
+                _markReadCommand.GetParameter(0).Value = new Guid(userId);
                 _markReadCommand.GetParameter(1).Value = isRead;
                 _markReadCommand.GetParameter(1).Value = isRead;
 
 
                 foreach (var id in notificationIdList)
                 foreach (var id in notificationIdList)

+ 8 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -56,6 +56,7 @@ using MediaBrowser.Server.Implementations.Library;
 using MediaBrowser.Server.Implementations.LiveTv;
 using MediaBrowser.Server.Implementations.LiveTv;
 using MediaBrowser.Server.Implementations.Localization;
 using MediaBrowser.Server.Implementations.Localization;
 using MediaBrowser.Server.Implementations.MediaEncoder;
 using MediaBrowser.Server.Implementations.MediaEncoder;
+using MediaBrowser.Server.Implementations.Notifications;
 using MediaBrowser.Server.Implementations.Persistence;
 using MediaBrowser.Server.Implementations.Persistence;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Session;
 using MediaBrowser.Server.Implementations.Session;
@@ -189,6 +190,8 @@ namespace MediaBrowser.ServerApplication
         private IFileOrganizationRepository FileOrganizationRepository { get; set; }
         private IFileOrganizationRepository FileOrganizationRepository { get; set; }
         private IProviderRepository ProviderRepository { get; set; }
         private IProviderRepository ProviderRepository { get; set; }
 
 
+        private INotificationManager NotificationManager { get; set; }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
         /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
         /// </summary>
         /// </summary>
@@ -523,6 +526,9 @@ namespace MediaBrowser.ServerApplication
             LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager);
             LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager);
             RegisterSingleInstance(LiveTvManager);
             RegisterSingleInstance(LiveTvManager);
 
 
+            NotificationManager = new NotificationManager(LogManager, UserManager);
+            RegisterSingleInstance(NotificationManager);
+
             var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
             var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
             var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
             var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
             var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false));
             var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false));
@@ -705,6 +711,8 @@ namespace MediaBrowser.ServerApplication
             SessionManager.AddParts(GetExports<ISessionControllerFactory>());
             SessionManager.AddParts(GetExports<ISessionControllerFactory>());
 
 
             ChannelManager.AddParts(GetExports<IChannel>());
             ChannelManager.AddParts(GetExports<IChannel>());
+
+            NotificationManager.AddParts(GetExports<INotificationService>());
         }
         }
 
 
         /// <summary>
         /// <summary>