From fadda8ef5663beea338f65ef9c69cd96ec1c5858 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 26 Apr 2014 23:42:05 -0400 Subject: [PATCH] add new notification features --- MediaBrowser.Api/Images/ImageService.cs | 188 +------------- MediaBrowser.Api/ItemRefreshService.cs | 179 +------------- MediaBrowser.Api/ItemUpdateService.cs | 174 ++----------- MediaBrowser.Api/Library/LibraryService.cs | 8 + MediaBrowser.Api/Music/AlbumsService.cs | 3 +- MediaBrowser.Api/NotificationsService.cs | 81 +++++- MediaBrowser.Api/UserLibrary/ItemsService.cs | 20 +- MediaBrowser.Controller/Entities/BaseItem.cs | 4 + .../Library/PlaybackProgressEventArgs.cs | 3 + .../MediaBrowser.Controller.csproj | 2 + .../Notifications/INotificationManager.cs | 30 +-- .../Notifications/INotificationService.cs | 30 +++ .../Notifications/INotificationTypeFactory.cs | 14 ++ .../Providers/BaseItemXmlParser.cs | 2 +- MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 102 +++++++- MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 1 - MediaBrowser.Dlna/PlayTo/Device.cs | 36 +-- MediaBrowser.Dlna/PlayTo/DlnaController.cs | 66 ++--- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 96 ++++---- .../PlayTo/PlayToServerEntryPoint.cs | 127 ---------- MediaBrowser.Dlna/Ssdp/SsdpHandler.cs | 61 ++--- MediaBrowser.Dlna/Ssdp/SsdpHelper.cs | 43 ++-- MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs | 2 +- .../MediaBrowser.MediaEncoding.csproj | 8 +- .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + .../Configuration/NotificationOptions.cs | 149 +++++++++++ .../Configuration/ServerConfiguration.cs | 18 -- MediaBrowser.Model/Dlna/DeviceProfile.cs | 5 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 + MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../Notifications/Notification.cs | 41 ++- .../Manager/ProviderUtils.cs | 2 +- .../Collections/CollectionManager.cs | 2 +- .../Dto/DtoService.cs | 1 + .../EntryPoints/Notifications/Notifier.cs | 233 ++++++++++++------ .../Library/ResolverHelper.cs | 2 +- .../Localization/Server/ar.json | 33 ++- .../Localization/Server/ca.json | 33 ++- .../Localization/Server/cs.json | 33 ++- .../Localization/Server/de.json | 33 ++- .../Localization/Server/el.json | 33 ++- .../Localization/Server/en_GB.json | 33 ++- .../Localization/Server/en_US.json | 33 ++- .../Localization/Server/es.json | 33 ++- .../Localization/Server/es_MX.json | 31 ++- .../Localization/Server/fr.json | 53 ++-- .../Localization/Server/he.json | 33 ++- .../Localization/Server/it.json | 33 ++- .../Localization/Server/kk.json | 41 +-- .../Localization/Server/ms.json | 33 ++- .../Localization/Server/nb.json | 33 ++- .../Localization/Server/nl.json | 61 +++-- .../Localization/Server/pt_BR.json | 35 ++- .../Localization/Server/pt_PT.json | 47 ++-- .../Localization/Server/ru.json | 41 +-- .../Localization/Server/server.json | 34 ++- .../Localization/Server/sv.json | 73 +++--- .../Localization/Server/zh_TW.json | 33 ++- ...MediaBrowser.Server.Implementations.csproj | 1 + .../News/NewsEntryPoint.cs | 39 ++- .../News/NewsService.cs | 2 +- .../Notifications/CoreNotificationTypes.cs | 131 ++++++++++ .../Notifications/NotificationManager.cs | 116 ++++++++- .../Session/SessionManager.cs | 12 +- .../ApplicationHost.cs | 7 +- .../Api/DashboardService.cs | 1 + .../MediaBrowser.WebDashboard.csproj | 6 + Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 71 files changed, 1656 insertions(+), 1256 deletions(-) create mode 100644 MediaBrowser.Controller/Notifications/INotificationService.cs create mode 100644 MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs delete mode 100644 MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs create mode 100644 MediaBrowser.Model/Configuration/NotificationOptions.cs create mode 100644 MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index e85a5a6808..da21342ac9 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -37,23 +37,6 @@ namespace MediaBrowser.Api.Images public string Id { get; set; } } - [Route("/Artists/{Name}/Images", "GET")] - [Route("/Genres/{Name}/Images", "GET")] - [Route("/GameGenres/{Name}/Images", "GET")] - [Route("/MusicGenres/{Name}/Images", "GET")] - [Route("/Persons/{Name}/Images", "GET")] - [Route("/Studios/{Name}/Images", "GET")] - [Api(Description = "Gets information about an item's images")] - public class GetItemByNameImageInfos : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - [Route("/Items/{Id}/Images/{Type}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets an item image")] @@ -103,45 +86,6 @@ namespace MediaBrowser.Api.Images public int NewIndex { get; set; } } - [Route("/Artists/{Name}/Images/{Type}/{Index}/Index", "POST")] - [Route("/Genres/{Name}/Images/{Type}/{Index}/Index", "POST")] - [Route("/GameGenres/{Name}/Images/{Type}/{Index}/Index", "POST")] - [Route("/MusicGenres/{Name}/Images/{Type}/{Index}/Index", "POST")] - [Route("/Persons/{Name}/Images/{Type}/{Index}/Index", "POST")] - [Route("/Studios/{Name}/Images/{Type}/{Index}/Index", "POST")] - [Route("/Years/{Year}/Images/{Type}/{Index}/Index", "POST")] - [Api(Description = "Updates the index for an item image")] - public class UpdateItemByNameImageIndex : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - - /// - /// Gets or sets the type of the image. - /// - /// The type of the image. - [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public ImageType Type { get; set; } - - /// - /// Gets or sets the index. - /// - /// The index. - [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int Index { get; set; } - - /// - /// Gets or sets the new index. - /// - /// The new index. - [ApiMember(Name = "NewIndex", Description = "The new image index", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public int NewIndex { get; set; } - } - /// /// Class GetPersonImage /// @@ -202,31 +146,6 @@ namespace MediaBrowser.Api.Images public Guid Id { get; set; } } - [Route("/Artists/{Name}/Images/{Type}", "DELETE")] - [Route("/Artists/{Name}/Images/{Type}/{Index}", "DELETE")] - [Route("/Genres/{Name}/Images/{Type}", "DELETE")] - [Route("/Genres/{Name}/Images/{Type}/{Index}", "DELETE")] - [Route("/GameGenres/{Name}/Images/{Type}", "DELETE")] - [Route("/GameGenres/{Name}/Images/{Type}/{Index}", "DELETE")] - [Route("/MusicGenres/{Name}/Images/{Type}", "DELETE")] - [Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "DELETE")] - [Route("/Persons/{Name}/Images/{Type}", "DELETE")] - [Route("/Persons/{Name}/Images/{Type}/{Index}", "DELETE")] - [Route("/Studios/{Name}/Images/{Type}", "DELETE")] - [Route("/Studios/{Name}/Images/{Type}/{Index}", "DELETE")] - [Route("/Years/{Year}/Images/{Type}", "DELETE")] - [Route("/Years/{Year}/Images/{Type}/{Index}", "DELETE")] - [Api(Description = "Deletes an item image")] - public class DeleteItemByNameImage : DeleteImageRequest, IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Name { get; set; } - } - /// /// Class DeleteUserImage /// @@ -287,37 +206,6 @@ namespace MediaBrowser.Api.Images public Stream RequestStream { get; set; } } - [Route("/Artists/{Name}/Images/{Type}", "POST")] - [Route("/Artists/{Name}/Images/{Type}/{Index}", "POST")] - [Route("/Genres/{Name}/Images/{Type}", "POST")] - [Route("/Genres/{Name}/Images/{Type}/{Index}", "POST")] - [Route("/GameGenres/{Name}/Images/{Type}", "POST")] - [Route("/GameGenres/{Name}/Images/{Type}/{Index}", "POST")] - [Route("/MusicGenres/{Name}/Images/{Type}", "POST")] - [Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "POST")] - [Route("/Persons/{Name}/Images/{Type}", "POST")] - [Route("/Persons/{Name}/Images/{Type}/{Index}", "POST")] - [Route("/Studios/{Name}/Images/{Type}", "POST")] - [Route("/Studios/{Name}/Images/{Type}/{Index}", "POST")] - [Route("/Years/{Year}/Images/{Type}", "POST")] - [Route("/Years/{Year}/Images/{Type}/{Index}", "POST")] - [Api(Description = "Posts an item image")] - public class PostItemByNameImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - - /// - /// The raw Http Request Input Stream - /// - /// The request stream. - public Stream RequestStream { get; set; } - } - /// /// Class ImageService /// @@ -327,26 +215,21 @@ namespace MediaBrowser.Api.Images private readonly ILibraryManager _libraryManager; - private readonly IApplicationPaths _appPaths; - private readonly IProviderManager _providerManager; private readonly IItemRepository _itemRepo; - private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// - public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) + public ImageService(IUserManager userManager, ILibraryManager libraryManager, IProviderManager providerManager, IItemRepository itemRepo, IImageProcessor imageProcessor, IFileSystem fileSystem) { _userManager = userManager; _libraryManager = libraryManager; - _appPaths = appPaths; _providerManager = providerManager; _itemRepo = itemRepo; - _dtoService = dtoService; _imageProcessor = imageProcessor; _fileSystem = fileSystem; } @@ -365,28 +248,6 @@ namespace MediaBrowser.Api.Images return ToOptimizedSerializedResultUsingCache(result); } - public object Get(GetItemByNameImageInfos request) - { - var result = GetItemByNameImageInfos(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - /// - /// Gets the item by name image infos. - /// - /// The request. - /// Task{List{ImageInfo}}. - private List GetItemByNameImageInfos(GetItemByNameImageInfos request) - { - var pathInfo = PathInfo.Parse(Request.PathInfo); - var type = pathInfo.GetArgumentValue(0); - - var item = GetItemByName(request.Name, type, _libraryManager); - - return GetItemImageInfos(item); - } - /// /// Gets the item image infos. /// @@ -540,21 +401,6 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } - public void Post(PostItemByNameImage request) - { - var pathInfo = PathInfo.Parse(Request.PathInfo); - var type = pathInfo.GetArgumentValue(0); - var name = pathInfo.GetArgumentValue(1); - - request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue(3), true); - - var item = GetItemByName(name, type, _libraryManager); - - var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType); - - Task.WaitAll(task); - } - /// /// Posts the specified request. /// @@ -599,22 +445,6 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } - /// - /// Deletes the specified request. - /// - /// The request. - public void Delete(DeleteItemByNameImage request) - { - var pathInfo = PathInfo.Parse(Request.PathInfo); - var type = pathInfo.GetArgumentValue(0); - - var item = GetItemByName(request.Name, type, _libraryManager); - - var task = item.DeleteImage(request.Type, request.Index ?? 0); - - Task.WaitAll(task); - } - /// /// Posts the specified request. /// @@ -628,22 +458,6 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } - /// - /// Posts the specified request. - /// - /// The request. - public void Post(UpdateItemByNameImageIndex request) - { - var pathInfo = PathInfo.Parse(Request.PathInfo); - var type = pathInfo.GetArgumentValue(0); - - var item = GetItemByName(request.Name, type, _libraryManager); - - var task = UpdateItemIndex(item, request.Type, request.Index, request.NewIndex); - - Task.WaitAll(task); - } - /// /// Updates the index of the item. /// diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 3816332b26..4345fbc5cc 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -28,76 +27,17 @@ namespace MediaBrowser.Api public string Id { get; set; } } - [Route("/Artists/{Name}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for an artist")] - public class RefreshArtist : BaseRefreshRequest - { - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/Genres/{Name}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for a genre")] - public class RefreshGenre : BaseRefreshRequest - { - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/MusicGenres/{Name}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for a music genre")] - public class RefreshMusicGenre : BaseRefreshRequest - { - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/GameGenres/{Name}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for a game genre")] - public class RefreshGameGenre : BaseRefreshRequest - { - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/Persons/{Name}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for a person")] - public class RefreshPerson : BaseRefreshRequest - { - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/Studios/{Name}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for a studio")] - public class RefreshStudio : BaseRefreshRequest - { - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - } - public class ItemRefreshService : BaseApiService { private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - public ItemRefreshService(ILibraryManager libraryManager, IDtoService dtoService) + public ItemRefreshService(ILibraryManager libraryManager) { _libraryManager = libraryManager; - _dtoService = dtoService; } - public void Post(RefreshArtist request) + private async Task RefreshArtist(RefreshItem request, MusicArtist item) { - var task = RefreshArtist(request); - - Task.WaitAll(task); - } - - private async Task RefreshArtist(RefreshArtist request) - { - var item = GetArtist(request.Name, _libraryManager); - var cancellationToken = CancellationToken.None; var albums = _libraryManager.RootFolder @@ -127,118 +67,15 @@ namespace MediaBrowser.Api } } - public void Post(RefreshGenre request) - { - var task = RefreshGenre(request); - - Task.WaitAll(task); - } - - private async Task RefreshGenre(RefreshGenre request) - { - var item = GetGenre(request.Name, _libraryManager); - - try - { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing library", ex); - } - } - - public void Post(RefreshMusicGenre request) - { - var task = RefreshMusicGenre(request); - - Task.WaitAll(task); - } - - private async Task RefreshMusicGenre(RefreshMusicGenre request) - { - var item = GetMusicGenre(request.Name, _libraryManager); - - try - { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing library", ex); - } - } - - public void Post(RefreshGameGenre request) - { - var task = RefreshGameGenre(request); - - Task.WaitAll(task); - } - - private async Task RefreshGameGenre(RefreshGameGenre request) - { - var item = GetGameGenre(request.Name, _libraryManager); - - try - { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing library", ex); - } - } - - public void Post(RefreshPerson request) - { - var task = RefreshPerson(request); - - Task.WaitAll(task); - } - - private async Task RefreshPerson(RefreshPerson request) - { - var item = GetPerson(request.Name, _libraryManager); - - try - { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing library", ex); - } - } - - public void Post(RefreshStudio request) - { - var task = RefreshStudio(request); - - Task.WaitAll(task); - } - - private async Task RefreshStudio(RefreshStudio request) - { - var item = GetStudio(request.Name, _libraryManager); - - try - { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing library", ex); - } - } - /// /// Posts the specified request. /// /// The request. public void Post(RefreshItem request) { - var task = RefreshItem(request); + var item = _libraryManager.GetItemById(request.Id); + + var task = item is MusicArtist ? RefreshArtist(request, (MusicArtist)item) : RefreshItem(request, item); Task.WaitAll(task); } @@ -248,10 +85,8 @@ namespace MediaBrowser.Api /// /// The request. /// Task. - private async Task RefreshItem(RefreshItem request) + private async Task RefreshItem(RefreshItem request, BaseItem item) { - var item = _libraryManager.GetItemById(request.Id); - var options = GetRefreshOptions(request); try diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 5f05fdc3f2..92e6a098d2 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -1,9 +1,7 @@ -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; using ServiceStack; using System; @@ -13,73 +11,20 @@ using System.Threading.Tasks; namespace MediaBrowser.Api { - [Route("/Items/{ItemId}", "POST")] - [Api(("Updates an item"))] + [Route("/Items/{ItemId}", "POST", Summary = "Updates an item")] public class UpdateItem : BaseItemDto, IReturnVoid { [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string ItemId { get; set; } } - [Route("/Artists/{ArtistName}", "POST")] - [Api(("Updates an artist"))] - public class UpdateArtist : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "ArtistName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string ArtistName { get; set; } - } - - [Route("/Studios/{StudioName}", "POST")] - [Api(("Updates a studio"))] - public class UpdateStudio : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "StudioName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string StudioName { get; set; } - } - - [Route("/Persons/{PersonName}", "POST")] - [Api(("Updates a person"))] - public class UpdatePerson : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "PersonName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string PersonName { get; set; } - } - - [Route("/MusicGenres/{GenreName}", "POST")] - [Api(("Updates a music genre"))] - public class UpdateMusicGenre : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "GenreName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string GenreName { get; set; } - } - - [Route("/GameGenres/{GenreName}", "POST")] - [Api(("Updates a game genre"))] - public class UpdateGameGenre : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "GenreName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string GenreName { get; set; } - } - - [Route("/Genres/{GenreName}", "POST")] - [Api(("Updates a genre"))] - public class UpdateGenre : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "GenreName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string GenreName { get; set; } - } - public class ItemUpdateService : BaseApiService { private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - private readonly ILiveTvManager _liveTv; - public ItemUpdateService(ILibraryManager libraryManager, IDtoService dtoService, ILiveTvManager liveTv) + public ItemUpdateService(ILibraryManager libraryManager) { _libraryManager = libraryManager; - _dtoService = dtoService; - _liveTv = liveTv; } public void Post(UpdateItem request) @@ -94,120 +39,29 @@ namespace MediaBrowser.Api var item = _libraryManager.GetItemById(request.ItemId); var newLockData = request.LockData ?? false; - var dontFetchMetaChanged = item.IsLocked != newLockData; + var isLockedChanged = item.IsLocked != newLockData; UpdateItem(request, item); + if (isLockedChanged && item.IsLocked) + { + item.IsUnidentified = false; + } + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - if (dontFetchMetaChanged && item.IsFolder) + if (isLockedChanged && item.IsFolder) { var folder = (Folder)item; foreach (var child in folder.RecursiveChildren.ToList()) { - child.DontFetchMeta = newLockData; + child.IsLocked = newLockData; await child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } } } - public void Post(UpdatePerson request) - { - var task = UpdateItem(request); - - Task.WaitAll(task); - } - - private async Task UpdateItem(UpdatePerson request) - { - var item = GetPerson(request.PersonName, _libraryManager); - - UpdateItem(request, item); - - await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } - - public void Post(UpdateArtist request) - { - var task = UpdateItem(request); - - Task.WaitAll(task); - } - - private async Task UpdateItem(UpdateArtist request) - { - var item = GetArtist(request.ArtistName, _libraryManager); - - UpdateItem(request, item); - - await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } - - public void Post(UpdateStudio request) - { - var task = UpdateItem(request); - - Task.WaitAll(task); - } - - private async Task UpdateItem(UpdateStudio request) - { - var item = GetStudio(request.StudioName, _libraryManager); - - UpdateItem(request, item); - - await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } - - public void Post(UpdateMusicGenre request) - { - var task = UpdateItem(request); - - Task.WaitAll(task); - } - - private async Task UpdateItem(UpdateMusicGenre request) - { - var item = GetMusicGenre(request.GenreName, _libraryManager); - - UpdateItem(request, item); - - await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } - - public void Post(UpdateGameGenre request) - { - var task = UpdateItem(request); - - Task.WaitAll(task); - } - - private async Task UpdateItem(UpdateGameGenre request) - { - var item = GetGameGenre(request.GenreName, _libraryManager); - - UpdateItem(request, item); - - await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } - - public void Post(UpdateGenre request) - { - var task = UpdateItem(request); - - Task.WaitAll(task); - } - - private async Task UpdateItem(UpdateGenre request) - { - var item = GetGenre(request.GenreName, _libraryManager); - - UpdateItem(request, item); - - await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } - private void UpdateItem(BaseItemDto request, BaseItem item) { item.Name = request.Name; @@ -246,7 +100,7 @@ namespace MediaBrowser.Api episode.AirsBeforeSeasonNumber = request.AirsBeforeSeasonNumber; episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber; } - + var hasTags = item as IHasTags; if (hasTags != null) { @@ -295,14 +149,14 @@ namespace MediaBrowser.Api { hasDisplayOrder.DisplayOrder = request.DisplayOrder; } - + var hasAspectRatio = item as IHasAspectRatio; if (hasAspectRatio != null) { hasAspectRatio.AspectRatio = request.AspectRatio; } - item.DontFetchMeta = (request.LockData ?? false); + item.IsLocked = (request.LockData ?? false); if (request.LockedFields != null) { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 2900736529..2b5b278041 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -231,6 +231,14 @@ namespace MediaBrowser.Api.Library } + [Route("/Library/Series/Updated", "POST")] + [Api(Description = "Reports that new episodes of a series have been added by an external source")] + public class PostUpdatedSeries : IReturnVoid + { + [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string TvdbId { get; set; } + } + /// /// Class LibraryService /// diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index a80dd796a0..d8abf81ba9 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -10,8 +10,7 @@ using System.Linq; namespace MediaBrowser.Api.Music { - [Route("/Albums/{Id}/Similar", "GET")] - [Api(Description = "Finds albums similar to a given album.")] + [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] public class GetSimilarAlbums : BaseGetSimilarItemsFromItem { } diff --git a/MediaBrowser.Api/NotificationsService.cs b/MediaBrowser.Api/NotificationsService.cs index 796fcdab11..d17b3c3684 100644 --- a/MediaBrowser.Api/NotificationsService.cs +++ b/MediaBrowser.Api/NotificationsService.cs @@ -1,8 +1,10 @@ -using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Notifications; using MediaBrowser.Model.Notifications; using ServiceStack; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -31,12 +33,38 @@ namespace MediaBrowser.Api public string UserId { get; set; } } - [Route("/Notifications/{UserId}", "POST", Summary = "Adds a notifications")] - public class AddUserNotification : IReturnVoid + [Route("/Notifications/Types", "GET", Summary = "Gets notification types")] + public class GetNotificationTypes : IReturn> + { + } + + [Route("/Notifications/Services", "GET", Summary = "Gets notification types")] + public class GetNotificationServices : IReturn> + { + } + + [Route("/Notifications", "POST", Summary = "Sends a notification to all admin users")] + public class AddAdminNotification : 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")] - public string Id { get; set; } + [ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Name { get; set; } + + [ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Description { get; set; } + + [ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ImageUrl { get; set; } + [ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Url { get; set; } + + [ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public NotificationLevel Level { get; set; } + } + + [Route("/Notifications/{UserId}", "POST", Summary = "Sends a notification to a user")] + public class AddUserNotification : IReturnVoid + { [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string UserId { get; set; } @@ -77,11 +105,13 @@ namespace MediaBrowser.Api { private readonly INotificationsRepository _notificationsRepo; private readonly INotificationManager _notificationManager; + private readonly IUserManager _userManager; - public NotificationsService(INotificationsRepository notificationsRepo, INotificationManager notificationManager) + public NotificationsService(INotificationsRepository notificationsRepo, INotificationManager notificationManager, IUserManager userManager) { _notificationsRepo = notificationsRepo; _notificationManager = notificationManager; + _userManager = userManager; } public void Post(AddUserNotification request) @@ -91,11 +121,25 @@ namespace MediaBrowser.Api Task.WaitAll(task); } + public object Get(GetNotificationTypes request) + { + var result = _notificationManager.GetNotificationTypes().ToList(); + + return ToOptimizedResult(result); + } + + public object Get(GetNotificationServices request) + { + var result = _notificationManager.GetNotificationServices().ToList(); + + return ToOptimizedResult(result); + } + public object Get(GetNotificationsSummary request) { var result = _notificationsRepo.GetNotificationsSummary(request.UserId); - return result; + return ToOptimizedResult(result); } private async Task AddNotification(AddUserNotification request) @@ -113,6 +157,29 @@ namespace MediaBrowser.Api await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false); } + public void Post(AddAdminNotification request) + { + // This endpoint really just exists as post of a real with sickbeard + var task = AddNotification(request); + + Task.WaitAll(task); + } + + private async Task AddNotification(AddAdminNotification request) + { + var notification = new NotificationRequest + { + Date = DateTime.UtcNow, + Description = request.Description, + Level = request.Level, + Name = request.Name, + Url = request.Url, + UserIds = _userManager.Users.Where(i => i.Configuration.IsAdministrator).Select(i => i.Id.ToString("N")).ToList() + }; + + await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false); + } + public void Post(MarkRead request) { var task = MarkRead(request.Ids, request.UserId, true); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f1b8b2d52b..1682afb10e 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -301,9 +301,10 @@ namespace MediaBrowser.Api.UserLibrary /// Task{ItemsResult}. private ItemsResult GetItems(GetItems request) { + var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId); var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; - var items = GetItemsToSerialize(request, user); + var items = GetItemsToSerialize(request, user, parentItem); items = items.AsParallel(); @@ -320,7 +321,7 @@ namespace MediaBrowser.Api.UserLibrary items = items.AsEnumerable(); - if (CollapseBoxSetItems(request)) + if (CollapseBoxSetItems(request, parentItem)) { items = _collectionManager.CollapseItemsWithinBoxSets(items, user); } @@ -348,8 +349,14 @@ namespace MediaBrowser.Api.UserLibrary }; } - private bool CollapseBoxSetItems(GetItems request) + private bool CollapseBoxSetItems(GetItems request, BaseItem parentItem) { + // Could end up stuck in a loop like this + if (parentItem is BoxSet) + { + return false; + } + var param = request.CollapseBoxSetItems; if (!param.HasValue) @@ -369,13 +376,14 @@ namespace MediaBrowser.Api.UserLibrary /// /// The request. /// The user. + /// The parent item. /// IEnumerable{BaseItem}. /// - private IEnumerable GetItemsToSerialize(GetItems request, User user) + private IEnumerable GetItemsToSerialize(GetItems request, User user, BaseItem parentItem) { var item = string.IsNullOrEmpty(request.ParentId) ? user == null ? _libraryManager.RootFolder : user.RootFolder : - _libraryManager.GetItemById(request.ParentId); + parentItem; // Default list type = children IEnumerable items; @@ -1382,7 +1390,7 @@ namespace MediaBrowser.Api.UserLibrary { return false; } - + return true; } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index be64d20c33..80fa3b8690 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -236,6 +236,10 @@ namespace MediaBrowser.Controller.Entities { return DontFetchMeta; } + set + { + DontFetchMeta = value; + } } public bool IsUnidentified { get; set; } diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index 8387881cfd..7482607fff 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -16,6 +16,9 @@ namespace MediaBrowser.Controller.Library public BaseItemInfo MediaInfo { get; set; } public string MediaSourceId { get; set; } + public string DeviceName { get; set; } + public string ClientName { get; set; } + public PlaybackProgressEventArgs() { Users = new List(); diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 417c03f277..b54ad2272b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -168,7 +168,9 @@ + + diff --git a/MediaBrowser.Controller/Notifications/INotificationManager.cs b/MediaBrowser.Controller/Notifications/INotificationManager.cs index 8ed62f59f7..cb1e3da902 100644 --- a/MediaBrowser.Controller/Notifications/INotificationManager.cs +++ b/MediaBrowser.Controller/Notifications/INotificationManager.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Notifications; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -20,30 +19,19 @@ namespace MediaBrowser.Controller.Notifications /// Adds the parts. /// /// The services. - void AddParts(IEnumerable services); - } + /// The notification type factories. + void AddParts(IEnumerable services, IEnumerable notificationTypeFactories); - public interface INotificationService - { /// - /// Gets the name. + /// Gets the notification types. /// - /// The name. - string Name { get; } - - /// - /// Sends the notification. - /// - /// The request. - /// The cancellation token. - /// Task. - Task SendNotification(UserNotification request, CancellationToken cancellationToken); + /// IEnumerable{NotificationTypeInfo}. + IEnumerable GetNotificationTypes(); /// - /// Determines whether [is enabled for user] [the specified user identifier]. + /// Gets the notification services. /// - /// The user. - /// true if [is enabled for user] [the specified user identifier]; otherwise, false. - bool IsEnabledForUser(User user); + /// IEnumerable{NotificationServiceInfo}. + IEnumerable GetNotificationServices(); } } diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs new file mode 100644 index 0000000000..b1e313b873 --- /dev/null +++ b/MediaBrowser.Controller/Notifications/INotificationService.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Controller.Entities; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Notifications +{ + public interface INotificationService + { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + + /// + /// Sends the notification. + /// + /// The request. + /// The cancellation token. + /// Task. + Task SendNotification(UserNotification request, CancellationToken cancellationToken); + + /// + /// Determines whether [is enabled for user] [the specified user identifier]. + /// + /// The user. + /// true if [is enabled for user] [the specified user identifier]; otherwise, false. + bool IsEnabledForUser(User user); + } +} diff --git a/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs b/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs new file mode 100644 index 0000000000..bf92aae2da --- /dev/null +++ b/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs @@ -0,0 +1,14 @@ +using MediaBrowser.Model.Notifications; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Notifications +{ + public interface INotificationTypeFactory + { + /// + /// Gets the notification types. + /// + /// IEnumerable{NotificationTypeInfo}. + IEnumerable GetNotificationTypes(); + } +} diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 70b49efece..e24daf8538 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -429,7 +429,7 @@ namespace MediaBrowser.Controller.Providers if (!string.IsNullOrWhiteSpace(val)) { - item.DontFetchMeta = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); } break; } diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 3746630be3..2952773a4c 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -1,8 +1,15 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Dlna.PlayTo; using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Logging; using System; @@ -15,19 +22,39 @@ namespace MediaBrowser.Dlna.Main { private readonly IServerConfigurationManager _config; private readonly ILogger _logger; - private readonly IApplicationHost _appHost; + private readonly IServerApplicationHost _appHost; private readonly INetworkManager _network; + private PlayToManager _manager; + private readonly ISessionManager _sessionManager; + private readonly IHttpClient _httpClient; + private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly INetworkManager _networkManager; + private readonly IUserManager _userManager; + private readonly IDlnaManager _dlnaManager; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + private SsdpHandler _ssdpHandler; private readonly List _registeredServerIds = new List(); private bool _dlnaServerStarted; - public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network) + public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IDtoService dtoService, IImageProcessor imageProcessor) { _config = config; _appHost = appHost; _network = network; + _sessionManager = sessionManager; + _httpClient = httpClient; + _itemRepo = itemRepo; + _libraryManager = libraryManager; + _networkManager = networkManager; + _userManager = userManager; + _dlnaManager = dlnaManager; + _dtoService = dtoService; + _imageProcessor = imageProcessor; _logger = logManager.GetLogger("Dlna"); } @@ -46,16 +73,27 @@ namespace MediaBrowser.Dlna.Main private void ReloadComponents() { - var isStarted = _dlnaServerStarted; + var isServerStarted = _dlnaServerStarted; - if (_config.Configuration.DlnaOptions.EnableServer && !isStarted) + if (_config.Configuration.DlnaOptions.EnableServer && !isServerStarted) { StartDlnaServer(); } - else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted) + else if (!_config.Configuration.DlnaOptions.EnableServer && isServerStarted) { DisposeDlnaServer(); } + + var isPlayToStarted = _manager != null; + + if (_config.Configuration.DlnaOptions.EnablePlayTo && !isPlayToStarted) + { + StartPlayToManager(); + } + else if (!_config.Configuration.DlnaOptions.EnablePlayTo && isPlayToStarted) + { + DisposePlayToManager(); + } } private void StartSsdpHandler() @@ -145,9 +183,59 @@ namespace MediaBrowser.Dlna.Main ); } + private readonly object _syncLock = new object(); + private void StartPlayToManager() + { + lock (_syncLock) + { + try + { + _manager = new PlayToManager(_logger, + _config, + _sessionManager, + _httpClient, + _itemRepo, + _libraryManager, + _networkManager, + _userManager, + _dlnaManager, + _appHost, + _dtoService, + _imageProcessor, + _ssdpHandler); + + _manager.Start(); + } + catch (Exception ex) + { + _logger.ErrorException("Error starting PlayTo manager", ex); + } + } + } + + private void DisposePlayToManager() + { + lock (_syncLock) + { + if (_manager != null) + { + try + { + _manager.Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing PlayTo manager", ex); + } + _manager = null; + } + } + } + public void Dispose() { DisposeDlnaServer(); + DisposePlayToManager(); DisposeSsdpHandler(); } diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index 97da7b697e..fa609843b4 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -70,7 +70,6 @@ - diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 9a35897ee8..70e2d66304 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -409,30 +409,30 @@ namespace MediaBrowser.Dlna.PlayTo UpdateMediaInfo(currentObject, transportState.Value); } } - } - } - catch (Exception ex) - { - _logger.ErrorException("Error updating device info", ex); - } - if (_disposed) - return; + if (_disposed) + return; - // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive - if (TransportState == TRANSPORTSTATE.STOPPED) - { - _successiveStopCount++; + // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive + if (transportState.Value == TRANSPORTSTATE.STOPPED) + { + _successiveStopCount++; - if (_successiveStopCount >= 10) - { - RestartTimerInactive(); + if (_successiveStopCount >= 10) + { + RestartTimerInactive(); + } + } + else + { + _successiveStopCount = 0; + RestartTimer(); + } } } - else + catch (Exception ex) { - _successiveStopCount = 0; - RestartTimer(); + _logger.ErrorException("Error updating device info", ex); } } diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 64eb049464..0a8da4744e 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Dlna.Didl; +using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -38,6 +39,8 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; + private readonly SsdpHandler _ssdpHandler; + public bool SupportsMediaRemoteControl { get { return true; } @@ -51,7 +54,7 @@ namespace MediaBrowser.Dlna.PlayTo } } - public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor) + public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor, SsdpHandler ssdpHandler) { _session = session; _itemRepository = itemRepository; @@ -63,6 +66,7 @@ namespace MediaBrowser.Dlna.PlayTo _appHost = appHost; _dtoService = dtoService; _imageProcessor = imageProcessor; + _ssdpHandler = ssdpHandler; _logger = logger; } @@ -74,7 +78,35 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackStopped += _device_PlaybackStopped; _device.Start(); - _updateTimer = new Timer(updateTimer_Elapsed, null, 30000, 30000); + _ssdpHandler.MessageReceived += _SsdpHandler_MessageReceived; + } + + async void _SsdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e) + { + string nts; + e.Headers.TryGetValue("NTS", out nts); + + string usn; + if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + + string nt; + if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + if (string.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && + string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase)) + { + if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1) + { + if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 || + nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1) + { + if (!_disposed) + { + await _sessionManager.ReportSessionEnded(_session.Id).ConfigureAwait(false); + } + } + } + } } async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e) @@ -165,34 +197,6 @@ namespace MediaBrowser.Dlna.PlayTo }; } - #region Device EventHandlers & Update Timer - - Timer _updateTimer; - - /// - /// Handles the Elapsed event of the updateTimer control. - /// - /// The state. - private async void updateTimer_Elapsed(object state) - { - if (!IsSessionActive) - { - _updateTimer.Change(Timeout.Infinite, Timeout.Infinite); - - try - { - // Session is inactive, mark it for Disposal and don't start the elapsed timer. - await _sessionManager.ReportSessionEnded(_session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error in ReportSessionEnded", ex); - } - } - } - - #endregion - #region SendCommands public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) @@ -571,8 +575,8 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackStart -= _device_PlaybackStart; _device.PlaybackProgress -= _device_PlaybackProgress; _device.PlaybackStopped -= _device_PlaybackStopped; + _ssdpHandler.MessageReceived -= _SsdpHandler_MessageReceived; - _updateTimer.Dispose(); _device.Dispose(); } } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index 1ba0ded1f6..889501e663 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -1,5 +1,4 @@ -using System.Text; -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; @@ -12,12 +11,12 @@ using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -30,7 +29,6 @@ namespace MediaBrowser.Dlna.PlayTo private readonly ISessionManager _sessionManager; private readonly IHttpClient _httpClient; private readonly CancellationTokenSource _tokenSource; - private ConcurrentDictionary _locations; private readonly IItemRepository _itemRepository; private readonly ILibraryManager _libraryManager; @@ -42,9 +40,10 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; - public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor) + private readonly SsdpHandler _ssdpHandler; + + public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor, SsdpHandler ssdpHandler) { - _locations = new ConcurrentDictionary(); _tokenSource = new CancellationTokenSource(); _logger = logger; @@ -58,13 +57,12 @@ namespace MediaBrowser.Dlna.PlayTo _appHost = appHost; _dtoService = dtoService; _imageProcessor = imageProcessor; + _ssdpHandler = ssdpHandler; _config = config; } public void Start() { - _locations = new ConcurrentDictionary(); - foreach (var network in GetNetworkInterfaces()) { _logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus); @@ -121,7 +119,9 @@ namespace MediaBrowser.Dlna.PlayTo { var socket = GetMulticastSocket(networkInterfaceIndex); - socket.Bind(new IPEndPoint(localIp, 1900)); + var endPoint = new IPEndPoint(localIp, 1900); + + socket.Bind(endPoint); _logger.Info("Creating SSDP listener"); @@ -135,9 +135,9 @@ namespace MediaBrowser.Dlna.PlayTo if (receivedBytes > 0) { - var headers = SsdpHelper.ParseSsdpResponse(receiveBuffer); + var args = SsdpHelper.ParseSsdpResponse(receiveBuffer, endPoint); - TryCreateController(headers); + TryCreateController(args); } } @@ -154,11 +154,47 @@ namespace MediaBrowser.Dlna.PlayTo }, _tokenSource.Token, TaskCreationOptions.LongRunning); } - private void TryCreateController(IDictionary headers) + private void TryCreateController(SsdpMessageEventArgs args) { + string nts; + args.Headers.TryGetValue("NTS", out nts); + + string usn; + if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + + string nt; + if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + // Don't create a new controller when a device is indicating it's shutting down + if (string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + // It has to report that it's a media renderer + if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) + { + return; + } + + // Need to be able to download device description string location; + if (!args.Headers.TryGetValue("Location", out location) || + string.IsNullOrEmpty(location)) + { + return; + } - if (!headers.TryGetValue("Location", out location)) + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); + var headerText = string.Join(",", headerTexts.ToArray()); + + _logger.Debug("{0} PlayTo message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText); + } + + if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1)) { return; } @@ -230,12 +266,9 @@ namespace MediaBrowser.Dlna.PlayTo /// private async Task CreateController(Uri uri) { - if (!IsUriValid(uri)) - return; - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); - if (device != null && device.RendererCommands != null && !_sessionManager.Sessions.Any(s => string.Equals(s.DeviceId, device.Properties.UUID) && s.IsActive)) + if (device != null && device.RendererCommands != null) { var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null) .ConfigureAwait(false); @@ -244,7 +277,7 @@ namespace MediaBrowser.Dlna.PlayTo if (controller == null) { - sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService, _imageProcessor); + sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService, _imageProcessor, _ssdpHandler); controller.Init(device); @@ -271,33 +304,6 @@ namespace MediaBrowser.Dlna.PlayTo } } - /// - /// Determines if the Uri is valid for further inspection or not. - /// (the limit for reinspection is 5 minutes) - /// - /// The URI. - /// Returns True if the Uri is valid for further inspection - private bool IsUriValid(Uri uri) - { - if (uri == null) - return false; - - if (!_locations.ContainsKey(uri.OriginalString)) - { - _locations.AddOrUpdate(uri.OriginalString, DateTime.UtcNow, (key, existingVal) => existingVal); - - return true; - } - - var time = _locations[uri.OriginalString]; - - if ((DateTime.UtcNow - time).TotalMinutes <= 5) - { - return false; - } - return _locations.TryUpdate(uri.OriginalString, DateTime.UtcNow, time); - } - public void Dispose() { if (!_disposed) diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs deleted file mode 100644 index 15bebd9962..0000000000 --- a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs +++ /dev/null @@ -1,127 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using System; - -namespace MediaBrowser.Dlna.PlayTo -{ - public class PlayToServerEntryPoint : IServerEntryPoint - { - private PlayToManager _manager; - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly ISessionManager _sessionManager; - private readonly IHttpClient _httpClient; - private readonly IItemRepository _itemRepo; - private readonly ILibraryManager _libraryManager; - private readonly INetworkManager _networkManager; - private readonly IUserManager _userManager; - private readonly IDlnaManager _dlnaManager; - private readonly IServerApplicationHost _appHost; - private readonly IDtoService _dtoService; - private readonly IImageProcessor _imageProcessor; - - public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor) - { - _config = config; - _sessionManager = sessionManager; - _httpClient = httpClient; - _itemRepo = itemRepo; - _libraryManager = libraryManager; - _networkManager = networkManager; - _userManager = userManager; - _dlnaManager = dlnaManager; - _appHost = appHost; - _dtoService = dtoService; - _imageProcessor = imageProcessor; - _logger = logManager.GetLogger("PlayTo"); - } - - public void Run() - { - _config.ConfigurationUpdated += ConfigurationUpdated; - - ReloadPlayToManager(); - } - - void ConfigurationUpdated(object sender, EventArgs e) - { - ReloadPlayToManager(); - } - - private void ReloadPlayToManager() - { - var isStarted = _manager != null; - - if (_config.Configuration.DlnaOptions.EnablePlayTo && !isStarted) - { - StartPlayToManager(); - } - else if (!_config.Configuration.DlnaOptions.EnablePlayTo && isStarted) - { - DisposePlayToManager(); - } - } - - private readonly object _syncLock = new object(); - private void StartPlayToManager() - { - lock (_syncLock) - { - try - { - _manager = new PlayToManager(_logger, - _config, - _sessionManager, - _httpClient, - _itemRepo, - _libraryManager, - _networkManager, - _userManager, - _dlnaManager, - _appHost, - _dtoService, - _imageProcessor); - - _manager.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting PlayTo manager", ex); - } - } - } - - private void DisposePlayToManager() - { - lock (_syncLock) - { - if (_manager != null) - { - try - { - _manager.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing PlayTo manager", ex); - } - _manager = null; - } - } - } - - public void Dispose() - { - DisposePlayToManager(); - } - } -} diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index 01393c6ce4..ad7626f32a 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -1,14 +1,13 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Events; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Dlna.Server; using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; -using System.Text; using System.Threading; namespace MediaBrowser.Dlna.Ssdp @@ -29,13 +28,13 @@ namespace MediaBrowser.Dlna.Ssdp private Timer _queueTimer; private Timer _notificationTimer; - + private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false); private readonly ConcurrentQueue _messageQueue = new ConcurrentQueue(); private bool _isDisposed; private readonly ConcurrentDictionary> _devices = new ConcurrentDictionary>(); - + public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature) { _logger = logger; @@ -51,6 +50,8 @@ namespace MediaBrowser.Dlna.Ssdp { RespondToSearch(args.EndPoint, args.Headers["st"]); } + + EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger); } public IEnumerable RegisteredDevices @@ -60,7 +61,7 @@ namespace MediaBrowser.Dlna.Ssdp return _devices.Values.SelectMany(i => i).ToList(); } } - + public void Start() { _socket = CreateMulticastSocket(); @@ -79,8 +80,8 @@ namespace MediaBrowser.Dlna.Ssdp SendDatagram(header, values, _ssdpEndp, localAddress, sendCount); } - public void SendDatagram(string header, - Dictionary values, + public void SendDatagram(string header, + Dictionary values, IPEndPoint endpoint, IPAddress localAddress, int sendCount = 1) @@ -105,7 +106,7 @@ namespace MediaBrowser.Dlna.Ssdp { foreach (var d in RegisteredDevices) { - if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) || + if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) || string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase)) { SendDatagram(header, values, endpoint, d.Address); @@ -141,7 +142,7 @@ namespace MediaBrowser.Dlna.Ssdp _logger.Info("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString()); } - } + } } private readonly object _queueTimerSyncLock = new object(); @@ -223,43 +224,17 @@ namespace MediaBrowser.Dlna.Ssdp var receivedCount = _socket.EndReceiveFrom(result, ref endpoint); var received = (byte[])result.AsyncState; - if (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("{0} - SSDP Received a datagram", endpoint); - } + var args = SsdpHelper.ParseSsdpResponse(received, (IPEndPoint)endpoint); - using (var reader = new StreamReader(new MemoryStream(received), Encoding.ASCII)) + if (_config.Configuration.DlnaOptions.EnableDebugLogging) { - var proto = (reader.ReadLine() ?? string.Empty).Trim(); - var method = proto.Split(new[] { ' ' }, 2)[0]; - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) - { - line = line.Trim(); - if (string.IsNullOrEmpty(line)) - { - break; - } - var parts = line.Split(new[] { ':' }, 2); - - if (parts.Length >= 2) - { - headers[parts[0]] = parts[1].Trim(); - } - } + var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); + var headerText = string.Join(",", headerTexts.ToArray()); - if (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("{0} - Datagram method: {1}", endpoint, method); - } - - OnMessageReceived(new SsdpMessageEventArgs - { - Method = method, - Headers = headers, - EndPoint = (IPEndPoint)endpoint - }); + _logger.Debug("{0} message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText); } + + OnMessageReceived(args); } catch (Exception ex) { diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs b/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs index 2b5f386226..b666140d24 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs @@ -1,40 +1,45 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Text; namespace MediaBrowser.Dlna.Ssdp { public class SsdpHelper { - /// - /// Parses the socket response into a location Uri for the DeviceDescription.xml. - /// - /// The data. - /// - public static Dictionary ParseSsdpResponse(byte[] data) + public static SsdpMessageEventArgs ParseSsdpResponse(byte[] data, IPEndPoint endpoint) { - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (var reader = new StreamReader(new MemoryStream(data), Encoding.ASCII)) + using (var ms = new MemoryStream(data)) { - for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) + using (var reader = new StreamReader(ms, Encoding.ASCII)) { - line = line.Trim(); - if (string.IsNullOrEmpty(line)) + var proto = (reader.ReadLine() ?? string.Empty).Trim(); + var method = proto.Split(new[] { ' ' }, 2)[0]; + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) { - break; + line = line.Trim(); + if (string.IsNullOrEmpty(line)) + { + break; + } + var parts = line.Split(new[] { ':' }, 2); + + if (parts.Length >= 2) + { + headers[parts[0]] = parts[1].Trim(); + } } - var parts = line.Split(new[] { ':' }, 2); - if (parts.Length == 2) + return new SsdpMessageEventArgs { - headers[parts[0]] = parts[1].Trim(); - } + Method = method, + Headers = headers, + EndPoint = endpoint + }; } } - - return headers; } } } diff --git a/MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs b/MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs index 4e60cfe2e3..5f8ca1111a 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Dlna.Ssdp var values = new Dictionary(StringComparer.OrdinalIgnoreCase); values["HOST"] = "239.255.255.250:1900"; - values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/0.6.9.1"; + values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2"; values["ST"] = deviceSearchType; values["MAN"] = "\"ssdp:discover\""; values["MX"] = mx; diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index e129468d39..b9a8323fb4 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -80,13 +80,7 @@ - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - +